chore(deps): extract specifiers with ts.preProcessFile; document the transitive boundary
From Copilot's sixth pass of #1962. Take the TypeScript-parser suggestion, which was raised across three rounds and was right. The regex scan got both directions wrong and every widening traded one failure for the other: it *missed* valid syntax (`import x = require(…)`, an import-attributes argument, a comment between tokens — each a silent miss, so the package never entered the candidate set and its skew passed), and it *invented* names from prose, since `// adapted from "react"` is indistinguishable from an import to a pattern that cannot tell code from a comment. The earlier prose test only passed because `cwd omitted` isn't a valid package name. `ts.preProcessFile` is the cheap answer I'd missed while arguing against the full AST: TypeScript's lightweight pre-parse scanner, no parse tree and no type checking, purpose-built to return module specifiers. It handles every import form, trivia, strings, and regex literals, and never reads a comment as code. typescript resolves from `clients/web`, which already carries it, via the `createRequire` base `smoke-web-browser.mjs` established — a bare import would resolve relative to `scripts/`, not the cwd. The derived set drops 18 → 17 real names: every genuine package is retained and the one prose artifact is gone. Regression tests now use *real* package names in comments and strings, the case the old test couldn't cover. Also records the KNOWN BOUNDARY the review's other finding identified. Candidates cover what the shared sources name directly, so a package reaching the program only through another package's `.d.ts` is invisible — `@modelcontextprotocol/sdk` is the live example, skewed 1.29.0 vs 1.30.0 and present in web's program from both installs, never written in first-party code. Closing it properly changes what the guard measures, so it is tracked as #1965, with both candidate derivations measured there: a lockfile closure is unusable (155 packages, 25 skewed, nearly all irrelevant tooling, and it misses the SDK anyway), while `tsc --listFilesOnly` is correct and small (15 for clients/web). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDdo1rNRVQnVRRdsqSrvG3
This commit is contained in:
parent
2f1d97e7bb
commit
fcd45037c9
1 changed files with 66 additions and 59 deletions
|
|
@ -155,26 +155,26 @@ test("importedPackageNames: the three specifier forms that introduce types", ()
|
|||
]);
|
||||
});
|
||||
|
||||
test("importedPackageNames: prose after `from` is not an import", () => {
|
||||
// The scan over-approximates on purpose, but must not invent package names
|
||||
// out of comment text — a bogus name absent from every lockfile is inert,
|
||||
// yet a *plausible* one would silently widen the candidate set.
|
||||
test("importedPackageNames: prose in comments never becomes a package (Copilot, #1962)", () => {
|
||||
// The regex scan this replaced could not tell code from a comment, so
|
||||
// `// adapted from "react"` added `react` to the candidate set — and if that
|
||||
// installed package were skewed, an unrelated comment would fail `validate`.
|
||||
// These use REAL package names, which is the case the old prose test missed:
|
||||
// it only passed because `cwd omitted` isn't a valid package name.
|
||||
const source = `
|
||||
// Resolved relative to the runner dir, not from "cwd omitted" by the caller.
|
||||
/** Reads the manifest from "file:" URLs. */
|
||||
// adapted from "react"
|
||||
/** Mirrors the behavior of "express", see require("yaml") below. */
|
||||
/** The excluded set derived from \\\`hono\\\`-style paths. */
|
||||
// const disabled = await import("undici");
|
||||
import { z } from "zod";
|
||||
`;
|
||||
assert.deepEqual([...importedPackageNames(source)], ["zod"]);
|
||||
});
|
||||
|
||||
test("importedPackageNames: a backticked `from` in prose is not an import", () => {
|
||||
// Backticks are legal in the *call* forms (a static template literal) but
|
||||
// never after `from`, which requires a string literal. Accepting them there
|
||||
// would sweep in inline code from comments — this codebase writes it with
|
||||
// backticks throughout — and a prose word colliding with a real package name
|
||||
// would silently widen the candidate set.
|
||||
test("importedPackageNames: a specifier inside a string literal is not an import", () => {
|
||||
const source = `
|
||||
/** The excluded set derived from \\\`tools\\\`, keyed off \\\`express\\\`-style paths. */
|
||||
const msg = 'run require("chokidar") to load it';
|
||||
const re = /"jose"/;
|
||||
import { z } from "zod";
|
||||
`;
|
||||
assert.deepEqual([...importedPackageNames(source)], ["zod"]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue