• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ota-meshi / eslint-plugin-jsonc / 22219409734

20 Feb 2026 09:52AM UTC coverage: 71.429% (+0.4%) from 71.001%
22219409734

push

github

web-flow
feat: improve type definitions (#476)

1034 of 1276 branches covered (81.03%)

Branch coverage included in aggregate %.

380 of 562 new or added lines in 34 files covered. (67.62%)

2 existing lines in 2 files now uncovered.

7461 of 10617 relevant lines covered (70.27%)

78.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.32
/lib/language/jsonc-source-code.ts
1
/**
1✔
2
 * @fileoverview The JSONCSourceCode class.
1✔
3
 */
1✔
4

1✔
5
import { traverseNodes, VisitorKeys } from "jsonc-eslint-parser";
1✔
6
import type { AST } from "jsonc-eslint-parser";
1✔
7
import type {
1✔
8
  TraversalStep,
1✔
9
  IDirective as Directive,
1✔
10
} from "@eslint/plugin-kit";
1✔
11
import {
1✔
12
  TextSourceCodeBase,
1✔
13
  CallMethodStep,
1✔
14
  VisitNodeStep,
1✔
15
  ConfigCommentParser,
1✔
16
  Directive as DirectiveImpl,
1✔
17
} from "@eslint/plugin-kit";
1✔
18
import type { DirectiveType, FileProblem, RulesConfig } from "@eslint/core";
1✔
19
import {
1✔
20
  TokenStore,
1✔
21
  type CursorWithSkipOptionsWithoutFilter,
1✔
22
  type CursorWithSkipOptionsWithFilter,
1✔
23
  type CursorWithSkipOptionsWithComment,
1✔
24
  type CursorWithCountOptionsWithoutFilter,
1✔
25
  type CursorWithCountOptionsWithFilter,
1✔
26
  type CursorWithCountOptionsWithComment,
1✔
27
} from "@ota-meshi/ast-token-store";
1✔
28
import type { AST as ESLintAST } from "eslint";
1✔
29
import type { Scope } from "eslint";
1✔
30
import type { Comment } from "estree";
1✔
31

1✔
32
//-----------------------------------------------------------------------------
1✔
33
// Helpers
1✔
34
//-----------------------------------------------------------------------------
1✔
35

1✔
36
const commentParser = new ConfigCommentParser();
1✔
37

1✔
38
/**
1✔
39
 * Pattern to match ESLint inline configuration comments in JSONC.
1✔
40
 * Matches: eslint, eslint-disable, eslint-enable, eslint-disable-line, eslint-disable-next-line
1✔
41
 */
1✔
42
const INLINE_CONFIG =
1✔
43
  /^\s*eslint(?:-enable|-disable(?:(?:-next)?-line)?)?(?:\s|$)/u;
1✔
44

1✔
45
//-----------------------------------------------------------------------------
1✔
46
// Types
1✔
47
//-----------------------------------------------------------------------------
1✔
48

1✔
49
/**
1✔
50
 * A comment token with required range and loc.
1✔
51
 */
1✔
52
export type JSONCComment = Comment & {
1✔
53
  range: [number, number];
1✔
54
  loc: AST.SourceLocation;
1✔
55
};
1✔
56

1✔
57
/**
1✔
58
 * JSONC-specific syntax element type
1✔
59
 */
1✔
60
export type JSONCSyntaxElement = AST.JSONNode | JSONCTokenOrComment;
1✔
61
export type JSONCToken = ESLintAST.Token;
1✔
62
export type JSONCTokenOrComment = JSONCToken | JSONCComment;
1✔
63

1✔
64
/**
1✔
65
 * JSONC Source Code Object
1✔
66
 */
1✔
67
export class JSONCSourceCode extends TextSourceCodeBase<{
1✔
68
  LangOptions: Record<never, never>;
1✔
69
  RootNode: AST.JSONProgram;
1✔
70
  SyntaxElementWithLoc: JSONCSyntaxElement;
1✔
71
  ConfigNode: JSONCComment;
1✔
72
}> {
1✔
73
  public readonly hasBOM: boolean;
1,082✔
74

1,082✔
75
  public readonly parserServices: {
1,082✔
76
    isJSON?: boolean;
1,082✔
77
    parseError?: unknown;
1,082✔
78
  };
1,082✔
79

1,082✔
80
  public readonly visitorKeys: Record<string, string[]>;
1,082✔
81

1,082✔
82
  private readonly tokenStore: TokenStore<
1,082✔
83
    AST.JSONNode,
1,082✔
84
    JSONCToken,
1,082✔
85
    JSONCComment
1,082✔
86
  >;
1,082✔
87

1,082✔
88
  #steps: TraversalStep[] | null = null;
1,082✔
89

1,082✔
90
  #cacheTokensAndComments: (JSONCToken | JSONCComment)[] | null = null;
1,082✔
91

1,082✔
92
  #inlineConfigComments: JSONCComment[] | null = null;
1,082✔
93

1,082✔
94
  /**
1,082✔
95
   * Creates a new instance.
1,082✔
96
   */
1,082✔
97
  public constructor(config: {
1,082✔
98
    text: string;
×
99
    ast: AST.JSONProgram;
×
100
    hasBOM: boolean;
×
101
    parserServices: { isJSON: boolean; parseError?: unknown };
×
102
    visitorKeys?: Record<string, string[]> | null | undefined;
×
103
  }) {
×
104
    super({
×
105
      ast: config.ast,
×
106
      text: config.text,
×
107
    });
×
108
    this.hasBOM = Boolean(config.hasBOM);
×
109
    this.parserServices = config.parserServices;
×
110
    this.visitorKeys = config.visitorKeys || VisitorKeys;
×
NEW
111
    this.tokenStore = new TokenStore<AST.JSONNode, JSONCToken, JSONCComment>({
×
112
      tokens: [
×
NEW
113
        ...(config.ast.tokens as (JSONCToken | JSONCComment)[]),
×
114
        ...(config.ast.comments as JSONCComment[]),
1,082✔
115
      ],
×
116
      isComment: (token): token is JSONCComment =>
✔
117
        token.type === "Block" || token.type === "Line",
3,978✔
118
    });
×
119
  }
×
120

×
121
  public traverse(): Iterable<TraversalStep> {
✔
122
    if (this.#steps != null) {
1,059!
123
      return this.#steps;
×
124
    }
×
125

1,059✔
126
    const steps: (VisitNodeStep | CallMethodStep)[] = [];
1,059✔
127
    this.#steps = steps;
1,059✔
128

1,059✔
129
    const root = this.ast;
1,059✔
130
    steps.push(
1,059✔
131
      // ESLint core rule compatibility: onCodePathStart is called with two arguments.
×
132
      new CallMethodStep({
×
133
        target: "onCodePathStart",
×
134
        args: [{}, root],
×
135
      }),
×
136
    );
×
137

×
138
    traverseNodes(root, {
×
139
      enterNode(n) {
✔
140
        steps.push(
11,608✔
141
          new VisitNodeStep({
11,608✔
142
            target: n,
11,608✔
143
            phase: 1,
11,608✔
144
            args: [n],
×
145
          }),
×
146
        );
×
147
      },
×
148
      leaveNode(n) {
✔
149
        steps.push(
11,608✔
150
          new VisitNodeStep({
11,608✔
151
            target: n,
11,608✔
152
            phase: 2,
11,608✔
153
            args: [n],
11,608✔
154
          }),
11,608✔
155
        );
11,608✔
156
      },
11,608✔
157
    });
×
158

×
159
    steps.push(
×
160
      // ESLint core rule compatibility: onCodePathEnd is called with two arguments.
×
161
      new CallMethodStep({
×
162
        target: "onCodePathEnd",
×
163
        args: [{}, root],
×
164
      }),
×
165
    );
×
166
    return steps;
×
167
  }
×
168

×
169
  /**
×
170
   * Gets all tokens and comments.
×
171
   */
×
NEW
172
  public get tokensAndComments(): JSONCTokenOrComment[] {
✔
173
    return (this.#cacheTokensAndComments ??= [
26✔
174
      ...this.ast.tokens,
×
175
      ...(this.ast.comments as JSONCComment[]),
×
176
    ].sort((a, b) => a.range[0] - b.range[0]));
✔
177
  }
×
178

×
179
  public getLines(): string[] {
✔
180
    return this.lines;
2✔
181
  }
2✔
182

×
183
  public getAllComments(): JSONCComment[] {
✔
184
    return this.tokenStore.getAllComments();
67✔
185
  }
67✔
186

×
187
  /**
×
188
   * Returns an array of all inline configuration nodes found in the source code.
×
189
   * This includes eslint-disable, eslint-enable, eslint-disable-line,
×
190
   * eslint-disable-next-line, and eslint (for inline config) comments.
×
191
   */
×
192
  public getInlineConfigNodes(): JSONCComment[] {
✔
193
    if (!this.#inlineConfigComments) {
2,118✔
194
      this.#inlineConfigComments = (this.ast.comments as JSONCComment[]).filter(
1,059✔
195
        (comment) => INLINE_CONFIG.test(comment.value),
1,059✔
196
      );
1,059✔
197
    }
1,059✔
198

2,118✔
199
    return this.#inlineConfigComments;
2,118✔
200
  }
