@exercode/problem-utils 4.3.3 → 4.3.4
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/cli/exercodeProblem.cjs +1 -1
- package/dist/cli/exercodeProblem.cjs.map +1 -1
- package/dist/cli/exercodeProblem.js +1 -1
- package/dist/cli/exercodeProblem.js.map +1 -1
- package/dist/helpers/javaJudge.cjs +1 -1
- package/dist/helpers/javaJudge.cjs.map +1 -1
- package/dist/helpers/javaJudge.js +1 -1
- package/dist/helpers/javaJudge.js.map +1 -1
- package/dist/helpers/parseCsv.cjs +2 -2
- package/dist/helpers/parseCsv.cjs.map +1 -1
- package/dist/helpers/parseCsv.js +2 -2
- package/dist/helpers/parseCsv.js.map +1 -1
- package/dist/helpers/removeCommentsInSourceCode.cjs +4 -4
- package/dist/helpers/removeCommentsInSourceCode.cjs.map +1 -1
- package/dist/helpers/removeCommentsInSourceCode.js +4 -4
- package/dist/helpers/removeCommentsInSourceCode.js.map +1 -1
- package/dist/helpers/runHarnessProcess.cjs +1 -1
- package/dist/helpers/runHarnessProcess.cjs.map +1 -1
- package/dist/helpers/runHarnessProcess.js +1 -1
- package/dist/helpers/runHarnessProcess.js.map +1 -1
- package/dist/helpers/spawnWithLimits.cjs +1 -1
- package/dist/helpers/spawnWithLimits.cjs.map +1 -1
- package/dist/helpers/spawnWithLimits.js +1 -1
- package/dist/helpers/spawnWithLimits.js.map +1 -1
- package/dist/helpers/startHttpServer.cjs +1 -1
- package/dist/helpers/startHttpServer.cjs.map +1 -1
- package/dist/helpers/startHttpServer.js +1 -1
- package/dist/helpers/startHttpServer.js.map +1 -1
- package/dist/learningMaterial/validateCourse.cjs +1 -1
- package/dist/learningMaterial/validateCourse.cjs.map +1 -1
- package/dist/learningMaterial/validateCourse.js +1 -1
- package/dist/learningMaterial/validateCourse.js.map +1 -1
- package/dist/learningMaterial/validateProblem.cjs +1 -1
- package/dist/learningMaterial/validateProblem.cjs.map +1 -1
- package/dist/learningMaterial/validateProblem.js +1 -1
- package/dist/learningMaterial/validateProblem.js.map +1 -1
- package/dist/presets/guiCommand.cjs +1 -1
- package/dist/presets/guiCommand.cjs.map +1 -1
- package/dist/presets/guiCommand.js +1 -1
- package/dist/presets/guiCommand.js.map +1 -1
- package/package.json +7 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeCommentsInSourceCode.cjs","names":["removeCommentsInSourceCode","removeCommentsAndMaybeStringsInSourceCode","grammar","sourceCode","removeCommentsAndStringsInSourceCode","compileCommentOrStringGrammars","options","newSourceCodeSlices","lastIndex","first","commentOrStringGrammer","commentOrStringGrammars","startRegExp","match","shouldPreferMatch","stringPrefixStartIndex","getPythonStringPrefixStartIndex","closeRegExp","findStringEndIndex","stringLiteral","preserveStringInterpolationExpressions","makeGlobalRegExp","v","regExp","findKotlinStringEndIndex","stringStartIndex","findJavaScriptTemplateEndIndex","hasPythonFStringPrefix","findPythonFStringEndIndex","quote","getStringQuote","isRawString","index","readBalancedExpression","threeCharacterQuote","prefix","getPythonStringPrefix","stringGrammar","preserveKotlinStringTemplateExpressions","preserveJavaScriptTemplateExpressions","preservePythonFStringExpressions","quoteLength","expressions","expression","isKotlinIdentifierStart","endIndex","isKotlinIdentifierPart","character","depth","startIndex","char","skipQuotedSpan","skipJavaScriptSingleLineComment","skipJavaScriptBlockComment","skipJavaScriptRegExpLiteral","closeChar","inCharacterClass","newLineIndex"],"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":["removeCommentsInSourceCode","removeCommentsAndMaybeStringsInSourceCode","grammar","sourceCode","removeCommentsAndStringsInSourceCode","compileCommentOrStringGrammars","options","newSourceCodeSlices","lastIndex","first","commentOrStringGrammer","commentOrStringGrammars","startRegExp","match","shouldPreferMatch","stringPrefixStartIndex","getPythonStringPrefixStartIndex","closeRegExp","findStringEndIndex","stringLiteral","preserveStringInterpolationExpressions","makeGlobalRegExp","v","regExp","findKotlinStringEndIndex","stringStartIndex","findJavaScriptTemplateEndIndex","hasPythonFStringPrefix","findPythonFStringEndIndex","quote","getStringQuote","isRawString","index","readBalancedExpression","threeCharacterQuote","prefix","getPythonStringPrefix","stringGrammar","preserveKotlinStringTemplateExpressions","preserveJavaScriptTemplateExpressions","preservePythonFStringExpressions","quoteLength","expressions","expression","isKotlinIdentifierStart","endIndex","isKotlinIdentifierPart","character","depth","startIndex","char","skipQuotedSpan","skipJavaScriptSingleLineComment","skipJavaScriptBlockComment","skipJavaScriptRegExpLiteral","closeChar","inCharacterClass","newLineIndex"],"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,7 +1,7 @@
|
|
|
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
|
|
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;)
|
|
3
|
-
`)}function y(e,t,r){let i=u(r,0).length,a=[],o=i;for(;o<r.length-i;)
|
|
4
|
-
`)}function b(e,t,n,r){let i=1,a=t;for(;a<e.length;){let o=e[a];if(o===`"`&&r.supportsKotlinStrings)
|
|
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;!r&&e[i]===`\\`?i+=2:e[i]===`$`&&e[i+1]===`{`?i=b(e,i+2,`}`,{slashStartsComments:!0,slashStartsRegExp:!1,supportsKotlinStrings:!0}).endIndex+1:i+=1}return e.length}function c(e,t){let n=t+1;for(;n<e.length;)if(e[n]===`\\`)n+=2;else{if(e[n]==="`")return n+1;e[n]===`$`&&e[n+1]===`{`?n=b(e,n+2,`}`,{slashStartsRegExp:!0}).endIndex+1: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;e[r]===`{`&&e[r+1]===`{`?r+=2:e[r]===`{`?r=b(e,r+1,`}`,{slashStartsRegExp:!1}).endIndex+1: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;else if(r[s]!==`$`)s+=1;else 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}else if(h(r[s+1])){let e=s+2;for(;g(r[e]);)e+=1;o.push(r.slice(s+1,e)),s=e}else s+=1;return o.join(`
|
|
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;else 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}else a+=1;return i.join(`
|
|
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;else if(r[o]===`{`){let i=b(r,o+1,`}`,{slashStartsRegExp:!1});a.push(n(e,i.content,{removeStrings:!0},t)),o=i.endIndex+1}else o+=r[o]===`\\`?2:1;return a.join(`
|
|
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);else if(o===`"`||o===`'`||o==="`")a=x(e,a);else if((r.slashStartsComments||r.slashStartsRegExp)&&e[a]===`/`&&e[a+1]===`/`)a=C(e,a);else if((r.slashStartsComments||r.slashStartsRegExp)&&e[a]===`/`&&e[a+1]===`*`)a=w(e,a);else if(r.slashStartsRegExp&&o===`/`)a=S(e,a);else{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;else{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;else{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]===`
|
|
5
5
|
`)return t+1;n+=1}return t+1}function C(e,t){let n=e.indexOf(`
|
|
6
6
|
`,t+2);return n===-1?e.length:n}function w(e,t){let n=e.indexOf(`*/`,t+2);return n===-1?e.length:n+2}export{t as removeCommentsAndStringsInSourceCode,e as removeCommentsInSourceCode};
|
|
7
7
|
//# sourceMappingURL=removeCommentsInSourceCode.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeCommentsInSourceCode.js","names":["removeCommentsInSourceCode","removeCommentsAndMaybeStringsInSourceCode","grammar","sourceCode","removeCommentsAndStringsInSourceCode","compileCommentOrStringGrammars","options","newSourceCodeSlices","lastIndex","first","commentOrStringGrammer","commentOrStringGrammars","startRegExp","match","shouldPreferMatch","stringPrefixStartIndex","getPythonStringPrefixStartIndex","closeRegExp","findStringEndIndex","stringLiteral","preserveStringInterpolationExpressions","makeGlobalRegExp","v","regExp","findKotlinStringEndIndex","stringStartIndex","findJavaScriptTemplateEndIndex","hasPythonFStringPrefix","findPythonFStringEndIndex","quote","getStringQuote","isRawString","index","readBalancedExpression","threeCharacterQuote","prefix","getPythonStringPrefix","stringGrammar","preserveKotlinStringTemplateExpressions","preserveJavaScriptTemplateExpressions","preservePythonFStringExpressions","quoteLength","expressions","expression","isKotlinIdentifierStart","endIndex","isKotlinIdentifierPart","character","depth","startIndex","char","skipQuotedSpan","skipJavaScriptSingleLineComment","skipJavaScriptBlockComment","skipJavaScriptRegExpLiteral","closeChar","inCharacterClass","newLineIndex"],"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":["removeCommentsInSourceCode","removeCommentsAndMaybeStringsInSourceCode","grammar","sourceCode","removeCommentsAndStringsInSourceCode","compileCommentOrStringGrammars","options","newSourceCodeSlices","lastIndex","first","commentOrStringGrammer","commentOrStringGrammars","startRegExp","match","shouldPreferMatch","stringPrefixStartIndex","getPythonStringPrefixStartIndex","closeRegExp","findStringEndIndex","stringLiteral","preserveStringInterpolationExpressions","makeGlobalRegExp","v","regExp","findKotlinStringEndIndex","stringStartIndex","findJavaScriptTemplateEndIndex","hasPythonFStringPrefix","findPythonFStringEndIndex","quote","getStringQuote","isRawString","index","readBalancedExpression","threeCharacterQuote","prefix","getPythonStringPrefix","stringGrammar","preserveKotlinStringTemplateExpressions","preserveJavaScriptTemplateExpressions","preservePythonFStringExpressions","quoteLength","expressions","expression","isKotlinIdentifierStart","endIndex","isKotlinIdentifierPart","character","depth","startIndex","char","skipQuotedSpan","skipJavaScriptSingleLineComment","skipJavaScriptBlockComment","skipJavaScriptRegExpLiteral","closeChar","inCharacterClass","newLineIndex"],"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,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./temporaryProblemDirCopy.cjs");let n=require("node:child_process");n=e.__toESM(n,1);const r=/* @__PURE__ */ new Set;let i=!1;function a(e,t){return o(),new Promise(i=>{let a=n.default.spawn(process.execPath,e,{cwd:t.cwd,detached:process.platform!==`win32`,env:t.tempRoot?{...t.env,TMPDIR:t.tempRoot}:t.env,stdio:[`ignore`,`pipe`,`pipe`]}),o=a.pid===void 0?void 0:{pid:a.pid,tempRoot:t.tempRoot};o&&r.add(o);let c=[],l=[],u=0,d,f=!1,p=!1,m=e=>{if(d!==void 0)return!1;d=e,a.stdout?.destroy(),a.stderr?.destroy();try{a.pid===void 0?a.kill(`SIGKILL`):s(a.pid)}catch{a.kill(`SIGKILL`)}return!0},h=setTimeout(()=>{f=m(`timed out after ${t.timeoutMs/1e3} seconds`)},t.timeoutMs),g=(e,n)=>{
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./temporaryProblemDirCopy.cjs");let n=require("node:child_process");n=e.__toESM(n,1);const r=/* @__PURE__ */ new Set;let i=!1;function a(e,t){return o(),new Promise(i=>{let a=n.default.spawn(process.execPath,e,{cwd:t.cwd,detached:process.platform!==`win32`,env:t.tempRoot?{...t.env,TMPDIR:t.tempRoot}:t.env,stdio:[`ignore`,`pipe`,`pipe`]}),o=a.pid===void 0?void 0:{pid:a.pid,tempRoot:t.tempRoot};o&&r.add(o);let c=[],l=[],u=0,d,f=!1,p=!1,m=e=>{if(d!==void 0)return!1;d=e,a.stdout?.destroy(),a.stderr?.destroy();try{a.pid===void 0?a.kill(`SIGKILL`):s(a.pid)}catch{a.kill(`SIGKILL`)}return!0},h=setTimeout(()=>{f=m(`timed out after ${t.timeoutMs/1e3} seconds`)},t.timeoutMs),g=(e,n)=>{u+=e.byteLength,u>t.maxOutputBytes?m(`the harness printed more than ${t.maxOutputBytes/1024/1024} MB of output`):n.push(e)};a.stdout?.on(`data`,e=>g(e,c)),a.stderr?.on(`data`,e=>g(e,l));let _=(e,t,n)=>{p||(p=!0,clearTimeout(h),o&&r.delete(o),i({stdout:Buffer.concat(c).toString(`utf8`),stderr:Buffer.concat(l).toString(`utf8`),exitCode:e,signal:t,failureReason:d??(n?`failed to run the harness: ${n.message}`:void 0),timedOut:f}))};a.on(`error`,e=>_(void 0,void 0,e)),a.on(`close`,(e,t)=>_(e??void 0,t??void 0))})}function o(){if(!i){i=!0;for(let e of[`SIGINT`,`SIGTERM`])process.once(e,()=>{for(let e of r){try{s(e.pid)}catch{}e.tempRoot&&t.forciblyRemoveDirectorySync(e.tempRoot)}process.kill(process.pid,e)})}}function s(e){process.platform===`win32`?n.default.spawnSync(`taskkill`,[`/PID`,String(e),`/T`,`/F`],{stdio:`ignore`}):process.kill(-e,`SIGKILL`)}exports.runHarnessProcess=a;
|
|
2
2
|
//# sourceMappingURL=runHarnessProcess.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runHarnessProcess.cjs","names":["child_process","require_runtime","require_temporaryProblemDirCopy","node_child_process","liveHarnessRuns","signalHandlersInstalled","runHarnessProcess","installSignalHandlers","child","commandArgs","options","liveRun","stdoutChunks","stderrChunks","totalOutputBytes","failureReason","timedOut","settled","killProcessGroup","reason","killHarnessTree","timeoutId","appendOutput","chunk","chunks","settle","resolve","spawnError","error","exitCode","signal","pid"],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded. The submissions a harness judges through\n * `spawnWithLimits` run in sessions of their own, out of that kill's reach; each wrapper's process\n * group is bounded by its own GNU `timeout` instead, while a descendant that moved to yet another\n * session may survive without stalling the harness.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n // The parent removes this root even when SIGKILL prevents child cleanup.\n env: options.tempRoot ? { ...options.env, TMPDIR: options.tempRoot } : options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"kKAiCA,MAAMI,kBAAkB,IAAI,IAC5B,IAAIC,EAA0B,GAS9B,SAAgBC,EACd,EACA,EAC+B,CAE/B,OADAC,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAMC,EAAQR,EAAAA,QAAc,MAAM,QAAQ,SAAUS,EAAa,CAC/D,IAAKC,EAAQ,IACb,SAAU,QAAQ,WAAa,QAE/B,IAAKA,EAAQ,SAAW,CAAE,GAAGA,EAAQ,IAAK,OAAQA,EAAQ,QAAS,EAAIA,EAAQ,IAC/E,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACKC,EACJH,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAKA,EAAM,IAAK,SAAUE,EAAQ,QAAS,EACjFC,GAASP,EAAgB,IAAIO,CAAO,EACxC,IAAMC,EAAyB,CAAC,EAC1BC,EAAyB,CAAC,EAC5BC,EAAmB,EACnBC,EACAC,EAAW,GACXC,EAAU,GAGRC,EAAoB,GAA4B,CACpD,GAAIH,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgBI,EAGhBX,EAAM,QAAQ,QAAQ,EACtBA,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACEA,EAAM,MAAQ,IAAA,GAChBA,EAAM,KAAK,SAAS,EAEpBY,EAAgBZ,EAAM,GAAG,CAE7B,MAAQ,CACNA,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACMa,EAAY,eAAiB,CACjC,EAAWH,EAAiB,mBAAmBR,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAGA,EAAQ,SAAS,EAEdY,GAAgB,EAAe,IAA2B,
|
|
1
|
+
{"version":3,"file":"runHarnessProcess.cjs","names":["child_process","require_runtime","require_temporaryProblemDirCopy","node_child_process","liveHarnessRuns","signalHandlersInstalled","runHarnessProcess","installSignalHandlers","child","commandArgs","options","liveRun","stdoutChunks","stderrChunks","totalOutputBytes","failureReason","timedOut","settled","killProcessGroup","reason","killHarnessTree","timeoutId","appendOutput","chunk","chunks","settle","resolve","spawnError","error","exitCode","signal","pid"],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded. The submissions a harness judges through\n * `spawnWithLimits` run in sessions of their own, out of that kill's reach; each wrapper's process\n * group is bounded by its own GNU `timeout` instead, while a descendant that moved to yet another\n * session may survive without stalling the harness.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n // The parent removes this root even when SIGKILL prevents child cleanup.\n env: options.tempRoot ? { ...options.env, TMPDIR: options.tempRoot } : options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"kKAiCA,MAAMI,kBAAkB,IAAI,IAC5B,IAAIC,EAA0B,GAS9B,SAAgBC,EACd,EACA,EAC+B,CAE/B,OADAC,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAMC,EAAQR,EAAAA,QAAc,MAAM,QAAQ,SAAUS,EAAa,CAC/D,IAAKC,EAAQ,IACb,SAAU,QAAQ,WAAa,QAE/B,IAAKA,EAAQ,SAAW,CAAE,GAAGA,EAAQ,IAAK,OAAQA,EAAQ,QAAS,EAAIA,EAAQ,IAC/E,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACKC,EACJH,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAKA,EAAM,IAAK,SAAUE,EAAQ,QAAS,EACjFC,GAASP,EAAgB,IAAIO,CAAO,EACxC,IAAMC,EAAyB,CAAC,EAC1BC,EAAyB,CAAC,EAC5BC,EAAmB,EACnBC,EACAC,EAAW,GACXC,EAAU,GAGRC,EAAoB,GAA4B,CACpD,GAAIH,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgBI,EAGhBX,EAAM,QAAQ,QAAQ,EACtBA,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACEA,EAAM,MAAQ,IAAA,GAChBA,EAAM,KAAK,SAAS,EAEpBY,EAAgBZ,EAAM,GAAG,CAE7B,MAAQ,CACNA,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACMa,EAAY,eAAiB,CACjC,EAAWH,EAAiB,mBAAmBR,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAGA,EAAQ,SAAS,EAEdY,GAAgB,EAAe,IAA2B,CAC9D,GAAoBC,EAAM,WACtBT,EAAmBJ,EAAQ,eAC7BQ,EAAiB,iCAAiCR,EAAQ,eAAiB,KAAO,KAAK,cAAc,EAGvGc,EAAO,KAAKD,CAAK,CACnB,EACAf,EAAM,QAAQ,GAAG,OAAS,GAAkBc,EAAaC,EAAOX,CAAY,CAAC,EAC7EJ,EAAM,QAAQ,GAAG,OAAS,GAAkBc,EAAaC,EAAOV,CAAY,CAAC,EAE7E,IAAMY,GAAU,EAA8B,EAAoC,IAA6B,CACzGR,IACJ,EAAU,GACV,aAAaI,CAAS,EAClBV,GAASP,EAAgB,OAAOO,CAAO,EAC3Ce,EAAQ,CACN,OAAQ,OAAO,OAAOd,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,OAAQ,OAAO,OAAOC,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,WACA,SACA,cAAeE,IAAkBY,EAAa,8BAA8BA,EAAW,UAAY,IAAA,IACnG,UACF,CAAC,EACH,EACAnB,EAAM,GAAG,QAAU,GAAUiB,EAAO,IAAA,GAAW,IAAA,GAAWG,CAAK,CAAC,EAChEpB,EAAM,GAAG,SAAU,EAAU,IAAWiB,EAAOI,GAAY,IAAA,GAAWC,GAAU,IAAA,EAAS,CAAC,CAC5F,CAAC,CACH,CAEA,SAASvB,GAA8B,CACrC,GAAI,GACJ,GAA0B,GAC1B,IAAK,IAAMuB,IAAU,CAAC,SAAU,SAAS,EACvC,QAAQ,KAAKA,MAAc,CACzB,IAAK,IAAMnB,KAAWP,EAAiB,CACrC,GAAI,CACFgB,EAAgBT,EAAQ,GAAG,CAC7B,MAAQ,CAER,CAEIA,EAAQ,UAAUT,EAAA,4BAA4BS,EAAQ,QAAQ,CACpE,CACA,QAAQ,KAAK,QAAQ,IAAKmB,CAAM,CAClC,CAAC,CAbuB,CAe5B,CAIA,SAASV,EAAgB,EAAmB,CACtC,QAAQ,WAAa,QACvBjB,EAAA,QAAc,UAAU,WAAY,CAAC,OAAQ,OAAO4B,CAAG,EAAG,KAAM,IAAI,EAAG,CAAE,MAAO,QAAS,CAAC,EAE1F,QAAQ,KAAK,CAACA,EAAK,SAAS,CAEhC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{forciblyRemoveDirectorySync as e}from"./temporaryProblemDirCopy.js";import t from"node:child_process";const n=/* @__PURE__ */ new Set;let r=!1;function i(e,r){return a(),new Promise(i=>{let a=t.spawn(process.execPath,e,{cwd:r.cwd,detached:process.platform!==`win32`,env:r.tempRoot?{...r.env,TMPDIR:r.tempRoot}:r.env,stdio:[`ignore`,`pipe`,`pipe`]}),s=a.pid===void 0?void 0:{pid:a.pid,tempRoot:r.tempRoot};s&&n.add(s);let c=[],l=[],u=0,d,f=!1,p=!1,m=e=>{if(d!==void 0)return!1;d=e,a.stdout?.destroy(),a.stderr?.destroy();try{a.pid===void 0?a.kill(`SIGKILL`):o(a.pid)}catch{a.kill(`SIGKILL`)}return!0},h=setTimeout(()=>{f=m(`timed out after ${r.timeoutMs/1e3} seconds`)},r.timeoutMs),g=(e,t)=>{
|
|
1
|
+
import{forciblyRemoveDirectorySync as e}from"./temporaryProblemDirCopy.js";import t from"node:child_process";const n=/* @__PURE__ */ new Set;let r=!1;function i(e,r){return a(),new Promise(i=>{let a=t.spawn(process.execPath,e,{cwd:r.cwd,detached:process.platform!==`win32`,env:r.tempRoot?{...r.env,TMPDIR:r.tempRoot}:r.env,stdio:[`ignore`,`pipe`,`pipe`]}),s=a.pid===void 0?void 0:{pid:a.pid,tempRoot:r.tempRoot};s&&n.add(s);let c=[],l=[],u=0,d,f=!1,p=!1,m=e=>{if(d!==void 0)return!1;d=e,a.stdout?.destroy(),a.stderr?.destroy();try{a.pid===void 0?a.kill(`SIGKILL`):o(a.pid)}catch{a.kill(`SIGKILL`)}return!0},h=setTimeout(()=>{f=m(`timed out after ${r.timeoutMs/1e3} seconds`)},r.timeoutMs),g=(e,t)=>{u+=e.byteLength,u>r.maxOutputBytes?m(`the harness printed more than ${r.maxOutputBytes/1024/1024} MB of output`):t.push(e)};a.stdout?.on(`data`,e=>g(e,c)),a.stderr?.on(`data`,e=>g(e,l));let _=(e,t,r)=>{p||(p=!0,clearTimeout(h),s&&n.delete(s),i({stdout:Buffer.concat(c).toString(`utf8`),stderr:Buffer.concat(l).toString(`utf8`),exitCode:e,signal:t,failureReason:d??(r?`failed to run the harness: ${r.message}`:void 0),timedOut:f}))};a.on(`error`,e=>_(void 0,void 0,e)),a.on(`close`,(e,t)=>_(e??void 0,t??void 0))})}function a(){if(!r){r=!0;for(let t of[`SIGINT`,`SIGTERM`])process.once(t,()=>{for(let t of n){try{o(t.pid)}catch{}t.tempRoot&&e(t.tempRoot)}process.kill(process.pid,t)})}}function o(e){process.platform===`win32`?t.spawnSync(`taskkill`,[`/PID`,String(e),`/T`,`/F`],{stdio:`ignore`}):process.kill(-e,`SIGKILL`)}export{i as runHarnessProcess};
|
|
2
2
|
//# sourceMappingURL=runHarnessProcess.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runHarnessProcess.js","names":["child_process","childProcess","liveHarnessRuns","signalHandlersInstalled","runHarnessProcess","installSignalHandlers","child","commandArgs","options","liveRun","stdoutChunks","stderrChunks","totalOutputBytes","failureReason","timedOut","settled","killProcessGroup","reason","killHarnessTree","timeoutId","appendOutput","chunk","chunks","settle","resolve","spawnError","error","exitCode","signal","forciblyRemoveDirectorySync","pid"],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded. The submissions a harness judges through\n * `spawnWithLimits` run in sessions of their own, out of that kill's reach; each wrapper's process\n * group is bounded by its own GNU `timeout` instead, while a descendant that moved to yet another\n * session may survive without stalling the harness.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n // The parent removes this root even when SIGKILL prevents child cleanup.\n env: options.tempRoot ? { ...options.env, TMPDIR: options.tempRoot } : options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"6GAiCA,MAAME,kBAAkB,IAAI,IAC5B,IAAIC,EAA0B,GAS9B,SAAgBC,EACd,EACA,EAC+B,CAE/B,OADAC,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAMC,EAAQN,EAAc,MAAM,QAAQ,SAAUO,EAAa,CAC/D,IAAKC,EAAQ,IACb,SAAU,QAAQ,WAAa,QAE/B,IAAKA,EAAQ,SAAW,CAAE,GAAGA,EAAQ,IAAK,OAAQA,EAAQ,QAAS,EAAIA,EAAQ,IAC/E,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACKC,EACJH,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAKA,EAAM,IAAK,SAAUE,EAAQ,QAAS,EACjFC,GAASP,EAAgB,IAAIO,CAAO,EACxC,IAAMC,EAAyB,CAAC,EAC1BC,EAAyB,CAAC,EAC5BC,EAAmB,EACnBC,EACAC,EAAW,GACXC,EAAU,GAGRC,EAAoB,GAA4B,CACpD,GAAIH,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgBI,EAGhBX,EAAM,QAAQ,QAAQ,EACtBA,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACEA,EAAM,MAAQ,IAAA,GAChBA,EAAM,KAAK,SAAS,EAEpBY,EAAgBZ,EAAM,GAAG,CAE7B,MAAQ,CACNA,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACMa,EAAY,eAAiB,CACjC,EAAWH,EAAiB,mBAAmBR,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAGA,EAAQ,SAAS,EAEdY,GAAgB,EAAe,IAA2B,
|
|
1
|
+
{"version":3,"file":"runHarnessProcess.js","names":["child_process","childProcess","liveHarnessRuns","signalHandlersInstalled","runHarnessProcess","installSignalHandlers","child","commandArgs","options","liveRun","stdoutChunks","stderrChunks","totalOutputBytes","failureReason","timedOut","settled","killProcessGroup","reason","killHarnessTree","timeoutId","appendOutput","chunk","chunks","settle","resolve","spawnError","error","exitCode","signal","forciblyRemoveDirectorySync","pid"],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded. The submissions a harness judges through\n * `spawnWithLimits` run in sessions of their own, out of that kill's reach; each wrapper's process\n * group is bounded by its own GNU `timeout` instead, while a descendant that moved to yet another\n * session may survive without stalling the harness.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n // The parent removes this root even when SIGKILL prevents child cleanup.\n env: options.tempRoot ? { ...options.env, TMPDIR: options.tempRoot } : options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"6GAiCA,MAAME,kBAAkB,IAAI,IAC5B,IAAIC,EAA0B,GAS9B,SAAgBC,EACd,EACA,EAC+B,CAE/B,OADAC,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAMC,EAAQN,EAAc,MAAM,QAAQ,SAAUO,EAAa,CAC/D,IAAKC,EAAQ,IACb,SAAU,QAAQ,WAAa,QAE/B,IAAKA,EAAQ,SAAW,CAAE,GAAGA,EAAQ,IAAK,OAAQA,EAAQ,QAAS,EAAIA,EAAQ,IAC/E,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACKC,EACJH,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAKA,EAAM,IAAK,SAAUE,EAAQ,QAAS,EACjFC,GAASP,EAAgB,IAAIO,CAAO,EACxC,IAAMC,EAAyB,CAAC,EAC1BC,EAAyB,CAAC,EAC5BC,EAAmB,EACnBC,EACAC,EAAW,GACXC,EAAU,GAGRC,EAAoB,GAA4B,CACpD,GAAIH,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgBI,EAGhBX,EAAM,QAAQ,QAAQ,EACtBA,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACEA,EAAM,MAAQ,IAAA,GAChBA,EAAM,KAAK,SAAS,EAEpBY,EAAgBZ,EAAM,GAAG,CAE7B,MAAQ,CACNA,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACMa,EAAY,eAAiB,CACjC,EAAWH,EAAiB,mBAAmBR,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAGA,EAAQ,SAAS,EAEdY,GAAgB,EAAe,IAA2B,CAC9D,GAAoBC,EAAM,WACtBT,EAAmBJ,EAAQ,eAC7BQ,EAAiB,iCAAiCR,EAAQ,eAAiB,KAAO,KAAK,cAAc,EAGvGc,EAAO,KAAKD,CAAK,CACnB,EACAf,EAAM,QAAQ,GAAG,OAAS,GAAkBc,EAAaC,EAAOX,CAAY,CAAC,EAC7EJ,EAAM,QAAQ,GAAG,OAAS,GAAkBc,EAAaC,EAAOV,CAAY,CAAC,EAE7E,IAAMY,GAAU,EAA8B,EAAoC,IAA6B,CACzGR,IACJ,EAAU,GACV,aAAaI,CAAS,EAClBV,GAASP,EAAgB,OAAOO,CAAO,EAC3Ce,EAAQ,CACN,OAAQ,OAAO,OAAOd,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,OAAQ,OAAO,OAAOC,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,WACA,SACA,cAAeE,IAAkBY,EAAa,8BAA8BA,EAAW,UAAY,IAAA,IACnG,UACF,CAAC,EACH,EACAnB,EAAM,GAAG,QAAU,GAAUiB,EAAO,IAAA,GAAW,IAAA,GAAWG,CAAK,CAAC,EAChEpB,EAAM,GAAG,SAAU,EAAU,IAAWiB,EAAOI,GAAY,IAAA,GAAWC,GAAU,IAAA,EAAS,CAAC,CAC5F,CAAC,CACH,CAEA,SAASvB,GAA8B,CACrC,GAAI,GACJ,GAA0B,GAC1B,IAAK,IAAMuB,IAAU,CAAC,SAAU,SAAS,EACvC,QAAQ,KAAKA,MAAc,CACzB,IAAK,IAAMnB,KAAWP,EAAiB,CACrC,GAAI,CACFgB,EAAgBT,EAAQ,GAAG,CAC7B,MAAQ,CAER,CAEIA,EAAQ,UAAUoB,EAA4BpB,EAAQ,QAAQ,CACpE,CACA,QAAQ,KAAK,QAAQ,IAAKmB,CAAM,CAClC,CAAC,CAbuB,CAe5B,CAIA,SAASV,EAAgB,EAAmB,CACtC,QAAQ,WAAa,QACvBjB,EAAc,UAAU,WAAY,CAAC,OAAQ,OAAO6B,CAAG,EAAG,KAAM,IAAI,EAAG,CAAE,MAAO,QAAS,CAAC,EAE1F,QAAQ,KAAK,CAACA,EAAK,SAAS,CAEhC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs/promises");n=e.__toESM(n,1);let r=require("node:os");r=e.__toESM(r,1);let i=require("@willbooster/shared-lib"),a=require("node:child_process");a=e.__toESM(a,1);const o=1e3,s=_(),c=s!==void 0,l=/* @__PURE__ */ new Set;process.once(`exit`,()=>{for(let e of l)f(e,`SIGKILL`)});async function u(e,t){let n=s===void 0?void 0:await p(`exercode-time-`);try{let r=process.platform!==`win32`,i=r?[`timeout`,`--foreground`,`-k`,`1`,String(Math.max(t.timeLimitSeconds,.001)),...e]:e,c=s===void 0?i:[...s,`--output=/dev/fd/3`,...i],u=r?t.timeLimitSeconds*1e3+2*o:t.timeLimitSeconds*1e3,p=performance.now(),g=a.default.spawn(c[0],c.slice(1),{cwd:t.cwd,detached:r,env:t.env,stdio:n===void 0?[`pipe`,`pipe`,`pipe`]:[`pipe`,`pipe`,`pipe`,n.fd]});l.add(g);let _=[],v=[],y=0,b=!1,x=!1,S=0,C,w=(e,n)=>{if(y>=t.outputLimitBytes){n.byteLength>0&&(x=!0,f(g,`SIGKILL`));return}let r=t.outputLimitBytes-y,i=n.byteLength>r?n.subarray(0,r):n;e.push(i),y+=i.byteLength,n.byteLength>r&&(x=!0,f(g,`SIGKILL`))};g.stdout.on(`data`,e=>w(_,e)),g.stderr.on(`data`,e=>w(v,e));let T=setTimeout(()=>{b=!0,f(g,`SIGTERM`)},u),E=setTimeout(()=>{b&&f(g,`SIGKILL`)},u+o);E.unref();let{status:D,signal:O}=await new Promise((e,n)=>{let r=!1,i,a,s=(t,o)=>{
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs/promises");n=e.__toESM(n,1);let r=require("node:os");r=e.__toESM(r,1);let i=require("@willbooster/shared-lib"),a=require("node:child_process");a=e.__toESM(a,1);const o=1e3,s=_(),c=s!==void 0,l=/* @__PURE__ */ new Set;process.once(`exit`,()=>{for(let e of l)f(e,`SIGKILL`)});async function u(e,t){let n=s===void 0?void 0:await p(`exercode-time-`);try{let r=process.platform!==`win32`,i=r?[`timeout`,`--foreground`,`-k`,`1`,String(Math.max(t.timeLimitSeconds,.001)),...e]:e,c=s===void 0?i:[...s,`--output=/dev/fd/3`,...i],u=r?t.timeLimitSeconds*1e3+2*o:t.timeLimitSeconds*1e3,p=performance.now(),g=a.default.spawn(c[0],c.slice(1),{cwd:t.cwd,detached:r,env:t.env,stdio:n===void 0?[`pipe`,`pipe`,`pipe`]:[`pipe`,`pipe`,`pipe`,n.fd]});l.add(g);let _=[],v=[],y=0,b=!1,x=!1,S=0,C,w=(e,n)=>{if(y>=t.outputLimitBytes){n.byteLength>0&&(x=!0,f(g,`SIGKILL`));return}let r=t.outputLimitBytes-y,i=n.byteLength>r?n.subarray(0,r):n;e.push(i),y+=i.byteLength,n.byteLength>r&&(x=!0,f(g,`SIGKILL`))};g.stdout.on(`data`,e=>w(_,e)),g.stderr.on(`data`,e=>w(v,e));let T=setTimeout(()=>{b=!0,f(g,`SIGTERM`)},u),E=setTimeout(()=>{b&&f(g,`SIGKILL`)},u+o);E.unref();let{status:D,signal:O}=await new Promise((e,n)=>{let r=!1,i,a,s=(t,o)=>{r||(r=!0,clearTimeout(a),i?n(i):e({status:t??void 0,signal:o??void 0}))},c=e=>{r||(g.pid===void 0?(C=e.message,s(void 0,void 0)):(i=e,f(g,`SIGKILL`)))};g.on(`error`,c),g.stdin.on(`error`,e=>{e.code!==`EPIPE`&&c(e)}),g.on(`exit`,(e,t)=>{clearTimeout(T),clearTimeout(E),S=(performance.now()-p)/1e3,f(g,`SIGKILL`),a=setTimeout(()=>{g.stdout.destroy(),g.stderr.destroy(),s(e,t)},o)}),g.on(`close`,s),g.stdin.end(t.stdin)}).finally(()=>{clearTimeout(T),clearTimeout(E),l.delete(g)}),k=n===void 0?void 0:h(await m(n,4096));return{stdout:Buffer.concat(_).toString(),stderr:C??Buffer.concat(v).toString(),status:D,signal:O,timeSeconds:k?.timeSeconds||S,cpuTimeSeconds:k?.cpuTimeSeconds??0,memoryBytes:k?.memoryBytes??0,timeCommandMessage:k?.message,timedOut:b||d(D,S,t.timeLimitSeconds),outputLimitExceeded:x}}finally{await n?.close()}}function d(e,t,n){return e===124?t>=n:e===137&&t>=n+o/1e3}function f(e,t){if(e.pid!==void 0)try{if(process.platform===`win32`){e.kill(t);return}process.kill(-e.pid,t)}catch(e){if(!(0,i.hasErrorCode)(e,`ESRCH`)&&!(0,i.hasErrorCode)(e,`EPERM`))throw e}}async function p(e){let i=await n.default.mkdtemp(t.default.join(r.default.tmpdir(),e));try{return await n.default.open(t.default.join(i,`record`),`wx+`,384)}finally{await n.default.rm(i,{recursive:!0,force:!0})}}async function m(e,t){let{size:n}=await e.stat(),r=Math.min(n,t),{buffer:i}=await e.read(Buffer.alloc(r),0,r,n-r);return i.toString()}function h(e){let t=/(?:^|\n)(\d+(?:[.,]\d+)?) (\d+(?:[.,]\d+)?) (\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(e);if(t)return{timeSeconds:g(t[1]),cpuTimeSeconds:g(t[2])+g(t[3]),memoryBytes:Number(t[4])*1024,message:e.slice(0,t.index).trim()||void 0}}function g(e){return Number(e.replace(`,`,`.`))}function _(){let e=r.default.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=a.default.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %U %S %M`]}exports.isTimeCommandAvailable=c,exports.spawnWithLimits=u;
|
|
2
2
|
//# sourceMappingURL=spawnWithLimits.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawnWithLimits.cjs","names":["childProcess","hasErrorCode","fs","path","os","require_runtime","node_path","node_fs_promises","node_os","_willbooster_shared_lib","node_child_process","killGracePeriodMilliseconds","timeCommand","resolveTimeCommand","isTimeCommandAvailable","liveSubprocesses","subprocess","killSubprocessGroup","spawnWithLimits","timeOutput","openUnlinkedFile","detached","boundedCommand","context","command","spawnedCommand","timeLimitMilliseconds","startTimeMilliseconds","stdoutChunks","stderrChunks","outputBytes","timedOut","outputLimitExceeded","wallTimeSeconds","spawnErrorMessage","appendOutputChunk","chunk","remainingBytes","appendedChunk","chunks","timeout","killTimeout","settled","pendingError","closeTimeout","settle","reject","resolve","code","exitSignal","failAfterClose","error","timeResult","parseTimeOutput","readTail","endedByTimeout","status","timeLimitSeconds","signal","directory","prefix","file","length","size","maxBytes","buffer","match","content","parseSeconds","text","result"],"sources":["../../src/helpers/spawnWithLimits.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { hasErrorCode } from '@willbooster/shared-lib';\n\nexport interface SpawnWithLimitsResult {\n stdout: string;\n stderr: string;\n status: number | undefined;\n signal: NodeJS.Signals | undefined;\n /** Wall time measured by GNU time, or measured here until the command exited when it produced none. */\n timeSeconds: number;\n /**\n * User plus system CPU time of the command and the descendants it waited for, measured by GNU\n * time, or 0 when it produced no measurement. A run `timeout` ended is measured too, so a caller\n * can tell a program that used up its time limit from one that waited for the CPU.\n */\n cpuTimeSeconds: number;\n /**\n * Peak resident set size measured by GNU time, or 0 when it produced no measurement. GNU time\n * measures its child `timeout` and the descendants it waited for, so the value is at least\n * `timeout`'s own footprint (about 1 MiB), which only shows for the smallest native programs.\n */\n memoryBytes: number;\n /** The note GNU time adds when the command exits abnormally, e.g. `Command terminated by signal 11`. */\n timeCommandMessage: string | undefined;\n timedOut: boolean;\n outputLimitExceeded: boolean;\n}\n\nconst killGracePeriodMilliseconds = 1000;\nconst MIN_TIMEOUT_SECONDS = 0.001;\n// GNU `timeout` exits with 124 after ending a run at its limit, or with 128 + SIGKILL when the run\n// ignored its SIGTERM and it had to be killed after the grace period.\nconst TIMEOUT_EXIT_STATUS = 124;\nconst KILLED_AFTER_TIMEOUT_EXIT_STATUS = 137;\n// GNU time writes its record to a file this process opened and unlinked at once, handed down as fd 3\n// of the command and reopened by GNU time through `/dev/fd`: the command runs as the same OS user,\n// so a record reachable by path could be swapped for a fake or for a FIFO that blocks the read.\nconst TIME_OUTPUT_FD = 3;\n// The command inherits that descriptor too, so only the tail that can hold GNU time's record is read.\nconst TIME_OUTPUT_TAIL_BYTES = 4096;\nconst timeCommand = resolveTimeCommand();\n\n/** Whether GNU time is available, i.e. whether `timeSeconds`, `cpuTimeSeconds`, and `memoryBytes` are measured at all. */\nexport const isTimeCommandAvailable = timeCommand !== undefined;\n\n// The commands run in their own sessions, so they outlive this process unless it ends them itself\n// on the way out (e.g. a preset's SIGINT handler calling `process.exit`).\nconst liveSubprocesses = new Set<childProcess.ChildProcess>();\nprocess.once('exit', () => {\n for (const subprocess of liveSubprocesses) killSubprocessGroup(subprocess, 'SIGKILL');\n});\n\n/**\n * Runs a command in its own process group with `stdin` piped in, killing the whole group once it\n * exceeds the time or output limit, and reports the wall time, CPU time, and peak memory measured\n * by GNU time.\n */\nexport async function spawnWithLimits(\n command: readonly [string, ...string[]],\n context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n outputLimitBytes: number;\n stdin: string;\n timeLimitSeconds: number;\n }\n): Promise<SpawnWithLimitsResult> {\n const timeOutput = timeCommand === undefined ? undefined : await openUnlinkedFile('exercode-time-');\n try {\n const detached = process.platform !== 'win32';\n // The command runs in its own session so the group kill below cannot hit this process, but that\n // also puts it out of reach of whoever kills this process. GNU `timeout` keeps the run bounded\n // in that case, and it is what normally ends a run at its limit: GNU time wraps it, so a run\n // that hit the limit still gets its CPU time recorded (a group kill from here would take GNU\n // time down with the command), and `--foreground` keeps the command in this group so the kill\n // below still reaches every descendant. The timers below only back `timeout` up. Should this\n // process die first, `timeout` ends the command alone (`--foreground` signals no descendant),\n // an accepted gap: the judge server sweeps every process of the request's sandbox user anyway.\n const boundedCommand: readonly [string, ...string[]] = detached\n ? [\n 'timeout',\n '--foreground',\n '-k',\n String(killGracePeriodMilliseconds / 1000),\n // A zero duration would disable `timeout`; the front matter allows a zero time limit.\n String(Math.max(context.timeLimitSeconds, MIN_TIMEOUT_SECONDS)),\n ...command,\n ]\n : command;\n const spawnedCommand: readonly [string, ...string[]] =\n timeCommand === undefined\n ? boundedCommand\n : [...timeCommand, `--output=/dev/fd/${TIME_OUTPUT_FD}`, ...boundedCommand];\n // `timeout` sends SIGKILL a grace period after its SIGTERM; the timers here fire once even that\n // should have ended the run.\n const timeLimitMilliseconds = detached\n ? context.timeLimitSeconds * 1000 + 2 * killGracePeriodMilliseconds\n : context.timeLimitSeconds * 1000;\n // Monotonic: a wall-clock step must not turn a run `timeout` ended into one that finished in time.\n const startTimeMilliseconds = performance.now();\n // The standard streams are always pipes; the type cannot express that with the extra entry.\n const subprocess = childProcess.spawn(spawnedCommand[0], spawnedCommand.slice(1), {\n cwd: context.cwd,\n detached,\n env: context.env,\n stdio: timeOutput === undefined ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe', timeOutput.fd],\n }) as childProcess.ChildProcessWithoutNullStreams;\n liveSubprocesses.add(subprocess);\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let outputBytes = 0;\n let timedOut = false;\n let outputLimitExceeded = false;\n let wallTimeSeconds = 0;\n let spawnErrorMessage: string | undefined;\n\n const appendOutputChunk = (chunks: Buffer[], chunk: Buffer): void => {\n if (outputBytes >= context.outputLimitBytes) {\n if (chunk.byteLength > 0) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n return;\n }\n\n const remainingBytes = context.outputLimitBytes - outputBytes;\n const appendedChunk = chunk.byteLength > remainingBytes ? chunk.subarray(0, remainingBytes) : chunk;\n chunks.push(appendedChunk);\n outputBytes += appendedChunk.byteLength;\n\n if (chunk.byteLength > remainingBytes) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n };\n\n subprocess.stdout.on('data', (chunk: Buffer) => appendOutputChunk(stdoutChunks, chunk));\n subprocess.stderr.on('data', (chunk: Buffer) => appendOutputChunk(stderrChunks, chunk));\n\n const timeout = setTimeout(() => {\n timedOut = true;\n killSubprocessGroup(subprocess, 'SIGTERM');\n }, timeLimitMilliseconds);\n const killTimeout = setTimeout(() => {\n if (timedOut) killSubprocessGroup(subprocess, 'SIGKILL');\n }, timeLimitMilliseconds + killGracePeriodMilliseconds);\n killTimeout.unref();\n\n const { status, signal } = await new Promise<{ status: number | undefined; signal: NodeJS.Signals | undefined }>(\n (resolve, reject) => {\n let settled = false;\n let pendingError: Error | undefined;\n let closeTimeout: NodeJS.Timeout | undefined;\n const settle = (code: number | null | undefined, exitSignal: NodeJS.Signals | null | undefined): void => {\n if (settled) return;\n settled = true;\n clearTimeout(closeTimeout);\n if (pendingError) {\n reject(pendingError);\n return;\n }\n resolve({ status: code ?? undefined, signal: exitSignal ?? undefined });\n };\n const failAfterClose = (error: Error): void => {\n if (settled) return;\n if (subprocess.pid === undefined) {\n // The spawn itself failed, i.e. the head of the chain (GNU time, or `timeout` without\n // it, or the command itself on Windows) or the cwd is missing. A missing judged command\n // is reported as status 127 by the wrapper instead. Report this like a run that produced\n // only this message.\n spawnErrorMessage = error.message;\n settle(undefined, undefined);\n return;\n }\n pendingError = error;\n killSubprocessGroup(subprocess, 'SIGKILL');\n };\n subprocess.on('error', failAfterClose);\n subprocess.stdin.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code !== 'EPIPE') failAfterClose(error);\n });\n subprocess.on('exit', (code, exitSignal) => {\n // The command is gone, so the limit timers must not fire during the grace period below. A\n // descendant that inherited the pipes (e.g. `sleep 1000 &`) would keep them open and hold\n // back 'close' until it ends: kill what is left of the group. A descendant that moved to\n // its own session survives that, so stop waiting for the pipes after the grace period and\n // keep the output read so far.\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n wallTimeSeconds = (performance.now() - startTimeMilliseconds) / 1000;\n killSubprocessGroup(subprocess, 'SIGKILL');\n closeTimeout = setTimeout(() => {\n subprocess.stdout.destroy();\n subprocess.stderr.destroy();\n settle(code, exitSignal);\n }, killGracePeriodMilliseconds);\n });\n subprocess.on('close', settle);\n subprocess.stdin.end(context.stdin);\n }\n ).finally(() => {\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n liveSubprocesses.delete(subprocess);\n });\n\n const timeResult =\n timeOutput === undefined ? undefined : parseTimeOutput(await readTail(timeOutput, TIME_OUTPUT_TAIL_BYTES));\n\n return {\n stdout: Buffer.concat(stdoutChunks).toString(),\n stderr: spawnErrorMessage ?? Buffer.concat(stderrChunks).toString(),\n status,\n signal,\n // GNU time rounds a fast run to 0.00; the wall time until 'exit' stands in for it (never the\n // grace period spent on the pipes afterwards).\n timeSeconds: timeResult?.timeSeconds || wallTimeSeconds,\n cpuTimeSeconds: timeResult?.cpuTimeSeconds ?? 0,\n memoryBytes: timeResult?.memoryBytes ?? 0,\n timeCommandMessage: timeResult?.message,\n timedOut: timedOut || endedByTimeout(status, wallTimeSeconds, context.timeLimitSeconds),\n outputLimitExceeded,\n };\n } finally {\n await timeOutput?.close();\n }\n}\n\n/**\n * Whether `timeout` ended the run: 124 says it did so at the limit, and 137 that the run ignored its\n * SIGTERM and had to be killed after the grace period. A program can exit with either status on its\n * own, so each is trusted only once the wall time reached the moment `timeout` would have produced it.\n */\nfunction endedByTimeout(status: number | undefined, wallTimeSeconds: number, timeLimitSeconds: number): boolean {\n if (status === TIMEOUT_EXIT_STATUS) return wallTimeSeconds >= timeLimitSeconds;\n if (status === KILLED_AFTER_TIMEOUT_EXIT_STATUS) {\n return wallTimeSeconds >= timeLimitSeconds + killGracePeriodMilliseconds / 1000;\n }\n return false;\n}\n\nfunction killSubprocessGroup(subprocess: childProcess.ChildProcess, signal: NodeJS.Signals): void {\n if (subprocess.pid === undefined) return;\n\n try {\n if (process.platform === 'win32') {\n subprocess.kill(signal);\n return;\n }\n process.kill(-subprocess.pid, signal);\n } catch (error) {\n if (!hasErrorCode(error, 'ESRCH') && !hasErrorCode(error, 'EPERM')) throw error;\n }\n}\n\n// The file is unlinked before it is returned, so nothing can reach it by path anymore.\nasync function openUnlinkedFile(prefix: string): Promise<fs.FileHandle> {\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), prefix));\n try {\n return await fs.open(path.join(directory, 'record'), 'wx+', 0o600);\n } finally {\n await fs.rm(directory, { recursive: true, force: true });\n }\n}\n\nasync function readTail(file: fs.FileHandle, maxBytes: number): Promise<string> {\n const { size } = await file.stat();\n const length = Math.min(size, maxBytes);\n const { buffer } = await file.read(Buffer.alloc(length), 0, length, size - length);\n return buffer.toString();\n}\n\n// Empty when the command was killed before GNU time wrote its record.\nfunction parseTimeOutput(\n content: string\n): { timeSeconds: number; cpuTimeSeconds: number; memoryBytes: number; message: string | undefined } | undefined {\n const match = /(?:^|\\n)(\\d+(?:[.,]\\d+)?) (\\d+(?:[.,]\\d+)?) (\\d+(?:[.,]\\d+)?) (\\d+)\\s*$/.exec(content);\n if (!match) return undefined;\n\n return {\n timeSeconds: parseSeconds(match[1]!),\n cpuTimeSeconds: parseSeconds(match[2]!) + parseSeconds(match[3]!),\n memoryBytes: Number(match[4]) * 1024,\n message: content.slice(0, match.index).trim() || undefined,\n };\n}\n\n// GNU time formats its seconds with the locale's decimal separator.\nfunction parseSeconds(text: string): number {\n return Number(text.replace(',', '.'));\n}\n\nfunction resolveTimeCommand(): readonly [string, ...string[]] | undefined {\n const command = os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time';\n const result = childProcess.spawnSync(command, ['--version'], { stdio: 'ignore' });\n if (result.error || result.status !== 0) return undefined;\n\n // Wall seconds, user CPU seconds, system CPU seconds, peak resident set size in KiB.\n return [command, '--format', '%e %U %S %M'];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"spawnWithLimits.cjs","names":["childProcess","hasErrorCode","fs","path","os","require_runtime","node_path","node_fs_promises","node_os","_willbooster_shared_lib","node_child_process","killGracePeriodMilliseconds","timeCommand","resolveTimeCommand","isTimeCommandAvailable","liveSubprocesses","subprocess","killSubprocessGroup","spawnWithLimits","timeOutput","openUnlinkedFile","detached","boundedCommand","context","command","spawnedCommand","timeLimitMilliseconds","startTimeMilliseconds","stdoutChunks","stderrChunks","outputBytes","timedOut","outputLimitExceeded","wallTimeSeconds","spawnErrorMessage","appendOutputChunk","chunk","remainingBytes","appendedChunk","chunks","timeout","killTimeout","settled","pendingError","closeTimeout","settle","reject","resolve","code","exitSignal","failAfterClose","error","timeResult","parseTimeOutput","readTail","endedByTimeout","status","timeLimitSeconds","signal","directory","prefix","file","length","size","maxBytes","buffer","match","content","parseSeconds","text","result"],"sources":["../../src/helpers/spawnWithLimits.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { hasErrorCode } from '@willbooster/shared-lib';\n\nexport interface SpawnWithLimitsResult {\n stdout: string;\n stderr: string;\n status: number | undefined;\n signal: NodeJS.Signals | undefined;\n /** Wall time measured by GNU time, or measured here until the command exited when it produced none. */\n timeSeconds: number;\n /**\n * User plus system CPU time of the command and the descendants it waited for, measured by GNU\n * time, or 0 when it produced no measurement. A run `timeout` ended is measured too, so a caller\n * can tell a program that used up its time limit from one that waited for the CPU.\n */\n cpuTimeSeconds: number;\n /**\n * Peak resident set size measured by GNU time, or 0 when it produced no measurement. GNU time\n * measures its child `timeout` and the descendants it waited for, so the value is at least\n * `timeout`'s own footprint (about 1 MiB), which only shows for the smallest native programs.\n */\n memoryBytes: number;\n /** The note GNU time adds when the command exits abnormally, e.g. `Command terminated by signal 11`. */\n timeCommandMessage: string | undefined;\n timedOut: boolean;\n outputLimitExceeded: boolean;\n}\n\nconst killGracePeriodMilliseconds = 1000;\nconst MIN_TIMEOUT_SECONDS = 0.001;\n// GNU `timeout` exits with 124 after ending a run at its limit, or with 128 + SIGKILL when the run\n// ignored its SIGTERM and it had to be killed after the grace period.\nconst TIMEOUT_EXIT_STATUS = 124;\nconst KILLED_AFTER_TIMEOUT_EXIT_STATUS = 137;\n// GNU time writes its record to a file this process opened and unlinked at once, handed down as fd 3\n// of the command and reopened by GNU time through `/dev/fd`: the command runs as the same OS user,\n// so a record reachable by path could be swapped for a fake or for a FIFO that blocks the read.\nconst TIME_OUTPUT_FD = 3;\n// The command inherits that descriptor too, so only the tail that can hold GNU time's record is read.\nconst TIME_OUTPUT_TAIL_BYTES = 4096;\nconst timeCommand = resolveTimeCommand();\n\n/** Whether GNU time is available, i.e. whether `timeSeconds`, `cpuTimeSeconds`, and `memoryBytes` are measured at all. */\nexport const isTimeCommandAvailable = timeCommand !== undefined;\n\n// The commands run in their own sessions, so they outlive this process unless it ends them itself\n// on the way out (e.g. a preset's SIGINT handler calling `process.exit`).\nconst liveSubprocesses = new Set<childProcess.ChildProcess>();\nprocess.once('exit', () => {\n for (const subprocess of liveSubprocesses) killSubprocessGroup(subprocess, 'SIGKILL');\n});\n\n/**\n * Runs a command in its own process group with `stdin` piped in, killing the whole group once it\n * exceeds the time or output limit, and reports the wall time, CPU time, and peak memory measured\n * by GNU time.\n */\nexport async function spawnWithLimits(\n command: readonly [string, ...string[]],\n context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n outputLimitBytes: number;\n stdin: string;\n timeLimitSeconds: number;\n }\n): Promise<SpawnWithLimitsResult> {\n const timeOutput = timeCommand === undefined ? undefined : await openUnlinkedFile('exercode-time-');\n try {\n const detached = process.platform !== 'win32';\n // The command runs in its own session so the group kill below cannot hit this process, but that\n // also puts it out of reach of whoever kills this process. GNU `timeout` keeps the run bounded\n // in that case, and it is what normally ends a run at its limit: GNU time wraps it, so a run\n // that hit the limit still gets its CPU time recorded (a group kill from here would take GNU\n // time down with the command), and `--foreground` keeps the command in this group so the kill\n // below still reaches every descendant. The timers below only back `timeout` up. Should this\n // process die first, `timeout` ends the command alone (`--foreground` signals no descendant),\n // an accepted gap: the judge server sweeps every process of the request's sandbox user anyway.\n const boundedCommand: readonly [string, ...string[]] = detached\n ? [\n 'timeout',\n '--foreground',\n '-k',\n String(killGracePeriodMilliseconds / 1000),\n // A zero duration would disable `timeout`; the front matter allows a zero time limit.\n String(Math.max(context.timeLimitSeconds, MIN_TIMEOUT_SECONDS)),\n ...command,\n ]\n : command;\n const spawnedCommand: readonly [string, ...string[]] =\n timeCommand === undefined\n ? boundedCommand\n : [...timeCommand, `--output=/dev/fd/${TIME_OUTPUT_FD}`, ...boundedCommand];\n // `timeout` sends SIGKILL a grace period after its SIGTERM; the timers here fire once even that\n // should have ended the run.\n const timeLimitMilliseconds = detached\n ? context.timeLimitSeconds * 1000 + 2 * killGracePeriodMilliseconds\n : context.timeLimitSeconds * 1000;\n // Monotonic: a wall-clock step must not turn a run `timeout` ended into one that finished in time.\n const startTimeMilliseconds = performance.now();\n // The standard streams are always pipes; the type cannot express that with the extra entry.\n const subprocess = childProcess.spawn(spawnedCommand[0], spawnedCommand.slice(1), {\n cwd: context.cwd,\n detached,\n env: context.env,\n stdio: timeOutput === undefined ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe', timeOutput.fd],\n }) as childProcess.ChildProcessWithoutNullStreams;\n liveSubprocesses.add(subprocess);\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let outputBytes = 0;\n let timedOut = false;\n let outputLimitExceeded = false;\n let wallTimeSeconds = 0;\n let spawnErrorMessage: string | undefined;\n\n const appendOutputChunk = (chunks: Buffer[], chunk: Buffer): void => {\n if (outputBytes >= context.outputLimitBytes) {\n if (chunk.byteLength > 0) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n return;\n }\n\n const remainingBytes = context.outputLimitBytes - outputBytes;\n const appendedChunk = chunk.byteLength > remainingBytes ? chunk.subarray(0, remainingBytes) : chunk;\n chunks.push(appendedChunk);\n outputBytes += appendedChunk.byteLength;\n\n if (chunk.byteLength > remainingBytes) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n };\n\n subprocess.stdout.on('data', (chunk: Buffer) => appendOutputChunk(stdoutChunks, chunk));\n subprocess.stderr.on('data', (chunk: Buffer) => appendOutputChunk(stderrChunks, chunk));\n\n const timeout = setTimeout(() => {\n timedOut = true;\n killSubprocessGroup(subprocess, 'SIGTERM');\n }, timeLimitMilliseconds);\n const killTimeout = setTimeout(() => {\n if (timedOut) killSubprocessGroup(subprocess, 'SIGKILL');\n }, timeLimitMilliseconds + killGracePeriodMilliseconds);\n killTimeout.unref();\n\n const { status, signal } = await new Promise<{ status: number | undefined; signal: NodeJS.Signals | undefined }>(\n (resolve, reject) => {\n let settled = false;\n let pendingError: Error | undefined;\n let closeTimeout: NodeJS.Timeout | undefined;\n const settle = (code: number | null | undefined, exitSignal: NodeJS.Signals | null | undefined): void => {\n if (settled) return;\n settled = true;\n clearTimeout(closeTimeout);\n if (pendingError) {\n reject(pendingError);\n return;\n }\n resolve({ status: code ?? undefined, signal: exitSignal ?? undefined });\n };\n const failAfterClose = (error: Error): void => {\n if (settled) return;\n if (subprocess.pid === undefined) {\n // The spawn itself failed, i.e. the head of the chain (GNU time, or `timeout` without\n // it, or the command itself on Windows) or the cwd is missing. A missing judged command\n // is reported as status 127 by the wrapper instead. Report this like a run that produced\n // only this message.\n spawnErrorMessage = error.message;\n settle(undefined, undefined);\n return;\n }\n pendingError = error;\n killSubprocessGroup(subprocess, 'SIGKILL');\n };\n subprocess.on('error', failAfterClose);\n subprocess.stdin.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code !== 'EPIPE') failAfterClose(error);\n });\n subprocess.on('exit', (code, exitSignal) => {\n // The command is gone, so the limit timers must not fire during the grace period below. A\n // descendant that inherited the pipes (e.g. `sleep 1000 &`) would keep them open and hold\n // back 'close' until it ends: kill what is left of the group. A descendant that moved to\n // its own session survives that, so stop waiting for the pipes after the grace period and\n // keep the output read so far.\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n wallTimeSeconds = (performance.now() - startTimeMilliseconds) / 1000;\n killSubprocessGroup(subprocess, 'SIGKILL');\n closeTimeout = setTimeout(() => {\n subprocess.stdout.destroy();\n subprocess.stderr.destroy();\n settle(code, exitSignal);\n }, killGracePeriodMilliseconds);\n });\n subprocess.on('close', settle);\n subprocess.stdin.end(context.stdin);\n }\n ).finally(() => {\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n liveSubprocesses.delete(subprocess);\n });\n\n const timeResult =\n timeOutput === undefined ? undefined : parseTimeOutput(await readTail(timeOutput, TIME_OUTPUT_TAIL_BYTES));\n\n return {\n stdout: Buffer.concat(stdoutChunks).toString(),\n stderr: spawnErrorMessage ?? Buffer.concat(stderrChunks).toString(),\n status,\n signal,\n // GNU time rounds a fast run to 0.00; the wall time until 'exit' stands in for it (never the\n // grace period spent on the pipes afterwards).\n timeSeconds: timeResult?.timeSeconds || wallTimeSeconds,\n cpuTimeSeconds: timeResult?.cpuTimeSeconds ?? 0,\n memoryBytes: timeResult?.memoryBytes ?? 0,\n timeCommandMessage: timeResult?.message,\n timedOut: timedOut || endedByTimeout(status, wallTimeSeconds, context.timeLimitSeconds),\n outputLimitExceeded,\n };\n } finally {\n await timeOutput?.close();\n }\n}\n\n/**\n * Whether `timeout` ended the run: 124 says it did so at the limit, and 137 that the run ignored its\n * SIGTERM and had to be killed after the grace period. A program can exit with either status on its\n * own, so each is trusted only once the wall time reached the moment `timeout` would have produced it.\n */\nfunction endedByTimeout(status: number | undefined, wallTimeSeconds: number, timeLimitSeconds: number): boolean {\n if (status === TIMEOUT_EXIT_STATUS) return wallTimeSeconds >= timeLimitSeconds;\n if (status === KILLED_AFTER_TIMEOUT_EXIT_STATUS) {\n return wallTimeSeconds >= timeLimitSeconds + killGracePeriodMilliseconds / 1000;\n }\n return false;\n}\n\nfunction killSubprocessGroup(subprocess: childProcess.ChildProcess, signal: NodeJS.Signals): void {\n if (subprocess.pid === undefined) return;\n\n try {\n if (process.platform === 'win32') {\n subprocess.kill(signal);\n return;\n }\n process.kill(-subprocess.pid, signal);\n } catch (error) {\n if (!hasErrorCode(error, 'ESRCH') && !hasErrorCode(error, 'EPERM')) throw error;\n }\n}\n\n// The file is unlinked before it is returned, so nothing can reach it by path anymore.\nasync function openUnlinkedFile(prefix: string): Promise<fs.FileHandle> {\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), prefix));\n try {\n return await fs.open(path.join(directory, 'record'), 'wx+', 0o600);\n } finally {\n await fs.rm(directory, { recursive: true, force: true });\n }\n}\n\nasync function readTail(file: fs.FileHandle, maxBytes: number): Promise<string> {\n const { size } = await file.stat();\n const length = Math.min(size, maxBytes);\n const { buffer } = await file.read(Buffer.alloc(length), 0, length, size - length);\n return buffer.toString();\n}\n\n// Empty when the command was killed before GNU time wrote its record.\nfunction parseTimeOutput(\n content: string\n): { timeSeconds: number; cpuTimeSeconds: number; memoryBytes: number; message: string | undefined } | undefined {\n const match = /(?:^|\\n)(\\d+(?:[.,]\\d+)?) (\\d+(?:[.,]\\d+)?) (\\d+(?:[.,]\\d+)?) (\\d+)\\s*$/.exec(content);\n if (!match) return undefined;\n\n return {\n timeSeconds: parseSeconds(match[1]!),\n cpuTimeSeconds: parseSeconds(match[2]!) + parseSeconds(match[3]!),\n memoryBytes: Number(match[4]) * 1024,\n message: content.slice(0, match.index).trim() || undefined,\n };\n}\n\n// GNU time formats its seconds with the locale's decimal separator.\nfunction parseSeconds(text: string): number {\n return Number(text.replace(',', '.'));\n}\n\nfunction resolveTimeCommand(): readonly [string, ...string[]] | undefined {\n const command = os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time';\n const result = childProcess.spawnSync(command, ['--version'], { stdio: 'ignore' });\n if (result.error || result.status !== 0) return undefined;\n\n // Wall seconds, user CPU seconds, system CPU seconds, peak resident set size in KiB.\n return [command, '--format', '%e %U %S %M'];\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"node:path";import t from"node:fs/promises";import n from"node:os";import{hasErrorCode as r}from"@willbooster/shared-lib";import i from"node:child_process";const a=1e3,o=g(),s=o!==void 0,c=/* @__PURE__ */ new Set;process.once(`exit`,()=>{for(let e of c)d(e,`SIGKILL`)});async function l(e,t){let n=o===void 0?void 0:await f(`exercode-time-`);try{let r=process.platform!==`win32`,s=r?[`timeout`,`--foreground`,`-k`,`1`,String(Math.max(t.timeLimitSeconds,.001)),...e]:e,l=o===void 0?s:[...o,`--output=/dev/fd/3`,...s],f=r?t.timeLimitSeconds*1e3+2*a:t.timeLimitSeconds*1e3,h=performance.now(),g=i.spawn(l[0],l.slice(1),{cwd:t.cwd,detached:r,env:t.env,stdio:n===void 0?[`pipe`,`pipe`,`pipe`]:[`pipe`,`pipe`,`pipe`,n.fd]});c.add(g);let _=[],v=[],y=0,b=!1,x=!1,S=0,C,w=(e,n)=>{if(y>=t.outputLimitBytes){n.byteLength>0&&(x=!0,d(g,`SIGKILL`));return}let r=t.outputLimitBytes-y,i=n.byteLength>r?n.subarray(0,r):n;e.push(i),y+=i.byteLength,n.byteLength>r&&(x=!0,d(g,`SIGKILL`))};g.stdout.on(`data`,e=>w(_,e)),g.stderr.on(`data`,e=>w(v,e));let T=setTimeout(()=>{b=!0,d(g,`SIGTERM`)},f),E=setTimeout(()=>{b&&d(g,`SIGKILL`)},f+a);E.unref();let{status:D,signal:O}=await new Promise((e,n)=>{let r=!1,i,o,s=(t,a)=>{
|
|
1
|
+
import e from"node:path";import t from"node:fs/promises";import n from"node:os";import{hasErrorCode as r}from"@willbooster/shared-lib";import i from"node:child_process";const a=1e3,o=g(),s=o!==void 0,c=/* @__PURE__ */ new Set;process.once(`exit`,()=>{for(let e of c)d(e,`SIGKILL`)});async function l(e,t){let n=o===void 0?void 0:await f(`exercode-time-`);try{let r=process.platform!==`win32`,s=r?[`timeout`,`--foreground`,`-k`,`1`,String(Math.max(t.timeLimitSeconds,.001)),...e]:e,l=o===void 0?s:[...o,`--output=/dev/fd/3`,...s],f=r?t.timeLimitSeconds*1e3+2*a:t.timeLimitSeconds*1e3,h=performance.now(),g=i.spawn(l[0],l.slice(1),{cwd:t.cwd,detached:r,env:t.env,stdio:n===void 0?[`pipe`,`pipe`,`pipe`]:[`pipe`,`pipe`,`pipe`,n.fd]});c.add(g);let _=[],v=[],y=0,b=!1,x=!1,S=0,C,w=(e,n)=>{if(y>=t.outputLimitBytes){n.byteLength>0&&(x=!0,d(g,`SIGKILL`));return}let r=t.outputLimitBytes-y,i=n.byteLength>r?n.subarray(0,r):n;e.push(i),y+=i.byteLength,n.byteLength>r&&(x=!0,d(g,`SIGKILL`))};g.stdout.on(`data`,e=>w(_,e)),g.stderr.on(`data`,e=>w(v,e));let T=setTimeout(()=>{b=!0,d(g,`SIGTERM`)},f),E=setTimeout(()=>{b&&d(g,`SIGKILL`)},f+a);E.unref();let{status:D,signal:O}=await new Promise((e,n)=>{let r=!1,i,o,s=(t,a)=>{r||(r=!0,clearTimeout(o),i?n(i):e({status:t??void 0,signal:a??void 0}))},c=e=>{r||(g.pid===void 0?(C=e.message,s(void 0,void 0)):(i=e,d(g,`SIGKILL`)))};g.on(`error`,c),g.stdin.on(`error`,e=>{e.code!==`EPIPE`&&c(e)}),g.on(`exit`,(e,t)=>{clearTimeout(T),clearTimeout(E),S=(performance.now()-h)/1e3,d(g,`SIGKILL`),o=setTimeout(()=>{g.stdout.destroy(),g.stderr.destroy(),s(e,t)},a)}),g.on(`close`,s),g.stdin.end(t.stdin)}).finally(()=>{clearTimeout(T),clearTimeout(E),c.delete(g)}),k=n===void 0?void 0:m(await p(n,4096));return{stdout:Buffer.concat(_).toString(),stderr:C??Buffer.concat(v).toString(),status:D,signal:O,timeSeconds:k?.timeSeconds||S,cpuTimeSeconds:k?.cpuTimeSeconds??0,memoryBytes:k?.memoryBytes??0,timeCommandMessage:k?.message,timedOut:b||u(D,S,t.timeLimitSeconds),outputLimitExceeded:x}}finally{await n?.close()}}function u(e,t,n){return e===124?t>=n:e===137&&t>=n+a/1e3}function d(e,t){if(e.pid!==void 0)try{if(process.platform===`win32`){e.kill(t);return}process.kill(-e.pid,t)}catch(e){if(!r(e,`ESRCH`)&&!r(e,`EPERM`))throw e}}async function f(r){let i=await t.mkdtemp(e.join(n.tmpdir(),r));try{return await t.open(e.join(i,`record`),`wx+`,384)}finally{await t.rm(i,{recursive:!0,force:!0})}}async function p(e,t){let{size:n}=await e.stat(),r=Math.min(n,t),{buffer:i}=await e.read(Buffer.alloc(r),0,r,n-r);return i.toString()}function m(e){let t=/(?:^|\n)(\d+(?:[.,]\d+)?) (\d+(?:[.,]\d+)?) (\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(e);if(t)return{timeSeconds:h(t[1]),cpuTimeSeconds:h(t[2])+h(t[3]),memoryBytes:Number(t[4])*1024,message:e.slice(0,t.index).trim()||void 0}}function h(e){return Number(e.replace(`,`,`.`))}function g(){let e=n.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=i.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %U %S %M`]}export{s as isTimeCommandAvailable,l as spawnWithLimits};
|
|
2
2
|
//# sourceMappingURL=spawnWithLimits.js.map
|