From ab37605253f4cd15207a8bca6925efbd559a6448 Mon Sep 17 00:00:00 2001 From: overlookmotel Date: Tue, 24 Feb 2026 14:15:26 +0000 Subject: [PATCH] fix: add `hashbang` property to AST --- src/test262.js | 11 +++++++++++ src/typescript-eslint.js | 13 +++++++++++-- src/utils/json.js | 2 -- .../hashbang/line-terminator-carriage-return.json | 7 ++++++- .../hashbang/line-terminator-line-separator.json | 7 ++++++- .../line-terminator-paragraph-separator.json | 7 ++++++- .../test/language/comments/hashbang/module.json | 7 ++++++- .../test/language/comments/hashbang/not-empty.json | 7 ++++++- .../language/comments/hashbang/use-strict.json | 7 ++++++- .../cases/compiler/emitBundleWithShebang1.ts.md | 7 ++++++- .../cases/compiler/emitBundleWithShebang2.ts.md | 14 ++++++++++++-- ...itBundleWithShebangAndPrologueDirectives1.ts.md | 7 ++++++- ...itBundleWithShebangAndPrologueDirectives2.ts.md | 14 ++++++++++++-- .../typescript/tests/cases/compiler/shebang.ts.md | 7 ++++++- .../cases/compiler/shebangBeforeReferences.ts.md | 7 ++++++- 15 files changed, 106 insertions(+), 18 deletions(-) diff --git a/src/test262.js b/src/test262.js index d2edf16c0ec..58da90feaf2 100644 --- a/src/test262.js +++ b/src/test262.js @@ -42,6 +42,7 @@ await run({ const isModule = preamble.flags?.includes("module"); let ast; + const comments = []; try { ast = AcornParser.parse(code, { ecmaVersion: "latest", @@ -51,6 +52,7 @@ await run({ allowReturnOutsideFunction: true, // Note: Do not specify `allowAwaitOutsideFunction` option. // It defaults to `true` for modules, `false` for scripts, which is what we want. + onComment: comments, }); } catch { try { @@ -63,6 +65,7 @@ await run({ globalReturn: true, webcompat: true, // I think this enables support for Annex B next: true, // Enable parsing decorators and import attributes + onComment: comments, }); } catch { return; @@ -71,6 +74,14 @@ await run({ fixMeriyahValue(ast); } + // Add `hashbang` property to AST if file starts with a hashbang. + // This property is non-standard and exclusive to Oxc. + if (comments.length > 0 && code.startsWith("#!") && comments[0].type === "Line") { + ast.hashbang = { ...comments[0], type: "Hashbang" }; + } else { + ast.hashbang = null; + } + // Parse tokens const tokensJson = parseEspreeTokens(code, isModule, false); diff --git a/src/typescript-eslint.js b/src/typescript-eslint.js index d010eaf8795..f53016559b4 100644 --- a/src/typescript-eslint.js +++ b/src/typescript-eslint.js @@ -22,14 +22,19 @@ await run({ let output = ""; for (const test of tests) { try { - const result = parseForESLint(test.content, { + // Convert hashbang to normal line comment. + // This is what ESLint does, before passing code to parser. + let code = test.content; + const hasHashbang = code.startsWith("#!"); + if (hasHashbang) code = `//${code.slice(2)}`; + + const result = parseForESLint(code, { filePath: path, sourceType: test.sourceType.module ? "module" : "script", ecmaFeatures: { jsx: test.sourceType.jsx, }, }); - // oxlint-disable-next-line no-unused-vars const { comments, tokens, ...program } = result.ast; // TS-ESLint parser has no `unambiguous` option, so emulate it here @@ -73,6 +78,10 @@ await run({ } } + // Add `hashbang` property to AST if file starts with a hashbang. + // This property is non-standard and exclusive to Oxc. + program.hashbang = hasHashbang ? { ...comments[0], type: "Hashbang" } : null; + const astJson = stringifyWith(program, transformerTs); output += "__ESTREE_TEST__:AST:\n```json\n" + astJson + "\n```\n"; diff --git a/src/utils/json.js b/src/utils/json.js index 74e861a9fe2..e30a72b957f 100644 --- a/src/utils/json.js +++ b/src/utils/json.js @@ -135,8 +135,6 @@ export function transformerTs(_key, value) { } export function stringifyWith(ast, transformer) { - // Add `hashbang` field - ast.hashbang = null; // Serialize to JSON, with modifications let json = JSON.stringify(ast, transformer, 2); json = json.replace(INFINITY_REGEXP, "1e+400"); diff --git a/tests/test262/test/language/comments/hashbang/line-terminator-carriage-return.json b/tests/test262/test/language/comments/hashbang/line-terminator-carriage-return.json index 2980965bdf9..aba409b6282 100644 --- a/tests/test262/test/language/comments/hashbang/line-terminator-carriage-return.json +++ b/tests/test262/test/language/comments/hashbang/line-terminator-carriage-return.json @@ -9,7 +9,12 @@ } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": " this comment ends with a Carriage Return (U+000D)", + "start": 0, + "end": 52 + }, "start": 0, "end": 637 } \ No newline at end of file diff --git a/tests/test262/test/language/comments/hashbang/line-terminator-line-separator.json b/tests/test262/test/language/comments/hashbang/line-terminator-line-separator.json index a24cb890533..37d4628bb54 100644 --- a/tests/test262/test/language/comments/hashbang/line-terminator-line-separator.json +++ b/tests/test262/test/language/comments/hashbang/line-terminator-line-separator.json @@ -9,7 +9,12 @@ } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": " this comment ends with a Line Separator (U+2028)", + "start": 0, + "end": 51 + }, "start": 0, "end": 635 } \ No newline at end of file diff --git a/tests/test262/test/language/comments/hashbang/line-terminator-paragraph-separator.json b/tests/test262/test/language/comments/hashbang/line-terminator-paragraph-separator.json index 18fcf713518..679c2bebe17 100644 --- a/tests/test262/test/language/comments/hashbang/line-terminator-paragraph-separator.json +++ b/tests/test262/test/language/comments/hashbang/line-terminator-paragraph-separator.json @@ -9,7 +9,12 @@ } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": " this comment ends with a Paragraph Separator (U+2029)", + "start": 0, + "end": 56 + }, "start": 0, "end": 645 } \ No newline at end of file diff --git a/tests/test262/test/language/comments/hashbang/module.json b/tests/test262/test/language/comments/hashbang/module.json index accd9edcef2..1da8a68315f 100644 --- a/tests/test262/test/language/comments/hashbang/module.json +++ b/tests/test262/test/language/comments/hashbang/module.json @@ -2,7 +2,12 @@ "type": "Program", "body": [], "sourceType": "module", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "", + "start": 0, + "end": 2 + }, "start": 0, "end": 333 } \ No newline at end of file diff --git a/tests/test262/test/language/comments/hashbang/not-empty.json b/tests/test262/test/language/comments/hashbang/not-empty.json index 2b6845116c0..84cba97d3d8 100644 --- a/tests/test262/test/language/comments/hashbang/not-empty.json +++ b/tests/test262/test/language/comments/hashbang/not-empty.json @@ -2,7 +2,12 @@ "type": "Program", "body": [], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": " these characters should be treated as a comment", + "start": 0, + "end": 50 + }, "start": 0, "end": 412 } \ No newline at end of file diff --git a/tests/test262/test/language/comments/hashbang/use-strict.json b/tests/test262/test/language/comments/hashbang/use-strict.json index 9798b9d796f..7959e817138 100644 --- a/tests/test262/test/language/comments/hashbang/use-strict.json +++ b/tests/test262/test/language/comments/hashbang/use-strict.json @@ -20,7 +20,12 @@ } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "\"use strict\"", + "start": 0, + "end": 14 + }, "start": 0, "end": 391 } \ No newline at end of file diff --git a/tests/typescript/tests/cases/compiler/emitBundleWithShebang1.ts.md b/tests/typescript/tests/cases/compiler/emitBundleWithShebang1.ts.md index 2f61e04e6ab..137e826cb67 100644 --- a/tests/typescript/tests/cases/compiler/emitBundleWithShebang1.ts.md +++ b/tests/typescript/tests/cases/compiler/emitBundleWithShebang1.ts.md @@ -67,7 +67,12 @@ __ESTREE_TEST__:AST: } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "/usr/bin/env gjs", + "start": 0, + "end": 18 + }, "start": 19, "end": 59 } diff --git a/tests/typescript/tests/cases/compiler/emitBundleWithShebang2.ts.md b/tests/typescript/tests/cases/compiler/emitBundleWithShebang2.ts.md index 723db0f17d6..6418031e883 100644 --- a/tests/typescript/tests/cases/compiler/emitBundleWithShebang2.ts.md +++ b/tests/typescript/tests/cases/compiler/emitBundleWithShebang2.ts.md @@ -67,7 +67,12 @@ __ESTREE_TEST__:AST: } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "/usr/bin/env gjs", + "start": 0, + "end": 18 + }, "start": 19, "end": 60 } @@ -206,7 +211,12 @@ __ESTREE_TEST__:AST: } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "/usr/bin/env js", + "start": 0, + "end": 17 + }, "start": 18, "end": 61 } diff --git a/tests/typescript/tests/cases/compiler/emitBundleWithShebangAndPrologueDirectives1.ts.md b/tests/typescript/tests/cases/compiler/emitBundleWithShebangAndPrologueDirectives1.ts.md index 3151782402a..4c4452d3b6b 100644 --- a/tests/typescript/tests/cases/compiler/emitBundleWithShebangAndPrologueDirectives1.ts.md +++ b/tests/typescript/tests/cases/compiler/emitBundleWithShebangAndPrologueDirectives1.ts.md @@ -80,7 +80,12 @@ __ESTREE_TEST__:AST: } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "/usr/bin/env gjs", + "start": 0, + "end": 18 + }, "start": 19, "end": 72 } diff --git a/tests/typescript/tests/cases/compiler/emitBundleWithShebangAndPrologueDirectives2.ts.md b/tests/typescript/tests/cases/compiler/emitBundleWithShebangAndPrologueDirectives2.ts.md index 0a0adedfe1f..d8cb2dc4606 100644 --- a/tests/typescript/tests/cases/compiler/emitBundleWithShebangAndPrologueDirectives2.ts.md +++ b/tests/typescript/tests/cases/compiler/emitBundleWithShebangAndPrologueDirectives2.ts.md @@ -80,7 +80,12 @@ __ESTREE_TEST__:AST: } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "/usr/bin/env gjs", + "start": 0, + "end": 18 + }, "start": 19, "end": 73 } @@ -251,7 +256,12 @@ __ESTREE_TEST__:AST: } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "/usr/bin/env gjs", + "start": 0, + "end": 18 + }, "start": 19, "end": 94 } diff --git a/tests/typescript/tests/cases/compiler/shebang.ts.md b/tests/typescript/tests/cases/compiler/shebang.ts.md index 4b251873987..08b67a4ce4c 100644 --- a/tests/typescript/tests/cases/compiler/shebang.ts.md +++ b/tests/typescript/tests/cases/compiler/shebang.ts.md @@ -36,7 +36,12 @@ __ESTREE_TEST__:AST: } ], "sourceType": "script", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "/usr/bin/env node", + "start": 0, + "end": 19 + }, "start": 20, "end": 79 } diff --git a/tests/typescript/tests/cases/compiler/shebangBeforeReferences.ts.md b/tests/typescript/tests/cases/compiler/shebangBeforeReferences.ts.md index c34bdd08fa5..dcfcd676849 100644 --- a/tests/typescript/tests/cases/compiler/shebangBeforeReferences.ts.md +++ b/tests/typescript/tests/cases/compiler/shebangBeforeReferences.ts.md @@ -262,7 +262,12 @@ __ESTREE_TEST__:AST: } ], "sourceType": "module", - "hashbang": null, + "hashbang": { + "type": "Hashbang", + "value": "/usr/bin/env node", + "start": 0, + "end": 19 + }, "start": 53, "end": 123 }