2,118✔
201

1,082✔
202
  /**
1,082✔
203
   * Returns directives that enable or disable rules along with any problems
1,082✔
204
   * encountered while parsing the directives.
1,082✔
205
   */
1,082✔
206
  public getDisableDirectives(): {
1,082✔
207
    directives: Directive[];
1,059✔
208
    problems: FileProblem[];
1,059✔
209
  } {
1,059✔
210
    const problems: FileProblem[] = [];
1,059✔
211
    const directives: Directive[] = [];
1,059✔
212

1,059✔
213
    this.getInlineConfigNodes().forEach((comment) => {
1,059✔
214
      const directive = commentParser.parseDirective(comment.value);
10✔
215

10✔
216
      if (!directive) {
10!
217
        return;
×
218
      }
×
219

10✔
220
      const { label, value, justification } = directive;
10✔
221

10✔
222
      // `eslint-disable-line` directives are not allowed to span multiple lines
10✔
223
      // as it would be confusing to which lines they apply
10✔
224
      if (
10✔
225
        label === "eslint-disable-line" &&
10✔
226
        comment.loc.start.line !== comment.loc.end.line
2✔
227
      ) {
10!
228
        const message = `${label} comment should not span multiple lines.`;
×
229

×
230
        problems.push({
×
231
          ruleId: null,
×
232
          message,
×
233
          loc: comment.loc,
×
234
        });
×
235
        return;
×
236
      }
×
237

10✔
238
      switch (label) {
10✔
239
        case "eslint-disable":
10✔
240
        case "eslint-enable":
3✔
241
        case "eslint-disable-next-line":
10✔
242
        case "eslint-disable-line": {
10✔
243
          const directiveType = label.slice("eslint-".length);
8✔
244

8✔
245
          directives.push(
8✔
246
            new DirectiveImpl({
8✔
247
              type: directiveType as DirectiveType,
8✔
248
              node: comment,
8✔
249
              value,
8✔
250
              justification,
8✔
251
            }),
8✔
252
          );
8✔
253
          break;
8✔
254
        }
8✔
255
        // no default
10✔
256
      }
10✔
257
    });
1,059✔
258

1,059✔
259
    return { problems, directives };
1,059✔
260
  }
