mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(reference-imports): retain exported import-equals dependencies
Route exported external import-equals declarations through the same relationship owner as plain declarations so their required target is not dropped. Fixes #6655
This commit is contained in:
+8
-2
@@ -398,11 +398,17 @@ Entries that change during an import retain an unavailable-entry diagnostic
|
||||
alongside readable siblings. If enumeration of an already admitted root fails,
|
||||
the graph keeps its observed children and records the root failure as a
|
||||
top-level limitation. That partial inventory does not establish absence among
|
||||
unexamined children.
|
||||
unexamined children. POSIX directory-entry bytes that are not valid UTF-8 cannot
|
||||
be represented as graph paths; the containing directory (or root) is marked
|
||||
unsupported, and REA does not access the entry through a replacement-character
|
||||
name. Readable siblings remain inventoried.
|
||||
|
||||
JavaScript and TypeScript import parsing requires valid UTF-8. Malformed source
|
||||
bytes retain their original hashes and sizes with a decoding diagnostic; REA
|
||||
does not infer module targets from replacement characters.
|
||||
does not infer module targets from replacement characters. Exact dynamic-import
|
||||
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.
|
||||
|
||||
Historical-source import requires safe no-follow file opens on Linux or
|
||||
macOS. Native Windows returns `unsupported_host`; use Linux REA inside WSL
|
||||
|
||||
@@ -152,6 +152,23 @@ const appendRelationship = (
|
||||
});
|
||||
};
|
||||
|
||||
const appendImportEquals = (
|
||||
statement: Node,
|
||||
from_path: string,
|
||||
relationships: ReferenceSourceImportRelationship[],
|
||||
limitations: string[],
|
||||
): void => {
|
||||
if (!isTSImportEqualsDeclaration(statement)) return;
|
||||
const reference = statement.moduleReference;
|
||||
if (!isTSExternalModuleReference(reference)) return;
|
||||
appendRelationship(relationships, limitations, {
|
||||
fromPath: from_path,
|
||||
to: safeModuleName(reference.expression),
|
||||
kind: "requires",
|
||||
parseState: "parsed",
|
||||
});
|
||||
};
|
||||
|
||||
const extractImportDeclarations = (
|
||||
body: readonly Node[],
|
||||
from_path: string,
|
||||
@@ -177,7 +194,16 @@ const extractImportDeclarations = (
|
||||
kind: "imports",
|
||||
parseState: "parsed",
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
statement.declaration !== null &&
|
||||
statement.declaration !== undefined
|
||||
)
|
||||
appendImportEquals(
|
||||
statement.declaration,
|
||||
from_path,
|
||||
relationships,
|
||||
limitations,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -192,15 +218,7 @@ const extractImportDeclarations = (
|
||||
}
|
||||
|
||||
if (isTSImportEqualsDeclaration(statement)) {
|
||||
const reference = statement.moduleReference;
|
||||
if (isTSExternalModuleReference(reference)) {
|
||||
appendRelationship(relationships, limitations, {
|
||||
fromPath: from_path,
|
||||
to: safeModuleName(reference.expression),
|
||||
kind: "requires",
|
||||
parseState: "parsed",
|
||||
});
|
||||
}
|
||||
appendImportEquals(statement, from_path, relationships, limitations);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { expect } from "vitest";
|
||||
|
||||
import { historicalSourceGraphSchema } from "../../../src/domain/referenceSourceGraph.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"preserves exact historical dynamic and exported import-equals edges",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-reference-exact-imports-");
|
||||
const config = await createTestTempDirectory(
|
||||
"rea-reference-exact-imports-config-",
|
||||
);
|
||||
await Promise.all([
|
||||
writeFile(
|
||||
join(root, "main.ts"),
|
||||
[
|
||||
"const templated = import(`./template.mjs`);",
|
||||
'const wrapped = import("./wrapped.mjs" as const);',
|
||||
'const name = "./not-guessed.mjs"; import(name);',
|
||||
].join("\n"),
|
||||
),
|
||||
writeFile(
|
||||
join(root, "exported.cts"),
|
||||
'export import dep = require("./dep.cjs");\n',
|
||||
),
|
||||
writeFile(
|
||||
join(root, "plain.cts"),
|
||||
'import dep = require("./dep.cjs"); export { dep };\n',
|
||||
),
|
||||
...["template.mjs", "wrapped.mjs"].map((path) =>
|
||||
writeFile(join(root, path), "export {};\n"),
|
||||
),
|
||||
writeFile(join(root, "dep.cjs"), "module.exports = { value: 1 };\n"),
|
||||
]);
|
||||
|
||||
const result = await cli.run({
|
||||
arguments: ["import-reference-source", root, "--json"],
|
||||
environment: {
|
||||
HOME: config,
|
||||
XDG_CONFIG_HOME: config,
|
||||
XDG_CACHE_HOME: config,
|
||||
},
|
||||
});
|
||||
expect(result.exitCode, result.stderr + JSON.stringify(result.json)).toBe(
|
||||
0,
|
||||
);
|
||||
const graph = historicalSourceGraphSchema.parse(result.json);
|
||||
expect(graph.relationships).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
from_path: "main.ts",
|
||||
to: "template.mjs",
|
||||
kind: "imports",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
{
|
||||
from_path: "main.ts",
|
||||
to: "wrapped.mjs",
|
||||
kind: "imports",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
{
|
||||
from_path: "main.ts",
|
||||
to: "<dynamic-import>",
|
||||
kind: "imports",
|
||||
resolution: "unknown",
|
||||
parse_state: "partial",
|
||||
},
|
||||
{
|
||||
from_path: "exported.cts",
|
||||
to: "dep.cjs",
|
||||
kind: "requires",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
{
|
||||
from_path: "plain.cts",
|
||||
to: "dep.cjs",
|
||||
kind: "requires",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(graph.relationships).toHaveLength(5);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,51 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { importReferenceSource } from "../../support/referenceSourceResourceScope.js";
|
||||
|
||||
it("retains external-module targets from exported and plain import-equals declarations", async () => {
|
||||
const root = await createTestTempDirectory(
|
||||
"rea-reference-exported-import-equals-",
|
||||
);
|
||||
await Promise.all([
|
||||
writeFile(
|
||||
join(root, "exported.cts"),
|
||||
'export import dep = require("./dep.cjs");\n',
|
||||
),
|
||||
writeFile(
|
||||
join(root, "plain.cts"),
|
||||
'import dep = require("./dep.cjs"); export { dep };\n',
|
||||
),
|
||||
writeFile(join(root, "dep.cjs"), "module.exports = { value: 1 };\n"),
|
||||
]);
|
||||
|
||||
const result = await importReferenceSource({
|
||||
root,
|
||||
caller: "reference-exported-import-equals-test",
|
||||
policy: { secretPatterns: [] },
|
||||
});
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.parse_failures).toEqual([]);
|
||||
expect(result.value.relationships).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
from_path: "exported.cts",
|
||||
to: "dep.cjs",
|
||||
kind: "requires",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
{
|
||||
from_path: "plain.cts",
|
||||
to: "dep.cjs",
|
||||
kind: "requires",
|
||||
resolution: "internal",
|
||||
parse_state: "parsed",
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(result.value.relationships).toHaveLength(2);
|
||||
});
|
||||
Reference in New Issue
Block a user