Files
rea/tests/boundary/filesystem/referenceSourceComputedRequire.test.ts
Hemang VarshneyandHemang Varshney 4842f23765 fix(source): retain CommonJS requires spelled as exact no-substitution templates (#4511)
* fix(source): retain CommonJS requires spelled as exact no-substitution templates

Admit expression-free template arguments at the CommonJS require gate and
read their single cooked quasi through the shared exact-literal reader.
Scope is the require path only: static ESM template declarations keep their
parse failures and dynamic import() keeps the explicit unknown/partial
<dynamic-import> relationship with no invented target.

Refs: #4382

* fix(source): admit TypeScript-wrapped exact require arguments via the shared literal reader

Address review: the require gate matched the outer AST node, so
runtime-transparent TS wrappers (as const, !, satisfies) around exact
strings or expression-free templates were still silently omitted even
though readExactJavaScriptLiteral unwraps them. Remove the gate and let
the shared exact-literal reader own argument exactness; computed
arguments remain silent and dynamic import() behavior is unchanged.

Refs: #4382

---------

Co-authored-by: Hemang Varshney <218273197+hemv-857@users.noreply.github.com>
2026-10-22 06:12:42 +08:00

153 lines
4.7 KiB
TypeScript

import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { promisify } from "node:util";
import { expect, it } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { importReferenceSource } from "../../support/referenceSourceResourceScope.js";
it("distinguishes computed require member values from literal members in source evidence", async () => {
const root = await createTestTempDirectory("rea-reference-require-members-");
const literalCallees = [
"require",
"require.resolve",
'require["resolve"]',
"require.main",
'require["main"]',
'require[("resolve")]',
'require["res\\u006flve"]',
];
const ignoredSources = [
'require[resolve]("./dep.js");',
'require[main]("./dep.js");',
'const resolve = "toString"; require[resolve]("./dep.js");',
'const main = "toString"; require[main]("./dep.js");',
'const resolve = "resolve"; require[resolve]("./dep.js");',
'const main = "main"; require[main]("./dep.js");',
'const method = "resolve"; require[method]("./dep.js");',
'require[`resolve`]("./dep.js");',
'require["re" + "solve"]("./dep.js");',
...[
"require?.",
"require.resolve?.",
'require["resolve"]?.',
"require.main?.",
'require["main"]?.',
"require?.resolve",
'require?.["resolve"]',
"require?.[resolve]",
"require?.main",
'require?.["main"]',
"require?.[main]",
"(require?.resolve)",
"require.resolve.call",
"require.main.require",
"module.require",
"other.resolve",
"other.require",
"require.toString",
'require["toString"]',
].map((callee) => `${callee}("./dep.js");`),
];
const sources: Record<string, string> = {
"computed.cjs": [
'const resolve = "toString";',
'const main = "toString";',
'const value = require[resolve]("./dep.js");',
'require[main]("./dep.js");',
"console.log(typeof value);",
].join("\n"),
"literal.cjs": 'console.log(require["resolve"]("./dep.js"));\n',
"dep.js": 'throw new Error("The dependency must not execute");\n',
"ignored.cjs": ignoredSources.map((source) => `{ ${source} }`).join("\n"),
"unknown.cjs": [
"function load(target) {",
...literalCallees.flatMap((callee) => [
` ${callee}(target);`,
` ${callee}(\`./dep.js\`);`,
` ${callee}(getTarget());`,
` ${callee}();`,
]),
" return import(target);",
"}",
].join("\n"),
};
for (const [index, callee] of literalCallees.entries())
sources[`member-${index}.cjs`] = `${callee}("./dep.js");\n`;
await Promise.all(
Object.entries(sources).map(([path, source]) =>
writeFile(join(root, path), source),
),
);
// These owned producers demonstrate the member's runtime meaning without
// loading the throwing dependency. The importer itself never executes them.
const runNode = promisify(execFile);
for (const [path, stdout] of [
["computed.cjs", "string\n"],
["literal.cjs", `${join(root, "dep.js")}\n`],
] as const) {
const executed = await runNode(process.execPath, [join(root, path)], {
cwd: root,
timeout: 5_000,
});
expect(executed.stdout).toBe(stdout);
expect(executed.stderr).toBe("");
}
const result = await importReferenceSource({
root,
caller: "reference-require-members-test",
policy: { secretPatterns: [] },
});
if (!result.ok) throw result.error;
const graph = result.value;
expect(graph.parse_failures).toEqual([]);
expect(graph.relationships).toEqual([
{
from_path: "literal.cjs",
to: "dep.js",
kind: "requires",
resolution: "internal",
parse_state: "parsed",
},
...literalCallees.map((_, index) => ({
from_path: `member-${index}.cjs`,
to: "dep.js",
kind: "requires",
resolution: "internal",
parse_state: "parsed",
})),
{
from_path: "unknown.cjs",
to: "<dynamic-import>",
kind: "imports",
resolution: "unknown",
parse_state: "partial",
},
{
from_path: "unknown.cjs",
to: "dep.js",
kind: "requires",
resolution: "internal",
parse_state: "parsed",
},
]);
expect(graph.entries).toHaveLength(Object.keys(sources).length);
for (const [path, source] of Object.entries(sources)) {
expect(graph.entries).toContainEqual({
path,
kind: "file",
sha256: createHash("sha256").update(source).digest("hex"),
size: Buffer.byteLength(source),
language: "JavaScript",
classifications: ["source"],
content_state: "hashed",
limitations: [],
});
}
});