1,059✔
261

1,082✔
262
  /**
1,082✔
263
   * Returns inline rule configurations along with any problems
1,082✔
264
   * encountered while parsing the configurations.
1,082✔
265
   */
1,082✔
266
  public applyInlineConfig(): {
1,082✔
267
    configs: { config: { rules: RulesConfig }; loc: AST.SourceLocation }[];
1,059✔
268
    problems: FileProblem[];
1,059✔
269
  } {
1,059✔
270
    const problems: FileProblem[] = [];
1,059✔
271
    const configs: {
1,059✔
272
      config: { rules: RulesConfig };
1,059✔
273
      loc: AST.SourceLocation;
1,059✔
274
    }[] = [];
1,059✔
275

1,059✔
276
    this.getInlineConfigNodes().forEach((comment) => {
1,059✔
277
      const directive = commentParser.parseDirective(comment.value);
10✔
278

10✔
279
      if (!directive) {
10!
280
        return;
×
281
      }
×
282

10✔
283
      const { label, value } = directive;
10✔
284

10✔
285
      if (label === "eslint") {
10✔
286
        const parseResult = commentParser.parseJSONLikeConfig(value);
2✔
287

×
288
        if (parseResult.ok) {
✔
289
          configs.push({
×
290
            config: {
×
291
              rules: parseResult.config,
×
292
            },
×
293
            loc: comment.loc,
×
294
          });
×
295
        } else {
×
296
          problems.push({
×
297
            ruleId: null,
×
298
            message: parseResult.error.message,
×
299
            loc: comment.loc,
×
300
          });
×
301
        }
×
302
      }
×
303
    });
×
304

×
305
    return { configs, problems };
×
306
  }
×
307

×
308
  /**
×
309
   * Gets the source text for the given node or the entire source if no node is provided.
×
310
   */
×
311
  public getText(
✔
312
    node?: JSONCSyntaxElement,
75✔
313
    beforeCount?: number,
75✔
314
    afterCount?: number,
75✔
315
  ): string {
75✔
316
    if (!node) return this.text;
75✔
317
    const range = (node as { range: [number, number] }).range;
75✔
318
    const start = range[0] - (beforeCount ?? 0);
75✔
319
    const end = range[1] + (afterCount ?? 0);
75✔
320
    return this.text.slice(Math.max(0, start), Math.min(this.text.length, end));
75✔
321
  }
75✔
322

×
323
  public getNodeByRangeIndex(index: number): AST.JSONNode | null {
✔
324
    let node = find([this.ast]);
95✔
325
    if (!node) return null;
95✔
326
    while (true) {
95✔
327
      const child = find(this._getChildren(node));
493✔
328
      if (!child) return node;
493✔
329
      node = child;
493✔
330
    }
399✔
331

95✔
332
    /**
95✔
333
     * Finds a node that contains the given index.
95✔
334
     */
95✔
335
    function find(nodes: AST.JSONNode[]) {
95!
336
      for (const node of nodes) {
588✔
337
        if (node.range[0] <= index && index < node.range[1]) {
713✔
338
          return node;
493✔
339
        }
493✔
340
      }
713✔
341
      return null;
588✔
342
    }
588✔
343
  }
95✔
344

×
345
  /**
×
346
   * Gets the first token of the given node.
×
347
   */
×
NEW
348
  public getFirstToken(node: JSONCSyntaxElement): JSONCToken;
×
349

×
350
  /**
×
351
   * Gets the first token of the given node with options.
×
352
   */
×
353
  public getFirstToken(
×
354
    node: JSONCSyntaxElement,
×
355
    options: CursorWithSkipOptionsWithoutFilter,
×
NEW
356
  ): JSONCToken | null;
×
357

×
358
  /**
×
359
   * Gets the first token of the given node with filter options.
×
360
   */
×
NEW
361
  public getFirstToken<R extends JSONCToken>(
×
362
    node: JSONCSyntaxElement,
×
NEW
363
    options: CursorWithSkipOptionsWithFilter<JSONCToken, R>,
×
364
  ): R | null;
×
365

×
366
  /**
×
367
   * Gets the first token of the given node with comment options.
×
368
   */
×
NEW
369
  public getFirstToken<R extends JSONCToken | JSONCComment>(
×
370
    node: JSONCSyntaxElement,
×
NEW
371
    options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>,
×
372
  ): R | null;
×
373

×
374
  public getFirstToken(
✔
375
    node: JSONCSyntaxElement,
788✔
376
    options?:
788✔
377
      | CursorWithSkipOptionsWithoutFilter
788✔
378
      | CursorWithSkipOptionsWithFilter<JSONCToken>
788✔
379
      | CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment>,
788✔
380
  ): JSONCToken | JSONCComment | null {
788✔
381
    return this.tokenStore.getFirstToken(node, options as never);
788✔
382
  }
