22 * Regression tests for the JS-fallback parse path.
33 *
44 * The JS path runs when @typescript-eslint/parser isn't available (or when
5- * `useBabel: true` is set). Historically this path produced an AST that
6- * ESLint refused — `oxc-parser` only emits `start`/`end` byte offsets, so
7- * `Program.loc` and `Program.range` were missing and `Program.tokens` was
8- * empty. Lint of any .gjs file blew up with `TypeError: AST is missing
9- * location information.`, and rules that walk tokens (e.g. no-dupe-args)
10- * crashed even after that.
5+ * `useBabel: true` is set). It now defers to @babel/eslint-parser, which
6+ * picks up the consuming app's babel config — so any plugin enabled there
7+ * (decorators, etc.) is honoured when linting `.gjs` files.
118 *
12- * These tests force the JS path via `useBabel: true` and pin the contract
13- * at two layers: the AST shape the parser returns, and a real Linter pass
14- * driving an ESLint rule that walks tokens. Both fail under oxc.
9+ * Two earlier shapes of this path each broke ESLint differently:
10+ * - oxc-parser emitted only `start`/`end` byte offsets, so `Program.loc`
11+ * and `Program.range` were missing and `Program.tokens` was empty.
12+ * Lint of any .gjs file blew up with `TypeError: AST is missing
13+ * location information.`, and rules that walk tokens (e.g.
14+ * no-dupe-args) crashed even after that.
15+ * - raw espree carries loc/tokens but rejects modern syntax outright —
16+ * `@tracked count = 0;` produced `SyntaxError: Unexpected character '@'`
17+ * for every JS-only ember app.
1518 *
16- * Switch back to oxc once https://github.com/oxc-project/oxc/issues/10307
17- * lands native loc support.
19+ * These tests force the JS path via `useBabel: true` and pin three
20+ * contracts: the AST shape ESLint requires, decorator syntax parses
21+ * cleanly, and a real Linter pass walks tokens without throwing.
1822 */
1923
2024import { describe , expect , it } from 'vitest' ;
2125import { Linter } from 'eslint' ;
2226import { parseForESLint } from '../src/parser/gjs-gts-parser.js' ;
27+ import { traverse } from '../src/parser/transforms.js' ;
2328
2429describe ( 'JS path (useBabel) — AST shape ESLint requires' , ( ) => {
2530 const code = [
@@ -62,6 +67,42 @@ describe('JS path (useBabel) — AST shape ESLint requires', () => {
6267 } ) ;
6368} ) ;
6469
70+ describe ( 'JS path (useBabel) — class decorators' , ( ) => {
71+ // Regression: when @typescript -eslint/parser isn't installed, the JS path
72+ // historically fell back to raw espree, which rejects `@decorator` syntax
73+ // outright (`SyntaxError: Unexpected character '@'`). Routing the JS path
74+ // through @babel /eslint-parser picks up the consuming app's babel config —
75+ // the decorators plugin lives there for ember apps — so a `.gjs` file with
76+ // a `@tracked` field parses cleanly without any TypeScript tooling.
77+ const code = [
78+ "import Component from '@glimmer/component';" ,
79+ "import { tracked } from '@glimmer/tracking';" ,
80+ '' ,
81+ 'export default class Counter extends Component {' ,
82+ ' @tracked count = 0;' ,
83+ ' <template>{{this.count}}</template>' ,
84+ '}' ,
85+ ] . join ( '\n' ) ;
86+
87+ it ( 'parses a class field decorator without throwing' , ( ) => {
88+ const result = parseForESLint ( code , {
89+ filePath : 'fixture.gjs' ,
90+ useBabel : true ,
91+ } ) ;
92+ expect ( result . ast . type ) . toBe ( 'Program' ) ;
93+
94+ let decoratorName = null ;
95+ traverse ( result . visitorKeys , result . ast , ( path ) => {
96+ const decorators = path . node ?. decorators ;
97+ if ( Array . isArray ( decorators ) && decorators . length > 0 ) {
98+ const expr = decorators [ 0 ] . expression ;
99+ decoratorName = expr ?. name ?? expr ?. callee ?. name ?? null ;
100+ }
101+ } ) ;
102+ expect ( decoratorName ) . toBe ( 'tracked' ) ;
103+ } ) ;
104+ } ) ;
105+
65106describe ( 'JS path (useBabel) — end-to-end Linter pass' , ( ) => {
66107 function makeLinter ( ) {
67108 const linter = new Linter ( ) ;
@@ -83,10 +124,10 @@ describe('JS path (useBabel) — end-to-end Linter pass', () => {
83124
84125 // `no-dupe-args` is a core rule that calls
85126 // `astUtils.getOpeningParenOfParams(...).loc.start`, which reaches
86- // through the token stream. Under the oxc path this throws because
87- // `program.tokens` is empty. Enabling the rule keeps that surface
88- // covered even if a future change quietly restores `loc` but still
89- // ships an empty token array .
127+ // through the token stream. Enabling it pins the contract that the
128+ // JS path emits a populated `program.tokens` — earlier oxc-based
129+ // implementations shipped an empty array and crashed every rule
130+ // that walked tokens .
90131 const messages = linter . verify (
91132 code ,
92133 {
0 commit comments