Share parser-owned package and runtime facts across dependency consumers. Preserve uncertain loader requests and explicit wrapper bindings without inventing unbound require authority in known modules. Cover selected source grammar through the public MCP boundary and retain ambient/type dependencies. Open each selected directory before loading its scoped policy, retaining partial directory failures and cleanup ownership. Preserve explicit caller negation precedence and source diagnostics across inventory projections. Clarify how version comparison retains container provenance without claiming semantic drift. Use independent unknown source inputs to keep candidate-limit fixtures meaningful as conditional branch evaluation becomes more precise. Fixes #6891 Fixes #6882 Fixes #6883 Fixes #6859
387 lines
11 KiB
TypeScript
387 lines
11 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
|
|
import { expect, it } from "vitest";
|
|
|
|
import { parseReferenceSourceEntries } from "./ReferenceSourceImportEntries.js";
|
|
import { 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", () => {
|
|
const read: ReferenceSourceRead = {
|
|
root: "/selected/source",
|
|
entries: [
|
|
{
|
|
status: "failed",
|
|
kind: "other",
|
|
path: "src/pipe",
|
|
code: "unsupported",
|
|
message: "Entry is not a regular file",
|
|
size: 0,
|
|
},
|
|
],
|
|
bytesRead: 0,
|
|
limitations: [],
|
|
};
|
|
const parsed = parseReferenceSourceEntries(read, new Set(), []);
|
|
expect(parsed.entries).toContainEqual({
|
|
path: "src/pipe",
|
|
kind: "unknown",
|
|
classifications: ["unknown"],
|
|
limitations: [
|
|
"Entry is not a regular file Exclude this entry or replace it with a regular file.",
|
|
],
|
|
});
|
|
expect(parsed.entries.some((entry) => entry.kind === "file")).toBe(false);
|
|
});
|
|
|
|
it("keeps a failed regular file as file kind", () => {
|
|
const read: ReferenceSourceRead = {
|
|
root: "/selected/source",
|
|
entries: [
|
|
{
|
|
status: "failed",
|
|
kind: "file",
|
|
path: "src/broken.ts",
|
|
code: "io",
|
|
message: "Entry could not be read",
|
|
},
|
|
],
|
|
bytesRead: 0,
|
|
limitations: [],
|
|
};
|
|
const parsed = parseReferenceSourceEntries(read, new Set(), []);
|
|
expect(parsed.entries).toContainEqual({
|
|
path: "src/broken.ts",
|
|
kind: "file",
|
|
sha256: null,
|
|
size: null,
|
|
language: null,
|
|
classifications: ["source"],
|
|
content_state: "unreadable",
|
|
limitations: [
|
|
"Entry could not be read This file could not be read. Check its permissions, then try again.",
|
|
],
|
|
});
|
|
});
|
|
|
|
it("keeps source entries when a module has an empty specifier", () => {
|
|
const files = [
|
|
["main.mjs", 'import "";\n'],
|
|
["other.txt", "retained\n"],
|
|
] as const;
|
|
const read: ReferenceSourceRead = {
|
|
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: [],
|
|
};
|
|
|
|
const parsed = parseReferenceSourceEntries(
|
|
read,
|
|
new Set(files.map(([path]) => path)),
|
|
[],
|
|
);
|
|
|
|
expect(parsed.entries.map(({ path }) => path)).toEqual([
|
|
"main.mjs",
|
|
"other.txt",
|
|
]);
|
|
expect(parsed.relationships).toEqual([]);
|
|
expect(parsed.limitations).toEqual([
|
|
"Empty module specifier in main.mjs was not recorded as a relationship.",
|
|
]);
|
|
});
|
|
|
|
it("retains Kotlin source identity and language across the inventory projection", () => {
|
|
const files = [
|
|
[
|
|
"MainActivity.kt",
|
|
"import android.app.Activity\nclass MainActivity : Activity()\n",
|
|
["source"],
|
|
"Kotlin",
|
|
],
|
|
["task.kts", 'println("hello")\n', ["source"], "Kotlin"],
|
|
[
|
|
"build.gradle.kts",
|
|
'plugins { kotlin("jvm") version "2.3.0" }\n',
|
|
["manifest", "source"],
|
|
"Kotlin",
|
|
],
|
|
["MainActivity.kt.backup", "backup\n", ["unknown"], null],
|
|
["Legacy.java", "class Legacy {}\n", ["source"], "Java"],
|
|
] as const;
|
|
const read: ReferenceSourceRead = {
|
|
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: [],
|
|
};
|
|
const parsed = parseReferenceSourceEntries(
|
|
read,
|
|
new Set(files.map(([path]) => path)),
|
|
[],
|
|
);
|
|
for (const [path, content, classifications, language] of files)
|
|
expect(parsed.entries).toContainEqual({
|
|
path,
|
|
kind: "file",
|
|
language,
|
|
classifications,
|
|
size: Buffer.byteLength(content),
|
|
sha256: createHash("sha256").update(content).digest("hex"),
|
|
content_state: "hashed",
|
|
limitations: [],
|
|
});
|
|
expect(historicalSourceLanguages(parsed.entries)).toEqual(["Java", "Kotlin"]);
|
|
expect(parsed.parseFailures).toEqual([]);
|
|
expect(parsed.relationships).toEqual([]);
|
|
});
|
|
|
|
it("resolves declared package mains and index fallback from source-owned fields", () => {
|
|
const files = [
|
|
[
|
|
"main.cjs",
|
|
'require("./pkg");\nrequire("./dep");\nrequire("./fallback");\n',
|
|
],
|
|
[
|
|
"main.cts",
|
|
'import dep = require("./typed");\n' +
|
|
'require("./typed");\n' +
|
|
'import data = require("./json-package");\n' +
|
|
'require("./emitted.js");\n',
|
|
],
|
|
["pkg/package.json", '{"main":"actual.js"}\n'],
|
|
["pkg/actual.js", 'module.exports = "package main";\n'],
|
|
["fallback/package.json", "{}\n"],
|
|
["fallback/index.js", 'module.exports = "index";\n'],
|
|
["fallback/phantom.js", 'module.exports = "host decoy";\n'],
|
|
["dep.js", 'module.exports = "file";\n'],
|
|
["dep/index.ts", 'export const value = "index";\n'],
|
|
["typed.js", 'module.exports = "runtime file";\n'],
|
|
["typed/index.ts", 'export const value = "source index";\n'],
|
|
["json-package/package.json", '{"main":"data"}\n'],
|
|
["json-package/data.json", '{"value":"package main"}\n'],
|
|
["json-package/index.js", 'module.exports = "fallback index";\n'],
|
|
["emitted.ts", 'export const value = "source counterpart";\n'],
|
|
] as const;
|
|
const read: ReferenceSourceRead = {
|
|
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: [],
|
|
};
|
|
|
|
const previous = Object.getOwnPropertyDescriptor(Object.prototype, "main");
|
|
let parsed: ReturnType<typeof parseReferenceSourceEntries>;
|
|
try {
|
|
Object.defineProperty(Object.prototype, "main", {
|
|
configurable: true,
|
|
writable: true,
|
|
value: "phantom.js",
|
|
});
|
|
parsed = parseReferenceSourceEntries(
|
|
read,
|
|
new Set(files.map(([path]) => path)),
|
|
[],
|
|
);
|
|
} finally {
|
|
if (previous === undefined)
|
|
Reflect.deleteProperty(Object.prototype, "main");
|
|
else Object.defineProperty(Object.prototype, "main", previous);
|
|
}
|
|
|
|
expect(parsed.relationships).toContainEqual({
|
|
from_path: "main.cjs",
|
|
to: "fallback/index.js",
|
|
kind: "requires",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
});
|
|
expect(parsed.relationships).toContainEqual({
|
|
from_path: "main.cjs",
|
|
to: "pkg/actual.js",
|
|
kind: "requires",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
});
|
|
expect(parsed.relationships).toContainEqual({
|
|
from_path: "main.cjs",
|
|
to: "dep.js",
|
|
kind: "requires",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
});
|
|
expect(parsed.relationships).toContainEqual({
|
|
from_path: "main.cts",
|
|
to: "typed/index.ts",
|
|
kind: "requires",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
});
|
|
expect(parsed.relationships).toContainEqual({
|
|
from_path: "main.cts",
|
|
to: "typed.js",
|
|
kind: "requires",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
});
|
|
expect(parsed.relationships).toContainEqual({
|
|
from_path: "main.cts",
|
|
to: "json-package/data.json",
|
|
kind: "requires",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
});
|
|
expect(parsed.relationships).toContainEqual({
|
|
from_path: "main.cts",
|
|
to: "emitted.ts",
|
|
kind: "requires",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
});
|
|
});
|
|
|
|
it("matches package manifests by exact or unique canonical directory identity", () => {
|
|
const composed = "caf\u00e9";
|
|
const decomposed = "cafe\u0301";
|
|
const composedTwin = "r\u00e9sum\u00e9";
|
|
const decomposedTwin = "re\u0301sume\u0301";
|
|
const files = [
|
|
[
|
|
"main.cjs",
|
|
[composed, composedTwin, decomposedTwin]
|
|
.map((name) => `require(${JSON.stringify(`./${name}`)});`)
|
|
.join("\n"),
|
|
],
|
|
[`${decomposed}/package.json`, '{"main":"unique.js"}\n'],
|
|
[`${decomposed}/unique.js`, 'module.exports = "unique";\n'],
|
|
[`${composed}/unique.js`, 'module.exports = "unrelated exact spelling";\n'],
|
|
[`${composedTwin}/package.json`, '{"main":"exact-composed.js"}\n'],
|
|
[`${composedTwin}/exact-composed.js`, 'module.exports = "composed";\n'],
|
|
[`${decomposedTwin}/package.json`, '{"main":"exact-decomposed.js"}\n'],
|
|
[
|
|
`${decomposedTwin}/exact-decomposed.js`,
|
|
'module.exports = "decomposed";\n',
|
|
],
|
|
] as const;
|
|
const read: ReferenceSourceRead = {
|
|
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: [],
|
|
};
|
|
|
|
const parsed = parseReferenceSourceEntries(
|
|
read,
|
|
new Set(files.map(([path]) => path)),
|
|
[],
|
|
);
|
|
|
|
expect(parsed.relationships.map(({ to }) => to)).toEqual([
|
|
`${decomposed}/unique.js`,
|
|
`${composedTwin}/exact-composed.js`,
|
|
`${decomposedTwin}/exact-decomposed.js`,
|
|
]);
|
|
});
|
|
|
|
it("resolves extensionless imports and requires to .mts and .cts files and directory indexes", () => {
|
|
const files = [
|
|
[
|
|
"src/index.ts",
|
|
'import "./esm";\nimport "./pkg-esm";\nimport cjs = require("./cjs");\nimport pkgCjs = require("./pkg-cjs");\n',
|
|
],
|
|
["src/esm.mts", "export const esm = 1;\n"],
|
|
["src/pkg-esm/index.mts", "export const pkgEsm = 2;\n"],
|
|
["src/cjs.cts", "module.exports = { cjs: 3 };\n"],
|
|
["src/pkg-cjs/index.cts", "module.exports = { pkgCjs: 4 };\n"],
|
|
] as const;
|
|
const read: ReferenceSourceRead = {
|
|
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: [],
|
|
};
|
|
const parsed = parseReferenceSourceEntries(
|
|
read,
|
|
new Set(files.map(([path]) => path)),
|
|
[],
|
|
);
|
|
expect(parsed.relationships).toEqual(
|
|
expect.arrayContaining([
|
|
{
|
|
from_path: "src/index.ts",
|
|
to: "src/esm.mts",
|
|
kind: "imports",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
},
|
|
{
|
|
from_path: "src/index.ts",
|
|
to: "src/pkg-esm/index.mts",
|
|
kind: "imports",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
},
|
|
{
|
|
from_path: "src/index.ts",
|
|
to: "src/cjs.cts",
|
|
kind: "requires",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
},
|
|
{
|
|
from_path: "src/index.ts",
|
|
to: "src/pkg-cjs/index.cts",
|
|
kind: "requires",
|
|
resolution: "internal",
|
|
parse_state: "parsed",
|
|
},
|
|
]),
|
|
);
|
|
});
|