788✔
383

×
384
  /**
×
385
   * Gets the first tokens of the given node.
×
386
   */
×
387
  public getFirstTokens(
×
388
    node: JSONCSyntaxElement,
×
389
    options?: CursorWithCountOptionsWithoutFilter,
×
NEW
390
  ): JSONCToken[];
×
391

×
392
  /**
×
393
   * Gets the first tokens of the given node with filter options.
×
394
   */
×
NEW
395
  public getFirstTokens<R extends JSONCToken>(
×
396
    node: JSONCSyntaxElement,
×
NEW
397
    options: CursorWithCountOptionsWithFilter<JSONCToken, R>,
×
398
  ): R[];
×
399

×
400
  /**
×
401
   * Gets the first tokens of the given node with comment options.
×
402
   */
×
NEW
403
  public getFirstTokens<R extends JSONCToken | JSONCComment>(
×
404
    node: JSONCSyntaxElement,
×
NEW
405
    options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>,
×
406
  ): R[];
×
407

×
408
  public getFirstTokens(
✔
409
    node: JSONCSyntaxElement,
12✔
410
    options?:
12✔
411
      | CursorWithCountOptionsWithoutFilter
12✔
412
      | CursorWithCountOptionsWithFilter<JSONCToken>
12✔
413
      | CursorWithCountOptionsWithComment<JSONCToken, JSONCComment>,
12✔
414
  ): (JSONCToken | JSONCComment)[] {
12✔
415
    return this.tokenStore.getFirstTokens(node, options as never);
12✔
416
  }
12✔
417

×
418
  /**
×
419
   * Gets the last token of the given node.
×
420
   */
×
NEW
421
  public getLastToken(node: JSONCSyntaxElement): JSONCToken;
×
422

×
423
  /**
×
424
   * Gets the last token of the given node with options.
×
425
   */
×
426
  public getLastToken(
×
427
    node: JSONCSyntaxElement,
×
428
    options: CursorWithSkipOptionsWithoutFilter,
×
NEW
429
  ): JSONCToken | null;
×
430

×
431
  /**
×
432
   * Gets the last token of the given node with filter options.
×
433
   */
×
NEW
434
  public getLastToken<R extends JSONCToken>(
×
435
    node: JSONCSyntaxElement,
×
NEW
436
    options: CursorWithSkipOptionsWithFilter<JSONCToken, R>,
×
437
  ): R | null;
×
438

×
439
  /**
×
440
   * Gets the last token of the given node with comment options.
×
441
   */
×
NEW
442
  public getLastToken<R extends JSONCToken | JSONCComment>(
×
443
    node: JSONCSyntaxElement,
×
NEW
444
    options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>,
×
445
  ): R | null;
×
446

×
447
  public getLastToken(
✔
448
    node: JSONCSyntaxElement,
600✔
449
    options?:
600✔
450
      | CursorWithSkipOptionsWithoutFilter
600✔
451
      | CursorWithSkipOptionsWithFilter<JSONCToken>
600✔
452
      | CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment>,
600✔
453
  ): (JSONCToken | JSONCComment) | null {
600✔
454
    return this.tokenStore.getLastToken(node, options as never);
600✔
455
  }
600✔
456

×
457
  /**
×
458
   * Get the last tokens of the given node.
×
459
   */
×
460
  public getLastTokens(
×
461
    node: JSONCSyntaxElement,
×
462
    options?: CursorWithCountOptionsWithoutFilter,
×
NEW
463
  ): JSONCToken[];
×
464

×
465
  /**
×
466
   * Get the last tokens of the given node with filter options.
×
467
   */
×
NEW
468
  public getLastTokens<R extends JSONCToken>(
×
469
    node: JSONCSyntaxElement,
×
NEW
470
    options: CursorWithCountOptionsWithFilter<JSONCToken, R>,
×
471
  ): R[];
×
472

×
473
  /**
×
474
   * Get the last tokens of the given node with comment options.
×
475
   */
×
NEW
476
  public getLastTokens<R extends JSONCToken | JSONCComment>(
×
477
    node: JSONCSyntaxElement,
×
NEW
478
    options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>,
×
479
  ): R[];
×
480

×
481
  public getLastTokens(
×
482
    node: JSONCSyntaxElement,
×
483
    options?:
×
484
      | CursorWithCountOptionsWithoutFilter
×
NEW
485
      | CursorWithCountOptionsWithFilter<JSONCToken>
×
NEW
486
      | CursorWithCountOptionsWithComment<JSONCToken, JSONCComment>,
×
NEW
487
  ): (JSONCToken | JSONCComment)[] {
×
NEW
488
    return this.tokenStore.getLastTokens(node, options as never);
×
489
  }
×
490

×
491
  /**
×
492
   * Gets the token that precedes a given node or token.
×
493
   */
×
494
  public getTokenBefore(
×
495
    node: JSONCSyntaxElement,
×
496
    options?: CursorWithSkipOptionsWithoutFilter,
×
NEW
497
  ): JSONCToken | null;
×
498

×
499
  /**
×
500
   * Gets the token that precedes a given node or token with filter options.
×
501
   */
×
NEW
502
  public getTokenBefore<R extends JSONCToken>(
×
503
    node: JSONCSyntaxElement,
×
NEW
504
    options: CursorWithSkipOptionsWithFilter<JSONCToken, R>,
×
505
  ): R | null;
