mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
refactor(javascript): name retained require call facts explicitly
This commit is contained in:
+8
-3
@@ -410,11 +410,16 @@ strings, expression-free templates and runtime-transparent TypeScript wrappers
|
||||
retain their targets; computed imports remain unknown. TypeScript import-equals
|
||||
dependencies remain `requires` relationships whether or not they are exported.
|
||||
|
||||
CommonJS `require` relationships retain the requested specifier. REA resolves
|
||||
them only when the loader base is proven: this includes the exact
|
||||
REA derives historical CommonJS paths only when the selected loader and its
|
||||
base are proven: this includes the exact
|
||||
`createRequire(new URL("./file.cjs", import.meta.url))` form and
|
||||
`require.resolve` with `{ paths: [__dirname] }`. Other custom bases and search
|
||||
options remain partial with unknown resolution. An excluded `package.json`
|
||||
options remain partial with unknown resolution and the literal requested
|
||||
specifier. Overwritten resolver methods retain that uncertainty; they are not
|
||||
attributed to Node. `require.main` is a module object, so calling it does not
|
||||
establish a loader reference. These relationships do not establish execution;
|
||||
Node's [`require.resolve`](https://nodejs.org/docs/v24.22.1/api/modules.html#requireresolverequest-options)
|
||||
performs lookup without loading the target. An excluded `package.json`
|
||||
also leaves package-directory selection unknown rather than treating the
|
||||
manifest as absent and applying an `index` fallback.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
|
||||
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
|
||||
import { referenceSourceRequireCallBases } from "../referenceSourceRequireBindings.js";
|
||||
import { referenceSourceRequireCalls } from "../referenceSourceRequireBindings.js";
|
||||
import { parseReferenceSourceImports } from "../referenceSourceImportParsing.js";
|
||||
|
||||
it.each(["require", "require.resolve", "require.main"])(
|
||||
@@ -33,7 +33,7 @@ it.each(["require", "require.resolve", "require.main"])(
|
||||
);
|
||||
const parsed = parseJavaScriptSource(source);
|
||||
if (parsed === null) throw new Error("Expected loader association fixture");
|
||||
expect(referenceSourceRequireCallBases(parsed, "entry.cjs").size).toBe(
|
||||
expect(referenceSourceRequireCalls(parsed, "entry.cjs").size).toBe(
|
||||
callee === "require.main" ? 0 : 1,
|
||||
);
|
||||
},
|
||||
@@ -64,7 +64,7 @@ it.each([
|
||||
(_name, source, associated, specifier) => {
|
||||
const parsed = parseJavaScriptSource(source);
|
||||
if (parsed === null) throw new Error("Expected factory proof fixture");
|
||||
expect(referenceSourceRequireCallBases(parsed, "entry.mjs").size).toBe(
|
||||
expect(referenceSourceRequireCalls(parsed, "entry.mjs").size).toBe(
|
||||
associated,
|
||||
);
|
||||
const analysis = analyzeJavaScriptStaticSource(source);
|
||||
@@ -80,7 +80,7 @@ it("associates a renamed createRequire resolve method with search options", () =
|
||||
`${imported}const load=make(import.meta.url);load.resolve('node:path',{paths:[]});`,
|
||||
);
|
||||
if (parsed === null) throw new Error("Expected factory proof fixture");
|
||||
expect(referenceSourceRequireCallBases(parsed, "entry.mjs").size).toBe(1);
|
||||
expect(referenceSourceRequireCalls(parsed, "entry.mjs").size).toBe(1);
|
||||
});
|
||||
|
||||
it("keeps current-file and mixed createRequire bases distinct", () => {
|
||||
@@ -90,19 +90,18 @@ it("keeps current-file and mixed createRequire bases distinct", () => {
|
||||
if (currentFile === null)
|
||||
throw new Error("Expected current-file base fixture");
|
||||
const currentCall = [
|
||||
...referenceSourceRequireCallBases(currentFile, "sub/main.mjs").keys(),
|
||||
...referenceSourceRequireCalls(currentFile, "sub/main.mjs").keys(),
|
||||
][0];
|
||||
expect(
|
||||
referenceSourceRequireCallBases(currentFile, "sub/main.mjs").get(
|
||||
currentCall!,
|
||||
)?.resolutionBase,
|
||||
referenceSourceRequireCalls(currentFile, "sub/main.mjs").get(currentCall!)
|
||||
?.resolutionBase,
|
||||
).toBe("source-file");
|
||||
|
||||
const mixed = parseJavaScriptSource(
|
||||
`${imported}const load=make(flag?import.meta.url:new URL('./sub/entry.cjs',import.meta.url));load('./dep.cjs');`,
|
||||
);
|
||||
if (mixed === null) throw new Error("Expected mixed-base fixture");
|
||||
const mixedBases = referenceSourceRequireCallBases(mixed, "main.mjs");
|
||||
const mixedBases = referenceSourceRequireCalls(mixed, "main.mjs");
|
||||
expect(
|
||||
[...mixedBases.values()].map(({ resolutionBase }) => resolutionBase),
|
||||
).toEqual(["unknown"]);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
parserPluginsForPath,
|
||||
} from "./javascript/javascriptSourceParser.js";
|
||||
import {
|
||||
referenceSourceRequireCallBases,
|
||||
referenceSourceRequireCalls,
|
||||
type ReferenceSourceRequireCall,
|
||||
} from "./referenceSourceRequireBindings.js";
|
||||
import { isRelativeJavaScriptModuleSpecifier } from "./javascript/javascriptModuleSpecifier.js";
|
||||
@@ -245,7 +245,7 @@ const extractRequireAndDynamicImports = (
|
||||
from_path: string,
|
||||
relationships: ReferenceSourceImportRelationship[],
|
||||
limitations: string[],
|
||||
requireCallBases: ReadonlyMap<CallExpression, ReferenceSourceRequireCall>,
|
||||
requireCalls: ReadonlyMap<CallExpression, ReferenceSourceRequireCall>,
|
||||
): void => {
|
||||
// Single traversal owner: iterative, VISITOR_KEYS-gated, no recursion over
|
||||
// loc/comment objects. Survives deeply nested generated member chains that
|
||||
@@ -274,7 +274,7 @@ const extractRequireAndDynamicImports = (
|
||||
// The shared exact-literal reader owns argument exactness: it unwraps
|
||||
// runtime-transparent TS wrappers (`as const`, `!`, `satisfies`) and
|
||||
// admits quoted and expression-free template spellings alike.
|
||||
const requireCall = requireCallBases.get(call);
|
||||
const requireCall = requireCalls.get(call);
|
||||
if (requireCall !== undefined) {
|
||||
const result = moduleSpecifierFromExpression(requireCall.arguments?.[0]);
|
||||
if (result !== undefined) {
|
||||
@@ -411,7 +411,7 @@ export const parseReferenceSourceImports = (
|
||||
path,
|
||||
relationships,
|
||||
limitations,
|
||||
referenceSourceRequireCallBases(ast, path),
|
||||
referenceSourceRequireCalls(ast, path),
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -33,7 +33,7 @@ export interface ReferenceSourceRequireCall {
|
||||
}
|
||||
|
||||
/** Identify exact calls through a source-owned Node loader and their resolver base. */
|
||||
export const referenceSourceRequireCallBases = (
|
||||
export const referenceSourceRequireCalls = (
|
||||
file: Pick<ParsedJavaScriptSource, "program">,
|
||||
path: string,
|
||||
): ReadonlyMap<t.CallExpression, ReferenceSourceRequireCall> => {
|
||||
|
||||
@@ -9,6 +9,17 @@ import { expect, it } from "vitest";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { importReferenceSource } from "../../support/referenceSourceResourceScope.js";
|
||||
|
||||
const hashedSourceEntry = (path: string, source: string) => ({
|
||||
path,
|
||||
kind: "file",
|
||||
sha256: createHash("sha256").update(source).digest("hex"),
|
||||
size: Buffer.byteLength(source),
|
||||
language: "JavaScript",
|
||||
classifications: ["source"],
|
||||
content_state: "hashed",
|
||||
limitations: [],
|
||||
});
|
||||
|
||||
it("distinguishes computed require member values from literal members in source evidence", async () => {
|
||||
const root = await createTestTempDirectory("rea-reference-require-members-");
|
||||
const literalCallees = [
|
||||
@@ -107,6 +118,13 @@ it("distinguishes computed require member values from literal members in source
|
||||
const graph = result.value;
|
||||
expect(graph.parse_failures).toEqual([]);
|
||||
expect(graph.relationships).toEqual([
|
||||
{
|
||||
from_path: "ignored.cjs",
|
||||
to: "./dep.js",
|
||||
kind: "requires",
|
||||
resolution: "unknown",
|
||||
parse_state: "partial",
|
||||
},
|
||||
{
|
||||
from_path: "literal.cjs",
|
||||
to: "dep.js",
|
||||
@@ -114,13 +132,26 @@ it("distinguishes computed require member values from literal members in source
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
...literalCallees.map((_, index) => ({
|
||||
from_path: `member-${index}.cjs`,
|
||||
to: "dep.js",
|
||||
...literalCallees.flatMap((callee, index) =>
|
||||
callee === "require.main" || callee === 'require["main"]'
|
||||
? []
|
||||
: [
|
||||
{
|
||||
from_path: `member-${index}.cjs`,
|
||||
to: "dep.js",
|
||||
kind: "requires",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
],
|
||||
),
|
||||
{
|
||||
from_path: "unknown.cjs",
|
||||
to: "./dep.js",
|
||||
kind: "requires",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
})),
|
||||
resolution: "unknown",
|
||||
parse_state: "partial",
|
||||
},
|
||||
{
|
||||
from_path: "unknown.cjs",
|
||||
to: "<dynamic-import>",
|
||||
@@ -137,16 +168,6 @@ it("distinguishes computed require member values from literal members in source
|
||||
},
|
||||
]);
|
||||
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: [],
|
||||
});
|
||||
}
|
||||
for (const [path, source] of Object.entries(sources))
|
||||
expect(graph.entries).toContainEqual(hashedSourceEntry(path, source));
|
||||
});
|
||||
|
||||
@@ -332,7 +332,10 @@ it("does not attribute overwritten require members to Node's resolver", async ()
|
||||
if (!result.ok) throw result.error;
|
||||
expect(
|
||||
result.value.relationships
|
||||
.filter(({ kind }) => kind === "requires")
|
||||
.filter(
|
||||
({ kind, resolution }) =>
|
||||
kind === "requires" && resolution === "internal",
|
||||
)
|
||||
.map(({ from_path, to }) => [from_path, to])
|
||||
.sort((left, right) => (left[0] ?? "").localeCompare(right[0] ?? "")),
|
||||
).toEqual([
|
||||
@@ -341,4 +344,17 @@ it("does not attribute overwritten require members to Node's resolver", async ()
|
||||
["local-loader.cjs", "loaded.cjs"],
|
||||
["optional-call.cjs", "loaded.cjs"],
|
||||
]);
|
||||
expect(
|
||||
result.value.relationships.filter(
|
||||
({ kind, resolution }) => kind === "requires" && resolution === "unknown",
|
||||
),
|
||||
).toEqual(
|
||||
["assigned.cjs", "computed.cjs", "defined.cjs"].map((from_path) => ({
|
||||
from_path,
|
||||
to: "./loaded.cjs",
|
||||
kind: "requires",
|
||||
resolution: "unknown",
|
||||
parse_state: "partial",
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user