mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 17:30:32 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75c66f51a6 | ||
|
|
d082dd934d |
No files matched your search
@@ -3,7 +3,10 @@ import { createHash } from "node:crypto";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { parseReferenceSourceEntries } from "./ReferenceSourceImportEntries.js";
|
||||
import { historicalSourceLanguages } from "../domain/referenceSourceGraph.js";
|
||||
import {
|
||||
historicalSourceInventoryHasPartialFacts,
|
||||
historicalSourceLanguages,
|
||||
} from "../domain/referenceSourceGraph.js";
|
||||
import type { ReferenceSourceRead } from "../reference/ReferenceSourceReaderTypes.js";
|
||||
|
||||
it("projects an unsupported non-regular entry as unknown kind instead of file", () => {
|
||||
@@ -98,6 +101,100 @@ it("keeps source entries when a module has an empty specifier", () => {
|
||||
expect(parsed.limitations[0]).toMatch(/not recorded.*relationship/iu);
|
||||
});
|
||||
|
||||
it("resolves immediately invoked loaders from their proven source bases", () => {
|
||||
const files = [
|
||||
[
|
||||
"main.cjs",
|
||||
'(() => require)()("./dep.cjs"); (() => () => {})()("./local.cjs");',
|
||||
],
|
||||
[
|
||||
"main.mjs",
|
||||
[
|
||||
'import {createRequire} from "node:module";',
|
||||
'createRequire(new URL("./sub/base.cjs", import.meta.url))("./dep.cjs");',
|
||||
'createRequire(new URL("./sub/base.cjs", import.meta.url)).call(null, "./member.cjs");',
|
||||
].join("\n"),
|
||||
],
|
||||
["dep.cjs", ""],
|
||||
["sub/dep.cjs", ""],
|
||||
["member.cjs", ""],
|
||||
["sub/member.cjs", ""],
|
||||
] as const;
|
||||
const parsed = parseReferenceSourceEntries(
|
||||
{
|
||||
root: "/selected/source",
|
||||
entries: files.map(([path, content]) => ({
|
||||
status: "read",
|
||||
kind: "file",
|
||||
path,
|
||||
bytes: Buffer.from(content),
|
||||
size: Buffer.byteLength(content),
|
||||
})),
|
||||
bytesRead: files.reduce(
|
||||
(sum, [, content]) => sum + Buffer.byteLength(content),
|
||||
0,
|
||||
),
|
||||
limitations: [],
|
||||
},
|
||||
[],
|
||||
);
|
||||
expect(
|
||||
parsed.relationships.filter(({ kind }) => kind === "requires"),
|
||||
).toEqual([
|
||||
{
|
||||
from_path: "main.cjs",
|
||||
to: "dep.cjs",
|
||||
kind: "requires",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
{
|
||||
from_path: "main.mjs",
|
||||
to: "sub/dep.cjs",
|
||||
kind: "requires",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["function load(name) { require(name); }", true],
|
||||
["function load(require, name) { require(name); }", false],
|
||||
["require(7); require();", false],
|
||||
] as const)(
|
||||
"reports unresolved CommonJS target coverage for %s",
|
||||
(source, partial) => {
|
||||
const bytes = Buffer.from(source);
|
||||
const parsed = parseReferenceSourceEntries(
|
||||
{
|
||||
root: "/selected/source",
|
||||
entries: [
|
||||
{
|
||||
status: "read",
|
||||
kind: "file",
|
||||
path: "main.cjs",
|
||||
bytes,
|
||||
size: bytes.length,
|
||||
},
|
||||
],
|
||||
bytesRead: bytes.length,
|
||||
limitations: [],
|
||||
},
|
||||
[],
|
||||
);
|
||||
expect(parsed.relationships).toEqual([]);
|
||||
expect(
|
||||
historicalSourceInventoryHasPartialFacts({
|
||||
...parsed,
|
||||
parse_failures: parsed.parseFailures,
|
||||
exclusions: [],
|
||||
}),
|
||||
).toBe(partial);
|
||||
expect(parsed.entries[0]?.limitations).toEqual(parsed.limitations);
|
||||
},
|
||||
);
|
||||
|
||||
it("retains Kotlin source identity and language across the inventory projection", () => {
|
||||
const files = [
|
||||
[
|
||||
|
||||
@@ -157,6 +157,7 @@ const extractImports = (
|
||||
ReferenceSourceRequireCall
|
||||
>,
|
||||
): void => {
|
||||
let unresolvedRequireTarget = false;
|
||||
traverseJavaScriptAst(ast.program, {
|
||||
enter: (node) => {
|
||||
if (
|
||||
@@ -197,8 +198,14 @@ const extractImports = (
|
||||
) {
|
||||
const requireCall = requireCalls.get(node);
|
||||
if (requireCall === undefined) return;
|
||||
const specifier = exactStringModuleName(requireCall.arguments?.[0]);
|
||||
if (specifier === undefined) return;
|
||||
const target = requireCall.arguments?.[0];
|
||||
if (target === undefined) return;
|
||||
const literal = readExactJavaScriptLiteral(target);
|
||||
if (!literal.found) {
|
||||
unresolvedRequireTarget = true;
|
||||
return;
|
||||
}
|
||||
if (typeof literal.value !== "string") return;
|
||||
if (
|
||||
requireCall.limitation !== undefined &&
|
||||
!limitations.includes(requireCall.limitation)
|
||||
@@ -207,7 +214,7 @@ const extractImports = (
|
||||
const uncertainResolution = requireCall.resolutionBase === "unknown";
|
||||
appendRelationship(relationships, limitations, {
|
||||
fromPath: from_path,
|
||||
to: specifier,
|
||||
to: literal.value,
|
||||
kind: "requires",
|
||||
parseState: uncertainResolution ? "partial" : "parsed",
|
||||
...(uncertainResolution ? { resolution: "unknown" } : {}),
|
||||
@@ -221,6 +228,10 @@ const extractImports = (
|
||||
}
|
||||
},
|
||||
});
|
||||
if (unresolvedRequireTarget)
|
||||
limitations.push(
|
||||
`A CommonJS module target in ${from_path} could not be determined statically.`,
|
||||
);
|
||||
};
|
||||
|
||||
const parseWithBabel = (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { posix } from "node:path";
|
||||
import { err, ok, type Result } from "./result.js";
|
||||
|
||||
export interface ReferenceSourcePathLookup {
|
||||
@@ -6,8 +5,6 @@ export interface ReferenceSourcePathLookup {
|
||||
readonly has: (path: string) => boolean;
|
||||
readonly resolve: (path: string) => string | undefined;
|
||||
readonly resolveCandidates: (paths: readonly string[]) => string | undefined;
|
||||
/** Resolve a path to the closest recorded parent identity. */
|
||||
readonly resolveAncestor: (path: string) => string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,17 +32,6 @@ export const createReferenceSourcePathLookup = (
|
||||
resolveCandidates: (candidates) =>
|
||||
candidates.find((path) => exact.has(path)) ??
|
||||
candidates.map(equivalent).find((path) => path !== undefined),
|
||||
resolveAncestor: (path) => {
|
||||
for (
|
||||
let candidate = path;
|
||||
candidate !== "." && candidate !== "/";
|
||||
candidate = posix.dirname(candidate)
|
||||
) {
|
||||
const found = resolve(candidate);
|
||||
if (found !== undefined) return found;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -75,9 +75,14 @@ export const referenceSourceRequireCalls = (
|
||||
!t.isNewExpression(node)
|
||||
)
|
||||
return;
|
||||
if (callReceiverIdentifier(node.callee) === undefined) return;
|
||||
let fact = semanticRequireLoaderAt(node, index.bindings, factoryBindings);
|
||||
const callee = unwrapJavaScriptExpression(node.callee).node;
|
||||
if (
|
||||
!t.isCallExpression(callee) &&
|
||||
!t.isOptionalCallExpression(callee) &&
|
||||
!hasIdentifierReceiver(callee)
|
||||
)
|
||||
return;
|
||||
let fact = semanticRequireLoaderAt(node, index.bindings, factoryBindings);
|
||||
if (
|
||||
fact === "create-require" ||
|
||||
((t.isMemberExpression(callee) ||
|
||||
@@ -153,12 +158,7 @@ export const referenceSourceRequireCalls = (
|
||||
});
|
||||
},
|
||||
});
|
||||
if (
|
||||
candidates.some(
|
||||
({ call }) =>
|
||||
!t.isIdentifier(unwrapJavaScriptExpression(call.callee).node),
|
||||
)
|
||||
)
|
||||
if (candidates.some(({ call }) => hasMemberCallee(call)))
|
||||
completeSemanticSteps(index.indexMemberMutationFactsSteps());
|
||||
return new Map(
|
||||
candidates.map(
|
||||
@@ -169,8 +169,7 @@ export const referenceSourceRequireCalls = (
|
||||
arguments: callArguments,
|
||||
...(limitation === undefined ? {} : { limitation }),
|
||||
resolutionBase:
|
||||
!t.isIdentifier(unwrapJavaScriptExpression(call.callee).node) &&
|
||||
hasPriorMemberMutation(index, call)
|
||||
hasMemberCallee(call) && hasPriorMemberMutation(index, call)
|
||||
? "unknown"
|
||||
: resolutionBase,
|
||||
},
|
||||
@@ -220,15 +219,17 @@ const hasPriorMemberMutation = (
|
||||
);
|
||||
};
|
||||
|
||||
const callReceiverIdentifier = (
|
||||
callee: t.Node | null | undefined,
|
||||
): t.Identifier | undefined => {
|
||||
if (!t.isNode(callee)) return undefined;
|
||||
let expression = unwrapJavaScriptExpression(callee).node;
|
||||
const hasMemberCallee = (call: ReferenceSourceRequireInvocation): boolean => {
|
||||
const callee = unwrapJavaScriptExpression(call.callee).node;
|
||||
return t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee);
|
||||
};
|
||||
|
||||
const hasIdentifierReceiver = (callee: t.Node): boolean => {
|
||||
let expression = callee;
|
||||
while (
|
||||
t.isMemberExpression(expression) ||
|
||||
t.isOptionalMemberExpression(expression)
|
||||
)
|
||||
expression = unwrapJavaScriptExpression(expression.object).node;
|
||||
return t.isIdentifier(expression) ? expression : undefined;
|
||||
return t.isIdentifier(expression);
|
||||
};
|
||||
Reference in new issue
Block a user