×
506

×
507
  /**
×
508
   * Gets the token that precedes a given node or token with comment options.
×
509
   */
×
NEW
510
  public getTokenBefore<R extends JSONCToken | JSONCComment>(
×
511
    node: JSONCSyntaxElement,
×
NEW
512
    options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>,
×
513
  ): R | null;
×
514

×
515
  public getTokenBefore(
✔
516
    node: JSONCSyntaxElement,
837✔
517
    options?:
837✔
518
      | CursorWithSkipOptionsWithoutFilter
837✔
519
      | CursorWithSkipOptionsWithFilter<JSONCToken>
837✔
520
      | CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment>,
837✔
521
  ): JSONCToken | JSONCComment | null {
837✔
522
    return this.tokenStore.getTokenBefore(node, options as never);
837✔
523
  }
837✔
524

×
525
  /**
×
526
   * Gets the `count` tokens that precedes a given node or token.
×
527
   */
×
528
  public getTokensBefore(
×
529
    node: JSONCSyntaxElement,
×
530
    options?: CursorWithCountOptionsWithoutFilter,
×
NEW
531
  ): JSONCToken[];
×
532

×
533
  /**
×
534
   * Gets the `count` tokens that precedes a given node or token with filter options.
×
535
   */
×
NEW
536
  public getTokensBefore<R extends JSONCToken>(
×
537
    node: JSONCSyntaxElement,
×
NEW
538
    options: CursorWithCountOptionsWithFilter<JSONCToken, R>,
×
539
  ): R[];
×
540

×
541
  /**
×
542
   * Gets the `count` tokens that precedes a given node or token with comment options.
×
543
   */
×
NEW
544
  public getTokensBefore<R extends JSONCToken | JSONCComment>(
×
545
    node: JSONCSyntaxElement,
×
NEW
546
    options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>,
×
547
  ): R[];
×
548

×
549
  public getTokensBefore(
×
550
    node: JSONCSyntaxElement,
×
551
    options?:
×
552
      | CursorWithCountOptionsWithoutFilter
×
NEW
553
      | CursorWithCountOptionsWithFilter<JSONCToken>
×
NEW
554
      | CursorWithCountOptionsWithComment<JSONCToken, JSONCComment>,
×
NEW
555
  ): (JSONCToken | JSONCComment)[] {
×
NEW
556
    return this.tokenStore.getTokensBefore(node, options as never);
×
557
  }
×
558

×
559
  /**
×
560
   * Gets the token that follows a given node or token.
×
561
   */
×
562
  public getTokenAfter(
×
563
    node: JSONCSyntaxElement,
×
564
    options?: CursorWithSkipOptionsWithoutFilter,
×
NEW
565
  ): JSONCToken | null;
×
566

×
567
  /**
×
568
   * Gets the token that follows a given node or token with filter options.
×
569
   */
×
NEW
570
  public getTokenAfter<R extends JSONCToken>(
×
571
    node: JSONCSyntaxElement,
×
NEW
572
    options: CursorWithSkipOptionsWithFilter<JSONCToken, R>,
×
573
  ): R | null;
×
574

×
575
  /**
×
576
   * Gets the token that follows a given node or token with comment options.
×
577
   */
×
NEW
578
  public getTokenAfter<R extends JSONCToken | JSONCComment>(
×
579
    node: JSONCSyntaxElement,
×
NEW
580
    options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>,
×
581
  ): R | null;
×
582

×
583
  public getTokenAfter(
✔
584
    node: JSONCSyntaxElement,
1,448✔
585
    options?:
1,448✔
586
      | CursorWithSkipOptionsWithoutFilter
1,448✔
587
      | CursorWithSkipOptionsWithFilter<JSONCToken>
1,448✔
588
      | CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment>,
1,448✔
589
  ): JSONCToken | JSONCComment | null {
1,448✔
590
    return this.tokenStore.getTokenAfter(node, options as never);
1,448✔
591
  }
1,448✔
592

1,082✔
593
  /**
1,082✔
594
   * Gets the `count` tokens that follows a given node or token.
1,082✔
595
   */
1,082✔
596
  public getTokensAfter(
1,082✔
597
    node: JSONCSyntaxElement,
1,082✔
598
    options?: CursorWithCountOptionsWithoutFilter,
1,082✔
599
  ): JSONCToken[];
1,082✔
600

1,082✔
601
  /**
1,082✔
602
   * Gets the `count` tokens that follows a given node or token with filter options.
1,082✔
603
   */
1,082✔
604
  public getTokensAfter<R extends JSONCToken>(
1,082✔
605
    node: JSONCSyntaxElement,
1,082✔
606
    options: CursorWithCountOptionsWithFilter<JSONCToken, R>,
1,082✔
607
  ): R[];
1,082✔
608

1,082✔
609
  /**
1,082✔
610
   * Gets the `count` tokens that follows a given node or token with comment options.
1,082✔
611
   */
1,082✔
612
  public getTokensAfter<R extends JSONCToken | JSONCComment>(
1,082✔
613
    node: JSONCSyntaxElement,
1,082✔
614
    options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>,
1,082✔
615
  ): R[];
1,082✔
616

