@exercode/problem-utils 4.2.1 → 4.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/checkAllProblems.cjs +1 -1
- package/dist/cli/checkAllProblems.cjs.map +1 -1
- package/dist/cli/checkAllProblems.js +1 -1
- package/dist/cli/checkAllProblems.js.map +1 -1
- package/dist/helpers/removeCommentsInSourceCode.cjs +1 -1
- package/dist/helpers/removeCommentsInSourceCode.cjs.map +1 -1
- package/dist/helpers/removeCommentsInSourceCode.js +1 -1
- package/dist/helpers/removeCommentsInSourceCode.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/testCaseResult.cjs"),n=require("../helpers/defaultStdioHarness.cjs"),r=require("../helpers/readTestCases.cjs"),i=require("../helpers/temporaryProblemDirCopy.cjs"),a=require("../helpers/readProblemMarkdownFrontMatter.cjs"),o=require("../types/decisionCode.cjs"),s=require("../helpers/runHarnessProcess.cjs"),c=require("../helpers/findModelAnswerDirs.cjs"),l=require("./runSingleHarness.cjs");let u=require("node:path");u=e.__toESM(u,1);let d=require("node:fs/promises");d=e.__toESM(d,1);const f=1e3,p=new Map(Object.entries(o.DecisionCode).map(([e,t])=>[t,e]));async function m(e){let t=_(e),i=u.default.resolve(t.rootDir),o=e=>(u.default.relative(i,e)||`.`).replaceAll(u.default.sep,`/`),s=await v(i),d=s.filter(e=>{let n=o(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(d.length===0)return console.error(s.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${s.length} problem directories under ${i} were excluded by --only/--skip.`),1;let f=[],p=[];for(let e of d){let t=await n.findDefaultStdioHarnessFiles(e);if(t.length>0){f.push(`${o(e)}: ${l.formatDefaultHarnessError(t)}`);continue}let i=await c.findModelAnswerDirs(e);if(i.length===0){f.push(`${o(e)}: no model answers found under model_answers/`);continue}if(!await b(u.default.join(e,`judge.ts`))&&(await r.readTestCases(u.default.join(e,`test_cases`))).length===0){let t;try{t=await a.readProblemMarkdownFrontMatter(e)}catch(t){f.push(`${o(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!a.judgesWithoutTestCases(t)){f.push(`${o(e)}: ${l.MISSING_TEST_CASES_ERROR}`);continue}}let s=await c.findFailingModelAnswerDirs(e);p.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...s.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of f)console.error(`✗ ${e}`);let m=u.default.resolve(process.argv[1]??``),g=0,y=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,p.length))},async()=>{for(;y<p.length;){let e=p[y++];if(!e)return;let t=`${o(e.problemDir)} ${u.default.relative(e.problemDir,e.answerDir).replaceAll(u.default.sep,`/`)}`,n=await h(e,m);n===void 0?(g++,console.info(`✓ ${t}`)):(f.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${g} passed, ${f.length} failed (${p.length} runs, ${d.length} problems)`),f.length===0?0:1}async function h(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i.copyProblemDirToTemporaryRoot(e.problemDir))}catch(e){return x(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let a,o;try{a=g(e,await s.runHarnessProcess([`run`,t,`judge`,u.default.relative(e.problemDir,e.answerDir)],{cwd:r,env:process.env,timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){a=x(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{o=await i.forciblyRemoveDirectory(n)}if(o)return a;let c=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return a===void 0?c:x(`${a}; ${c}`)}function g(e,n){let{stdout:r,stderr:i}=n;if(n.failureReason!==void 0)return x(n.failureReason);if(n.exitCode!==0)return x(`harness exited with ${n.exitCode??`a signal`}${i.trim()?`: ${i.trim()}`:``}`);let a=r.split(/\r?\n/).filter(e=>e.startsWith(t.TEST_CASE_RESULT_PREFIX)),s=[];for(let e of a){let n;try{n=t.testCaseResultSchema.safeParse(JSON.parse(e.slice(t.TEST_CASE_RESULT_PREFIX.length)))}catch{n=void 0}if(!n?.success)return x(`malformed test case result line: ${e}`);s.push(n.data)}if(s.length===0)return`no test case results were printed`;if(e.expectation===`accepted`){let e=s.find(e=>e.decisionCode!==o.DecisionCode.ACCEPTED);return e?x(`${p.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return s.every(e=>e.decisionCode===o.DecisionCode.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function _(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function v(e){let t=[];return await y(e,t),t.toSorted()}async function y(e,t){let n=await d.default.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/testCaseResult.cjs"),n=require("../helpers/defaultStdioHarness.cjs"),r=require("../helpers/readTestCases.cjs"),i=require("../helpers/temporaryProblemDirCopy.cjs"),a=require("../helpers/readProblemMarkdownFrontMatter.cjs"),o=require("../types/decisionCode.cjs"),s=require("../helpers/runHarnessProcess.cjs"),c=require("../helpers/findModelAnswerDirs.cjs"),l=require("./runSingleHarness.cjs");let u=require("node:path");u=e.__toESM(u,1);let d=require("node:fs/promises");d=e.__toESM(d,1);const f=1e3,p=new Map(Object.entries(o.DecisionCode).map(([e,t])=>[t,e]));async function m(e){let t=_(e),i=u.default.resolve(t.rootDir),o=e=>(u.default.relative(i,e)||`.`).replaceAll(u.default.sep,`/`),s=await v(i),d=s.filter(e=>{let n=o(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(d.length===0)return console.error(s.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${s.length} problem directories under ${i} were excluded by --only/--skip.`),1;let f=[],p=[];for(let e of d){let t=await n.findDefaultStdioHarnessFiles(e);if(t.length>0){f.push(`${o(e)}: ${l.formatDefaultHarnessError(t)}`);continue}let i=await c.findModelAnswerDirs(e);if(i.length===0){f.push(`${o(e)}: no model answers found under model_answers/`);continue}if(!await b(u.default.join(e,`judge.ts`))&&(await r.readTestCases(u.default.join(e,`test_cases`))).length===0){let t;try{t=await a.readProblemMarkdownFrontMatter(e)}catch(t){f.push(`${o(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!a.judgesWithoutTestCases(t)){f.push(`${o(e)}: ${l.MISSING_TEST_CASES_ERROR}`);continue}}let s=await c.findFailingModelAnswerDirs(e);p.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...s.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of f)console.error(`✗ ${e}`);let m=u.default.resolve(process.argv[1]??``),g=0,y=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,p.length))},async()=>{for(;y<p.length;){let e=p[y++];if(!e)return;let t=`${o(e.problemDir)} ${u.default.relative(e.problemDir,e.answerDir).replaceAll(u.default.sep,`/`)}`,n=await h(e,m);n===void 0?(g++,console.info(`✓ ${t}`)):(f.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${g} passed, ${f.length} failed (${p.length} runs, ${d.length} problems)`),f.length===0?0:1}async function h(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i.copyProblemDirToTemporaryRoot(e.problemDir))}catch(e){return x(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let a,o;try{a=g(e,await s.runHarnessProcess([`run`,t,`judge`,u.default.relative(e.problemDir,e.answerDir)],{cwd:r,env:process.env,timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){a=x(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{o=await i.forciblyRemoveDirectory(n)}if(o)return a;let c=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return a===void 0?c:x(`${a}; ${c}`)}function g(e,n){let{stdout:r,stderr:i}=n;if(n.failureReason!==void 0)return x(n.failureReason);if(n.exitCode!==0)return x(`harness exited with ${n.exitCode??`a signal`}${i.trim()?`: ${i.trim()}`:``}`);let a=r.split(/\r?\n/).filter(e=>e.startsWith(t.TEST_CASE_RESULT_PREFIX)),s=[];for(let e of a){let n;try{n=t.testCaseResultSchema.safeParse(JSON.parse(e.slice(t.TEST_CASE_RESULT_PREFIX.length)))}catch{n=void 0}if(!n?.success)return x(`malformed test case result line: ${e}`);s.push(n.data)}if(s.length===0)return`no test case results were printed`;if(e.expectation===`accepted`){let e=s.find(e=>e.decisionCode!==o.DecisionCode.ACCEPTED);return e?x(`${p.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return s.every(e=>e.decisionCode===o.DecisionCode.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function _(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function v(e){let t=[];return await y(e,t),t.toSorted()}async function y(e,t){let n=await d.default.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)r.isDirectory()&&r.name!==`node_modules`&&!r.name.startsWith(`.`)&&await y(u.default.join(e,r.name),t)}async function b(e){try{return await d.default.stat(e),!0}catch{return!1}}function x(e){return e.length<=f?e:`${e.slice(0,f)}...`}exports.checkAllProblems=m;
|
|
2
2
|
//# sourceMappingURL=checkAllProblems.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkAllProblems.cjs","names":["DecisionCode","path","findDefaultStdioHarnessFiles","formatDefaultHarnessError","findModelAnswerDirs","readTestCases","readProblemMarkdownFrontMatter","judgesWithoutTestCases","MISSING_TEST_CASES_ERROR","findFailingModelAnswerDirs","copyProblemDirToTemporaryRoot","runHarnessProcess","forciblyRemoveDirectory","TEST_CASE_RESULT_PREFIX","testCaseResultSchema","fs"],"sources":["../../src/cli/checkAllProblems.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { findFailingModelAnswerDirs, findModelAnswerDirs } from '../helpers/findModelAnswerDirs.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { type HarnessProcessResult, runHarnessProcess } from '../helpers/runHarnessProcess.js';\nimport { copyProblemDirToTemporaryRoot, forciblyRemoveDirectory } from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { TEST_CASE_RESULT_PREFIX, testCaseResultSchema } from '../types/testCaseResult.js';\n\nimport { formatDefaultHarnessError, MISSING_TEST_CASES_ERROR } from './runSingleHarness.js';\n\nconst RUN_TIMEOUT_MS = 600_000;\nconst MAX_RUN_OUTPUT_BYTES = 64 * 1024 * 1024;\nconst MAX_FAILURE_DETAIL_LENGTH = 1000;\n\n// DecisionCode is a plain const object (not a TypeScript enum), so it has no reverse mapping.\nconst decisionCodeNames = new Map<number, string>(Object.entries(DecisionCode).map(([name, code]) => [code, name]));\n\ninterface CheckOptions {\n rootDir: string;\n concurrency: number;\n only: string[];\n skip: string[];\n}\n\ninterface CheckRun {\n problemDir: string;\n answerDir: string;\n /** Whether all test cases must be accepted (`model_answers`) or at least one must fail (`model_answers.fails`). */\n expectation: 'accepted' | 'rejected';\n}\n\n/**\n * Judge all model answers of all problems (directories containing `problem.md` or\n * `<id>.problem.md`) under a root directory: `model_answers/*` must be fully accepted and\n * `model_answers.fails/*` must fail at least one test case. Returns the process exit code.\n */\nexport async function checkAllProblems(args: readonly string[]): Promise<number> {\n const options = parseCheckArgs(args);\n const rootDir = path.resolve(options.rootDir);\n // Normalize to forward slashes so --only / --skip substrings like courses/foo match on Windows.\n const toRelative = (dir: string): string => (path.relative(rootDir, dir) || '.').replaceAll(path.sep, '/');\n\n const allProblemDirs = await findProblemDirs(rootDir);\n const problemDirs = allProblemDirs.filter((problemDir) => {\n const relativeDir = toRelative(problemDir);\n if (options.only.length > 0 && !options.only.some((substring) => relativeDir.includes(substring))) return false;\n return !options.skip.some((substring) => relativeDir.includes(substring));\n });\n if (problemDirs.length === 0) {\n console.error(\n allProblemDirs.length === 0\n ? `No problem directories (containing problem.md or <id>.problem.md) found under ${rootDir}.`\n : `All ${allProblemDirs.length} problem directories under ${rootDir} were excluded by --only/--skip.`\n );\n return 1;\n }\n\n const failures: string[] = [];\n const runs: CheckRun[] = [];\n for (const problemDir of problemDirs) {\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n failures.push(`${toRelative(problemDir)}: ${formatDefaultHarnessError(defaultHarnessFileNames)}`);\n continue;\n }\n\n const modelAnswerDirs = await findModelAnswerDirs(problemDir);\n if (modelAnswerDirs.length === 0) {\n failures.push(`${toRelative(problemDir)}: no model answers found under model_answers/`);\n continue;\n }\n\n // Without test cases, stdioJudgePreset prints a single accepted sentinel result, so a standard\n // problem with an empty or missing test_cases/ would otherwise pass without being judged —\n // unless static-analysis rules or manual scoring make the problem judgeable without them.\n if (!(await fileExists(path.join(problemDir, 'judge.ts')))) {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0) {\n let frontMatter;\n try {\n frontMatter = await readProblemMarkdownFrontMatter(problemDir);\n } catch (error) {\n failures.push(\n `${toRelative(problemDir)}: failed to read the problem markdown front matter: ${error instanceof Error ? error.message : String(error)}`\n );\n continue;\n }\n if (!judgesWithoutTestCases(frontMatter)) {\n failures.push(`${toRelative(problemDir)}: ${MISSING_TEST_CASES_ERROR}`);\n continue;\n }\n }\n }\n\n const failingModelAnswerDirs = await findFailingModelAnswerDirs(problemDir);\n runs.push(\n ...modelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'accepted' })),\n ...failingModelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'rejected' }))\n );\n }\n for (const failure of failures) console.error(`✗ ${failure}`);\n\n const cliEntryPath = path.resolve(process.argv[1] ?? '');\n let passedCount = 0;\n let nextRunIndex = 0;\n await Promise.all(\n Array.from({ length: Math.max(1, Math.min(options.concurrency, runs.length)) }, async () => {\n while (nextRunIndex < runs.length) {\n const run = runs[nextRunIndex++];\n if (!run) return;\n const label = `${toRelative(run.problemDir)} ${path.relative(run.problemDir, run.answerDir).replaceAll(path.sep, '/')}`;\n const failureDetail = await executeCheckRun(run, cliEntryPath);\n if (failureDetail === undefined) {\n passedCount++;\n console.info(`✓ ${label}`);\n } else {\n failures.push(`${label}: ${failureDetail}`);\n console.error(`✗ ${label}: ${failureDetail}`);\n }\n }\n })\n );\n\n console.info(\n `\\n${passedCount} passed, ${failures.length} failed (${runs.length} runs, ${problemDirs.length} problems)`\n );\n return failures.length === 0 ? 0 : 1;\n}\n\n/**\n * Run one answer directory through the problem's harness and return a failure detail, if any.\n * The problem directory is copied to a temporary location first so that judging (e.g. build\n * artifacts in answer directories) never modifies the checked repository.\n */\nasync function executeCheckRun(run: CheckRun, cliEntryPath: string): Promise<string | undefined> {\n let tempRoot: string;\n let copiedProblemDir: string;\n try {\n ({ tempRoot, copiedProblemDir } = await copyProblemDirToTemporaryRoot(run.problemDir));\n } catch (error) {\n return truncate(\n `failed to copy the problem directory to a temporary location: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n let harnessFailureDetail;\n let removedTempRoot;\n try {\n const result = await runHarnessProcess(\n ['run', cliEntryPath, 'judge', path.relative(run.problemDir, run.answerDir)],\n {\n cwd: copiedProblemDir,\n env: process.env,\n timeoutMs: RUN_TIMEOUT_MS,\n maxOutputBytes: MAX_RUN_OUTPUT_BYTES,\n tempRoot,\n }\n );\n harnessFailureDetail = summarizeHarnessFailure(run, result);\n } catch (error) {\n harnessFailureDetail = truncate(\n `harness execution failed: ${error instanceof Error ? error.message : String(error)}`\n );\n } finally {\n removedTempRoot = await forciblyRemoveDirectory(tempRoot);\n }\n if (removedTempRoot) return harnessFailureDetail;\n const removalFailureDetail = `failed to remove the temporary copy at ${tempRoot} (judged code may have left permission-locked files)`;\n return harnessFailureDetail === undefined\n ? removalFailureDetail\n : truncate(`${harnessFailureDetail}; ${removalFailureDetail}`);\n}\n\n/** Return why the harness run failed the check, or `undefined` when it passed. */\nfunction summarizeHarnessFailure(run: CheckRun, result: HarnessProcessResult): string | undefined {\n const { stdout, stderr } = result;\n if (result.failureReason !== undefined) return truncate(result.failureReason);\n if (result.exitCode !== 0) {\n return truncate(`harness exited with ${result.exitCode ?? 'a signal'}${stderr.trim() ? `: ${stderr.trim()}` : ''}`);\n }\n\n const resultLines = stdout.split(/\\r?\\n/).filter((line) => line.startsWith(TEST_CASE_RESULT_PREFIX));\n const testCaseResults = [];\n for (const line of resultLines) {\n let parsedResult;\n try {\n parsedResult = testCaseResultSchema.safeParse(JSON.parse(line.slice(TEST_CASE_RESULT_PREFIX.length)));\n } catch {\n parsedResult = undefined;\n }\n if (!parsedResult?.success) return truncate(`malformed test case result line: ${line}`);\n testCaseResults.push(parsedResult.data);\n }\n if (testCaseResults.length === 0) return 'no test case results were printed';\n\n if (run.expectation === 'accepted') {\n const rejectedResult = testCaseResults.find((result) => result.decisionCode !== DecisionCode.ACCEPTED);\n if (rejectedResult) {\n return truncate(\n `${decisionCodeNames.get(rejectedResult.decisionCode) ?? rejectedResult.decisionCode} on test case ${rejectedResult.testCaseId}${rejectedResult.stderr?.trim() ? `: ${rejectedResult.stderr.trim()}` : ''}`\n );\n }\n return undefined;\n }\n return testCaseResults.every((result) => result.decisionCode === DecisionCode.ACCEPTED)\n ? 'expected at least one failing test case, but all test cases were accepted'\n : undefined;\n}\n\nfunction parseCheckArgs(args: readonly string[]): CheckOptions {\n const options: CheckOptions = {\n rootDir: '.',\n // Serial by default: judging decides TIME_LIMIT_EXCEEDED from wall-clock time, so parallel\n // runs on a small CI runner could fail timing-sensitive problems non-deterministically.\n concurrency: 1,\n only: [],\n skip: [],\n };\n let hasRootDir = false;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--concurrency' || arg === '--only' || arg === '--skip') {\n const value = args[++index];\n if (value === undefined) throw new Error(`${arg} requires a value`);\n if (arg === '--concurrency') {\n options.concurrency = Number(value);\n if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {\n throw new Error(`--concurrency requires a positive integer, but got ${value}`);\n }\n } else if (arg === '--only') {\n options.only.push(value);\n } else {\n options.skip.push(value);\n }\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else if (hasRootDir) {\n throw new Error(`Only one root directory can be specified, but got both ${options.rootDir} and ${arg}`);\n } else {\n options.rootDir = arg;\n hasRootDir = true;\n }\n }\n return options;\n}\n\n/** Find directories containing `problem.md` or `<id>.problem.md`, without descending into found problems. */\nasync function findProblemDirs(rootDir: string): Promise<string[]> {\n const problemDirs: string[] = [];\n await visitDirectory(rootDir, problemDirs);\n return problemDirs.toSorted();\n}\n\nasync function visitDirectory(dir: string, problemDirs: string[]): Promise<void> {\n // Traversal errors (e.g. an unreadable subtree) must fail the check: skipping them silently\n // could report a green result while covering only part of the repository.\n const entries = await fs.readdir(dir, { withFileTypes: true });\n if (entries.some((entry) => entry.isFile() && (entry.name === 'problem.md' || entry.name.endsWith('.problem.md')))) {\n problemDirs.push(dir);\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n await visitDirectory(path.join(dir, entry.name), problemDirs);\n }\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction truncate(text: string): string {\n return text.length <= MAX_FAILURE_DETAIL_LENGTH ? text : `${text.slice(0, MAX_FAILURE_DETAIL_LENGTH)}...`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checkAllProblems.cjs","names":["DecisionCode","path","findDefaultStdioHarnessFiles","formatDefaultHarnessError","findModelAnswerDirs","readTestCases","readProblemMarkdownFrontMatter","judgesWithoutTestCases","MISSING_TEST_CASES_ERROR","findFailingModelAnswerDirs","copyProblemDirToTemporaryRoot","runHarnessProcess","forciblyRemoveDirectory","TEST_CASE_RESULT_PREFIX","testCaseResultSchema","fs"],"sources":["../../src/cli/checkAllProblems.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { findFailingModelAnswerDirs, findModelAnswerDirs } from '../helpers/findModelAnswerDirs.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { type HarnessProcessResult, runHarnessProcess } from '../helpers/runHarnessProcess.js';\nimport { copyProblemDirToTemporaryRoot, forciblyRemoveDirectory } from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { TEST_CASE_RESULT_PREFIX, testCaseResultSchema } from '../types/testCaseResult.js';\n\nimport { formatDefaultHarnessError, MISSING_TEST_CASES_ERROR } from './runSingleHarness.js';\n\nconst RUN_TIMEOUT_MS = 600_000;\nconst MAX_RUN_OUTPUT_BYTES = 64 * 1024 * 1024;\nconst MAX_FAILURE_DETAIL_LENGTH = 1000;\n\n// DecisionCode is a plain const object (not a TypeScript enum), so it has no reverse mapping.\nconst decisionCodeNames = new Map<number, string>(Object.entries(DecisionCode).map(([name, code]) => [code, name]));\n\ninterface CheckOptions {\n rootDir: string;\n concurrency: number;\n only: string[];\n skip: string[];\n}\n\ninterface CheckRun {\n problemDir: string;\n answerDir: string;\n /** Whether all test cases must be accepted (`model_answers`) or at least one must fail (`model_answers.fails`). */\n expectation: 'accepted' | 'rejected';\n}\n\n/**\n * Judge all model answers of all problems (directories containing `problem.md` or\n * `<id>.problem.md`) under a root directory: `model_answers/*` must be fully accepted and\n * `model_answers.fails/*` must fail at least one test case. Returns the process exit code.\n */\nexport async function checkAllProblems(args: readonly string[]): Promise<number> {\n const options = parseCheckArgs(args);\n const rootDir = path.resolve(options.rootDir);\n // Normalize to forward slashes so --only / --skip substrings like courses/foo match on Windows.\n const toRelative = (dir: string): string => (path.relative(rootDir, dir) || '.').replaceAll(path.sep, '/');\n\n const allProblemDirs = await findProblemDirs(rootDir);\n const problemDirs = allProblemDirs.filter((problemDir) => {\n const relativeDir = toRelative(problemDir);\n if (options.only.length > 0 && !options.only.some((substring) => relativeDir.includes(substring))) return false;\n return !options.skip.some((substring) => relativeDir.includes(substring));\n });\n if (problemDirs.length === 0) {\n console.error(\n allProblemDirs.length === 0\n ? `No problem directories (containing problem.md or <id>.problem.md) found under ${rootDir}.`\n : `All ${allProblemDirs.length} problem directories under ${rootDir} were excluded by --only/--skip.`\n );\n return 1;\n }\n\n const failures: string[] = [];\n const runs: CheckRun[] = [];\n for (const problemDir of problemDirs) {\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n failures.push(`${toRelative(problemDir)}: ${formatDefaultHarnessError(defaultHarnessFileNames)}`);\n continue;\n }\n\n const modelAnswerDirs = await findModelAnswerDirs(problemDir);\n if (modelAnswerDirs.length === 0) {\n failures.push(`${toRelative(problemDir)}: no model answers found under model_answers/`);\n continue;\n }\n\n // Without test cases, stdioJudgePreset prints a single accepted sentinel result, so a standard\n // problem with an empty or missing test_cases/ would otherwise pass without being judged —\n // unless static-analysis rules or manual scoring make the problem judgeable without them.\n if (!(await fileExists(path.join(problemDir, 'judge.ts')))) {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0) {\n let frontMatter;\n try {\n frontMatter = await readProblemMarkdownFrontMatter(problemDir);\n } catch (error) {\n failures.push(\n `${toRelative(problemDir)}: failed to read the problem markdown front matter: ${error instanceof Error ? error.message : String(error)}`\n );\n continue;\n }\n if (!judgesWithoutTestCases(frontMatter)) {\n failures.push(`${toRelative(problemDir)}: ${MISSING_TEST_CASES_ERROR}`);\n continue;\n }\n }\n }\n\n const failingModelAnswerDirs = await findFailingModelAnswerDirs(problemDir);\n runs.push(\n ...modelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'accepted' })),\n ...failingModelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'rejected' }))\n );\n }\n for (const failure of failures) console.error(`✗ ${failure}`);\n\n const cliEntryPath = path.resolve(process.argv[1] ?? '');\n let passedCount = 0;\n let nextRunIndex = 0;\n await Promise.all(\n Array.from({ length: Math.max(1, Math.min(options.concurrency, runs.length)) }, async () => {\n while (nextRunIndex < runs.length) {\n const run = runs[nextRunIndex++];\n if (!run) return;\n const label = `${toRelative(run.problemDir)} ${path.relative(run.problemDir, run.answerDir).replaceAll(path.sep, '/')}`;\n const failureDetail = await executeCheckRun(run, cliEntryPath);\n if (failureDetail === undefined) {\n passedCount++;\n console.info(`✓ ${label}`);\n } else {\n failures.push(`${label}: ${failureDetail}`);\n console.error(`✗ ${label}: ${failureDetail}`);\n }\n }\n })\n );\n\n console.info(\n `\\n${passedCount} passed, ${failures.length} failed (${runs.length} runs, ${problemDirs.length} problems)`\n );\n return failures.length === 0 ? 0 : 1;\n}\n\n/**\n * Run one answer directory through the problem's harness and return a failure detail, if any.\n * The problem directory is copied to a temporary location first so that judging (e.g. build\n * artifacts in answer directories) never modifies the checked repository.\n */\nasync function executeCheckRun(run: CheckRun, cliEntryPath: string): Promise<string | undefined> {\n let tempRoot: string;\n let copiedProblemDir: string;\n try {\n ({ tempRoot, copiedProblemDir } = await copyProblemDirToTemporaryRoot(run.problemDir));\n } catch (error) {\n return truncate(\n `failed to copy the problem directory to a temporary location: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n let harnessFailureDetail;\n let removedTempRoot;\n try {\n const result = await runHarnessProcess(\n ['run', cliEntryPath, 'judge', path.relative(run.problemDir, run.answerDir)],\n {\n cwd: copiedProblemDir,\n env: process.env,\n timeoutMs: RUN_TIMEOUT_MS,\n maxOutputBytes: MAX_RUN_OUTPUT_BYTES,\n tempRoot,\n }\n );\n harnessFailureDetail = summarizeHarnessFailure(run, result);\n } catch (error) {\n harnessFailureDetail = truncate(\n `harness execution failed: ${error instanceof Error ? error.message : String(error)}`\n );\n } finally {\n removedTempRoot = await forciblyRemoveDirectory(tempRoot);\n }\n if (removedTempRoot) return harnessFailureDetail;\n const removalFailureDetail = `failed to remove the temporary copy at ${tempRoot} (judged code may have left permission-locked files)`;\n return harnessFailureDetail === undefined\n ? removalFailureDetail\n : truncate(`${harnessFailureDetail}; ${removalFailureDetail}`);\n}\n\n/** Return why the harness run failed the check, or `undefined` when it passed. */\nfunction summarizeHarnessFailure(run: CheckRun, result: HarnessProcessResult): string | undefined {\n const { stdout, stderr } = result;\n if (result.failureReason !== undefined) return truncate(result.failureReason);\n if (result.exitCode !== 0) {\n return truncate(`harness exited with ${result.exitCode ?? 'a signal'}${stderr.trim() ? `: ${stderr.trim()}` : ''}`);\n }\n\n const resultLines = stdout.split(/\\r?\\n/).filter((line) => line.startsWith(TEST_CASE_RESULT_PREFIX));\n const testCaseResults = [];\n for (const line of resultLines) {\n let parsedResult;\n try {\n parsedResult = testCaseResultSchema.safeParse(JSON.parse(line.slice(TEST_CASE_RESULT_PREFIX.length)));\n } catch {\n parsedResult = undefined;\n }\n if (!parsedResult?.success) return truncate(`malformed test case result line: ${line}`);\n testCaseResults.push(parsedResult.data);\n }\n if (testCaseResults.length === 0) return 'no test case results were printed';\n\n if (run.expectation === 'accepted') {\n const rejectedResult = testCaseResults.find((result) => result.decisionCode !== DecisionCode.ACCEPTED);\n if (rejectedResult) {\n return truncate(\n `${decisionCodeNames.get(rejectedResult.decisionCode) ?? rejectedResult.decisionCode} on test case ${rejectedResult.testCaseId}${rejectedResult.stderr?.trim() ? `: ${rejectedResult.stderr.trim()}` : ''}`\n );\n }\n return undefined;\n }\n return testCaseResults.every((result) => result.decisionCode === DecisionCode.ACCEPTED)\n ? 'expected at least one failing test case, but all test cases were accepted'\n : undefined;\n}\n\nfunction parseCheckArgs(args: readonly string[]): CheckOptions {\n const options: CheckOptions = {\n rootDir: '.',\n // Serial by default: judging decides TIME_LIMIT_EXCEEDED from wall-clock time, so parallel\n // runs on a small CI runner could fail timing-sensitive problems non-deterministically.\n concurrency: 1,\n only: [],\n skip: [],\n };\n let hasRootDir = false;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--concurrency' || arg === '--only' || arg === '--skip') {\n const value = args[++index];\n if (value === undefined) throw new Error(`${arg} requires a value`);\n if (arg === '--concurrency') {\n options.concurrency = Number(value);\n if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {\n throw new Error(`--concurrency requires a positive integer, but got ${value}`);\n }\n } else if (arg === '--only') {\n options.only.push(value);\n } else {\n options.skip.push(value);\n }\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else if (hasRootDir) {\n throw new Error(`Only one root directory can be specified, but got both ${options.rootDir} and ${arg}`);\n } else {\n options.rootDir = arg;\n hasRootDir = true;\n }\n }\n return options;\n}\n\n/** Find directories containing `problem.md` or `<id>.problem.md`, without descending into found problems. */\nasync function findProblemDirs(rootDir: string): Promise<string[]> {\n const problemDirs: string[] = [];\n await visitDirectory(rootDir, problemDirs);\n return problemDirs.toSorted();\n}\n\nasync function visitDirectory(dir: string, problemDirs: string[]): Promise<void> {\n // Traversal errors (e.g. an unreadable subtree) must fail the check: skipping them silently\n // could report a green result while covering only part of the repository.\n const entries = await fs.readdir(dir, { withFileTypes: true });\n if (entries.some((entry) => entry.isFile() && (entry.name === 'problem.md' || entry.name.endsWith('.problem.md')))) {\n problemDirs.push(dir);\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n await visitDirectory(path.join(dir, entry.name), problemDirs);\n }\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction truncate(text: string): string {\n return text.length <= MAX_FAILURE_DETAIL_LENGTH ? text : `${text.slice(0, MAX_FAILURE_DETAIL_LENGTH)}...`;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{TEST_CASE_RESULT_PREFIX as e,testCaseResultSchema as t}from"../types/testCaseResult.js";import{findDefaultStdioHarnessFiles as n}from"../helpers/defaultStdioHarness.js";import{readTestCases as r}from"../helpers/readTestCases.js";import{copyProblemDirToTemporaryRoot as i,forciblyRemoveDirectory as a}from"../helpers/temporaryProblemDirCopy.js";import{judgesWithoutTestCases as o,readProblemMarkdownFrontMatter as s}from"../helpers/readProblemMarkdownFrontMatter.js";import{DecisionCode as c}from"../types/decisionCode.js";import{runHarnessProcess as l}from"../helpers/runHarnessProcess.js";import{findFailingModelAnswerDirs as u,findModelAnswerDirs as d}from"../helpers/findModelAnswerDirs.js";import{MISSING_TEST_CASES_ERROR as f,formatDefaultHarnessError as p}from"./runSingleHarness.js";import m from"node:path";import h from"node:fs/promises";const g=1e3,_=new Map(Object.entries(c).map(([e,t])=>[t,e]));async function v(e){let t=x(e),i=m.resolve(t.rootDir),a=e=>(m.relative(i,e)||`.`).replaceAll(m.sep,`/`),c=await S(i),l=c.filter(e=>{let n=a(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(l.length===0)return console.error(c.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${c.length} problem directories under ${i} were excluded by --only/--skip.`),1;let h=[],g=[];for(let e of l){let t=await n(e);if(t.length>0){h.push(`${a(e)}: ${p(t)}`);continue}let i=await d(e);if(i.length===0){h.push(`${a(e)}: no model answers found under model_answers/`);continue}if(!await w(m.join(e,`judge.ts`))&&(await r(m.join(e,`test_cases`))).length===0){let t;try{t=await s(e)}catch(t){h.push(`${a(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!o(t)){h.push(`${a(e)}: ${f}`);continue}}let c=await u(e);g.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...c.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of h)console.error(`✗ ${e}`);let _=m.resolve(process.argv[1]??``),v=0,b=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,g.length))},async()=>{for(;b<g.length;){let e=g[b++];if(!e)return;let t=`${a(e.problemDir)} ${m.relative(e.problemDir,e.answerDir).replaceAll(m.sep,`/`)}`,n=await y(e,_);n===void 0?(v++,console.info(`✓ ${t}`)):(h.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${v} passed, ${h.length} failed (${g.length} runs, ${l.length} problems)`),h.length===0?0:1}async function y(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i(e.problemDir))}catch(e){return T(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let o,s;try{o=b(e,await l([`run`,t,`judge`,m.relative(e.problemDir,e.answerDir)],{cwd:r,env:process.env,timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){o=T(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{s=await a(n)}if(s)return o;let c=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return o===void 0?c:T(`${o}; ${c}`)}function b(n,r){let{stdout:i,stderr:a}=r;if(r.failureReason!==void 0)return T(r.failureReason);if(r.exitCode!==0)return T(`harness exited with ${r.exitCode??`a signal`}${a.trim()?`: ${a.trim()}`:``}`);let o=i.split(/\r?\n/).filter(t=>t.startsWith(e)),s=[];for(let n of o){let r;try{r=t.safeParse(JSON.parse(n.slice(e.length)))}catch{r=void 0}if(!r?.success)return T(`malformed test case result line: ${n}`);s.push(r.data)}if(s.length===0)return`no test case results were printed`;if(n.expectation===`accepted`){let e=s.find(e=>e.decisionCode!==c.ACCEPTED);return e?T(`${_.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return s.every(e=>e.decisionCode===c.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function x(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function S(e){let t=[];return await C(e,t),t.toSorted()}async function C(e,t){let n=await h.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)
|
|
1
|
+
import{TEST_CASE_RESULT_PREFIX as e,testCaseResultSchema as t}from"../types/testCaseResult.js";import{findDefaultStdioHarnessFiles as n}from"../helpers/defaultStdioHarness.js";import{readTestCases as r}from"../helpers/readTestCases.js";import{copyProblemDirToTemporaryRoot as i,forciblyRemoveDirectory as a}from"../helpers/temporaryProblemDirCopy.js";import{judgesWithoutTestCases as o,readProblemMarkdownFrontMatter as s}from"../helpers/readProblemMarkdownFrontMatter.js";import{DecisionCode as c}from"../types/decisionCode.js";import{runHarnessProcess as l}from"../helpers/runHarnessProcess.js";import{findFailingModelAnswerDirs as u,findModelAnswerDirs as d}from"../helpers/findModelAnswerDirs.js";import{MISSING_TEST_CASES_ERROR as f,formatDefaultHarnessError as p}from"./runSingleHarness.js";import m from"node:path";import h from"node:fs/promises";const g=1e3,_=new Map(Object.entries(c).map(([e,t])=>[t,e]));async function v(e){let t=x(e),i=m.resolve(t.rootDir),a=e=>(m.relative(i,e)||`.`).replaceAll(m.sep,`/`),c=await S(i),l=c.filter(e=>{let n=a(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(l.length===0)return console.error(c.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${c.length} problem directories under ${i} were excluded by --only/--skip.`),1;let h=[],g=[];for(let e of l){let t=await n(e);if(t.length>0){h.push(`${a(e)}: ${p(t)}`);continue}let i=await d(e);if(i.length===0){h.push(`${a(e)}: no model answers found under model_answers/`);continue}if(!await w(m.join(e,`judge.ts`))&&(await r(m.join(e,`test_cases`))).length===0){let t;try{t=await s(e)}catch(t){h.push(`${a(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!o(t)){h.push(`${a(e)}: ${f}`);continue}}let c=await u(e);g.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...c.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of h)console.error(`✗ ${e}`);let _=m.resolve(process.argv[1]??``),v=0,b=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,g.length))},async()=>{for(;b<g.length;){let e=g[b++];if(!e)return;let t=`${a(e.problemDir)} ${m.relative(e.problemDir,e.answerDir).replaceAll(m.sep,`/`)}`,n=await y(e,_);n===void 0?(v++,console.info(`✓ ${t}`)):(h.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${v} passed, ${h.length} failed (${g.length} runs, ${l.length} problems)`),h.length===0?0:1}async function y(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i(e.problemDir))}catch(e){return T(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let o,s;try{o=b(e,await l([`run`,t,`judge`,m.relative(e.problemDir,e.answerDir)],{cwd:r,env:process.env,timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){o=T(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{s=await a(n)}if(s)return o;let c=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return o===void 0?c:T(`${o}; ${c}`)}function b(n,r){let{stdout:i,stderr:a}=r;if(r.failureReason!==void 0)return T(r.failureReason);if(r.exitCode!==0)return T(`harness exited with ${r.exitCode??`a signal`}${a.trim()?`: ${a.trim()}`:``}`);let o=i.split(/\r?\n/).filter(t=>t.startsWith(e)),s=[];for(let n of o){let r;try{r=t.safeParse(JSON.parse(n.slice(e.length)))}catch{r=void 0}if(!r?.success)return T(`malformed test case result line: ${n}`);s.push(r.data)}if(s.length===0)return`no test case results were printed`;if(n.expectation===`accepted`){let e=s.find(e=>e.decisionCode!==c.ACCEPTED);return e?T(`${_.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return s.every(e=>e.decisionCode===c.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function x(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function S(e){let t=[];return await C(e,t),t.toSorted()}async function C(e,t){let n=await h.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)r.isDirectory()&&r.name!==`node_modules`&&!r.name.startsWith(`.`)&&await C(m.join(e,r.name),t)}async function w(e){try{return await h.stat(e),!0}catch{return!1}}function T(e){return e.length<=g?e:`${e.slice(0,g)}...`}export{v as checkAllProblems};
|
|
2
2
|
//# sourceMappingURL=checkAllProblems.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkAllProblems.js","names":[],"sources":["../../src/cli/checkAllProblems.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { findFailingModelAnswerDirs, findModelAnswerDirs } from '../helpers/findModelAnswerDirs.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { type HarnessProcessResult, runHarnessProcess } from '../helpers/runHarnessProcess.js';\nimport { copyProblemDirToTemporaryRoot, forciblyRemoveDirectory } from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { TEST_CASE_RESULT_PREFIX, testCaseResultSchema } from '../types/testCaseResult.js';\n\nimport { formatDefaultHarnessError, MISSING_TEST_CASES_ERROR } from './runSingleHarness.js';\n\nconst RUN_TIMEOUT_MS = 600_000;\nconst MAX_RUN_OUTPUT_BYTES = 64 * 1024 * 1024;\nconst MAX_FAILURE_DETAIL_LENGTH = 1000;\n\n// DecisionCode is a plain const object (not a TypeScript enum), so it has no reverse mapping.\nconst decisionCodeNames = new Map<number, string>(Object.entries(DecisionCode).map(([name, code]) => [code, name]));\n\ninterface CheckOptions {\n rootDir: string;\n concurrency: number;\n only: string[];\n skip: string[];\n}\n\ninterface CheckRun {\n problemDir: string;\n answerDir: string;\n /** Whether all test cases must be accepted (`model_answers`) or at least one must fail (`model_answers.fails`). */\n expectation: 'accepted' | 'rejected';\n}\n\n/**\n * Judge all model answers of all problems (directories containing `problem.md` or\n * `<id>.problem.md`) under a root directory: `model_answers/*` must be fully accepted and\n * `model_answers.fails/*` must fail at least one test case. Returns the process exit code.\n */\nexport async function checkAllProblems(args: readonly string[]): Promise<number> {\n const options = parseCheckArgs(args);\n const rootDir = path.resolve(options.rootDir);\n // Normalize to forward slashes so --only / --skip substrings like courses/foo match on Windows.\n const toRelative = (dir: string): string => (path.relative(rootDir, dir) || '.').replaceAll(path.sep, '/');\n\n const allProblemDirs = await findProblemDirs(rootDir);\n const problemDirs = allProblemDirs.filter((problemDir) => {\n const relativeDir = toRelative(problemDir);\n if (options.only.length > 0 && !options.only.some((substring) => relativeDir.includes(substring))) return false;\n return !options.skip.some((substring) => relativeDir.includes(substring));\n });\n if (problemDirs.length === 0) {\n console.error(\n allProblemDirs.length === 0\n ? `No problem directories (containing problem.md or <id>.problem.md) found under ${rootDir}.`\n : `All ${allProblemDirs.length} problem directories under ${rootDir} were excluded by --only/--skip.`\n );\n return 1;\n }\n\n const failures: string[] = [];\n const runs: CheckRun[] = [];\n for (const problemDir of problemDirs) {\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n failures.push(`${toRelative(problemDir)}: ${formatDefaultHarnessError(defaultHarnessFileNames)}`);\n continue;\n }\n\n const modelAnswerDirs = await findModelAnswerDirs(problemDir);\n if (modelAnswerDirs.length === 0) {\n failures.push(`${toRelative(problemDir)}: no model answers found under model_answers/`);\n continue;\n }\n\n // Without test cases, stdioJudgePreset prints a single accepted sentinel result, so a standard\n // problem with an empty or missing test_cases/ would otherwise pass without being judged —\n // unless static-analysis rules or manual scoring make the problem judgeable without them.\n if (!(await fileExists(path.join(problemDir, 'judge.ts')))) {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0) {\n let frontMatter;\n try {\n frontMatter = await readProblemMarkdownFrontMatter(problemDir);\n } catch (error) {\n failures.push(\n `${toRelative(problemDir)}: failed to read the problem markdown front matter: ${error instanceof Error ? error.message : String(error)}`\n );\n continue;\n }\n if (!judgesWithoutTestCases(frontMatter)) {\n failures.push(`${toRelative(problemDir)}: ${MISSING_TEST_CASES_ERROR}`);\n continue;\n }\n }\n }\n\n const failingModelAnswerDirs = await findFailingModelAnswerDirs(problemDir);\n runs.push(\n ...modelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'accepted' })),\n ...failingModelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'rejected' }))\n );\n }\n for (const failure of failures) console.error(`✗ ${failure}`);\n\n const cliEntryPath = path.resolve(process.argv[1] ?? '');\n let passedCount = 0;\n let nextRunIndex = 0;\n await Promise.all(\n Array.from({ length: Math.max(1, Math.min(options.concurrency, runs.length)) }, async () => {\n while (nextRunIndex < runs.length) {\n const run = runs[nextRunIndex++];\n if (!run) return;\n const label = `${toRelative(run.problemDir)} ${path.relative(run.problemDir, run.answerDir).replaceAll(path.sep, '/')}`;\n const failureDetail = await executeCheckRun(run, cliEntryPath);\n if (failureDetail === undefined) {\n passedCount++;\n console.info(`✓ ${label}`);\n } else {\n failures.push(`${label}: ${failureDetail}`);\n console.error(`✗ ${label}: ${failureDetail}`);\n }\n }\n })\n );\n\n console.info(\n `\\n${passedCount} passed, ${failures.length} failed (${runs.length} runs, ${problemDirs.length} problems)`\n );\n return failures.length === 0 ? 0 : 1;\n}\n\n/**\n * Run one answer directory through the problem's harness and return a failure detail, if any.\n * The problem directory is copied to a temporary location first so that judging (e.g. build\n * artifacts in answer directories) never modifies the checked repository.\n */\nasync function executeCheckRun(run: CheckRun, cliEntryPath: string): Promise<string | undefined> {\n let tempRoot: string;\n let copiedProblemDir: string;\n try {\n ({ tempRoot, copiedProblemDir } = await copyProblemDirToTemporaryRoot(run.problemDir));\n } catch (error) {\n return truncate(\n `failed to copy the problem directory to a temporary location: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n let harnessFailureDetail;\n let removedTempRoot;\n try {\n const result = await runHarnessProcess(\n ['run', cliEntryPath, 'judge', path.relative(run.problemDir, run.answerDir)],\n {\n cwd: copiedProblemDir,\n env: process.env,\n timeoutMs: RUN_TIMEOUT_MS,\n maxOutputBytes: MAX_RUN_OUTPUT_BYTES,\n tempRoot,\n }\n );\n harnessFailureDetail = summarizeHarnessFailure(run, result);\n } catch (error) {\n harnessFailureDetail = truncate(\n `harness execution failed: ${error instanceof Error ? error.message : String(error)}`\n );\n } finally {\n removedTempRoot = await forciblyRemoveDirectory(tempRoot);\n }\n if (removedTempRoot) return harnessFailureDetail;\n const removalFailureDetail = `failed to remove the temporary copy at ${tempRoot} (judged code may have left permission-locked files)`;\n return harnessFailureDetail === undefined\n ? removalFailureDetail\n : truncate(`${harnessFailureDetail}; ${removalFailureDetail}`);\n}\n\n/** Return why the harness run failed the check, or `undefined` when it passed. */\nfunction summarizeHarnessFailure(run: CheckRun, result: HarnessProcessResult): string | undefined {\n const { stdout, stderr } = result;\n if (result.failureReason !== undefined) return truncate(result.failureReason);\n if (result.exitCode !== 0) {\n return truncate(`harness exited with ${result.exitCode ?? 'a signal'}${stderr.trim() ? `: ${stderr.trim()}` : ''}`);\n }\n\n const resultLines = stdout.split(/\\r?\\n/).filter((line) => line.startsWith(TEST_CASE_RESULT_PREFIX));\n const testCaseResults = [];\n for (const line of resultLines) {\n let parsedResult;\n try {\n parsedResult = testCaseResultSchema.safeParse(JSON.parse(line.slice(TEST_CASE_RESULT_PREFIX.length)));\n } catch {\n parsedResult = undefined;\n }\n if (!parsedResult?.success) return truncate(`malformed test case result line: ${line}`);\n testCaseResults.push(parsedResult.data);\n }\n if (testCaseResults.length === 0) return 'no test case results were printed';\n\n if (run.expectation === 'accepted') {\n const rejectedResult = testCaseResults.find((result) => result.decisionCode !== DecisionCode.ACCEPTED);\n if (rejectedResult) {\n return truncate(\n `${decisionCodeNames.get(rejectedResult.decisionCode) ?? rejectedResult.decisionCode} on test case ${rejectedResult.testCaseId}${rejectedResult.stderr?.trim() ? `: ${rejectedResult.stderr.trim()}` : ''}`\n );\n }\n return undefined;\n }\n return testCaseResults.every((result) => result.decisionCode === DecisionCode.ACCEPTED)\n ? 'expected at least one failing test case, but all test cases were accepted'\n : undefined;\n}\n\nfunction parseCheckArgs(args: readonly string[]): CheckOptions {\n const options: CheckOptions = {\n rootDir: '.',\n // Serial by default: judging decides TIME_LIMIT_EXCEEDED from wall-clock time, so parallel\n // runs on a small CI runner could fail timing-sensitive problems non-deterministically.\n concurrency: 1,\n only: [],\n skip: [],\n };\n let hasRootDir = false;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--concurrency' || arg === '--only' || arg === '--skip') {\n const value = args[++index];\n if (value === undefined) throw new Error(`${arg} requires a value`);\n if (arg === '--concurrency') {\n options.concurrency = Number(value);\n if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {\n throw new Error(`--concurrency requires a positive integer, but got ${value}`);\n }\n } else if (arg === '--only') {\n options.only.push(value);\n } else {\n options.skip.push(value);\n }\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else if (hasRootDir) {\n throw new Error(`Only one root directory can be specified, but got both ${options.rootDir} and ${arg}`);\n } else {\n options.rootDir = arg;\n hasRootDir = true;\n }\n }\n return options;\n}\n\n/** Find directories containing `problem.md` or `<id>.problem.md`, without descending into found problems. */\nasync function findProblemDirs(rootDir: string): Promise<string[]> {\n const problemDirs: string[] = [];\n await visitDirectory(rootDir, problemDirs);\n return problemDirs.toSorted();\n}\n\nasync function visitDirectory(dir: string, problemDirs: string[]): Promise<void> {\n // Traversal errors (e.g. an unreadable subtree) must fail the check: skipping them silently\n // could report a green result while covering only part of the repository.\n const entries = await fs.readdir(dir, { withFileTypes: true });\n if (entries.some((entry) => entry.isFile() && (entry.name === 'problem.md' || entry.name.endsWith('.problem.md')))) {\n problemDirs.push(dir);\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n await visitDirectory(path.join(dir, entry.name), problemDirs);\n }\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction truncate(text: string): string {\n return text.length <= MAX_FAILURE_DETAIL_LENGTH ? text : `${text.slice(0, MAX_FAILURE_DETAIL_LENGTH)}...`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checkAllProblems.js","names":[],"sources":["../../src/cli/checkAllProblems.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { findFailingModelAnswerDirs, findModelAnswerDirs } from '../helpers/findModelAnswerDirs.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { type HarnessProcessResult, runHarnessProcess } from '../helpers/runHarnessProcess.js';\nimport { copyProblemDirToTemporaryRoot, forciblyRemoveDirectory } from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { TEST_CASE_RESULT_PREFIX, testCaseResultSchema } from '../types/testCaseResult.js';\n\nimport { formatDefaultHarnessError, MISSING_TEST_CASES_ERROR } from './runSingleHarness.js';\n\nconst RUN_TIMEOUT_MS = 600_000;\nconst MAX_RUN_OUTPUT_BYTES = 64 * 1024 * 1024;\nconst MAX_FAILURE_DETAIL_LENGTH = 1000;\n\n// DecisionCode is a plain const object (not a TypeScript enum), so it has no reverse mapping.\nconst decisionCodeNames = new Map<number, string>(Object.entries(DecisionCode).map(([name, code]) => [code, name]));\n\ninterface CheckOptions {\n rootDir: string;\n concurrency: number;\n only: string[];\n skip: string[];\n}\n\ninterface CheckRun {\n problemDir: string;\n answerDir: string;\n /** Whether all test cases must be accepted (`model_answers`) or at least one must fail (`model_answers.fails`). */\n expectation: 'accepted' | 'rejected';\n}\n\n/**\n * Judge all model answers of all problems (directories containing `problem.md` or\n * `<id>.problem.md`) under a root directory: `model_answers/*` must be fully accepted and\n * `model_answers.fails/*` must fail at least one test case. Returns the process exit code.\n */\nexport async function checkAllProblems(args: readonly string[]): Promise<number> {\n const options = parseCheckArgs(args);\n const rootDir = path.resolve(options.rootDir);\n // Normalize to forward slashes so --only / --skip substrings like courses/foo match on Windows.\n const toRelative = (dir: string): string => (path.relative(rootDir, dir) || '.').replaceAll(path.sep, '/');\n\n const allProblemDirs = await findProblemDirs(rootDir);\n const problemDirs = allProblemDirs.filter((problemDir) => {\n const relativeDir = toRelative(problemDir);\n if (options.only.length > 0 && !options.only.some((substring) => relativeDir.includes(substring))) return false;\n return !options.skip.some((substring) => relativeDir.includes(substring));\n });\n if (problemDirs.length === 0) {\n console.error(\n allProblemDirs.length === 0\n ? `No problem directories (containing problem.md or <id>.problem.md) found under ${rootDir}.`\n : `All ${allProblemDirs.length} problem directories under ${rootDir} were excluded by --only/--skip.`\n );\n return 1;\n }\n\n const failures: string[] = [];\n const runs: CheckRun[] = [];\n for (const problemDir of problemDirs) {\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n failures.push(`${toRelative(problemDir)}: ${formatDefaultHarnessError(defaultHarnessFileNames)}`);\n continue;\n }\n\n const modelAnswerDirs = await findModelAnswerDirs(problemDir);\n if (modelAnswerDirs.length === 0) {\n failures.push(`${toRelative(problemDir)}: no model answers found under model_answers/`);\n continue;\n }\n\n // Without test cases, stdioJudgePreset prints a single accepted sentinel result, so a standard\n // problem with an empty or missing test_cases/ would otherwise pass without being judged —\n // unless static-analysis rules or manual scoring make the problem judgeable without them.\n if (!(await fileExists(path.join(problemDir, 'judge.ts')))) {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0) {\n let frontMatter;\n try {\n frontMatter = await readProblemMarkdownFrontMatter(problemDir);\n } catch (error) {\n failures.push(\n `${toRelative(problemDir)}: failed to read the problem markdown front matter: ${error instanceof Error ? error.message : String(error)}`\n );\n continue;\n }\n if (!judgesWithoutTestCases(frontMatter)) {\n failures.push(`${toRelative(problemDir)}: ${MISSING_TEST_CASES_ERROR}`);\n continue;\n }\n }\n }\n\n const failingModelAnswerDirs = await findFailingModelAnswerDirs(problemDir);\n runs.push(\n ...modelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'accepted' })),\n ...failingModelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'rejected' }))\n );\n }\n for (const failure of failures) console.error(`✗ ${failure}`);\n\n const cliEntryPath = path.resolve(process.argv[1] ?? '');\n let passedCount = 0;\n let nextRunIndex = 0;\n await Promise.all(\n Array.from({ length: Math.max(1, Math.min(options.concurrency, runs.length)) }, async () => {\n while (nextRunIndex < runs.length) {\n const run = runs[nextRunIndex++];\n if (!run) return;\n const label = `${toRelative(run.problemDir)} ${path.relative(run.problemDir, run.answerDir).replaceAll(path.sep, '/')}`;\n const failureDetail = await executeCheckRun(run, cliEntryPath);\n if (failureDetail === undefined) {\n passedCount++;\n console.info(`✓ ${label}`);\n } else {\n failures.push(`${label}: ${failureDetail}`);\n console.error(`✗ ${label}: ${failureDetail}`);\n }\n }\n })\n );\n\n console.info(\n `\\n${passedCount} passed, ${failures.length} failed (${runs.length} runs, ${problemDirs.length} problems)`\n );\n return failures.length === 0 ? 0 : 1;\n}\n\n/**\n * Run one answer directory through the problem's harness and return a failure detail, if any.\n * The problem directory is copied to a temporary location first so that judging (e.g. build\n * artifacts in answer directories) never modifies the checked repository.\n */\nasync function executeCheckRun(run: CheckRun, cliEntryPath: string): Promise<string | undefined> {\n let tempRoot: string;\n let copiedProblemDir: string;\n try {\n ({ tempRoot, copiedProblemDir } = await copyProblemDirToTemporaryRoot(run.problemDir));\n } catch (error) {\n return truncate(\n `failed to copy the problem directory to a temporary location: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n let harnessFailureDetail;\n let removedTempRoot;\n try {\n const result = await runHarnessProcess(\n ['run', cliEntryPath, 'judge', path.relative(run.problemDir, run.answerDir)],\n {\n cwd: copiedProblemDir,\n env: process.env,\n timeoutMs: RUN_TIMEOUT_MS,\n maxOutputBytes: MAX_RUN_OUTPUT_BYTES,\n tempRoot,\n }\n );\n harnessFailureDetail = summarizeHarnessFailure(run, result);\n } catch (error) {\n harnessFailureDetail = truncate(\n `harness execution failed: ${error instanceof Error ? error.message : String(error)}`\n );\n } finally {\n removedTempRoot = await forciblyRemoveDirectory(tempRoot);\n }\n if (removedTempRoot) return harnessFailureDetail;\n const removalFailureDetail = `failed to remove the temporary copy at ${tempRoot} (judged code may have left permission-locked files)`;\n return harnessFailureDetail === undefined\n ? removalFailureDetail\n : truncate(`${harnessFailureDetail}; ${removalFailureDetail}`);\n}\n\n/** Return why the harness run failed the check, or `undefined` when it passed. */\nfunction summarizeHarnessFailure(run: CheckRun, result: HarnessProcessResult): string | undefined {\n const { stdout, stderr } = result;\n if (result.failureReason !== undefined) return truncate(result.failureReason);\n if (result.exitCode !== 0) {\n return truncate(`harness exited with ${result.exitCode ?? 'a signal'}${stderr.trim() ? `: ${stderr.trim()}` : ''}`);\n }\n\n const resultLines = stdout.split(/\\r?\\n/).filter((line) => line.startsWith(TEST_CASE_RESULT_PREFIX));\n const testCaseResults = [];\n for (const line of resultLines) {\n let parsedResult;\n try {\n parsedResult = testCaseResultSchema.safeParse(JSON.parse(line.slice(TEST_CASE_RESULT_PREFIX.length)));\n } catch {\n parsedResult = undefined;\n }\n if (!parsedResult?.success) return truncate(`malformed test case result line: ${line}`);\n testCaseResults.push(parsedResult.data);\n }\n if (testCaseResults.length === 0) return 'no test case results were printed';\n\n if (run.expectation === 'accepted') {\n const rejectedResult = testCaseResults.find((result) => result.decisionCode !== DecisionCode.ACCEPTED);\n if (rejectedResult) {\n return truncate(\n `${decisionCodeNames.get(rejectedResult.decisionCode) ?? rejectedResult.decisionCode} on test case ${rejectedResult.testCaseId}${rejectedResult.stderr?.trim() ? `: ${rejectedResult.stderr.trim()}` : ''}`\n );\n }\n return undefined;\n }\n return testCaseResults.every((result) => result.decisionCode === DecisionCode.ACCEPTED)\n ? 'expected at least one failing test case, but all test cases were accepted'\n : undefined;\n}\n\nfunction parseCheckArgs(args: readonly string[]): CheckOptions {\n const options: CheckOptions = {\n rootDir: '.',\n // Serial by default: judging decides TIME_LIMIT_EXCEEDED from wall-clock time, so parallel\n // runs on a small CI runner could fail timing-sensitive problems non-deterministically.\n concurrency: 1,\n only: [],\n skip: [],\n };\n let hasRootDir = false;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--concurrency' || arg === '--only' || arg === '--skip') {\n const value = args[++index];\n if (value === undefined) throw new Error(`${arg} requires a value`);\n if (arg === '--concurrency') {\n options.concurrency = Number(value);\n if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {\n throw new Error(`--concurrency requires a positive integer, but got ${value}`);\n }\n } else if (arg === '--only') {\n options.only.push(value);\n } else {\n options.skip.push(value);\n }\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else if (hasRootDir) {\n throw new Error(`Only one root directory can be specified, but got both ${options.rootDir} and ${arg}`);\n } else {\n options.rootDir = arg;\n hasRootDir = true;\n }\n }\n return options;\n}\n\n/** Find directories containing `problem.md` or `<id>.problem.md`, without descending into found problems. */\nasync function findProblemDirs(rootDir: string): Promise<string[]> {\n const problemDirs: string[] = [];\n await visitDirectory(rootDir, problemDirs);\n return problemDirs.toSorted();\n}\n\nasync function visitDirectory(dir: string, problemDirs: string[]): Promise<void> {\n // Traversal errors (e.g. an unreadable subtree) must fail the check: skipping them silently\n // could report a green result while covering only part of the repository.\n const entries = await fs.readdir(dir, { withFileTypes: true });\n if (entries.some((entry) => entry.isFile() && (entry.name === 'problem.md' || entry.name.endsWith('.problem.md')))) {\n problemDirs.push(dir);\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n await visitDirectory(path.join(dir, entry.name), problemDirs);\n }\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction truncate(text: string): string {\n return text.length <= MAX_FAILURE_DETAIL_LENGTH ? text : `${text.slice(0, MAX_FAILURE_DETAIL_LENGTH)}...`;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";function e(e,t){return n(e,t,{removeStrings:!1})}function t(e,t){return n(e,t,{removeStrings:!0})}function n(e,t,n,a=r(e)){if(!e.comments?.length&&(!n.removeStrings||!e.strings?.length))return t;let s=[],c=0;for(;c<t.length;){let r;for(let e of a){let a=e.openRegExp;a.lastIndex=c;let o=a.exec(t);o&&i(o,e,r,n)&&(r={grammar:e,match:o})}if(r){let i=r.match.index;if(!r.grammar.isComment&&n.removeStrings&&(i=d(t,r.match.index)),s.push(t.slice(c,i)),r.grammar.isComment){let e=r.grammar.closeRegExp;e.lastIndex=r.match.index+r.match[0].length;let n=e.exec(t);c=n?n.index+n[0].length:t.length}else{c=o(t,r.match.index,r.grammar);let i=t.slice(r.match.index,c);n.removeStrings?s.push(p(e,a,r.grammar,t,r.match.index,i)):s.push(i)}}else s.push(t.slice(c)),c=t.length}return s.join(``)}function r(e){return[...e.comments?.map(e=>({closeRegExp:a(e.close??/(?=\n)/g),isComment:!0,openRegExp:a(e.open)}))??[],...e.strings?.map(e=>({closeRegExp:a(e.close),isComment:!1,openRegExp:a(e.open),templateSyntax:e.templateSyntax}))??[]]}function i(e,t,n,r){return!n||e.index<n.match.index?!0:e.index>n.match.index?!1:r.removeStrings&&n.grammar.isComment&&!t.isComment}function a(e){return new RegExp(e,e.flags.includes(`g`)?e.flags:`${e.flags}g`)}function o(e,t,n){if(n.templateSyntax===`kotlin`)return s(e,t);if(e[t]==="`")return c(e,t);if(_(e,t))return l(e,t);let r=n.closeRegExp;r.lastIndex=t+1;let i=r.exec(e);return i?i.index+i[0].length:e.length}function s(e,t){let n=u(e,t),r=n.length===3,i=t+n.length;for(;i<e.length;){if(e.startsWith(n,i))return i+n.length;if(!r&&e[i]===`\\`){i+=2;continue}if(e[i]===`$`&&e[i+1]===`{`){i=b(e,i+2,`}`,{slashStartsComments:!0,slashStartsRegExp:!1,supportsKotlinStrings:!0}).endIndex+1;continue}i+=1}return e.length}function c(e,t){let n=t+1;for(;n<e.length;){if(e[n]===`\\`){n+=2;continue}if(e[n]==="`")return n+1;if(e[n]===`$`&&e[n+1]===`{`){n=b(e,n+2,`}`,{slashStartsRegExp:!0}).endIndex+1;continue}n+=1}return e.length}function l(e,t){let n=u(e,t),r=t+n.length;for(;r<e.length;){if(e.startsWith(n,r))return r+n.length;if(e[r]===`{`&&e[r+1]===`{`){r+=2;continue}if(e[r]===`{`){r=b(e,r+1,`}`,{slashStartsRegExp:!1}).endIndex+1;continue}r+=e[r]===`\\`?2:1}return e.length}function u(e,t){let n=e.slice(t,t+3);return n===`'''`||n===`"""`?n:e[t]??``}function d(e,t){let n=f(e,t);return n?t-n.length:t}function f(e,t){if(e[t]!==`"`&&e[t]!==`'`)return;let n=t-1;for(;n>=0&&/[A-Za-z]/.test(e[n]??``);)--n;let r=e.slice(n+1,t);if(
|
|
1
|
+
"use strict";function e(e,t){return n(e,t,{removeStrings:!1})}function t(e,t){return n(e,t,{removeStrings:!0})}function n(e,t,n,a=r(e)){if(!e.comments?.length&&(!n.removeStrings||!e.strings?.length))return t;let s=[],c=0;for(;c<t.length;){let r;for(let e of a){let a=e.openRegExp;a.lastIndex=c;let o=a.exec(t);o&&i(o,e,r,n)&&(r={grammar:e,match:o})}if(r){let i=r.match.index;if(!r.grammar.isComment&&n.removeStrings&&(i=d(t,r.match.index)),s.push(t.slice(c,i)),r.grammar.isComment){let e=r.grammar.closeRegExp;e.lastIndex=r.match.index+r.match[0].length;let n=e.exec(t);c=n?n.index+n[0].length:t.length}else{c=o(t,r.match.index,r.grammar);let i=t.slice(r.match.index,c);n.removeStrings?s.push(p(e,a,r.grammar,t,r.match.index,i)):s.push(i)}}else s.push(t.slice(c)),c=t.length}return s.join(``)}function r(e){return[...e.comments?.map(e=>({closeRegExp:a(e.close??/(?=\n)/g),isComment:!0,openRegExp:a(e.open)}))??[],...e.strings?.map(e=>({closeRegExp:a(e.close),isComment:!1,openRegExp:a(e.open),templateSyntax:e.templateSyntax}))??[]]}function i(e,t,n,r){return!n||e.index<n.match.index?!0:e.index>n.match.index?!1:r.removeStrings&&n.grammar.isComment&&!t.isComment}function a(e){return new RegExp(e,e.flags.includes(`g`)?e.flags:`${e.flags}g`)}function o(e,t,n){if(n.templateSyntax===`kotlin`)return s(e,t);if(e[t]==="`")return c(e,t);if(_(e,t))return l(e,t);let r=n.closeRegExp;r.lastIndex=t+1;let i=r.exec(e);return i?i.index+i[0].length:e.length}function s(e,t){let n=u(e,t),r=n.length===3,i=t+n.length;for(;i<e.length;){if(e.startsWith(n,i))return i+n.length;if(!r&&e[i]===`\\`){i+=2;continue}if(e[i]===`$`&&e[i+1]===`{`){i=b(e,i+2,`}`,{slashStartsComments:!0,slashStartsRegExp:!1,supportsKotlinStrings:!0}).endIndex+1;continue}i+=1}return e.length}function c(e,t){let n=t+1;for(;n<e.length;){if(e[n]===`\\`){n+=2;continue}if(e[n]==="`")return n+1;if(e[n]===`$`&&e[n+1]===`{`){n=b(e,n+2,`}`,{slashStartsRegExp:!0}).endIndex+1;continue}n+=1}return e.length}function l(e,t){let n=u(e,t),r=t+n.length;for(;r<e.length;){if(e.startsWith(n,r))return r+n.length;if(e[r]===`{`&&e[r+1]===`{`){r+=2;continue}if(e[r]===`{`){r=b(e,r+1,`}`,{slashStartsRegExp:!1}).endIndex+1;continue}r+=e[r]===`\\`?2:1}return e.length}function u(e,t){let n=e.slice(t,t+3);return n===`'''`||n===`"""`?n:e[t]??``}function d(e,t){let n=f(e,t);return n?t-n.length:t}function f(e,t){if(e[t]!==`"`&&e[t]!==`'`)return;let n=t-1;for(;n>=0&&/[A-Za-z]/.test(e[n]??``);)--n;let r=e.slice(n+1,t);if(r&&/^[bfru]+$/i.test(r)&&!(n>=0&&/[\dA-Za-z_]/.test(e[n]??``)))return r}function p(e,t,n,r,i,a){return n.templateSyntax===`kotlin`?m(e,t,a):a.startsWith("`")?v(e,t,a):_(r,i)?y(e,t,a):``}function m(e,t,r){let i=u(r,0).length,a=i===3,o=[],s=i;for(;s<r.length-i;){if(!a&&r[s]===`\\`){s+=2;continue}if(r[s]!==`$`){s+=1;continue}if(r[s+1]===`{`){let i=b(r,s+2,`}`,{slashStartsComments:!0,slashStartsRegExp:!1,supportsKotlinStrings:!0});o.push(n(e,i.content,{removeStrings:!0},t)),s=i.endIndex+1;continue}if(h(r[s+1])){let e=s+2;for(;g(r[e]);)e+=1;o.push(r.slice(s+1,e)),s=e;continue}s+=1}return o.join(`
|
|
2
2
|
`)}function h(e){return e!==void 0&&/[\p{L}_]/u.test(e)}function g(e){return e!==void 0&&/[\p{L}\p{N}_]/u.test(e)}function _(e,t){return f(e,t)?.toLowerCase().includes(`f`)??!1}function v(e,t,r){let i=[],a=1;for(;a<r.length-1;){if(r[a]===`\\`){a+=2;continue}if(r[a]===`$`&&r[a+1]===`{`){let o=b(r,a+2,`}`,{slashStartsRegExp:!0});i.push(n(e,o.content,{removeStrings:!0},t)),a=o.endIndex+1;continue}a+=1}return i.join(`
|
|
3
3
|
`)}function y(e,t,r){let i=u(r,0).length,a=[],o=i;for(;o<r.length-i;){if(r[o]===`{`&&r[o+1]===`{`){o+=2;continue}if(r[o]===`{`){let i=b(r,o+1,`}`,{slashStartsRegExp:!1});a.push(n(e,i.content,{removeStrings:!0},t)),o=i.endIndex+1;continue}o+=r[o]===`\\`?2:1}return a.join(`
|
|
4
4
|
`)}function b(e,t,n,r){let i=1,a=t;for(;a<e.length;){let o=e[a];if(o===`"`&&r.supportsKotlinStrings){a=s(e,a);continue}if(o===`"`||o===`'`||o==="`"){a=x(e,a);continue}if((r.slashStartsComments||r.slashStartsRegExp)&&e[a]===`/`&&e[a+1]===`/`){a=C(e,a);continue}if((r.slashStartsComments||r.slashStartsRegExp)&&e[a]===`/`&&e[a+1]===`*`){a=w(e,a);continue}if(r.slashStartsRegExp&&o===`/`){a=S(e,a);continue}if(o===`{`&&(i+=1),o===n&&(--i,i===0))return{content:e.slice(t,a),endIndex:a};a+=1}return{content:e.slice(t),endIndex:e.length}}function x(e,t){let n=u(e,t),r=t+n.length;for(;r<e.length;){if(e[r]===`\\`){r+=2;continue}if(e.startsWith(n,r))return r+n.length;r+=1}return r}function S(e,t){let n=t+1,r=!1;for(;n<e.length;){if(e[n]===`\\`){n+=2;continue}if(e[n]===`[`&&(r=!0),e[n]===`]`&&(r=!1),e[n]===`/`&&!r){for(n+=1;/[A-Za-z]/.test(e[n]??``);)n+=1;return n}if(e[n]===`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeCommentsInSourceCode.cjs","names":[],"sources":["../../src/helpers/removeCommentsInSourceCode.ts"],"sourcesContent":["import type { LanguageDefinition } from '../types/language.js';\n\nexport type SourceCodeGrammar = NonNullable<LanguageDefinition['grammer']>;\n\n/** Removes comments from source code according to the provided language grammar. */\nexport function removeCommentsInSourceCode(grammar: SourceCodeGrammar, sourceCode: string): string {\n return removeCommentsAndMaybeStringsInSourceCode(grammar, sourceCode, { removeStrings: false });\n}\n\n/** Removes comments and string literal contents while preserving executable interpolation expressions. */\nexport function removeCommentsAndStringsInSourceCode(grammar: SourceCodeGrammar, sourceCode: string): string {\n return removeCommentsAndMaybeStringsInSourceCode(grammar, sourceCode, { removeStrings: true });\n}\n\ninterface CommentOrStringGrammar {\n closeRegExp: RegExp;\n isComment: boolean;\n openRegExp: RegExp;\n templateSyntax?: 'kotlin';\n}\n\ninterface BalancedExpressionOptions {\n slashStartsComments?: boolean;\n slashStartsRegExp: boolean;\n supportsKotlinStrings?: boolean;\n}\n\nfunction removeCommentsAndMaybeStringsInSourceCode(\n grammar: SourceCodeGrammar,\n sourceCode: string,\n options: { removeStrings: boolean },\n commentOrStringGrammars = compileCommentOrStringGrammars(grammar)\n): string {\n if (!grammar.comments?.length && (!options.removeStrings || !grammar.strings?.length)) return sourceCode;\n\n const newSourceCodeSlices: string[] = [];\n\n let lastIndex = 0;\n\n while (lastIndex < sourceCode.length) {\n let first: { grammar: CommentOrStringGrammar; match: RegExpExecArray } | undefined;\n\n for (const commentOrStringGrammer of commentOrStringGrammars) {\n const startRegExp = commentOrStringGrammer.openRegExp;\n startRegExp.lastIndex = lastIndex;\n\n const match = startRegExp.exec(sourceCode);\n\n if (match && shouldPreferMatch(match, commentOrStringGrammer, first, options)) {\n first = { grammar: commentOrStringGrammer, match };\n }\n }\n\n if (first) {\n let stringPrefixStartIndex = first.match.index;\n if (!first.grammar.isComment && options.removeStrings) {\n stringPrefixStartIndex = getPythonStringPrefixStartIndex(sourceCode, first.match.index);\n }\n newSourceCodeSlices.push(sourceCode.slice(lastIndex, stringPrefixStartIndex));\n\n if (!first.grammar.isComment) {\n lastIndex = findStringEndIndex(sourceCode, first.match.index, first.grammar);\n const stringLiteral = sourceCode.slice(first.match.index, lastIndex);\n if (options.removeStrings) {\n newSourceCodeSlices.push(\n preserveStringInterpolationExpressions(\n grammar,\n commentOrStringGrammars,\n first.grammar,\n sourceCode,\n first.match.index,\n stringLiteral\n )\n );\n } else {\n newSourceCodeSlices.push(stringLiteral);\n }\n } else {\n const closeRegExp = first.grammar.closeRegExp;\n closeRegExp.lastIndex = first.match.index + first.match[0].length;\n\n const match = closeRegExp.exec(sourceCode);\n\n lastIndex = match ? match.index + match[0].length : sourceCode.length;\n }\n } else {\n newSourceCodeSlices.push(sourceCode.slice(lastIndex));\n lastIndex = sourceCode.length;\n }\n }\n\n return newSourceCodeSlices.join('');\n}\n\nfunction compileCommentOrStringGrammars(grammar: SourceCodeGrammar): readonly CommentOrStringGrammar[] {\n return [\n ...(grammar.comments?.map((v) => ({\n closeRegExp: makeGlobalRegExp(v.close ?? /(?=\\n)/g),\n isComment: true,\n openRegExp: makeGlobalRegExp(v.open),\n })) ?? []),\n ...(grammar.strings?.map((v) => ({\n closeRegExp: makeGlobalRegExp(v.close),\n isComment: false,\n openRegExp: makeGlobalRegExp(v.open),\n templateSyntax: v.templateSyntax,\n })) ?? []),\n ] satisfies CommentOrStringGrammar[];\n}\n\nfunction shouldPreferMatch(\n match: RegExpExecArray,\n grammar: CommentOrStringGrammar,\n first: { grammar: CommentOrStringGrammar; match: RegExpExecArray } | undefined,\n options: { removeStrings: boolean }\n): boolean {\n if (!first) return true;\n if (match.index < first.match.index) return true;\n if (match.index > first.match.index) return false;\n return options.removeStrings && first.grammar.isComment && !grammar.isComment;\n}\n\nfunction makeGlobalRegExp(regExp: RegExp): RegExp {\n return new RegExp(regExp, regExp.flags.includes('g') ? regExp.flags : `${regExp.flags}g`);\n}\n\nfunction findStringEndIndex(sourceCode: string, stringStartIndex: number, grammar: CommentOrStringGrammar): number {\n if (grammar.templateSyntax === 'kotlin') return findKotlinStringEndIndex(sourceCode, stringStartIndex);\n if (sourceCode[stringStartIndex] === '`') return findJavaScriptTemplateEndIndex(sourceCode, stringStartIndex);\n if (hasPythonFStringPrefix(sourceCode, stringStartIndex))\n return findPythonFStringEndIndex(sourceCode, stringStartIndex);\n\n const closeRegExp = grammar.closeRegExp;\n closeRegExp.lastIndex = stringStartIndex + 1;\n const match = closeRegExp.exec(sourceCode);\n return match ? match.index + match[0].length : sourceCode.length;\n}\n\nfunction findKotlinStringEndIndex(sourceCode: string, stringStartIndex: number): number {\n const quote = getStringQuote(sourceCode, stringStartIndex);\n const isRawString = quote.length === 3;\n let index = stringStartIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n if (!isRawString && sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '$' && sourceCode[index + 1] === '{') {\n index =\n readBalancedExpression(sourceCode, index + 2, '}', {\n slashStartsComments: true,\n slashStartsRegExp: false,\n supportsKotlinStrings: true,\n }).endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return sourceCode.length;\n}\n\nfunction findJavaScriptTemplateEndIndex(sourceCode: string, stringStartIndex: number): number {\n let index = stringStartIndex + 1;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '`') return index + 1;\n if (sourceCode[index] === '$' && sourceCode[index + 1] === '{') {\n index = readBalancedExpression(sourceCode, index + 2, '}', { slashStartsRegExp: true }).endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return sourceCode.length;\n}\n\nfunction findPythonFStringEndIndex(sourceCode: string, stringStartIndex: number): number {\n const quote = getStringQuote(sourceCode, stringStartIndex);\n let index = stringStartIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n if (sourceCode[index] === '{' && sourceCode[index + 1] === '{') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '{') {\n index = readBalancedExpression(sourceCode, index + 1, '}', { slashStartsRegExp: false }).endIndex + 1;\n continue;\n }\n index += sourceCode[index] === '\\\\' ? 2 : 1;\n }\n\n return sourceCode.length;\n}\n\nfunction getStringQuote(sourceCode: string, stringStartIndex: number): string {\n const threeCharacterQuote = sourceCode.slice(stringStartIndex, stringStartIndex + 3);\n if (threeCharacterQuote === \"'''\" || threeCharacterQuote === '\"\"\"') return threeCharacterQuote;\n return sourceCode[stringStartIndex] ?? '';\n}\n\nfunction getPythonStringPrefixStartIndex(sourceCode: string, stringStartIndex: number): number {\n const prefix = getPythonStringPrefix(sourceCode, stringStartIndex);\n return prefix ? stringStartIndex - prefix.length : stringStartIndex;\n}\n\nfunction getPythonStringPrefix(sourceCode: string, stringStartIndex: number): string | undefined {\n if (sourceCode[stringStartIndex] !== '\"' && sourceCode[stringStartIndex] !== \"'\") return undefined;\n let index = stringStartIndex - 1;\n while (index >= 0 && /[A-Za-z]/.test(sourceCode[index] ?? '')) index -= 1;\n\n const prefix = sourceCode.slice(index + 1, stringStartIndex);\n if (!prefix || !/^[bfru]+$/i.test(prefix)) return undefined;\n if (index >= 0 && /[\\dA-Za-z_]/.test(sourceCode[index] ?? '')) return undefined;\n\n return prefix;\n}\n\nfunction preserveStringInterpolationExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringGrammar: CommentOrStringGrammar,\n sourceCode: string,\n stringStartIndex: number,\n stringLiteral: string\n): string {\n if (stringGrammar.templateSyntax === 'kotlin')\n return preserveKotlinStringTemplateExpressions(grammar, commentOrStringGrammars, stringLiteral);\n if (stringLiteral.startsWith('`'))\n return preserveJavaScriptTemplateExpressions(grammar, commentOrStringGrammars, stringLiteral);\n if (hasPythonFStringPrefix(sourceCode, stringStartIndex))\n return preservePythonFStringExpressions(grammar, commentOrStringGrammars, stringLiteral);\n return '';\n}\n\nfunction preserveKotlinStringTemplateExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const quoteLength = getStringQuote(stringLiteral, 0).length;\n const isRawString = quoteLength === 3;\n const expressions: string[] = [];\n let index = quoteLength;\n\n while (index < stringLiteral.length - quoteLength) {\n if (!isRawString && stringLiteral[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] !== '$') {\n index += 1;\n continue;\n }\n if (stringLiteral[index + 1] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 2, '}', {\n slashStartsComments: true,\n slashStartsRegExp: false,\n supportsKotlinStrings: true,\n });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n if (isKotlinIdentifierStart(stringLiteral[index + 1])) {\n let endIndex = index + 2;\n while (isKotlinIdentifierPart(stringLiteral[endIndex])) endIndex += 1;\n expressions.push(stringLiteral.slice(index + 1, endIndex));\n index = endIndex;\n continue;\n }\n index += 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction isKotlinIdentifierStart(character: string | undefined): boolean {\n return character !== undefined && /[\\p{L}_]/u.test(character);\n}\n\nfunction isKotlinIdentifierPart(character: string | undefined): boolean {\n return character !== undefined && /[\\p{L}\\p{N}_]/u.test(character);\n}\n\nfunction hasPythonFStringPrefix(sourceCode: string, stringStartIndex: number): boolean {\n return getPythonStringPrefix(sourceCode, stringStartIndex)?.toLowerCase().includes('f') ?? false;\n}\n\nfunction preserveJavaScriptTemplateExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const expressions: string[] = [];\n let index = 1;\n\n while (index < stringLiteral.length - 1) {\n if (stringLiteral[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] === '$' && stringLiteral[index + 1] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 2, '}', { slashStartsRegExp: true });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction preservePythonFStringExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const quoteLength = getStringQuote(stringLiteral, 0).length;\n const expressions: string[] = [];\n let index = quoteLength;\n\n while (index < stringLiteral.length - quoteLength) {\n if (stringLiteral[index] === '{' && stringLiteral[index + 1] === '{') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 1, '}', { slashStartsRegExp: false });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n index += stringLiteral[index] === '\\\\' ? 2 : 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction readBalancedExpression(\n sourceCode: string,\n startIndex: number,\n closeChar: string,\n options: BalancedExpressionOptions\n): { content: string; endIndex: number } {\n let depth = 1;\n let index = startIndex;\n\n while (index < sourceCode.length) {\n const char = sourceCode[index];\n if (char === '\"' && options.supportsKotlinStrings) {\n index = findKotlinStringEndIndex(sourceCode, index);\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n index = skipQuotedSpan(sourceCode, index);\n continue;\n }\n if (\n (options.slashStartsComments || options.slashStartsRegExp) &&\n sourceCode[index] === '/' &&\n sourceCode[index + 1] === '/'\n ) {\n index = skipJavaScriptSingleLineComment(sourceCode, index);\n continue;\n }\n if (\n (options.slashStartsComments || options.slashStartsRegExp) &&\n sourceCode[index] === '/' &&\n sourceCode[index + 1] === '*'\n ) {\n index = skipJavaScriptBlockComment(sourceCode, index);\n continue;\n }\n if (options.slashStartsRegExp && char === '/') {\n index = skipJavaScriptRegExpLiteral(sourceCode, index);\n continue;\n }\n if (char === '{') depth += 1;\n if (char === closeChar) {\n depth -= 1;\n if (depth === 0) return { content: sourceCode.slice(startIndex, index), endIndex: index };\n }\n index += 1;\n }\n\n return { content: sourceCode.slice(startIndex), endIndex: sourceCode.length };\n}\n\nfunction skipQuotedSpan(sourceCode: string, startIndex: number): number {\n const quote = getStringQuote(sourceCode, startIndex);\n let index = startIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n index += 1;\n }\n\n return index;\n}\n\nfunction skipJavaScriptRegExpLiteral(sourceCode: string, startIndex: number): number {\n let index = startIndex + 1;\n let inCharacterClass = false;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '[') inCharacterClass = true;\n if (sourceCode[index] === ']') inCharacterClass = false;\n if (sourceCode[index] === '/' && !inCharacterClass) {\n index += 1;\n while (/[A-Za-z]/.test(sourceCode[index] ?? '')) index += 1;\n return index;\n }\n if (sourceCode[index] === '\\n') return startIndex + 1;\n index += 1;\n }\n\n return startIndex + 1;\n}\n\nfunction skipJavaScriptSingleLineComment(sourceCode: string, startIndex: number): number {\n const newLineIndex = sourceCode.indexOf('\\n', startIndex + 2);\n return newLineIndex === -1 ? sourceCode.length : newLineIndex;\n}\n\nfunction skipJavaScriptBlockComment(sourceCode: string, startIndex: number): number {\n const endIndex = sourceCode.indexOf('*/', startIndex + 2);\n return endIndex === -1 ? sourceCode.length : endIndex + 2;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"removeCommentsInSourceCode.cjs","names":[],"sources":["../../src/helpers/removeCommentsInSourceCode.ts"],"sourcesContent":["import type { LanguageDefinition } from '../types/language.js';\n\nexport type SourceCodeGrammar = NonNullable<LanguageDefinition['grammer']>;\n\n/** Removes comments from source code according to the provided language grammar. */\nexport function removeCommentsInSourceCode(grammar: SourceCodeGrammar, sourceCode: string): string {\n return removeCommentsAndMaybeStringsInSourceCode(grammar, sourceCode, { removeStrings: false });\n}\n\n/** Removes comments and string literal contents while preserving executable interpolation expressions. */\nexport function removeCommentsAndStringsInSourceCode(grammar: SourceCodeGrammar, sourceCode: string): string {\n return removeCommentsAndMaybeStringsInSourceCode(grammar, sourceCode, { removeStrings: true });\n}\n\ninterface CommentOrStringGrammar {\n closeRegExp: RegExp;\n isComment: boolean;\n openRegExp: RegExp;\n templateSyntax?: 'kotlin';\n}\n\ninterface BalancedExpressionOptions {\n slashStartsComments?: boolean;\n slashStartsRegExp: boolean;\n supportsKotlinStrings?: boolean;\n}\n\nfunction removeCommentsAndMaybeStringsInSourceCode(\n grammar: SourceCodeGrammar,\n sourceCode: string,\n options: { removeStrings: boolean },\n commentOrStringGrammars = compileCommentOrStringGrammars(grammar)\n): string {\n if (!grammar.comments?.length && (!options.removeStrings || !grammar.strings?.length)) return sourceCode;\n\n const newSourceCodeSlices: string[] = [];\n\n let lastIndex = 0;\n\n while (lastIndex < sourceCode.length) {\n let first: { grammar: CommentOrStringGrammar; match: RegExpExecArray } | undefined;\n\n for (const commentOrStringGrammer of commentOrStringGrammars) {\n const startRegExp = commentOrStringGrammer.openRegExp;\n startRegExp.lastIndex = lastIndex;\n\n const match = startRegExp.exec(sourceCode);\n\n if (match && shouldPreferMatch(match, commentOrStringGrammer, first, options)) {\n first = { grammar: commentOrStringGrammer, match };\n }\n }\n\n if (first) {\n let stringPrefixStartIndex = first.match.index;\n if (!first.grammar.isComment && options.removeStrings) {\n stringPrefixStartIndex = getPythonStringPrefixStartIndex(sourceCode, first.match.index);\n }\n newSourceCodeSlices.push(sourceCode.slice(lastIndex, stringPrefixStartIndex));\n\n if (!first.grammar.isComment) {\n lastIndex = findStringEndIndex(sourceCode, first.match.index, first.grammar);\n const stringLiteral = sourceCode.slice(first.match.index, lastIndex);\n if (options.removeStrings) {\n newSourceCodeSlices.push(\n preserveStringInterpolationExpressions(\n grammar,\n commentOrStringGrammars,\n first.grammar,\n sourceCode,\n first.match.index,\n stringLiteral\n )\n );\n } else {\n newSourceCodeSlices.push(stringLiteral);\n }\n } else {\n const closeRegExp = first.grammar.closeRegExp;\n closeRegExp.lastIndex = first.match.index + first.match[0].length;\n\n const match = closeRegExp.exec(sourceCode);\n\n lastIndex = match ? match.index + match[0].length : sourceCode.length;\n }\n } else {\n newSourceCodeSlices.push(sourceCode.slice(lastIndex));\n lastIndex = sourceCode.length;\n }\n }\n\n return newSourceCodeSlices.join('');\n}\n\nfunction compileCommentOrStringGrammars(grammar: SourceCodeGrammar): readonly CommentOrStringGrammar[] {\n return [\n ...(grammar.comments?.map((v) => ({\n closeRegExp: makeGlobalRegExp(v.close ?? /(?=\\n)/g),\n isComment: true,\n openRegExp: makeGlobalRegExp(v.open),\n })) ?? []),\n ...(grammar.strings?.map((v) => ({\n closeRegExp: makeGlobalRegExp(v.close),\n isComment: false,\n openRegExp: makeGlobalRegExp(v.open),\n templateSyntax: v.templateSyntax,\n })) ?? []),\n ] satisfies CommentOrStringGrammar[];\n}\n\nfunction shouldPreferMatch(\n match: RegExpExecArray,\n grammar: CommentOrStringGrammar,\n first: { grammar: CommentOrStringGrammar; match: RegExpExecArray } | undefined,\n options: { removeStrings: boolean }\n): boolean {\n if (!first) return true;\n if (match.index < first.match.index) return true;\n if (match.index > first.match.index) return false;\n return options.removeStrings && first.grammar.isComment && !grammar.isComment;\n}\n\nfunction makeGlobalRegExp(regExp: RegExp): RegExp {\n return new RegExp(regExp, regExp.flags.includes('g') ? regExp.flags : `${regExp.flags}g`);\n}\n\nfunction findStringEndIndex(sourceCode: string, stringStartIndex: number, grammar: CommentOrStringGrammar): number {\n if (grammar.templateSyntax === 'kotlin') return findKotlinStringEndIndex(sourceCode, stringStartIndex);\n if (sourceCode[stringStartIndex] === '`') return findJavaScriptTemplateEndIndex(sourceCode, stringStartIndex);\n if (hasPythonFStringPrefix(sourceCode, stringStartIndex))\n return findPythonFStringEndIndex(sourceCode, stringStartIndex);\n\n const closeRegExp = grammar.closeRegExp;\n closeRegExp.lastIndex = stringStartIndex + 1;\n const match = closeRegExp.exec(sourceCode);\n return match ? match.index + match[0].length : sourceCode.length;\n}\n\nfunction findKotlinStringEndIndex(sourceCode: string, stringStartIndex: number): number {\n const quote = getStringQuote(sourceCode, stringStartIndex);\n const isRawString = quote.length === 3;\n let index = stringStartIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n if (!isRawString && sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '$' && sourceCode[index + 1] === '{') {\n index =\n readBalancedExpression(sourceCode, index + 2, '}', {\n slashStartsComments: true,\n slashStartsRegExp: false,\n supportsKotlinStrings: true,\n }).endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return sourceCode.length;\n}\n\nfunction findJavaScriptTemplateEndIndex(sourceCode: string, stringStartIndex: number): number {\n let index = stringStartIndex + 1;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '`') return index + 1;\n if (sourceCode[index] === '$' && sourceCode[index + 1] === '{') {\n index = readBalancedExpression(sourceCode, index + 2, '}', { slashStartsRegExp: true }).endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return sourceCode.length;\n}\n\nfunction findPythonFStringEndIndex(sourceCode: string, stringStartIndex: number): number {\n const quote = getStringQuote(sourceCode, stringStartIndex);\n let index = stringStartIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n if (sourceCode[index] === '{' && sourceCode[index + 1] === '{') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '{') {\n index = readBalancedExpression(sourceCode, index + 1, '}', { slashStartsRegExp: false }).endIndex + 1;\n continue;\n }\n index += sourceCode[index] === '\\\\' ? 2 : 1;\n }\n\n return sourceCode.length;\n}\n\nfunction getStringQuote(sourceCode: string, stringStartIndex: number): string {\n const threeCharacterQuote = sourceCode.slice(stringStartIndex, stringStartIndex + 3);\n if (threeCharacterQuote === \"'''\" || threeCharacterQuote === '\"\"\"') return threeCharacterQuote;\n return sourceCode[stringStartIndex] ?? '';\n}\n\nfunction getPythonStringPrefixStartIndex(sourceCode: string, stringStartIndex: number): number {\n const prefix = getPythonStringPrefix(sourceCode, stringStartIndex);\n return prefix ? stringStartIndex - prefix.length : stringStartIndex;\n}\n\nfunction getPythonStringPrefix(sourceCode: string, stringStartIndex: number): string | undefined {\n if (sourceCode[stringStartIndex] !== '\"' && sourceCode[stringStartIndex] !== \"'\") return undefined;\n let index = stringStartIndex - 1;\n while (index >= 0 && /[A-Za-z]/.test(sourceCode[index] ?? '')) index -= 1;\n\n const prefix = sourceCode.slice(index + 1, stringStartIndex);\n if (!prefix || !/^[bfru]+$/i.test(prefix)) return undefined;\n if (index >= 0 && /[\\dA-Za-z_]/.test(sourceCode[index] ?? '')) return undefined;\n\n return prefix;\n}\n\nfunction preserveStringInterpolationExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringGrammar: CommentOrStringGrammar,\n sourceCode: string,\n stringStartIndex: number,\n stringLiteral: string\n): string {\n if (stringGrammar.templateSyntax === 'kotlin')\n return preserveKotlinStringTemplateExpressions(grammar, commentOrStringGrammars, stringLiteral);\n if (stringLiteral.startsWith('`'))\n return preserveJavaScriptTemplateExpressions(grammar, commentOrStringGrammars, stringLiteral);\n if (hasPythonFStringPrefix(sourceCode, stringStartIndex))\n return preservePythonFStringExpressions(grammar, commentOrStringGrammars, stringLiteral);\n return '';\n}\n\nfunction preserveKotlinStringTemplateExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const quoteLength = getStringQuote(stringLiteral, 0).length;\n const isRawString = quoteLength === 3;\n const expressions: string[] = [];\n let index = quoteLength;\n\n while (index < stringLiteral.length - quoteLength) {\n if (!isRawString && stringLiteral[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] !== '$') {\n index += 1;\n continue;\n }\n if (stringLiteral[index + 1] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 2, '}', {\n slashStartsComments: true,\n slashStartsRegExp: false,\n supportsKotlinStrings: true,\n });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n if (isKotlinIdentifierStart(stringLiteral[index + 1])) {\n let endIndex = index + 2;\n while (isKotlinIdentifierPart(stringLiteral[endIndex])) endIndex += 1;\n expressions.push(stringLiteral.slice(index + 1, endIndex));\n index = endIndex;\n continue;\n }\n index += 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction isKotlinIdentifierStart(character: string | undefined): boolean {\n return character !== undefined && /[\\p{L}_]/u.test(character);\n}\n\nfunction isKotlinIdentifierPart(character: string | undefined): boolean {\n return character !== undefined && /[\\p{L}\\p{N}_]/u.test(character);\n}\n\nfunction hasPythonFStringPrefix(sourceCode: string, stringStartIndex: number): boolean {\n return getPythonStringPrefix(sourceCode, stringStartIndex)?.toLowerCase().includes('f') ?? false;\n}\n\nfunction preserveJavaScriptTemplateExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const expressions: string[] = [];\n let index = 1;\n\n while (index < stringLiteral.length - 1) {\n if (stringLiteral[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] === '$' && stringLiteral[index + 1] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 2, '}', { slashStartsRegExp: true });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction preservePythonFStringExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const quoteLength = getStringQuote(stringLiteral, 0).length;\n const expressions: string[] = [];\n let index = quoteLength;\n\n while (index < stringLiteral.length - quoteLength) {\n if (stringLiteral[index] === '{' && stringLiteral[index + 1] === '{') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 1, '}', { slashStartsRegExp: false });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n index += stringLiteral[index] === '\\\\' ? 2 : 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction readBalancedExpression(\n sourceCode: string,\n startIndex: number,\n closeChar: string,\n options: BalancedExpressionOptions\n): { content: string; endIndex: number } {\n let depth = 1;\n let index = startIndex;\n\n while (index < sourceCode.length) {\n const char = sourceCode[index];\n if (char === '\"' && options.supportsKotlinStrings) {\n index = findKotlinStringEndIndex(sourceCode, index);\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n index = skipQuotedSpan(sourceCode, index);\n continue;\n }\n if (\n (options.slashStartsComments || options.slashStartsRegExp) &&\n sourceCode[index] === '/' &&\n sourceCode[index + 1] === '/'\n ) {\n index = skipJavaScriptSingleLineComment(sourceCode, index);\n continue;\n }\n if (\n (options.slashStartsComments || options.slashStartsRegExp) &&\n sourceCode[index] === '/' &&\n sourceCode[index + 1] === '*'\n ) {\n index = skipJavaScriptBlockComment(sourceCode, index);\n continue;\n }\n if (options.slashStartsRegExp && char === '/') {\n index = skipJavaScriptRegExpLiteral(sourceCode, index);\n continue;\n }\n if (char === '{') depth += 1;\n if (char === closeChar) {\n depth -= 1;\n if (depth === 0) return { content: sourceCode.slice(startIndex, index), endIndex: index };\n }\n index += 1;\n }\n\n return { content: sourceCode.slice(startIndex), endIndex: sourceCode.length };\n}\n\nfunction skipQuotedSpan(sourceCode: string, startIndex: number): number {\n const quote = getStringQuote(sourceCode, startIndex);\n let index = startIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n index += 1;\n }\n\n return index;\n}\n\nfunction skipJavaScriptRegExpLiteral(sourceCode: string, startIndex: number): number {\n let index = startIndex + 1;\n let inCharacterClass = false;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '[') inCharacterClass = true;\n if (sourceCode[index] === ']') inCharacterClass = false;\n if (sourceCode[index] === '/' && !inCharacterClass) {\n index += 1;\n while (/[A-Za-z]/.test(sourceCode[index] ?? '')) index += 1;\n return index;\n }\n if (sourceCode[index] === '\\n') return startIndex + 1;\n index += 1;\n }\n\n return startIndex + 1;\n}\n\nfunction skipJavaScriptSingleLineComment(sourceCode: string, startIndex: number): number {\n const newLineIndex = sourceCode.indexOf('\\n', startIndex + 2);\n return newLineIndex === -1 ? sourceCode.length : newLineIndex;\n}\n\nfunction skipJavaScriptBlockComment(sourceCode: string, startIndex: number): number {\n const endIndex = sourceCode.indexOf('*/', startIndex + 2);\n return endIndex === -1 ? sourceCode.length : endIndex + 2;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function e(e,t){return n(e,t,{removeStrings:!1})}function t(e,t){return n(e,t,{removeStrings:!0})}function n(e,t,n,a=r(e)){if(!e.comments?.length&&(!n.removeStrings||!e.strings?.length))return t;let s=[],c=0;for(;c<t.length;){let r;for(let e of a){let a=e.openRegExp;a.lastIndex=c;let o=a.exec(t);o&&i(o,e,r,n)&&(r={grammar:e,match:o})}if(r){let i=r.match.index;if(!r.grammar.isComment&&n.removeStrings&&(i=d(t,r.match.index)),s.push(t.slice(c,i)),r.grammar.isComment){let e=r.grammar.closeRegExp;e.lastIndex=r.match.index+r.match[0].length;let n=e.exec(t);c=n?n.index+n[0].length:t.length}else{c=o(t,r.match.index,r.grammar);let i=t.slice(r.match.index,c);n.removeStrings?s.push(p(e,a,r.grammar,t,r.match.index,i)):s.push(i)}}else s.push(t.slice(c)),c=t.length}return s.join(``)}function r(e){return[...e.comments?.map(e=>({closeRegExp:a(e.close??/(?=\n)/g),isComment:!0,openRegExp:a(e.open)}))??[],...e.strings?.map(e=>({closeRegExp:a(e.close),isComment:!1,openRegExp:a(e.open),templateSyntax:e.templateSyntax}))??[]]}function i(e,t,n,r){return!n||e.index<n.match.index?!0:e.index>n.match.index?!1:r.removeStrings&&n.grammar.isComment&&!t.isComment}function a(e){return new RegExp(e,e.flags.includes(`g`)?e.flags:`${e.flags}g`)}function o(e,t,n){if(n.templateSyntax===`kotlin`)return s(e,t);if(e[t]==="`")return c(e,t);if(_(e,t))return l(e,t);let r=n.closeRegExp;r.lastIndex=t+1;let i=r.exec(e);return i?i.index+i[0].length:e.length}function s(e,t){let n=u(e,t),r=n.length===3,i=t+n.length;for(;i<e.length;){if(e.startsWith(n,i))return i+n.length;if(!r&&e[i]===`\\`){i+=2;continue}if(e[i]===`$`&&e[i+1]===`{`){i=b(e,i+2,`}`,{slashStartsComments:!0,slashStartsRegExp:!1,supportsKotlinStrings:!0}).endIndex+1;continue}i+=1}return e.length}function c(e,t){let n=t+1;for(;n<e.length;){if(e[n]===`\\`){n+=2;continue}if(e[n]==="`")return n+1;if(e[n]===`$`&&e[n+1]===`{`){n=b(e,n+2,`}`,{slashStartsRegExp:!0}).endIndex+1;continue}n+=1}return e.length}function l(e,t){let n=u(e,t),r=t+n.length;for(;r<e.length;){if(e.startsWith(n,r))return r+n.length;if(e[r]===`{`&&e[r+1]===`{`){r+=2;continue}if(e[r]===`{`){r=b(e,r+1,`}`,{slashStartsRegExp:!1}).endIndex+1;continue}r+=e[r]===`\\`?2:1}return e.length}function u(e,t){let n=e.slice(t,t+3);return n===`'''`||n===`"""`?n:e[t]??``}function d(e,t){let n=f(e,t);return n?t-n.length:t}function f(e,t){if(e[t]!==`"`&&e[t]!==`'`)return;let n=t-1;for(;n>=0&&/[A-Za-z]/.test(e[n]??``);)--n;let r=e.slice(n+1,t);if(
|
|
1
|
+
function e(e,t){return n(e,t,{removeStrings:!1})}function t(e,t){return n(e,t,{removeStrings:!0})}function n(e,t,n,a=r(e)){if(!e.comments?.length&&(!n.removeStrings||!e.strings?.length))return t;let s=[],c=0;for(;c<t.length;){let r;for(let e of a){let a=e.openRegExp;a.lastIndex=c;let o=a.exec(t);o&&i(o,e,r,n)&&(r={grammar:e,match:o})}if(r){let i=r.match.index;if(!r.grammar.isComment&&n.removeStrings&&(i=d(t,r.match.index)),s.push(t.slice(c,i)),r.grammar.isComment){let e=r.grammar.closeRegExp;e.lastIndex=r.match.index+r.match[0].length;let n=e.exec(t);c=n?n.index+n[0].length:t.length}else{c=o(t,r.match.index,r.grammar);let i=t.slice(r.match.index,c);n.removeStrings?s.push(p(e,a,r.grammar,t,r.match.index,i)):s.push(i)}}else s.push(t.slice(c)),c=t.length}return s.join(``)}function r(e){return[...e.comments?.map(e=>({closeRegExp:a(e.close??/(?=\n)/g),isComment:!0,openRegExp:a(e.open)}))??[],...e.strings?.map(e=>({closeRegExp:a(e.close),isComment:!1,openRegExp:a(e.open),templateSyntax:e.templateSyntax}))??[]]}function i(e,t,n,r){return!n||e.index<n.match.index?!0:e.index>n.match.index?!1:r.removeStrings&&n.grammar.isComment&&!t.isComment}function a(e){return new RegExp(e,e.flags.includes(`g`)?e.flags:`${e.flags}g`)}function o(e,t,n){if(n.templateSyntax===`kotlin`)return s(e,t);if(e[t]==="`")return c(e,t);if(_(e,t))return l(e,t);let r=n.closeRegExp;r.lastIndex=t+1;let i=r.exec(e);return i?i.index+i[0].length:e.length}function s(e,t){let n=u(e,t),r=n.length===3,i=t+n.length;for(;i<e.length;){if(e.startsWith(n,i))return i+n.length;if(!r&&e[i]===`\\`){i+=2;continue}if(e[i]===`$`&&e[i+1]===`{`){i=b(e,i+2,`}`,{slashStartsComments:!0,slashStartsRegExp:!1,supportsKotlinStrings:!0}).endIndex+1;continue}i+=1}return e.length}function c(e,t){let n=t+1;for(;n<e.length;){if(e[n]===`\\`){n+=2;continue}if(e[n]==="`")return n+1;if(e[n]===`$`&&e[n+1]===`{`){n=b(e,n+2,`}`,{slashStartsRegExp:!0}).endIndex+1;continue}n+=1}return e.length}function l(e,t){let n=u(e,t),r=t+n.length;for(;r<e.length;){if(e.startsWith(n,r))return r+n.length;if(e[r]===`{`&&e[r+1]===`{`){r+=2;continue}if(e[r]===`{`){r=b(e,r+1,`}`,{slashStartsRegExp:!1}).endIndex+1;continue}r+=e[r]===`\\`?2:1}return e.length}function u(e,t){let n=e.slice(t,t+3);return n===`'''`||n===`"""`?n:e[t]??``}function d(e,t){let n=f(e,t);return n?t-n.length:t}function f(e,t){if(e[t]!==`"`&&e[t]!==`'`)return;let n=t-1;for(;n>=0&&/[A-Za-z]/.test(e[n]??``);)--n;let r=e.slice(n+1,t);if(r&&/^[bfru]+$/i.test(r)&&!(n>=0&&/[\dA-Za-z_]/.test(e[n]??``)))return r}function p(e,t,n,r,i,a){return n.templateSyntax===`kotlin`?m(e,t,a):a.startsWith("`")?v(e,t,a):_(r,i)?y(e,t,a):``}function m(e,t,r){let i=u(r,0).length,a=i===3,o=[],s=i;for(;s<r.length-i;){if(!a&&r[s]===`\\`){s+=2;continue}if(r[s]!==`$`){s+=1;continue}if(r[s+1]===`{`){let i=b(r,s+2,`}`,{slashStartsComments:!0,slashStartsRegExp:!1,supportsKotlinStrings:!0});o.push(n(e,i.content,{removeStrings:!0},t)),s=i.endIndex+1;continue}if(h(r[s+1])){let e=s+2;for(;g(r[e]);)e+=1;o.push(r.slice(s+1,e)),s=e;continue}s+=1}return o.join(`
|
|
2
2
|
`)}function h(e){return e!==void 0&&/[\p{L}_]/u.test(e)}function g(e){return e!==void 0&&/[\p{L}\p{N}_]/u.test(e)}function _(e,t){return f(e,t)?.toLowerCase().includes(`f`)??!1}function v(e,t,r){let i=[],a=1;for(;a<r.length-1;){if(r[a]===`\\`){a+=2;continue}if(r[a]===`$`&&r[a+1]===`{`){let o=b(r,a+2,`}`,{slashStartsRegExp:!0});i.push(n(e,o.content,{removeStrings:!0},t)),a=o.endIndex+1;continue}a+=1}return i.join(`
|
|
3
3
|
`)}function y(e,t,r){let i=u(r,0).length,a=[],o=i;for(;o<r.length-i;){if(r[o]===`{`&&r[o+1]===`{`){o+=2;continue}if(r[o]===`{`){let i=b(r,o+1,`}`,{slashStartsRegExp:!1});a.push(n(e,i.content,{removeStrings:!0},t)),o=i.endIndex+1;continue}o+=r[o]===`\\`?2:1}return a.join(`
|
|
4
4
|
`)}function b(e,t,n,r){let i=1,a=t;for(;a<e.length;){let o=e[a];if(o===`"`&&r.supportsKotlinStrings){a=s(e,a);continue}if(o===`"`||o===`'`||o==="`"){a=x(e,a);continue}if((r.slashStartsComments||r.slashStartsRegExp)&&e[a]===`/`&&e[a+1]===`/`){a=C(e,a);continue}if((r.slashStartsComments||r.slashStartsRegExp)&&e[a]===`/`&&e[a+1]===`*`){a=w(e,a);continue}if(r.slashStartsRegExp&&o===`/`){a=S(e,a);continue}if(o===`{`&&(i+=1),o===n&&(--i,i===0))return{content:e.slice(t,a),endIndex:a};a+=1}return{content:e.slice(t),endIndex:e.length}}function x(e,t){let n=u(e,t),r=t+n.length;for(;r<e.length;){if(e[r]===`\\`){r+=2;continue}if(e.startsWith(n,r))return r+n.length;r+=1}return r}function S(e,t){let n=t+1,r=!1;for(;n<e.length;){if(e[n]===`\\`){n+=2;continue}if(e[n]===`[`&&(r=!0),e[n]===`]`&&(r=!1),e[n]===`/`&&!r){for(n+=1;/[A-Za-z]/.test(e[n]??``);)n+=1;return n}if(e[n]===`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeCommentsInSourceCode.js","names":[],"sources":["../../src/helpers/removeCommentsInSourceCode.ts"],"sourcesContent":["import type { LanguageDefinition } from '../types/language.js';\n\nexport type SourceCodeGrammar = NonNullable<LanguageDefinition['grammer']>;\n\n/** Removes comments from source code according to the provided language grammar. */\nexport function removeCommentsInSourceCode(grammar: SourceCodeGrammar, sourceCode: string): string {\n return removeCommentsAndMaybeStringsInSourceCode(grammar, sourceCode, { removeStrings: false });\n}\n\n/** Removes comments and string literal contents while preserving executable interpolation expressions. */\nexport function removeCommentsAndStringsInSourceCode(grammar: SourceCodeGrammar, sourceCode: string): string {\n return removeCommentsAndMaybeStringsInSourceCode(grammar, sourceCode, { removeStrings: true });\n}\n\ninterface CommentOrStringGrammar {\n closeRegExp: RegExp;\n isComment: boolean;\n openRegExp: RegExp;\n templateSyntax?: 'kotlin';\n}\n\ninterface BalancedExpressionOptions {\n slashStartsComments?: boolean;\n slashStartsRegExp: boolean;\n supportsKotlinStrings?: boolean;\n}\n\nfunction removeCommentsAndMaybeStringsInSourceCode(\n grammar: SourceCodeGrammar,\n sourceCode: string,\n options: { removeStrings: boolean },\n commentOrStringGrammars = compileCommentOrStringGrammars(grammar)\n): string {\n if (!grammar.comments?.length && (!options.removeStrings || !grammar.strings?.length)) return sourceCode;\n\n const newSourceCodeSlices: string[] = [];\n\n let lastIndex = 0;\n\n while (lastIndex < sourceCode.length) {\n let first: { grammar: CommentOrStringGrammar; match: RegExpExecArray } | undefined;\n\n for (const commentOrStringGrammer of commentOrStringGrammars) {\n const startRegExp = commentOrStringGrammer.openRegExp;\n startRegExp.lastIndex = lastIndex;\n\n const match = startRegExp.exec(sourceCode);\n\n if (match && shouldPreferMatch(match, commentOrStringGrammer, first, options)) {\n first = { grammar: commentOrStringGrammer, match };\n }\n }\n\n if (first) {\n let stringPrefixStartIndex = first.match.index;\n if (!first.grammar.isComment && options.removeStrings) {\n stringPrefixStartIndex = getPythonStringPrefixStartIndex(sourceCode, first.match.index);\n }\n newSourceCodeSlices.push(sourceCode.slice(lastIndex, stringPrefixStartIndex));\n\n if (!first.grammar.isComment) {\n lastIndex = findStringEndIndex(sourceCode, first.match.index, first.grammar);\n const stringLiteral = sourceCode.slice(first.match.index, lastIndex);\n if (options.removeStrings) {\n newSourceCodeSlices.push(\n preserveStringInterpolationExpressions(\n grammar,\n commentOrStringGrammars,\n first.grammar,\n sourceCode,\n first.match.index,\n stringLiteral\n )\n );\n } else {\n newSourceCodeSlices.push(stringLiteral);\n }\n } else {\n const closeRegExp = first.grammar.closeRegExp;\n closeRegExp.lastIndex = first.match.index + first.match[0].length;\n\n const match = closeRegExp.exec(sourceCode);\n\n lastIndex = match ? match.index + match[0].length : sourceCode.length;\n }\n } else {\n newSourceCodeSlices.push(sourceCode.slice(lastIndex));\n lastIndex = sourceCode.length;\n }\n }\n\n return newSourceCodeSlices.join('');\n}\n\nfunction compileCommentOrStringGrammars(grammar: SourceCodeGrammar): readonly CommentOrStringGrammar[] {\n return [\n ...(grammar.comments?.map((v) => ({\n closeRegExp: makeGlobalRegExp(v.close ?? /(?=\\n)/g),\n isComment: true,\n openRegExp: makeGlobalRegExp(v.open),\n })) ?? []),\n ...(grammar.strings?.map((v) => ({\n closeRegExp: makeGlobalRegExp(v.close),\n isComment: false,\n openRegExp: makeGlobalRegExp(v.open),\n templateSyntax: v.templateSyntax,\n })) ?? []),\n ] satisfies CommentOrStringGrammar[];\n}\n\nfunction shouldPreferMatch(\n match: RegExpExecArray,\n grammar: CommentOrStringGrammar,\n first: { grammar: CommentOrStringGrammar; match: RegExpExecArray } | undefined,\n options: { removeStrings: boolean }\n): boolean {\n if (!first) return true;\n if (match.index < first.match.index) return true;\n if (match.index > first.match.index) return false;\n return options.removeStrings && first.grammar.isComment && !grammar.isComment;\n}\n\nfunction makeGlobalRegExp(regExp: RegExp): RegExp {\n return new RegExp(regExp, regExp.flags.includes('g') ? regExp.flags : `${regExp.flags}g`);\n}\n\nfunction findStringEndIndex(sourceCode: string, stringStartIndex: number, grammar: CommentOrStringGrammar): number {\n if (grammar.templateSyntax === 'kotlin') return findKotlinStringEndIndex(sourceCode, stringStartIndex);\n if (sourceCode[stringStartIndex] === '`') return findJavaScriptTemplateEndIndex(sourceCode, stringStartIndex);\n if (hasPythonFStringPrefix(sourceCode, stringStartIndex))\n return findPythonFStringEndIndex(sourceCode, stringStartIndex);\n\n const closeRegExp = grammar.closeRegExp;\n closeRegExp.lastIndex = stringStartIndex + 1;\n const match = closeRegExp.exec(sourceCode);\n return match ? match.index + match[0].length : sourceCode.length;\n}\n\nfunction findKotlinStringEndIndex(sourceCode: string, stringStartIndex: number): number {\n const quote = getStringQuote(sourceCode, stringStartIndex);\n const isRawString = quote.length === 3;\n let index = stringStartIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n if (!isRawString && sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '$' && sourceCode[index + 1] === '{') {\n index =\n readBalancedExpression(sourceCode, index + 2, '}', {\n slashStartsComments: true,\n slashStartsRegExp: false,\n supportsKotlinStrings: true,\n }).endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return sourceCode.length;\n}\n\nfunction findJavaScriptTemplateEndIndex(sourceCode: string, stringStartIndex: number): number {\n let index = stringStartIndex + 1;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '`') return index + 1;\n if (sourceCode[index] === '$' && sourceCode[index + 1] === '{') {\n index = readBalancedExpression(sourceCode, index + 2, '}', { slashStartsRegExp: true }).endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return sourceCode.length;\n}\n\nfunction findPythonFStringEndIndex(sourceCode: string, stringStartIndex: number): number {\n const quote = getStringQuote(sourceCode, stringStartIndex);\n let index = stringStartIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n if (sourceCode[index] === '{' && sourceCode[index + 1] === '{') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '{') {\n index = readBalancedExpression(sourceCode, index + 1, '}', { slashStartsRegExp: false }).endIndex + 1;\n continue;\n }\n index += sourceCode[index] === '\\\\' ? 2 : 1;\n }\n\n return sourceCode.length;\n}\n\nfunction getStringQuote(sourceCode: string, stringStartIndex: number): string {\n const threeCharacterQuote = sourceCode.slice(stringStartIndex, stringStartIndex + 3);\n if (threeCharacterQuote === \"'''\" || threeCharacterQuote === '\"\"\"') return threeCharacterQuote;\n return sourceCode[stringStartIndex] ?? '';\n}\n\nfunction getPythonStringPrefixStartIndex(sourceCode: string, stringStartIndex: number): number {\n const prefix = getPythonStringPrefix(sourceCode, stringStartIndex);\n return prefix ? stringStartIndex - prefix.length : stringStartIndex;\n}\n\nfunction getPythonStringPrefix(sourceCode: string, stringStartIndex: number): string | undefined {\n if (sourceCode[stringStartIndex] !== '\"' && sourceCode[stringStartIndex] !== \"'\") return undefined;\n let index = stringStartIndex - 1;\n while (index >= 0 && /[A-Za-z]/.test(sourceCode[index] ?? '')) index -= 1;\n\n const prefix = sourceCode.slice(index + 1, stringStartIndex);\n if (!prefix || !/^[bfru]+$/i.test(prefix)) return undefined;\n if (index >= 0 && /[\\dA-Za-z_]/.test(sourceCode[index] ?? '')) return undefined;\n\n return prefix;\n}\n\nfunction preserveStringInterpolationExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringGrammar: CommentOrStringGrammar,\n sourceCode: string,\n stringStartIndex: number,\n stringLiteral: string\n): string {\n if (stringGrammar.templateSyntax === 'kotlin')\n return preserveKotlinStringTemplateExpressions(grammar, commentOrStringGrammars, stringLiteral);\n if (stringLiteral.startsWith('`'))\n return preserveJavaScriptTemplateExpressions(grammar, commentOrStringGrammars, stringLiteral);\n if (hasPythonFStringPrefix(sourceCode, stringStartIndex))\n return preservePythonFStringExpressions(grammar, commentOrStringGrammars, stringLiteral);\n return '';\n}\n\nfunction preserveKotlinStringTemplateExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const quoteLength = getStringQuote(stringLiteral, 0).length;\n const isRawString = quoteLength === 3;\n const expressions: string[] = [];\n let index = quoteLength;\n\n while (index < stringLiteral.length - quoteLength) {\n if (!isRawString && stringLiteral[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] !== '$') {\n index += 1;\n continue;\n }\n if (stringLiteral[index + 1] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 2, '}', {\n slashStartsComments: true,\n slashStartsRegExp: false,\n supportsKotlinStrings: true,\n });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n if (isKotlinIdentifierStart(stringLiteral[index + 1])) {\n let endIndex = index + 2;\n while (isKotlinIdentifierPart(stringLiteral[endIndex])) endIndex += 1;\n expressions.push(stringLiteral.slice(index + 1, endIndex));\n index = endIndex;\n continue;\n }\n index += 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction isKotlinIdentifierStart(character: string | undefined): boolean {\n return character !== undefined && /[\\p{L}_]/u.test(character);\n}\n\nfunction isKotlinIdentifierPart(character: string | undefined): boolean {\n return character !== undefined && /[\\p{L}\\p{N}_]/u.test(character);\n}\n\nfunction hasPythonFStringPrefix(sourceCode: string, stringStartIndex: number): boolean {\n return getPythonStringPrefix(sourceCode, stringStartIndex)?.toLowerCase().includes('f') ?? false;\n}\n\nfunction preserveJavaScriptTemplateExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const expressions: string[] = [];\n let index = 1;\n\n while (index < stringLiteral.length - 1) {\n if (stringLiteral[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] === '$' && stringLiteral[index + 1] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 2, '}', { slashStartsRegExp: true });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction preservePythonFStringExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const quoteLength = getStringQuote(stringLiteral, 0).length;\n const expressions: string[] = [];\n let index = quoteLength;\n\n while (index < stringLiteral.length - quoteLength) {\n if (stringLiteral[index] === '{' && stringLiteral[index + 1] === '{') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 1, '}', { slashStartsRegExp: false });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n index += stringLiteral[index] === '\\\\' ? 2 : 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction readBalancedExpression(\n sourceCode: string,\n startIndex: number,\n closeChar: string,\n options: BalancedExpressionOptions\n): { content: string; endIndex: number } {\n let depth = 1;\n let index = startIndex;\n\n while (index < sourceCode.length) {\n const char = sourceCode[index];\n if (char === '\"' && options.supportsKotlinStrings) {\n index = findKotlinStringEndIndex(sourceCode, index);\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n index = skipQuotedSpan(sourceCode, index);\n continue;\n }\n if (\n (options.slashStartsComments || options.slashStartsRegExp) &&\n sourceCode[index] === '/' &&\n sourceCode[index + 1] === '/'\n ) {\n index = skipJavaScriptSingleLineComment(sourceCode, index);\n continue;\n }\n if (\n (options.slashStartsComments || options.slashStartsRegExp) &&\n sourceCode[index] === '/' &&\n sourceCode[index + 1] === '*'\n ) {\n index = skipJavaScriptBlockComment(sourceCode, index);\n continue;\n }\n if (options.slashStartsRegExp && char === '/') {\n index = skipJavaScriptRegExpLiteral(sourceCode, index);\n continue;\n }\n if (char === '{') depth += 1;\n if (char === closeChar) {\n depth -= 1;\n if (depth === 0) return { content: sourceCode.slice(startIndex, index), endIndex: index };\n }\n index += 1;\n }\n\n return { content: sourceCode.slice(startIndex), endIndex: sourceCode.length };\n}\n\nfunction skipQuotedSpan(sourceCode: string, startIndex: number): number {\n const quote = getStringQuote(sourceCode, startIndex);\n let index = startIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n index += 1;\n }\n\n return index;\n}\n\nfunction skipJavaScriptRegExpLiteral(sourceCode: string, startIndex: number): number {\n let index = startIndex + 1;\n let inCharacterClass = false;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '[') inCharacterClass = true;\n if (sourceCode[index] === ']') inCharacterClass = false;\n if (sourceCode[index] === '/' && !inCharacterClass) {\n index += 1;\n while (/[A-Za-z]/.test(sourceCode[index] ?? '')) index += 1;\n return index;\n }\n if (sourceCode[index] === '\\n') return startIndex + 1;\n index += 1;\n }\n\n return startIndex + 1;\n}\n\nfunction skipJavaScriptSingleLineComment(sourceCode: string, startIndex: number): number {\n const newLineIndex = sourceCode.indexOf('\\n', startIndex + 2);\n return newLineIndex === -1 ? sourceCode.length : newLineIndex;\n}\n\nfunction skipJavaScriptBlockComment(sourceCode: string, startIndex: number): number {\n const endIndex = sourceCode.indexOf('*/', startIndex + 2);\n return endIndex === -1 ? sourceCode.length : endIndex + 2;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"removeCommentsInSourceCode.js","names":[],"sources":["../../src/helpers/removeCommentsInSourceCode.ts"],"sourcesContent":["import type { LanguageDefinition } from '../types/language.js';\n\nexport type SourceCodeGrammar = NonNullable<LanguageDefinition['grammer']>;\n\n/** Removes comments from source code according to the provided language grammar. */\nexport function removeCommentsInSourceCode(grammar: SourceCodeGrammar, sourceCode: string): string {\n return removeCommentsAndMaybeStringsInSourceCode(grammar, sourceCode, { removeStrings: false });\n}\n\n/** Removes comments and string literal contents while preserving executable interpolation expressions. */\nexport function removeCommentsAndStringsInSourceCode(grammar: SourceCodeGrammar, sourceCode: string): string {\n return removeCommentsAndMaybeStringsInSourceCode(grammar, sourceCode, { removeStrings: true });\n}\n\ninterface CommentOrStringGrammar {\n closeRegExp: RegExp;\n isComment: boolean;\n openRegExp: RegExp;\n templateSyntax?: 'kotlin';\n}\n\ninterface BalancedExpressionOptions {\n slashStartsComments?: boolean;\n slashStartsRegExp: boolean;\n supportsKotlinStrings?: boolean;\n}\n\nfunction removeCommentsAndMaybeStringsInSourceCode(\n grammar: SourceCodeGrammar,\n sourceCode: string,\n options: { removeStrings: boolean },\n commentOrStringGrammars = compileCommentOrStringGrammars(grammar)\n): string {\n if (!grammar.comments?.length && (!options.removeStrings || !grammar.strings?.length)) return sourceCode;\n\n const newSourceCodeSlices: string[] = [];\n\n let lastIndex = 0;\n\n while (lastIndex < sourceCode.length) {\n let first: { grammar: CommentOrStringGrammar; match: RegExpExecArray } | undefined;\n\n for (const commentOrStringGrammer of commentOrStringGrammars) {\n const startRegExp = commentOrStringGrammer.openRegExp;\n startRegExp.lastIndex = lastIndex;\n\n const match = startRegExp.exec(sourceCode);\n\n if (match && shouldPreferMatch(match, commentOrStringGrammer, first, options)) {\n first = { grammar: commentOrStringGrammer, match };\n }\n }\n\n if (first) {\n let stringPrefixStartIndex = first.match.index;\n if (!first.grammar.isComment && options.removeStrings) {\n stringPrefixStartIndex = getPythonStringPrefixStartIndex(sourceCode, first.match.index);\n }\n newSourceCodeSlices.push(sourceCode.slice(lastIndex, stringPrefixStartIndex));\n\n if (!first.grammar.isComment) {\n lastIndex = findStringEndIndex(sourceCode, first.match.index, first.grammar);\n const stringLiteral = sourceCode.slice(first.match.index, lastIndex);\n if (options.removeStrings) {\n newSourceCodeSlices.push(\n preserveStringInterpolationExpressions(\n grammar,\n commentOrStringGrammars,\n first.grammar,\n sourceCode,\n first.match.index,\n stringLiteral\n )\n );\n } else {\n newSourceCodeSlices.push(stringLiteral);\n }\n } else {\n const closeRegExp = first.grammar.closeRegExp;\n closeRegExp.lastIndex = first.match.index + first.match[0].length;\n\n const match = closeRegExp.exec(sourceCode);\n\n lastIndex = match ? match.index + match[0].length : sourceCode.length;\n }\n } else {\n newSourceCodeSlices.push(sourceCode.slice(lastIndex));\n lastIndex = sourceCode.length;\n }\n }\n\n return newSourceCodeSlices.join('');\n}\n\nfunction compileCommentOrStringGrammars(grammar: SourceCodeGrammar): readonly CommentOrStringGrammar[] {\n return [\n ...(grammar.comments?.map((v) => ({\n closeRegExp: makeGlobalRegExp(v.close ?? /(?=\\n)/g),\n isComment: true,\n openRegExp: makeGlobalRegExp(v.open),\n })) ?? []),\n ...(grammar.strings?.map((v) => ({\n closeRegExp: makeGlobalRegExp(v.close),\n isComment: false,\n openRegExp: makeGlobalRegExp(v.open),\n templateSyntax: v.templateSyntax,\n })) ?? []),\n ] satisfies CommentOrStringGrammar[];\n}\n\nfunction shouldPreferMatch(\n match: RegExpExecArray,\n grammar: CommentOrStringGrammar,\n first: { grammar: CommentOrStringGrammar; match: RegExpExecArray } | undefined,\n options: { removeStrings: boolean }\n): boolean {\n if (!first) return true;\n if (match.index < first.match.index) return true;\n if (match.index > first.match.index) return false;\n return options.removeStrings && first.grammar.isComment && !grammar.isComment;\n}\n\nfunction makeGlobalRegExp(regExp: RegExp): RegExp {\n return new RegExp(regExp, regExp.flags.includes('g') ? regExp.flags : `${regExp.flags}g`);\n}\n\nfunction findStringEndIndex(sourceCode: string, stringStartIndex: number, grammar: CommentOrStringGrammar): number {\n if (grammar.templateSyntax === 'kotlin') return findKotlinStringEndIndex(sourceCode, stringStartIndex);\n if (sourceCode[stringStartIndex] === '`') return findJavaScriptTemplateEndIndex(sourceCode, stringStartIndex);\n if (hasPythonFStringPrefix(sourceCode, stringStartIndex))\n return findPythonFStringEndIndex(sourceCode, stringStartIndex);\n\n const closeRegExp = grammar.closeRegExp;\n closeRegExp.lastIndex = stringStartIndex + 1;\n const match = closeRegExp.exec(sourceCode);\n return match ? match.index + match[0].length : sourceCode.length;\n}\n\nfunction findKotlinStringEndIndex(sourceCode: string, stringStartIndex: number): number {\n const quote = getStringQuote(sourceCode, stringStartIndex);\n const isRawString = quote.length === 3;\n let index = stringStartIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n if (!isRawString && sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '$' && sourceCode[index + 1] === '{') {\n index =\n readBalancedExpression(sourceCode, index + 2, '}', {\n slashStartsComments: true,\n slashStartsRegExp: false,\n supportsKotlinStrings: true,\n }).endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return sourceCode.length;\n}\n\nfunction findJavaScriptTemplateEndIndex(sourceCode: string, stringStartIndex: number): number {\n let index = stringStartIndex + 1;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '`') return index + 1;\n if (sourceCode[index] === '$' && sourceCode[index + 1] === '{') {\n index = readBalancedExpression(sourceCode, index + 2, '}', { slashStartsRegExp: true }).endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return sourceCode.length;\n}\n\nfunction findPythonFStringEndIndex(sourceCode: string, stringStartIndex: number): number {\n const quote = getStringQuote(sourceCode, stringStartIndex);\n let index = stringStartIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n if (sourceCode[index] === '{' && sourceCode[index + 1] === '{') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '{') {\n index = readBalancedExpression(sourceCode, index + 1, '}', { slashStartsRegExp: false }).endIndex + 1;\n continue;\n }\n index += sourceCode[index] === '\\\\' ? 2 : 1;\n }\n\n return sourceCode.length;\n}\n\nfunction getStringQuote(sourceCode: string, stringStartIndex: number): string {\n const threeCharacterQuote = sourceCode.slice(stringStartIndex, stringStartIndex + 3);\n if (threeCharacterQuote === \"'''\" || threeCharacterQuote === '\"\"\"') return threeCharacterQuote;\n return sourceCode[stringStartIndex] ?? '';\n}\n\nfunction getPythonStringPrefixStartIndex(sourceCode: string, stringStartIndex: number): number {\n const prefix = getPythonStringPrefix(sourceCode, stringStartIndex);\n return prefix ? stringStartIndex - prefix.length : stringStartIndex;\n}\n\nfunction getPythonStringPrefix(sourceCode: string, stringStartIndex: number): string | undefined {\n if (sourceCode[stringStartIndex] !== '\"' && sourceCode[stringStartIndex] !== \"'\") return undefined;\n let index = stringStartIndex - 1;\n while (index >= 0 && /[A-Za-z]/.test(sourceCode[index] ?? '')) index -= 1;\n\n const prefix = sourceCode.slice(index + 1, stringStartIndex);\n if (!prefix || !/^[bfru]+$/i.test(prefix)) return undefined;\n if (index >= 0 && /[\\dA-Za-z_]/.test(sourceCode[index] ?? '')) return undefined;\n\n return prefix;\n}\n\nfunction preserveStringInterpolationExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringGrammar: CommentOrStringGrammar,\n sourceCode: string,\n stringStartIndex: number,\n stringLiteral: string\n): string {\n if (stringGrammar.templateSyntax === 'kotlin')\n return preserveKotlinStringTemplateExpressions(grammar, commentOrStringGrammars, stringLiteral);\n if (stringLiteral.startsWith('`'))\n return preserveJavaScriptTemplateExpressions(grammar, commentOrStringGrammars, stringLiteral);\n if (hasPythonFStringPrefix(sourceCode, stringStartIndex))\n return preservePythonFStringExpressions(grammar, commentOrStringGrammars, stringLiteral);\n return '';\n}\n\nfunction preserveKotlinStringTemplateExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const quoteLength = getStringQuote(stringLiteral, 0).length;\n const isRawString = quoteLength === 3;\n const expressions: string[] = [];\n let index = quoteLength;\n\n while (index < stringLiteral.length - quoteLength) {\n if (!isRawString && stringLiteral[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] !== '$') {\n index += 1;\n continue;\n }\n if (stringLiteral[index + 1] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 2, '}', {\n slashStartsComments: true,\n slashStartsRegExp: false,\n supportsKotlinStrings: true,\n });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n if (isKotlinIdentifierStart(stringLiteral[index + 1])) {\n let endIndex = index + 2;\n while (isKotlinIdentifierPart(stringLiteral[endIndex])) endIndex += 1;\n expressions.push(stringLiteral.slice(index + 1, endIndex));\n index = endIndex;\n continue;\n }\n index += 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction isKotlinIdentifierStart(character: string | undefined): boolean {\n return character !== undefined && /[\\p{L}_]/u.test(character);\n}\n\nfunction isKotlinIdentifierPart(character: string | undefined): boolean {\n return character !== undefined && /[\\p{L}\\p{N}_]/u.test(character);\n}\n\nfunction hasPythonFStringPrefix(sourceCode: string, stringStartIndex: number): boolean {\n return getPythonStringPrefix(sourceCode, stringStartIndex)?.toLowerCase().includes('f') ?? false;\n}\n\nfunction preserveJavaScriptTemplateExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const expressions: string[] = [];\n let index = 1;\n\n while (index < stringLiteral.length - 1) {\n if (stringLiteral[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] === '$' && stringLiteral[index + 1] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 2, '}', { slashStartsRegExp: true });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n index += 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction preservePythonFStringExpressions(\n grammar: SourceCodeGrammar,\n commentOrStringGrammars: readonly CommentOrStringGrammar[],\n stringLiteral: string\n): string {\n const quoteLength = getStringQuote(stringLiteral, 0).length;\n const expressions: string[] = [];\n let index = quoteLength;\n\n while (index < stringLiteral.length - quoteLength) {\n if (stringLiteral[index] === '{' && stringLiteral[index + 1] === '{') {\n index += 2;\n continue;\n }\n if (stringLiteral[index] === '{') {\n const expression = readBalancedExpression(stringLiteral, index + 1, '}', { slashStartsRegExp: false });\n expressions.push(\n removeCommentsAndMaybeStringsInSourceCode(\n grammar,\n expression.content,\n { removeStrings: true },\n commentOrStringGrammars\n )\n );\n index = expression.endIndex + 1;\n continue;\n }\n index += stringLiteral[index] === '\\\\' ? 2 : 1;\n }\n\n return expressions.join('\\n');\n}\n\nfunction readBalancedExpression(\n sourceCode: string,\n startIndex: number,\n closeChar: string,\n options: BalancedExpressionOptions\n): { content: string; endIndex: number } {\n let depth = 1;\n let index = startIndex;\n\n while (index < sourceCode.length) {\n const char = sourceCode[index];\n if (char === '\"' && options.supportsKotlinStrings) {\n index = findKotlinStringEndIndex(sourceCode, index);\n continue;\n }\n if (char === '\"' || char === \"'\" || char === '`') {\n index = skipQuotedSpan(sourceCode, index);\n continue;\n }\n if (\n (options.slashStartsComments || options.slashStartsRegExp) &&\n sourceCode[index] === '/' &&\n sourceCode[index + 1] === '/'\n ) {\n index = skipJavaScriptSingleLineComment(sourceCode, index);\n continue;\n }\n if (\n (options.slashStartsComments || options.slashStartsRegExp) &&\n sourceCode[index] === '/' &&\n sourceCode[index + 1] === '*'\n ) {\n index = skipJavaScriptBlockComment(sourceCode, index);\n continue;\n }\n if (options.slashStartsRegExp && char === '/') {\n index = skipJavaScriptRegExpLiteral(sourceCode, index);\n continue;\n }\n if (char === '{') depth += 1;\n if (char === closeChar) {\n depth -= 1;\n if (depth === 0) return { content: sourceCode.slice(startIndex, index), endIndex: index };\n }\n index += 1;\n }\n\n return { content: sourceCode.slice(startIndex), endIndex: sourceCode.length };\n}\n\nfunction skipQuotedSpan(sourceCode: string, startIndex: number): number {\n const quote = getStringQuote(sourceCode, startIndex);\n let index = startIndex + quote.length;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode.startsWith(quote, index)) return index + quote.length;\n index += 1;\n }\n\n return index;\n}\n\nfunction skipJavaScriptRegExpLiteral(sourceCode: string, startIndex: number): number {\n let index = startIndex + 1;\n let inCharacterClass = false;\n\n while (index < sourceCode.length) {\n if (sourceCode[index] === '\\\\') {\n index += 2;\n continue;\n }\n if (sourceCode[index] === '[') inCharacterClass = true;\n if (sourceCode[index] === ']') inCharacterClass = false;\n if (sourceCode[index] === '/' && !inCharacterClass) {\n index += 1;\n while (/[A-Za-z]/.test(sourceCode[index] ?? '')) index += 1;\n return index;\n }\n if (sourceCode[index] === '\\n') return startIndex + 1;\n index += 1;\n }\n\n return startIndex + 1;\n}\n\nfunction skipJavaScriptSingleLineComment(sourceCode: string, startIndex: number): number {\n const newLineIndex = sourceCode.indexOf('\\n', startIndex + 2);\n return newLineIndex === -1 ? sourceCode.length : newLineIndex;\n}\n\nfunction skipJavaScriptBlockComment(sourceCode: string, startIndex: number): number {\n const endIndex = sourceCode.indexOf('*/', startIndex + 2);\n return endIndex === -1 ? sourceCode.length : endIndex + 2;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exercode/problem-utils",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.3",
|
|
4
4
|
"description": ":100: A set of utilities for judging programs on Exercode (https://exercode.willbooster.com/).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"exercode",
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"prettier": "@willbooster/prettier-config",
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"front-matter": "4.0.2",
|
|
46
|
-
"yaml": "2.9.
|
|
47
|
-
"zod": "4.
|
|
46
|
+
"yaml": "2.9.1",
|
|
47
|
+
"zod": "4.6.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"build-ts": "21.0.
|
|
50
|
+
"build-ts": "21.0.14",
|
|
51
51
|
"undici-types": "8.10.2"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|