fix(sdk): ignore escaped comment markers

This commit is contained in:
Kayshen-X 2026-07-15 21:10:08 +08:00
parent 78d9f5406e
commit 4988343a4b
2 changed files with 211 additions and 51 deletions

View file

@ -33,7 +33,21 @@ export function renderPackageManifest(source, version) {
function maskSdkCommentsAndTemplates(source) {
const masked = source.split('');
let state = 'code';
const regexPrefixKeywords = new Set([
'await',
'case',
'delete',
'else',
'in',
'instanceof',
'new',
'of',
'return',
'throw',
'typeof',
'void',
'yield',
]);
const mask = (index) => {
if (source[index] !== '\n' && source[index] !== '\r') {
@ -41,6 +55,28 @@ function maskSdkCommentsAndTemplates(source) {
}
};
function isEscaped(index) {
let backslashes = 0;
for (let cursor = index - 1; cursor >= 0 && source[cursor] === '\\'; cursor -= 1) {
backslashes += 1;
}
return backslashes % 2 === 1;
}
function skipQuoted(start, quote) {
let index = start + 1;
while (index < source.length) {
if (source[index] === '\\') {
index += 2;
} else if (source[index] === quote) {
return index + 1;
} else {
index += 1;
}
}
return index;
}
function maskQuoted(start, quote) {
let index = start;
while (index < source.length) {
@ -81,39 +117,114 @@ function maskSdkCommentsAndTemplates(source) {
return index;
}
function scanRegexLiteral(start, maskContents) {
let inCharacterClass = false;
let index = start + 1;
if (maskContents) {
mask(start);
}
while (index < source.length) {
const character = source[index];
if (character === '\n' || character === '\r') {
return index;
}
if (maskContents) {
mask(index);
}
if (character === '\\') {
if (maskContents) {
mask(index + 1);
}
index += 2;
} else if (character === '[') {
inCharacterClass = true;
index += 1;
} else if (character === ']') {
inCharacterClass = false;
index += 1;
} else if (character === '/' && !inCharacterClass) {
index += 1;
while (/[a-z]/i.test(source[index] ?? '')) {
if (maskContents) {
mask(index);
}
index += 1;
}
return index;
} else {
index += 1;
}
}
return index;
}
function scanIdentifier(start, maskContents) {
let index = start;
while (/[\w$]/.test(source[index] ?? '')) {
if (maskContents) {
mask(index);
}
index += 1;
}
return {
end: index,
canStartRegex: regexPrefixKeywords.has(source.slice(start, index)),
};
}
function maskTemplateExpression(start) {
let depth = 1;
let index = start;
let canStartRegex = true;
while (index < source.length) {
const character = source[index];
const next = source[index + 1];
if (character === "'" || character === '"') {
index = maskQuoted(index, character);
canStartRegex = false;
continue;
}
if (character === '`') {
index = maskTemplateLiteral(index);
canStartRegex = false;
continue;
}
if (character === '/' && next === '/') {
if (character === '/' && next === '/' && !isEscaped(index)) {
index = maskLineComment(index);
continue;
}
if (character === '/' && next === '*') {
if (character === '/' && next === '*' && !isEscaped(index)) {
index = maskBlockComment(index);
continue;
}
if (character === '/' && canStartRegex) {
index = scanRegexLiteral(index, true);
canStartRegex = false;
continue;
}
if (/[A-Za-z_$]/.test(character)) {
const identifier = scanIdentifier(index, true);
index = identifier.end;
canStartRegex = identifier.canStartRegex;
continue;
}
mask(index);
if (character === '{') {
depth += 1;
canStartRegex = true;
} else if (character === '}') {
depth -= 1;
index += 1;
if (depth === 0) {
return index;
}
canStartRegex = false;
continue;
} else if (/\d/.test(character) || '.)]'.includes(character)) {
canStartRegex = false;
} else if (!/\s/.test(character)) {
canStartRegex = true;
}
index += 1;
}
@ -145,58 +256,45 @@ function maskSdkCommentsAndTemplates(source) {
return index;
}
for (let index = 0; index < source.length; index += 1) {
let canStartRegex = true;
for (let index = 0; index < source.length; ) {
const character = source[index];
const next = source[index + 1];
if (state === 'single-quote' || state === 'double-quote') {
if (character === '\\') {
index += 1;
} else if (
(state === 'single-quote' && character === "'") ||
(state === 'double-quote' && character === '"')
) {
state = 'code';
}
continue;
}
if (state === 'line-comment') {
if (character === '\n' || character === '\r') {
state = 'code';
} else {
mask(index);
}
continue;
}
if (state === 'block-comment') {
mask(index);
if (character === '*' && next === '/') {
mask(index + 1);
index += 1;
state = 'code';
}
continue;
}
if (character === "'") {
state = 'single-quote';
} else if (character === '"') {
state = 'double-quote';
} else if (character === '`') {
index = maskTemplateLiteral(index) - 1;
} else if (character === '/' && next === '/') {
mask(index);
mask(index + 1);
if (/\s/.test(character)) {
index += 1;
state = 'line-comment';
} else if (character === '/' && next === '*') {
mask(index);
mask(index + 1);
index += 1;
state = 'block-comment';
continue;
}
if (character === "'" || character === '"') {
index = skipQuoted(index, character);
canStartRegex = false;
continue;
}
if (character === '`') {
index = maskTemplateLiteral(index);
canStartRegex = false;
continue;
}
if (character === '/' && next === '/' && !isEscaped(index)) {
index = maskLineComment(index);
continue;
}
if (character === '/' && next === '*' && !isEscaped(index)) {
index = maskBlockComment(index);
continue;
}
if (character === '/' && canStartRegex) {
index = scanRegexLiteral(index, false);
canStartRegex = false;
continue;
}
if (/[A-Za-z_$]/.test(character)) {
const identifier = scanIdentifier(index, false);
index = identifier.end;
canStartRegex = identifier.canStartRegex;
continue;
}
canStartRegex = !(/\d/.test(character) || '.)]}'.includes(character));
index += 1;
}
return masked.join('');

View file

@ -233,6 +233,68 @@ export const VERSION = '2.3.4';
);
});
test('SDK entry rendering ignores escaped block-comment markers in regex literals', () => {
const input = String.raw`const pattern = /[\/*]/;
export const VERSION = '1.0.0';
`;
assert.equal(
renderSdkEntry(input, '2.3.4'),
String.raw`const pattern = /[\/*]/;
export const VERSION = '2.3.4';
`,
);
});
test('SDK entry rendering ignores comment markers inside regex character classes', () => {
const input = `const pattern = /[/*]/;
export const VERSION = '1.0.0';
`;
assert.equal(
renderSdkEntry(input, '2.3.4'),
`const pattern = /[/*]/;
export const VERSION = '2.3.4';
`,
);
});
test('SDK entry rendering ignores escaped line-comment markers in regex literals', () => {
const input = String.raw`const pattern = /\//;
export const VERSION = '1.0.0';
`;
assert.equal(
renderSdkEntry(input, '2.3.4'),
String.raw`const pattern = /\//;
export const VERSION = '2.3.4';
`,
);
});
test('SDK entry rendering distinguishes division expressions from comments and regex literals', () => {
const input = `const ratio = left / right;
/*
export const VERSION = 'block-comment-only';
*/
const half = total / 2;
// export const VERSION = 'line-comment-only';
export const VERSION = '1.0.0';
`;
assert.equal(
renderSdkEntry(input, '2.3.4'),
`const ratio = left / right;
/*
export const VERSION = 'block-comment-only';
*/
const half = total / 2;
// export const VERSION = 'line-comment-only';
export const VERSION = '2.3.4';
`,
);
});
test('Bun lock inspection finds every versioned SDK workspace and ignores the root workspace', () => {
const lockfile = `{
"workspaces": {