1,082✔
617
  public getTokensAfter(
1,082✔
618
    node: JSONCSyntaxElement,
×
619
    options?:
×
620
      | CursorWithCountOptionsWithoutFilter
×
NEW
621
      | CursorWithCountOptionsWithFilter<JSONCToken>
×
NEW
622
      | CursorWithCountOptionsWithComment<JSONCToken, JSONCComment>,
×
NEW
623
  ): (JSONCToken | JSONCComment)[] {
×
NEW
624
    return this.tokenStore.getTokensAfter(node, options as never);
×
625
  }
×
626

×
627
  /**
×
628
   * Gets the first token between two non-overlapping nodes.
×
629
   */
×
630
  public getFirstTokenBetween(
×
631
    left: JSONCSyntaxElement,
×
632
    right: JSONCSyntaxElement,
×
633
    options?: CursorWithSkipOptionsWithoutFilter,
×
NEW
634
  ): JSONCToken | null;
×
635

×
636
  /**
×
637
   * Gets the first token between two non-overlapping nodes with filter options.
×
638
   */
×
NEW
639
  public getFirstTokenBetween<R extends JSONCToken>(
×
640
    left: JSONCSyntaxElement,
×
641
    right: JSONCSyntaxElement,
×
NEW
642
    options: CursorWithSkipOptionsWithFilter<JSONCToken, R>,
×
643
  ): R | null;
×
644

×
645
  /**
×
646
   * Gets the first token between two non-overlapping nodes with comment options.
×
647
   */
×
NEW
648
  public getFirstTokenBetween<R extends JSONCToken | JSONCComment>(
×
649
    left: JSONCSyntaxElement,
×
650
    right: JSONCSyntaxElement,
×
NEW
651
    options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>,
×
652
  ): R | null;
×
653

×
654
  public getFirstTokenBetween(
✔
655
    left: JSONCSyntaxElement,
20✔
656
    right: JSONCSyntaxElement,
20✔
657
    options?:
20✔
658
      | CursorWithSkipOptionsWithoutFilter
20✔
659
      | CursorWithSkipOptionsWithFilter<JSONCToken>
20✔
660
      | CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment>,
20✔
661
  ): JSONCToken | JSONCComment | null {
20✔
662
    return this.tokenStore.getFirstTokenBetween(left, right, options as never);
20✔
663
  }
20✔
664

×
665
  /**
×
666
   * Gets the first tokens between two non-overlapping nodes.
×
667
   */
×
668
  public getFirstTokensBetween(
×
669
    left: JSONCSyntaxElement,
×
670
    right: JSONCSyntaxElement,
×
671
    options?: CursorWithCountOptionsWithoutFilter,
×
NEW
672
  ): JSONCToken[];
×
673

×
674
  /**
×
675
   * Gets the first tokens between two non-overlapping nodes with filter options.
×
676
   */
×
NEW
677
  public getFirstTokensBetween<R extends JSONCToken>(
×
678
    left: JSONCSyntaxElement,
×
679
    right: JSONCSyntaxElement,
×
NEW
680
    options: CursorWithCountOptionsWithFilter<JSONCToken, R>,
×
681
  ): R[];
×
682

×
683
  /**
×
684
   * Gets the first tokens between two non-overlapping nodes with comment options.
×
685
   */
×
NEW
686
  public getFirstTokensBetween<R extends JSONCToken | JSONCComment>(
×
687
    left: JSONCSyntaxElement,
×
688
    right: JSONCSyntaxElement,
×
NEW
689
    options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>,
×
690
  ): R[];
×
691

×
692
  public getFirstTokensBetween(
×
693
    left: JSONCSyntaxElement,
×
694
    right: JSONCSyntaxElement,
×
695
    options?:
×
696
      | CursorWithCountOptionsWithoutFilter
×
NEW
697
      | CursorWithCountOptionsWithFilter<JSONCToken>
×
NEW
698
      | CursorWithCountOptionsWithComment<JSONCToken, JSONCComment>,
×
NEW
699
  ): (JSONCToken | JSONCComment)[] {
×
NEW
700
    return this.tokenStore.getFirstTokensBetween(left, right, options as never);
×
701
  }
×
702

×
703
  /**
×
704
   * Gets the last token between two non-overlapping nodes.
×
705
   */
×
706
  public getLastTokenBetween(
×
707
    left: JSONCSyntaxElement,
×
708
    right: JSONCSyntaxElement,
×
709
    options?: CursorWithSkipOptionsWithoutFilter,
×
NEW
710
  ): JSONCToken | null;
×
711

×
712
  /**
×
713
   * Gets the last token between two non-overlapping nodes with filter options.
×
714
   */
×
NEW
715
  public getLastTokenBetween<R extends JSONCToken>(
×
716
    left: JSONCSyntaxElement,
×
717
    right: JSONCSyntaxElement,
×
NEW
718
    options: CursorWithSkipOptionsWithFilter<JSONCToken, R>,
×
719
  ): R | null;
×
720

×
721
  /**
×
722
   * Gets the last token between two non-overlapping nodes with comment options.
×
723
   */
×
NEW
724
  public getLastTokenBetween<R extends JSONCToken | JSONCComment>(
×
725
    left: JSONCSyntaxElement,
×
726
    right: JSONCSyntaxElement,
×
NEW
727
    options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>,
×
728
  ): R | null;
×
729

