Summary
parseTsConfigText() in skills/understand/extract-import-map.mjs strips JSONC comments with a regex that is not string-aware. A tsconfig path alias "~/*" (or "@/*") contains the literal /*, which the block-comment regex treats as a comment opener. It scans forward to the next */ — typically inside "**/*.ts" in include — and deletes everything between, destroying compilerOptions.paths.
No comments are required to trigger this. A path alias plus an include glob is enough, and both ship in the stock Next.js tsconfig.json.
Version: 2.9.4
The code
const stripped = raw
.replace(/\/\*[\s\S]*?\*\//g, '') // <-- not string-aware
.replace(/(^|[^:])\/\/.*$/gm, '$1');
JSON.parse(stripped) then throws, the raw-text fallback also fails (real JSONC comments), and parseTsConfigText returns null — so the config contributes zero aliases.
Reproducer
const raw = `{
"compilerOptions": { "baseUrl": ".", "paths": { "~/*": ["./src/*"] } },
"include": ["**/*.ts"]
}`;
const stripped = raw
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|[^:])\/\/.*$/gm, '$1');
console.log(stripped);
// {
// "compilerOptions": { "baseUrl": ".", "paths": { "~*.ts"]
// }
JSON.parse(stripped);
// SyntaxError: Expected ':' after property name in JSON at position 59
Impact
The only signal is one stderr line:
Warning: extract-import-map: tsconfig.json at <path> failed to parse —
path aliases from this config will not be applied — relative imports unaffected
After that, every aliased import silently fails to resolve. Nothing downstream errors — the knowledge graph is just quietly missing most of its import relationships.
Measured on a real Next.js project (386 ~/… imports across 140 files, vs 209 relative imports):
|
before |
after fix |
totalEdges |
190 |
528 |
filesWithImports |
99 |
209 |
Roughly two thirds of the import graph was missing, with no error surfaced.
Suggested fix
Replace the regex pair with a single-pass stripper that tracks in-string state and backslash escapes:
function stripJsonCommentsOutsideStrings(text) {
let out = '';
let inString = false;
let escaped = false;
for (let i = 0; i < text.length; i++) {
const c = text[i];
if (inString) {
out += c;
if (escaped) escaped = false;
else if (c === '\\') escaped = true;
else if (c === '"') inString = false;
continue;
}
if (c === '"') { inString = true; out += c; continue; }
if (c === '/' && text[i + 1] === '*') {
const end = text.indexOf('*/', i + 2);
i = end === -1 ? text.length : end + 1;
continue;
}
if (c === '/' && text[i + 1] === '/') {
const nl = text.indexOf('\n', i);
if (nl === -1) break;
i = nl - 1;
continue;
}
out += c;
}
return out;
}
Then const stripped = stripJsonCommentsOutsideStrings(raw);.
Verified against: a real Next.js tsconfig with ~/*, an @/* alias, a URL containing //, a line comment, and an escaped quote inside a string — 5/5 parse correctly and retain paths.
Related
A sibling copy of the same naive regex exists in stripJsLikeComments() (used for require() extraction). Its docstring acknowledges the limitation and the blast radius is much smaller, so it may not be worth changing — flagging it only for completeness.
Found while analysing a production TypeScript + Python repo with /understand. Happy to open a PR with the fix and a regression test if useful.
Summary
parseTsConfigText()inskills/understand/extract-import-map.mjsstrips JSONC comments with a regex that is not string-aware. A tsconfig path alias"~/*"(or"@/*") contains the literal/*, which the block-comment regex treats as a comment opener. It scans forward to the next*/— typically inside"**/*.ts"ininclude— and deletes everything between, destroyingcompilerOptions.paths.No comments are required to trigger this. A path alias plus an
includeglob is enough, and both ship in the stock Next.jstsconfig.json.Version: 2.9.4
The code
JSON.parse(stripped)then throws, the raw-text fallback also fails (real JSONC comments), andparseTsConfigTextreturnsnull— so the config contributes zero aliases.Reproducer
Impact
The only signal is one stderr line:
After that, every aliased import silently fails to resolve. Nothing downstream errors — the knowledge graph is just quietly missing most of its import relationships.
Measured on a real Next.js project (386
~/…imports across 140 files, vs 209 relative imports):totalEdgesfilesWithImportsRoughly two thirds of the import graph was missing, with no error surfaced.
Suggested fix
Replace the regex pair with a single-pass stripper that tracks in-string state and backslash escapes:
Then
const stripped = stripJsonCommentsOutsideStrings(raw);.Verified against: a real Next.js tsconfig with
~/*, an@/*alias, a URL containing//, a line comment, and an escaped quote inside a string — 5/5 parse correctly and retainpaths.Related
A sibling copy of the same naive regex exists in
stripJsLikeComments()(used forrequire()extraction). Its docstring acknowledges the limitation and the blast radius is much smaller, so it may not be worth changing — flagging it only for completeness.Found while analysing a production TypeScript + Python repo with
/understand. Happy to open a PR with the fix and a regression test if useful.