mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-17 07:26:27 +00:00
62 lines
2.5 KiB
TypeScript
62 lines
2.5 KiB
TypeScript
/**
|
|||
|
|
* Is a work/ file a real solution or just a scaffolded stub?
|
||
|
|
*
|
||
|
|
* `bun run pick` writes a statement header plus an empty function body, so
|
||
|
|
* file existence alone means nothing: close-solved.ts must not close an issue
|
||
|
|
* on a stub, and work.ts must not ask for the spaced-repetition re-solve of a
|
||
|
|
* problem that was never solved in the first place.
|
||
|
|
*
|
||
|
|
* sync.ts keeps its own copy of the header split — it runs its whole pipeline
|
||
|
|
* on import, so it cannot be imported from. Don't "deduplicate" that one.
|
||
|
|
*
|
||
|
|
* Library: side-effect-free on import.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/** Placeholder bodies that leetcode-cli / a human leaves behind. */
|
||
|
|
const PLACEHOLDERS: Record<string, true> = { pass: true, "...": true, TODO: true };
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Decide whether a work/ file contains an implementation.
|
||
|
|
*
|
||
|
|
* The header comment is dropped the same way sync.ts splitSource() does it.
|
||
|
|
* Comments must go before any brace analysis: the scaffold's JSDoc carries
|
||
|
|
* `@param {number[]}`, whose braces would otherwise read as a body.
|
||
|
|
*
|
||
|
|
* A brace-language file counts as implemented when at least one *innermost*
|
||
|
|
* brace pair holds real content. That distinguishes a bare stub
|
||
|
|
* (`function(nums) {}`) and a class-shaped design stub (every method body
|
||
|
|
* empty) from any genuine solution, whose innermost block always has code.
|
||
|
|
* Every non-Python language the campaign has ever used is brace-shaped, so
|
||
|
|
* "not .py" is the only branch that matters.
|
||
|
|
*/
|
||
|
|
export function isImplemented(src: string, lang: "js" | "py"): boolean {
|
||
|
|
if (lang === "py") {
|
||
|
|
const open = src.indexOf('"""');
|
||
|
|
const close = src.indexOf('"""', open + 3);
|
||
|
|
const code = open === -1 || close === -1 ? src : src.slice(close + 3);
|
||
|
|
for (const raw of code.split("\n")) {
|
||
|
|
const line = raw.replace(/#.*$/, "").trim();
|
||
|
|
if (!line || PLACEHOLDERS[line]) continue;
|
||
|
|
if (/^(?:@|def\s|class\s)/.test(line)) continue;
|
||
|
|
return true; // a statement inside some def body
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const headerEnd = src.indexOf("*/");
|
||
|
|
const body = headerEnd === -1 ? src : src.slice(headerEnd + 2);
|
||
|
|
const code = body.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/.*$/gm, " ");
|
||
|
|
|
||
|
|
let innermost = -1;
|
||
|
|
for (let i = 0; i < code.length; i++) {
|
||
|
|
if (code[i] === "{") {
|
||
|
|
innermost = i;
|
||
|
|
} else if (code[i] === "}" && innermost !== -1) {
|
||
|
|
const inner = code.slice(innermost + 1, i).replace(/[\s;]/g, "");
|
||
|
|
if (inner && !PLACEHOLDERS[inner]) return true;
|
||
|
|
innermost = -1; // measured; the enclosing pair is not innermost
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|