×
730
  public getLastTokenBetween(
×
731
    left: JSONCSyntaxElement,
×
732
    right: JSONCSyntaxElement,
×
733
    options?:
×
734
      | CursorWithSkipOptionsWithoutFilter
×
NEW
735
      | CursorWithSkipOptionsWithFilter<JSONCToken>
×
NEW
736
      | CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment>,
×
NEW
737
  ): JSONCToken | JSONCComment | null {
×
NEW
738
    return this.tokenStore.getLastTokenBetween(left, right, options as never);
×
739
  }
×
740

×
741
  /**
×
742
   * Gets the last tokens between two non-overlapping nodes.
×
743
   */
×
744
  public getLastTokensBetween(
×
745
    left: JSONCSyntaxElement,
×
746
    right: JSONCSyntaxElement,
×
747
    options?: CursorWithCountOptionsWithoutFilter,
×
NEW
748
  ): JSONCToken[];
×
749

×
750
  /**
×
751
   * Gets the last tokens between two non-overlapping nodes with filter options.
×
752
   */
×
NEW
753
  public getLastTokensBetween<R extends JSONCToken>(
×
754
    left: JSONCSyntaxElement,
×
755
    right: JSONCSyntaxElement,
×
NEW
756
    options: CursorWithCountOptionsWithFilter<JSONCToken, R>,
×
757
  ): R[];
×
758

×
759
  /**
×
760
   * Gets the last tokens between two non-overlapping nodes with comment options.
×
761
   */
×
NEW
762
  public getLastTokensBetween<R extends JSONCToken | JSONCComment>(
×
763
    left: JSONCSyntaxElement,
×
764
    right: JSONCSyntaxElement,
×
NEW
765
    options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>,
×
766
  ): R[];
×
767

×
768
  public getLastTokensBetween(
×
769
    left: JSONCSyntaxElement,
×
770
    right: JSONCSyntaxElement,
×
771
    options?:
×
772
      | CursorWithCountOptionsWithoutFilter
×
NEW
773
      | CursorWithCountOptionsWithFilter<JSONCToken>
×
NEW
774
      | CursorWithCountOptionsWithComment<JSONCToken, JSONCComment>,
×
NEW
775
  ): (JSONCToken | JSONCComment)[] {
×
NEW
776
    return this.tokenStore.getLastTokensBetween(left, right, options as never);
×
777
  }
×
778

×
779
  /**
×
780
   * Gets all tokens that are related to the given node.
×
781
   */
×
782
  public getTokens(
×
783
    node: JSONCSyntaxElement,
×
784
    options?: CursorWithCountOptionsWithoutFilter,
×
NEW
785
  ): JSONCToken[];
×
786

×
787
  /**
×
788
   * Gets all tokens that are related to the given node with filter options.
×
789
   */
×
NEW
790
  public getTokens<R extends JSONCToken>(
×
791
    node: JSONCSyntaxElement,
×
NEW
792
    options: CursorWithCountOptionsWithFilter<JSONCToken, R>,
×
793
  ): R[];
×
794

×
795
  /**
×
796
   * Gets all tokens that are related to the given node with comment options.
×
797
   */
×
NEW
798
  public getTokens<R extends JSONCToken | JSONCComment>(
×
799
    node: JSONCSyntaxElement,
×
NEW
800
    options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>,
×
801
  ): R[];
×
802

×
803
  public getTokens(
✔
804
    node: JSONCSyntaxElement,
30✔
805
    options?:
30✔
806
      | CursorWithCountOptionsWithoutFilter
30✔
807
      | CursorWithCountOptionsWithFilter<JSONCToken>
30✔
808
      | CursorWithCountOptionsWithComment<JSONCToken, JSONCComment>,
30✔
809
  ): (JSONCToken | JSONCComment)[] {
30✔
810
    return this.tokenStore.getTokens(node, options as never);
30✔
811
  }
×
812

×
813
  /**
×
814
   * Gets all of the tokens between two non-overlapping nodes.
×
815
   */
×
816
  public getTokensBetween(
×
817
    left: JSONCSyntaxElement,
×
818
    right: JSONCSyntaxElement,
×
819
    options?: CursorWithCountOptionsWithoutFilter,
×
NEW
820
  ): JSONCToken[];
×
821

×
822
  /**
×
823
   * Gets all of the tokens between two non-overlapping nodes with filter options.
×
824
   */
×
NEW
825
  public getTokensBetween<R extends JSONCToken>(
×
826
    left: JSONCSyntaxElement,
×
827
    right: JSONCSyntaxElement,
×
NEW
828
    options: CursorWithCountOptionsWithFilter<JSONCToken, R>,
×
829
  ): R[];
×
830

×
831
  /**
×
832
   * Gets all of the tokens between two non-overlapping nodes with comment options.
×
833
   */
×
NEW
834
  public getTokensBetween<R extends JSONCToken | JSONCComment>(
×
835
    left: JSONCSyntaxElement,
×
836
    right: JSONCSyntaxElement,
×
NEW
837
    options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>,
×
838
  ): R[];
×
839

×
840
  public getTokensBetween(
✔
841
    left: JSONCSyntaxElement,
70✔
842
    right: JSONCSyntaxElement,
70✔
843
    options?:
70✔
844
      | CursorWithCountOptionsWithoutFilter
70✔
845
      | CursorWithCountOptionsWithFilter<JSONCToken>
70✔
846
      | CursorWithCountOptionsWithComment<JSONCToken, JSONCComment>,
70✔
847
  ): (JSONCToken | JSONCComment)[] {
70✔
848
    return this.tokenStore.getTokensBetween(left, right, options as never);
70✔
849
  }
