222 lines
7.1 KiB
TypeScript
222 lines
7.1 KiB
TypeScript
import { createHash } from "node:crypto";
|
||
import {
|
||
mkdir,
|
||
readFile,
|
||
readlink,
|
||
symlink,
|
||
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(
|
||
"retains malformed source bytes without inventing decoded import targets",
|
||
async ({ cli }) => {
|
||
const root = await createTestTempDirectory("rea-reference-utf8-");
|
||
const sources = new Map([
|
||
[
|
||
"invalid.mjs",
|
||
Buffer.concat([
|
||
Buffer.from('import "./caf'),
|
||
Buffer.from([0xff]),
|
||
Buffer.from('.js";\n'),
|
||
]),
|
||
],
|
||
[
|
||
"comment.ts",
|
||
Buffer.concat([
|
||
Buffer.from('import "./dep.js"; // '),
|
||
Buffer.from([0xc0, 0xaf]),
|
||
]),
|
||
],
|
||
["valid.mjs", Buffer.from('import "./caf<61>.js";\n')],
|
||
["caf<61>.js", Buffer.from("export const value = 1;\n")],
|
||
["dep.js", Buffer.from("export const value = 2;\n")],
|
||
]);
|
||
await Promise.all(
|
||
[...sources].map(([path, bytes]) => writeFile(join(root, path), bytes)),
|
||
);
|
||
const result = await cli.run({
|
||
arguments: ["import-reference-source", root, "--json"],
|
||
environment: { HOME: root, XDG_CONFIG_HOME: root, XDG_CACHE_HOME: root },
|
||
});
|
||
expect(result.exitCode, result.stderr + JSON.stringify(result.json)).toBe(
|
||
0,
|
||
);
|
||
const graph = historicalSourceGraphSchema.parse(result.json);
|
||
expect(graph.parse_failures).toEqual([
|
||
{
|
||
path: "comment.ts",
|
||
parser: "utf-8",
|
||
reason:
|
||
"Source bytes are not valid UTF-8; import targets were not parsed.",
|
||
},
|
||
{
|
||
path: "invalid.mjs",
|
||
parser: "utf-8",
|
||
reason:
|
||
"Source bytes are not valid UTF-8; import targets were not parsed.",
|
||
},
|
||
]);
|
||
expect(graph.relationships).toEqual([
|
||
{
|
||
from_path: "valid.mjs",
|
||
to: "caf<61>.js",
|
||
kind: "imports",
|
||
resolution: "internal",
|
||
parse_state: "parsed",
|
||
},
|
||
]);
|
||
for (const [path, bytes] of sources)
|
||
expect(graph.entries).toContainEqual(
|
||
expect.objectContaining({
|
||
path,
|
||
sha256: createHash("sha256").update(bytes).digest("hex"),
|
||
size: bytes.length,
|
||
content_state: "hashed",
|
||
}),
|
||
);
|
||
},
|
||
);
|
||
|
||
cliTest.skipIf(process.platform === "win32")(
|
||
"preserves historical literal-backslash entries and their exact owning import base",
|
||
async ({ cli }) => {
|
||
const root = await createTestTempDirectory("rea-reference-literal-paths-");
|
||
const config = await createTestTempDirectory(
|
||
"rea-reference-literal-config-",
|
||
);
|
||
await mkdir(join(root, "literal"));
|
||
await mkdir(join(root, "literal\\dir"));
|
||
const sources = new Map([
|
||
["literal\\name.js", "export const value = 'literal';\n"],
|
||
["literal/name.js", "export const value = 'slash';\n"],
|
||
["literal\\dir/main.js", 'import "./dep.js";\n'],
|
||
["literal\\dir/dep.js", "export const value = 'owned';\n"],
|
||
]);
|
||
for (const [path, bytes] of sources)
|
||
await writeFile(join(root, path), bytes);
|
||
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);
|
||
for (const [path, bytes] of sources)
|
||
expect(graph.entries).toContainEqual(
|
||
expect.objectContaining({
|
||
path,
|
||
sha256: createHash("sha256").update(bytes).digest("hex"),
|
||
content_state: "hashed",
|
||
}),
|
||
);
|
||
for (const [path, bytes] of sources)
|
||
expect(await readFile(join(root, path), "utf8")).toBe(bytes);
|
||
expect(graph.relationships).toContainEqual({
|
||
from_path: "literal\\dir/main.js",
|
||
to: "literal\\dir/dep.js",
|
||
kind: "imports",
|
||
resolution: "internal",
|
||
parse_state: "parsed",
|
||
});
|
||
},
|
||
);
|
||
|
||
cliTest.skipIf(process.platform === "win32")(
|
||
"preserves an observed historical symlink target with a literal backslash",
|
||
async ({ cli }) => {
|
||
const root = await createTestTempDirectory("rea-reference-literal-link-");
|
||
const config = await createTestTempDirectory(
|
||
"rea-reference-literal-link-config-",
|
||
);
|
||
await writeFile(
|
||
join(root, "literal\\name.js"),
|
||
"export const value = 42;\n",
|
||
);
|
||
await symlink("literal\\name.js", join(root, "link.js"));
|
||
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(await readlink(join(root, "link.js"))).toBe("literal\\name.js");
|
||
expect(await readFile(join(root, "literal\\name.js"), "utf8")).toBe(
|
||
"export const value = 42;\n",
|
||
);
|
||
expect(graph.entries).toContainEqual(
|
||
expect.objectContaining({
|
||
kind: "symlink",
|
||
path: "link.js",
|
||
target: "literal\\name.js",
|
||
target_state: "internal",
|
||
}),
|
||
);
|
||
},
|
||
);
|
||
|
||
cliTest.skipIf(process.platform === "win32")(
|
||
"imports an inventory-owned POSIX UNC-looking symlink target",
|
||
async ({ cli }) => {
|
||
const root = await createTestTempDirectory("rea-reference-unc-literal-");
|
||
const config = await createTestTempDirectory("rea-reference-unc-config-");
|
||
const literal = "\\\\server\\share.js";
|
||
const literalBytes = "export const value = 'literal';\n";
|
||
const slashBytes = "export const value = 'slash';\n";
|
||
await mkdir(join(root, "server"));
|
||
await writeFile(join(root, literal), literalBytes);
|
||
await writeFile(join(root, "server", "share.js"), slashBytes);
|
||
await symlink(literal, join(root, "link.js"));
|
||
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.entries).toContainEqual(
|
||
expect.objectContaining({
|
||
kind: "symlink",
|
||
path: "link.js",
|
||
target: literal,
|
||
target_state: "internal",
|
||
}),
|
||
);
|
||
for (const [path, bytes] of [
|
||
[literal, literalBytes],
|
||
["server/share.js", slashBytes],
|
||
]) {
|
||
expect(graph.entries).toContainEqual(
|
||
expect.objectContaining({
|
||
path,
|
||
sha256: createHash("sha256").update(bytes!).digest("hex"),
|
||
}),
|
||
);
|
||
expect(await readFile(join(root, path!), "utf8")).toBe(bytes);
|
||
}
|
||
expect(await readlink(join(root, "link.js"))).toBe(literal);
|
||
},
|
||
);
|