chore(deps): match CommonJS and template-literal imports in the shared-source scan
From the suppressed comment on Copilot's second pass of #1962. Under- approximating is the dangerous direction here: a package the scan misses never enters the candidate set, so its skew passes the guard silently. Three forms went unmatched. `import x = require("pkg")` and a bare `require("pkg")` are the ordinary import syntax in `.cts` — which the previous commit just admitted to the scan, so leaving them out would have been an inconsistency introduced by that widening. A dynamic import carrying import attributes (`import("pkg", { with: … })`) was missed because the pattern required the closing paren. A static template literal (`import(\`pkg\`)`) was missed for want of a backtick in the quote class. Backticks are accepted only in the *call* forms. A `from` clause requires a string literal, so allowing them there would buy nothing while reopening the prose hazard the anchored specifier pattern closes: inline code in comments is written with backticks throughout this codebase, and admitting it inflated the derived set from 18 names to 41 with prose fragments (`tools`, `connect()`, `messages`). Inert today, since a name absent from every lockfile contributes nothing — but a prose word colliding with a real package name would silently widen the set. Pinned with a regression test. The real set is unchanged at 18, so these forms add no package today; they close the syntax against the one that arrives later. 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
e480fdc49d
commit
47e0ee059a
2 changed files with 51 additions and 5 deletions
|
|
@ -101,12 +101,25 @@ export function packageNameOf(specifier) {
|
|||
return name;
|
||||
}
|
||||
|
||||
// `from "x"` (import and re-export), a side-effect `import "x"`, and a dynamic
|
||||
// `import("x")`. Only these three forms introduce a dependency's *types*.
|
||||
// Every form that can introduce a dependency's *types* (Copilot, #1962).
|
||||
// `import x = require("pkg")` and a bare `require("pkg")` matter because the
|
||||
// shared trees may hold `.cts` sources, where that is the ordinary import
|
||||
// syntax; and neither call form requires the closing paren, so an import
|
||||
// attributes argument (`import("pkg", { with: … })`) can't hide the specifier.
|
||||
//
|
||||
// Backticks are accepted ONLY in the call forms, where a static template
|
||||
// literal is legal. A `from` clause requires a string literal, so allowing
|
||||
// backticks there would buy nothing while reopening the prose hazard the
|
||||
// anchored specifier pattern exists to close — inline code in a comment is
|
||||
// written with backticks throughout this codebase, and "…derived from
|
||||
// `tools`…" would otherwise enter the candidate set as a package named
|
||||
// `tools`. Inert today, but a prose word that collides with a real package
|
||||
// name would silently widen the set.
|
||||
const SPECIFIER_FORMS = [
|
||||
/\bfrom\s*["']([^"']+)["']/g,
|
||||
/\bimport\s+["']([^"']+)["']/g,
|
||||
/\bimport\s*\(\s*["']([^"']+)["']\s*\)/g,
|
||||
/\bfrom\s*["']([^"']+)["']/g, // import … from "x" / export … from "x"
|
||||
/\bimport\s+["']([^"']+)["']/g, // side-effect import "x"
|
||||
/\bimport\s*\(\s*["'`]([^"'`]+)["'`]/g, // dynamic import("x"[, opts])
|
||||
/\brequire\s*\(\s*["'`]([^"'`]+)["'`]/g, // import x = require("x"), require("x")
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -79,6 +79,26 @@ test("packageNameOf: non-packages are rejected", () => {
|
|||
assert.equal(packageNameOf(undefined), null);
|
||||
});
|
||||
|
||||
test("importedPackageNames: CommonJS and awkward dynamic-import forms (Copilot, #1962)", () => {
|
||||
// Under-approximating is the dangerous direction: a package the scan misses
|
||||
// never enters the candidate set, so its skew passes the guard silently.
|
||||
// `.cts` sources in the shared trees use `import x = require(…)` as ordinary
|
||||
// syntax, and a dynamic import may carry import attributes or a static
|
||||
// template literal — none of which the original three patterns matched.
|
||||
const source = `
|
||||
import express = require("express");
|
||||
const yaml = require("yaml");
|
||||
const a = await import("undici", { with: { type: "json" } });
|
||||
const b = await import(\`jose\`);
|
||||
`;
|
||||
assert.deepEqual([...importedPackageNames(source)].sort(), [
|
||||
"express",
|
||||
"jose",
|
||||
"undici",
|
||||
"yaml",
|
||||
]);
|
||||
});
|
||||
|
||||
test("importedPackageNames: the three specifier forms that introduce types", () => {
|
||||
const source = `
|
||||
import { z } from "zod/v4";
|
||||
|
|
@ -109,6 +129,19 @@ test("importedPackageNames: prose after `from` is not an import", () => {
|
|||
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.
|
||||
const source = `
|
||||
/** The excluded set derived from \\\`tools\\\`, keyed off \\\`express\\\`-style paths. */
|
||||
import { z } from "zod";
|
||||
`;
|
||||
assert.deepEqual([...importedPackageNames(source)], ["zod"]);
|
||||
});
|
||||
|
||||
test("topLevelLockVersions: nested duplicates are ignored", () => {
|
||||
// A nested `node_modules/a/node_modules/b` is npm resolving a transitive
|
||||
// conflict *inside* one install — routine, and not the cross-install skew
|
||||
|
|
|
|||
Loading…
Reference in a new issue