* fix(reference): report sloppy scripts, PHP, C# and enclosing Git facts Importing the source/ subtree of a legacy PHP and C# checkout produced four wrong or missing facts: - Babel's unambiguous mode parses a module first and keeps its strict-mode recovery errors after falling back to a script, so a valid sloppy-mode `delete messages;` was reported as a parse failure and the semantic IR marked the file as parser-recovered. Parsing now goes through one owner that re-parses such a script as a script. - .php and .cs files had no language; they are now detected as PHP and C# (detection only, no parsing). - A root without its own .git was reported as having no version control even inside a checkout. The adapter now resolves the nearest enclosing repository, as Git does, and reports `none` only when no ancestor has one. - The CLI recorded importer_version as null although the package version is known. * fix(reference): keep module strictness and classify C# and PHP as source Review follow-up. `.mjs` and `.mts` files are modules by extension, but the script fallback re-parsed them in sloppy mode and erased real strict-mode errors. The parser boundary now owns the extension rule, parses explicit modules as modules and applies the fallback only when the mode is ambiguous; the require-binding analysis uses the same rule. `.cs` and `.php` received a language but stayed classified as unknown, so source-to-bundle comparison dropped them. Both are now source extensions, like the other detected languages. * fix(reference): honor package module scope and keep unknown-mode strict errors Review follow-up. A `.js` file under a package.json with `"type": "module"` is an ES module, but it went through the script fallback that erases strict-mode errors. The import now reads each in-inventory package.json and parses `.js` files in a module scope as modules. Other `.js` and `.ts` sources keep an unknown mode, because bundlers treat them as modules without that field: they are no longer forced to script, and errors that only the module parse reports are kept as an entry limitation. * fix(reference): bound package scopes and keep application parsing unchanged Review follow-up. - `.cjs` files parse as scripts, so module-only strict errors no longer become limitations on CommonJS trees. `.cts` keeps an unknown mode because TypeScript compiles ES import syntax there. - An unreadable or linked package.json now bounds the module scope with an unknown type instead of letting an outer `"type": "module"` apply. - The application pipeline parses as on main again. Its module-only strict errors keep a source partial; package-mode handling stays in the reference importer. * refactor(reference): keep unmarked module modes unknown instead of resolving package scope Review follow-up. Resolving Node package scope for `.js` kept pulling in more of Node's lookup semantics (require bindings, excluded or directory-named manifests). Only extensions now select a mode: `.mjs` and `.mts` parse as modules and `.cjs` as scripts. Every other source keeps an unknown mode, so module-only strict errors stay visible as an entry limitation instead of being dropped or asserted. This matches the require-binding analysis, which also treats unmarked paths as unknown.
91 lines
3.2 KiB
TypeScript
91 lines
3.2 KiB
TypeScript
import { mkdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { expect } from "vitest";
|
|
|
|
import { historicalSourceGraphSchema } from "../../../src/domain/referenceSourceGraph.js";
|
|
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
|
|
import { execFileOutput } from "../../../src/process/ExecFileOutput.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest(
|
|
"imports a legacy subtree of a Git checkout with its own source facts",
|
|
async ({ cli }) => {
|
|
const repository = await createTestTempDirectory("rea-reference-legacy-");
|
|
const source = join(repository, "source");
|
|
await mkdir(source);
|
|
await Promise.all([
|
|
// `.cjs` is CommonJS, so the strict-mode rule does not apply.
|
|
writeFile(
|
|
join(source, "legacy.cjs"),
|
|
"var messages = {};\ndelete messages;\n",
|
|
),
|
|
// Deleting a plain binding is valid only in a sloppy-mode script.
|
|
writeFile(
|
|
join(source, "legacy.js"),
|
|
"var messages = {};\ndelete messages;\n",
|
|
),
|
|
// An explicit module keeps the strict-mode error.
|
|
writeFile(
|
|
join(source, "module.mjs"),
|
|
"var messages = {};\ndelete messages;\n",
|
|
),
|
|
writeFile(join(source, "index.php"), "<?php echo 'legacy';\n"),
|
|
writeFile(join(source, "Program.cs"), "class Program {}\n"),
|
|
]);
|
|
const git = (...args: string[]) =>
|
|
execFileOutput("git", args, { cwd: repository });
|
|
await git("init", "--initial-branch=main");
|
|
await git("add", ".");
|
|
await git(
|
|
"-c",
|
|
"user.name=REA fixture",
|
|
"-c",
|
|
"user.email=fixture@example.invalid",
|
|
"-c",
|
|
"commit.gpgsign=false",
|
|
"commit",
|
|
"-m",
|
|
"fixture",
|
|
);
|
|
const head = (await git("rev-parse", "HEAD")).stdout.trim();
|
|
|
|
const result = await cli.run({
|
|
arguments: ["import-reference-source", source, "--json"],
|
|
environment: {
|
|
HOME: repository,
|
|
XDG_CONFIG_HOME: repository,
|
|
XDG_CACHE_HOME: repository,
|
|
},
|
|
});
|
|
|
|
expect(result.exitCode).toBe(0);
|
|
const graph = historicalSourceGraphSchema.parse(result.json);
|
|
expect(graph.parse_failures).toEqual([
|
|
{
|
|
path: "module.mjs",
|
|
parser: "babel",
|
|
reason: "Deleting local variable in strict mode. (2:0)",
|
|
},
|
|
]);
|
|
// Outside `.mjs`/`.mts`/`.cjs` the mode stays unknown, not forced to script.
|
|
const limitations = (path: string) =>
|
|
graph.entries.find((entry) => entry.path === path)?.limitations;
|
|
for (const path of ["legacy.js"])
|
|
expect(limitations(path)).toEqual([
|
|
"Strict-mode errors apply only if this file is an ES module: Deleting local variable in strict mode. (2:0)",
|
|
]);
|
|
expect(limitations("legacy.cjs")).toEqual([]);
|
|
expect(graph.languages).toEqual(["C#", "JavaScript", "PHP"]);
|
|
for (const path of ["Program.cs", "index.php"])
|
|
expect(
|
|
graph.entries.find((entry) => entry.path === path)?.classifications,
|
|
).toContain("source");
|
|
expect(graph.vcs).toEqual({ kind: "git", head, dirty: null });
|
|
expect(graph.provenance.importer_version).toBe(
|
|
PRODUCT_IDENTITY.packageVersion,
|
|
);
|
|
},
|
|
);
|