From a0ab7be6c656f3c96234c21d81082b418a343d53 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli-ai-agent <268630448+NullVoxPopuli-ai-agent@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:03:55 -0400 Subject: [PATCH] Expose the script parser's diagnostics as ast.errors oxc-parser returns an empty program.body on an unrecoverable script error, indistinguishable from an empty file. Forward its diagnostics on the File node so callers can tell the two apart. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01FsXgxh6vNRWMCNcAQ3g6TN --- README.md | 8 ++++++++ src/index.d.ts | 12 ++++++++++++ src/parse.js | 4 ++++ tests/toTree-options.test.js | 16 ++++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/README.md b/README.md index 4a54b80..92c3b1b 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,14 @@ console.log(ast.type); // "File" console.log(ast.program.body.length); // 2 — ImportDeclaration + ClassDeclaration ``` +Script syntax errors do not throw. oxc-parser reports them on `ast.errors`, and on an unrecoverable error it returns an empty `program.body`; check `ast.errors` to tell that apart from an empty file. + +```js +let ast = toTree(`let planets = []);`); +ast.program.body.length; // 0 +ast.errors.length; // 1 +``` + `parse` is a lower-level alternative that returns the `Program` node directly. ```js diff --git a/src/index.d.ts b/src/index.d.ts index f5e62f8..31961f5 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -10,10 +10,22 @@ export interface ASTNode { [key: string]: unknown; } +export interface ParseError { + message: string; + severity?: string; + [key: string]: unknown; +} + export interface FileNode extends ASTNode { type: "File"; program: ASTNode; comments: ASTNode[]; + /** + * Diagnostics from the JS/TS parser (oxc-parser on the default path). On an + * unrecoverable script error oxc returns an empty `program.body`; check + * this to tell that apart from an empty file. + */ + errors: ParseError[]; } export interface TemplateResult { diff --git a/src/parse.js b/src/parse.js index 4ce476c..312d5e1 100644 --- a/src/parse.js +++ b/src/parse.js @@ -115,6 +115,10 @@ export function toTree(source, options = {}) { type: "File", program: oxcResult.program, comments: oxcResult.comments || [], + // oxc's diagnostics for the script. On an unrecoverable error oxc + // returns an empty `program.body`; this is how callers tell that apart + // from an empty file. + errors: oxcResult.errors || [], start: oxcResult.program.start, end: oxcResult.program.end, }, diff --git a/tests/toTree-options.test.js b/tests/toTree-options.test.js index cbaa24a..bd362d6 100644 --- a/tests/toTree-options.test.js +++ b/tests/toTree-options.test.js @@ -76,6 +76,22 @@ describe("glimmerVisitorKeys", () => { }); }); +describe("toTree — script errors", () => { + it("reports oxc diagnostics on ast.errors", () => { + const ast = toTree("let planets = []);\nconst a = ;\n"); + expect(ast.errors.length).toBeGreaterThan(0); + expect(ast.errors[0].message).toMatch(/semicolon/i); + // oxc gives up on the whole program for this one. + expect(ast.program.body).toEqual([]); + }); + + it("has no errors for a valid file", () => { + const ast = toTree("const a = ;\n"); + expect(ast.errors).toEqual([]); + expect(ast.program.body).toHaveLength(1); + }); +}); + describe("toTree — error handling", () => { it("throws on invalid JS with a content-tag parse error", () => { expect(() => toTree("console.log('unterminated")).toThrow();