mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 06:46:10 +00:00
Merge pull request #695 from kaoru0822-kitauji/fix/reference-parser-recovery
fix(reference): retain recovered parser diagnostics
This commit is contained in:
3 files changed
+312
-16
No files matched your search
@@ -291,8 +291,7 @@ const parseWithBabel = (
|
||||
language: string | null,
|
||||
): {
|
||||
ast: File | undefined;
|
||||
parseState: ReferenceSourceImportParseState;
|
||||
reason?: string;
|
||||
reasons: readonly string[];
|
||||
} => {
|
||||
const plugins: ParserPlugin[] = ["jsx"];
|
||||
if (language === "TypeScript" || language === "TSX") {
|
||||
@@ -316,12 +315,11 @@ const parseWithBabel = (
|
||||
errorRecovery: true,
|
||||
plugins,
|
||||
});
|
||||
return { ast, parseState: "parsed" };
|
||||
return { ast, reasons: ast.errors.map((error) => error.message) };
|
||||
} catch (cause: unknown) {
|
||||
return {
|
||||
ast: undefined,
|
||||
parseState: "unknown",
|
||||
reason: cause instanceof Error ? cause.message : "Parse failed",
|
||||
reasons: [cause instanceof Error ? cause.message : "Parse failed"],
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -336,18 +334,17 @@ export const parseReferenceSourceImports = (
|
||||
return { relationships: [], parse_failures: [] };
|
||||
|
||||
const source = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
||||
const { ast, parseState, reason } = parseWithBabel(path, source, language);
|
||||
const { ast, reasons } = parseWithBabel(path, source, language);
|
||||
const parseFailures = reasons.map((reason) => ({
|
||||
path,
|
||||
parser: "babel",
|
||||
reason,
|
||||
}));
|
||||
|
||||
if (ast === undefined || parseState === "unknown") {
|
||||
if (ast === undefined) {
|
||||
return {
|
||||
relationships: [],
|
||||
parse_failures: [
|
||||
{
|
||||
path,
|
||||
parser: "babel",
|
||||
reason: reason ?? "Unknown parse failure",
|
||||
},
|
||||
],
|
||||
parse_failures: parseFailures,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -356,7 +353,13 @@ export const parseReferenceSourceImports = (
|
||||
extractRequireAndDynamicImports(ast.program.body, path, relationships);
|
||||
|
||||
return {
|
||||
relationships,
|
||||
parse_failures: [],
|
||||
relationships: relationships.map((relationship) => ({
|
||||
...relationship,
|
||||
parse_state:
|
||||
parseFailures.length > 0 && relationship.parse_state === "parsed"
|
||||
? "partial"
|
||||
: relationship.parse_state,
|
||||
})),
|
||||
parse_failures: parseFailures,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { parseReferenceSourceImports } from "./referenceSourceImportParsing.js";
|
||||
|
||||
const recoverySource = [
|
||||
"const missing;",
|
||||
"let duplicate;",
|
||||
"let duplicate;",
|
||||
"let duplicate;",
|
||||
].join("\n");
|
||||
|
||||
const recoveryReasons = [
|
||||
"Missing initializer in const declaration. (1:13)",
|
||||
"Identifier 'duplicate' has already been declared. (3:4)",
|
||||
"Identifier 'duplicate' has already been declared. (4:4)",
|
||||
];
|
||||
|
||||
const moduleSource = [
|
||||
'import "./static.js";',
|
||||
'export { value } from "./named.js";',
|
||||
'export * from "./all.js";',
|
||||
'require("./common.cjs");',
|
||||
'require.resolve("node:fs");',
|
||||
'import("./lazy.js");',
|
||||
"import(moduleName);",
|
||||
].join("\n");
|
||||
|
||||
const moduleRelationships = [
|
||||
["./static.js", "imports", "internal", "parsed"],
|
||||
["./named.js", "imports", "internal", "parsed"],
|
||||
["./all.js", "imports", "internal", "parsed"],
|
||||
["./common.cjs", "requires", "internal", "parsed"],
|
||||
["node:fs", "requires", "external", "parsed"],
|
||||
["./lazy.js", "imports", "internal", "parsed"],
|
||||
["<dynamic-import>", "imports", "unknown", "partial"],
|
||||
].map(([to, kind, resolution, parse_state]) => ({
|
||||
to,
|
||||
kind,
|
||||
resolution,
|
||||
parse_state,
|
||||
}));
|
||||
|
||||
const parseSource = (path: string, source: string, language: string | null) =>
|
||||
parseReferenceSourceImports(path, new TextEncoder().encode(source), language);
|
||||
|
||||
describe.each([
|
||||
["main.js", "JavaScript"],
|
||||
["main.jsx", "JSX"],
|
||||
["main.ts", "TypeScript"],
|
||||
["main.tsx", "TSX"],
|
||||
])("recovered reference imports in %s", (path, language) => {
|
||||
it("retains every diagnostic and marks recovered relationships partial", () => {
|
||||
const result = parseSource(
|
||||
path,
|
||||
`${recoverySource}\n${moduleSource}`,
|
||||
language,
|
||||
);
|
||||
|
||||
expect(result.parse_failures).toEqual(
|
||||
recoveryReasons.map((reason) => ({ path, parser: "babel", reason })),
|
||||
);
|
||||
expect(result.relationships).toEqual(
|
||||
moduleRelationships.map((relationship) => ({
|
||||
...relationship,
|
||||
from_path: path,
|
||||
parse_state: "partial",
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves valid and computed relationships without diagnostics", () => {
|
||||
expect(parseSource(path, moduleSource, language)).toEqual({
|
||||
relationships: moduleRelationships.map((relationship) => ({
|
||||
...relationship,
|
||||
from_path: path,
|
||||
})),
|
||||
parse_failures: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("reports recovery diagnostics when there are no relationships", () => {
|
||||
expect(parseSource(path, recoverySource, language)).toEqual({
|
||||
relationships: [],
|
||||
parse_failures: recoveryReasons.map((reason) => ({
|
||||
path,
|
||||
parser: "babel",
|
||||
reason,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
it("returns no relationships when parsing throws", () => {
|
||||
expect(
|
||||
parseSource(path, 'import "./before.js";\nconst = ;', language),
|
||||
).toEqual({
|
||||
relationships: [],
|
||||
parse_failures: [
|
||||
{ path, parser: "babel", reason: "Unexpected token (2:6)" },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["main.ts", "types.d.ts", "types.d.mts", "types.d.cts"])(
|
||||
"marks recovered TypeScript declarations partial in %s",
|
||||
(path) => {
|
||||
const result = parseSource(
|
||||
path,
|
||||
[
|
||||
"let duplicate: number;",
|
||||
"let duplicate: number;",
|
||||
'import dep = require("node:path");',
|
||||
'declare module "ambient-package" {}',
|
||||
].join("\n"),
|
||||
"TypeScript",
|
||||
);
|
||||
expect(result).toEqual({
|
||||
relationships: [
|
||||
{
|
||||
from_path: path,
|
||||
to: "node:path",
|
||||
kind: "requires",
|
||||
resolution: "external",
|
||||
parse_state: "partial",
|
||||
},
|
||||
{
|
||||
from_path: path,
|
||||
to: "ambient-package",
|
||||
kind: "declares-module",
|
||||
resolution: "unknown",
|
||||
parse_state: "partial",
|
||||
},
|
||||
],
|
||||
parse_failures: [
|
||||
{
|
||||
path,
|
||||
parser: "babel",
|
||||
reason: "Identifier 'duplicate' has already been declared. (2:4)",
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps unsupported source languages outside Babel parsing", () => {
|
||||
expect(parseSource("main.py", recoverySource, "Python")).toEqual({
|
||||
relationships: [],
|
||||
parse_failures: [],
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user