70✔
850

1,082✔
851
  public getCommentsInside(nodeOrToken: JSONCSyntaxElement): JSONCComment[] {
1,082✔
NEW
852
    return this.tokenStore.getCommentsInside(nodeOrToken);
×
853
  }
×
854

1,082✔
855
  public getCommentsBefore(nodeOrToken: JSONCSyntaxElement): JSONCComment[] {
1,082✔
856
    return this.tokenStore.getCommentsBefore(nodeOrToken);
2✔
857
  }
2✔
858

1,082✔
859
  public getCommentsAfter(nodeOrToken: JSONCSyntaxElement): JSONCComment[] {
1,082✔
NEW
860
    return this.tokenStore.getCommentsAfter(nodeOrToken);
×
861
  }
×
862

×
863
  public isSpaceBetween(
✔
864
    first: JSONCToken | JSONCComment,
540✔
865
    second: JSONCToken | JSONCComment,
540✔
866
  ): boolean {
540✔
867
    // Normalize order: ensure left comes before right
540✔
868
    const [left, right] =
540✔
869
      first.range[1] <= second.range[0] ? [first, second] : [second, first];
540✔
870
    return this.tokenStore.isSpaceBetween(left, right);
540✔
871
  }
540✔
872

×
873
  /**
×
874
   * Compatibility for ESLint's SourceCode API
×
875
   * @deprecated JSONC does not have scopes
×
876
   */
×
877
  public getScope(node?: AST.JSONNode): Scope.Scope | null {
✔
878
    if (node?.type !== "Program") {
39!
879
      return null;
×
880
    }
×
881
    return createFakeGlobalScope(this.ast);
39✔
882
  }
39✔
883

1,082✔
884
  /**
1,082✔
885
   * Compatibility for ESLint's SourceCode API
1,082✔
886
   * @deprecated JSONC does not have scopes
1,082✔
887
   */
1,082✔
888
  public get scopeManager(): Scope.ScopeManager | null {
1,082✔
889
    return {
×
890
      scopes: [],
×
891
      globalScope: createFakeGlobalScope(this.ast),
×
892
      acquire: (node) => {
×
893
        if (node.type === "Program") {
×
894
          return createFakeGlobalScope(this.ast);
×
895
        }
×
896
        return null;
×
897
      },
×
898
      getDeclaredVariables: () => [],
×
899
      addGlobals: () => {
×
900
        // noop
×
901
      },
×
902
    };
×
903
  }
×
904

1,082✔
905
  /**
1,082✔
906
   * Compatibility for ESLint's SourceCode API
1,082✔
907
   * @deprecated
1,082✔
908
   */
1,082✔
909
  public isSpaceBetweenTokens(
1,082✔
910
    first: JSONCTokenOrComment,
1✔
911
    second: JSONCTokenOrComment,
1✔
912
  ): boolean {
1✔
913
    return this.isSpaceBetween(first, second);
1✔
914
  }
1✔
915

1,082✔
916
  private _getChildren(node: AST.JSONNode) {
1,082✔
917
    const keys = this.visitorKeys[node.type] || [];
493!
918
    const children: AST.JSONNode[] = [];
493✔
919
    for (const key of keys) {
493✔
920
      const value = (node as unknown as Record<string, unknown>)[key];
533✔
921
      if (Array.isArray(value)) {
533✔
922
        for (const element of value) {
229✔
923
          if (isNode(element)) {
325✔
924
            children.push(element);
325✔
925
          }
325✔
926
        }
325✔
927
      } else if (isNode(value)) {
533✔
928
        children.push(value);
304✔
929
      }
304✔
930
    }
533✔
931
    return children;
493✔
932
  }
493✔
933
}
1,082✔
934

1✔
935
/**
1✔
936
 * Determines whether the given value is a JSONC AST node.
1✔
937
 */
1✔
938
function isNode(value: unknown): value is AST.JSONNode {
629✔
939
  return (
629✔
940
    typeof value === "object" &&
629✔
941
    value !== null &&
629✔
942
    typeof (value as Record<string, unknown>).type === "string" &&
629✔
943
    Array.isArray((value as Record<string, unknown>).range) &&
629✔
944
    Boolean((value as Record<string, unknown>).loc) &&
629✔
945
    typeof (value as Record<string, unknown>).loc === "object"
629✔
946
  );
629✔
947
}
629✔
948

1✔
949
/**
1✔
950
 * Creates a fake global scope for JSONC files.
1✔
951
 * @deprecated JSONC does not have scopes
1✔
952
 */
1✔
953
function createFakeGlobalScope(node: AST.JSONProgram): Scope.Scope {
39✔
954
  const fakeGlobalScope: Scope.Scope = {
39✔
955
    type: "global",
39✔
956
    block: node as never,
39✔
957
    set: new Map(),
39✔
958
    through: [],
39✔
959
    childScopes: [],
39✔
960
    variableScope: null as never,
39✔
961
    variables: [],
39✔
962
    references: [],
39✔
963
    functionExpressionScope: false,
39✔
964
    isStrict: false,
39✔
965
    upper: null,
39✔
966
    implicit: {
39✔
967
      variables: [],
39✔
968
      set: new Map(),
39✔
969
    },
39✔
970
  };
39✔
971
  fakeGlobalScope.variableScope = fakeGlobalScope;
39✔
972
  return fakeGlobalScope;
39✔
973
}
39✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc