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

ota-meshi / eslint-plugin-yml / 22098652669

17 Feb 2026 12:34PM UTC coverage: 89.86% (-0.05%) from 89.913%
22098652669

push

github

web-flow
feat: use `@ota-meshi/ast-token-store` (#573)

1538 of 1786 branches covered (86.11%)

Branch coverage included in aggregate %.

12 of 20 new or added lines in 1 file covered. (60.0%)

2769 of 3007 relevant lines covered (92.09%)

853.35 hits per line

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

80.0
/src/language/yaml-source-code.ts
1
/**
16,129!
2
 * @fileoverview The YAMLSourceCode class.
3
 */
4

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

29
//-----------------------------------------------------------------------------
30
// Helpers
31
//-----------------------------------------------------------------------------
32

33
const commentParser = new ConfigCommentParser();
1✔
34

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

42
//-----------------------------------------------------------------------------
43
// Types
44
//-----------------------------------------------------------------------------
45
/**
46
 * YAML-specific syntax element type
47
 */
48
export type YAMLSyntaxElement = AST.YAMLNode | AST.Token | AST.Comment;
49
export type YAMLToken = AST.Token | AST.Comment;
50

51
/**
52
 * YAML Source Code Object
53
 */
54
export class YAMLSourceCode extends TextSourceCodeBase<{
55
  LangOptions: Record<never, never>;
56
  RootNode: AST.YAMLProgram;
57
  SyntaxElementWithLoc: YAMLSyntaxElement;
58
  ConfigNode: AST.Comment;
59
}> {
1✔
60
  public readonly hasBOM: boolean;
61

62
  public readonly parserServices: { isYAML?: boolean; parseError?: unknown };
63

64
  public readonly visitorKeys: Record<string, string[]>;
65

66
  private readonly tokenStore: TokenStore<AST.YAMLNode, AST.Token, AST.Comment>;
67

68
  #steps: TraversalStep[] | null = null;
5,369✔
69

70
  #cacheTokensAndComments: (AST.Token | AST.Comment)[] | null = null;
5,369✔
71

72
  #inlineConfigComments: AST.Comment[] | null = null;
5,369✔
73

74
  /**
75
   * Creates a new instance.
76
   */
77
  public constructor(config: {
78
    text: string;
79
    ast: AST.YAMLProgram;
80
    hasBOM: boolean;
81
    parserServices: { isYAML: boolean; parseError?: unknown };
82
    visitorKeys?: Record<string, string[]> | null | undefined;
83
  }) {
84
    super({
5,369✔
85
      ast: config.ast,
86
      text: config.text,
87
    });
88
    this.hasBOM = Boolean(config.hasBOM);
5,369✔
89
    this.parserServices = config.parserServices;
5,369✔
90
    this.visitorKeys = config.visitorKeys || {};
5,369!
91
    this.tokenStore = new TokenStore<AST.YAMLNode, AST.Token, AST.Comment>({
5,369✔
92
      tokens: [...config.ast.tokens, ...config.ast.comments],
93
      isComment: (token): token is AST.Comment => token.type === "Block",
63,697✔
94
    });
95
  }
96

97
  public traverse(): Iterable<TraversalStep> {
98
    if (this.#steps != null) {
5,344!
99
      return this.#steps;
×
100
    }
101

102
    const steps: (VisitNodeStep | CallMethodStep)[] = [];
5,344✔
103
    this.#steps = steps;
5,344✔
104

105
    const root = this.ast;
5,344✔
106
    steps.push(
5,344✔
107
      // ESLint core rule compatibility: onCodePathStart is called with two arguments.
108
      new CallMethodStep({
109
        target: "onCodePathStart",
110
        args: [{}, root],
111
      }),
112
    );
5,344✔
113

114
    traverseNodes(root, {
115
      enterNode(n) {
116
        steps.push(
94,861✔
117
          new VisitNodeStep({
118
            target: n,
119
            phase: 1,
120
            args: [n],
121
          }),
122
        );
123
      },
124
      leaveNode(n) {
125
        steps.push(
94,861✔
126
          new VisitNodeStep({
127
            target: n,
128
            phase: 2,
129
            args: [n],
130
          }),
131
        );
132
      },
133
    });
134

135
    steps.push(
5,344✔
136
      // ESLint core rule compatibility: onCodePathEnd is called with two arguments.
137
      new CallMethodStep({
138
        target: "onCodePathEnd",
139
        args: [{}, root],
140
      }),
141
    );
142
    return steps;
5,344✔
143
  }
144

145
  /**
146
   * Gets all tokens and comments.
147
   */
148
  public get tokensAndComments(): YAMLToken[] {
149
    return (this.#cacheTokensAndComments ??= [
11✔
150
      ...this.ast.tokens,
151
      ...this.ast.comments,
152
    ].sort((a, b) => a.range[0] - b.range[0]));
447✔
153
  }
154

155
  public getLines(): string[] {
156
    return this.lines;
2,160✔
157
  }
158

159
  public getAllComments(): AST.Comment[] {
160
    return this.tokenStore.getAllComments();
38✔
161
  }
162

163
  /**
164
   * Returns an array of all inline configuration nodes found in the source code.
165
   * This includes eslint-disable, eslint-enable, eslint-disable-line,
166
   * eslint-disable-next-line, and eslint (for inline config) comments.
167
   */
168
  public getInlineConfigNodes(): AST.Comment[] {
169
    if (!this.#inlineConfigComments) {
10,691✔
170
      this.#inlineConfigComments = this.ast.comments.filter((comment) =>
5,347✔
171
        INLINE_CONFIG.test(comment.value),
8,408✔
172
      );
173
    }
174

175
    return this.#inlineConfigComments;
10,691✔
176
  }
177

178
  /**
179
   * Returns directives that enable or disable rules along with any problems
180
   * encountered while parsing the directives.
181
   */
182
  public getDisableDirectives(): {
183
    directives: Directive[];
184
    problems: FileProblem[];
185
  } {
186
    const problems: FileProblem[] = [];
5,344✔
187
    const directives: Directive[] = [];
5,344✔
188

189
    this.getInlineConfigNodes().forEach((comment) => {
5,344✔
190
      const directive = commentParser.parseDirective(comment.value);
22✔
191

192
      if (!directive) {
22!
193
        return;
×
194
      }
195

196
      const { label, value, justification } = directive;
22✔
197

198
      // `eslint-disable-line` directives are not allowed to span multiple lines
199
      // as it would be confusing to which lines they apply
200
      if (
22!
201
        label === "eslint-disable-line" &&
25✔
202
        comment.loc.start.line !== comment.loc.end.line
203
      ) {
204
        const message = `${label} comment should not span multiple lines.`;
×
205

206
        problems.push({
×
207
          ruleId: null,
208
          message,
209
          loc: comment.loc,
210
        });
211
        return;
×
212
      }
213

214
      switch (label) {
22✔
215
        case "eslint-disable":
216
        case "eslint-enable":
217
        case "eslint-disable-next-line":
218
        case "eslint-disable-line": {
219
          const directiveType = label.slice("eslint-".length);
16✔
220

221
          directives.push(
16✔
222
            new DirectiveImpl({
223
              type: directiveType as DirectiveType,
224
              node: comment,
225
              value,
226
              justification,
227
            }),
228
          );
229
          break;
16✔
230
        }
231
        // no default
232
      }
233
    });
234

235
    return { problems, directives };
5,344✔
236
  }
237

238
  /**
239
   * Returns inline rule configurations along with any problems
240
   * encountered while parsing the configurations.
241
   */
242
  public applyInlineConfig(): {
243
    configs: { config: { rules: RulesConfig }; loc: AST.SourceLocation }[];
244
    problems: FileProblem[];
245
  } {
246
    const problems: FileProblem[] = [];
5,344✔
247
    const configs: {
248
      config: { rules: RulesConfig };
249
      loc: AST.SourceLocation;
250
    }[] = [];
5,344✔
251

252
    this.getInlineConfigNodes().forEach((comment) => {
5,344✔
253
      const directive = commentParser.parseDirective(comment.value);
22✔
254

255
      if (!directive) {
22!
256
        return;
×
257
      }
258

259
      const { label, value } = directive;
22✔
260

261
      if (label === "eslint") {
22✔
262
        const parseResult = commentParser.parseJSONLikeConfig(value);
6✔
263

264
        if (parseResult.ok) {
6✔
265
          configs.push({
5✔
266
            config: {
267
              rules: parseResult.config,
268
            },
269
            loc: comment.loc,
270
          });
271
        } else {
272
          problems.push({
1✔
273
            ruleId: null,
274
            message: parseResult.error.message,
275
            loc: comment.loc,
276
          });
277
        }
278
      }
279
    });
280

281
    return { configs, problems };
5,344✔
282
  }
283

284
  public getNodeByRangeIndex(index: number): AST.YAMLNode | null {
285
    let node = find([this.ast]);
15✔
286
    if (!node) return null;
15✔
287
    while (true) {
14✔
288
      const child = find(this._getChildren(node));
79✔
289
      if (!child) return node;
79✔
290
      node = child;
65✔
291
    }
292

293
    /**
294
     * Finds a node that contains the given index.
295
     */
296
    function find(nodes: AST.YAMLNode[]) {
×
297
      for (const node of nodes) {
94✔
298
        if (node.range[0] <= index && index < node.range[1]) {
118✔
299
          return node;
79✔
300
        }
301
      }
302
      return null;
15✔
303
    }
304
  }
305

306
  /**
307
   * Gets the first token of the given node.
308
   */
309
  public getFirstToken(node: YAMLSyntaxElement): AST.Token;
310

311
  /**
312
   * Gets the first token of the given node with options.
313
   */
314
  public getFirstToken(
315
    node: YAMLSyntaxElement,
316
    options: CursorWithSkipOptionsWithoutFilter,
317
  ): AST.Token | null;
318

319
  /**
320
   * Gets the first token of the given node with filter options.
321
   */
322
  public getFirstToken<R extends AST.Token>(
323
    node: YAMLSyntaxElement,
324
    options: CursorWithSkipOptionsWithFilter<AST.Token, R>,
325
  ): R | null;
326

327
  /**
328
   * Gets the first token of the given node with comment options.
329
   */
330
  public getFirstToken<R extends AST.Token | AST.Comment>(
331
    node: YAMLSyntaxElement,
332
    options: CursorWithSkipOptionsWithComment<AST.Token, AST.Comment, R>,
333
  ): R | null;
334

335
  public getFirstToken(
336
    node: YAMLSyntaxElement,
337
    options?:
338
      | CursorWithSkipOptionsWithoutFilter
339
      | CursorWithSkipOptionsWithFilter<AST.Token>
340
      | CursorWithSkipOptionsWithComment<AST.Token, AST.Comment>,
341
  ): AST.Token | AST.Comment | null {
342
    return this.tokenStore.getFirstToken(node, options as never);
31,222✔
343
  }
344

345
  /**
346
   * Gets the first tokens of the given node.
347
   */
348
  public getFirstTokens(
349
    node: YAMLSyntaxElement,
350
    options?: CursorWithCountOptionsWithoutFilter,
351
  ): AST.Token[];
352

353
  /**
354
   * Gets the first tokens of the given node with filter options.
355
   */
356
  public getFirstTokens<R extends AST.Token>(
357
    node: YAMLSyntaxElement,
358
    options: CursorWithCountOptionsWithFilter<AST.Token, R>,
359
  ): R[];
360

361
  /**
362
   * Gets the first tokens of the given node with comment options.
363
   */
364
  public getFirstTokens<R extends AST.Token | AST.Comment>(
365
    node: YAMLSyntaxElement,
366
    options: CursorWithCountOptionsWithComment<AST.Token, AST.Comment, R>,
367
  ): R[];
368

369
  public getFirstTokens(
370
    node: YAMLSyntaxElement,
371
    options?:
372
      | CursorWithCountOptionsWithoutFilter
373
      | CursorWithCountOptionsWithFilter<AST.Token>
374
      | CursorWithCountOptionsWithComment<AST.Token, AST.Comment>,
375
  ): (AST.Token | AST.Comment)[] {
NEW
376
    return this.tokenStore.getFirstTokens(node, options as never);
×
377
  }
378

379
  /**
380
   * Gets the last token of the given node.
381
   */
382
  public getLastToken(node: YAMLSyntaxElement): AST.Token;
383

384
  /**
385
   * Gets the last token of the given node with options.
386
   */
387
  public getLastToken(
388
    node: YAMLSyntaxElement,
389
    options: CursorWithSkipOptionsWithoutFilter,
390
  ): AST.Token | null;
391

392
  /**
393
   * Gets the last token of the given node with filter options.
394
   */
395
  public getLastToken<R extends AST.Token>(
396
    node: YAMLSyntaxElement,
397
    options: CursorWithSkipOptionsWithFilter<AST.Token, R>,
398
  ): R | null;
399

400
  /**
401
   * Gets the last token of the given node with comment options.
402
   */
403
  public getLastToken<R extends AST.Token | AST.Comment>(
404
    node: YAMLSyntaxElement,
405
    options: CursorWithSkipOptionsWithComment<AST.Token, AST.Comment, R>,
406
  ): R | null;
407

408
  public getLastToken(
409
    node: YAMLSyntaxElement,
410
    options?:
411
      | CursorWithSkipOptionsWithoutFilter
412
      | CursorWithSkipOptionsWithFilter<AST.Token>
413
      | CursorWithSkipOptionsWithComment<AST.Token, AST.Comment>,
414
  ): (AST.Token | AST.Comment) | null {
415
    return this.tokenStore.getLastToken(node, options as never);
8,227✔
416
  }
417

418
  /**
419
   * Get the last tokens of the given node.
420
   */
421
  public getLastTokens(
422
    node: YAMLSyntaxElement,
423
    options?: CursorWithCountOptionsWithoutFilter,
424
  ): AST.Token[];
425

426
  /**
427
   * Get the last tokens of the given node with filter options.
428
   */
429
  public getLastTokens<R extends AST.Token>(
430
    node: YAMLSyntaxElement,
431
    options: CursorWithCountOptionsWithFilter<AST.Token, R>,
432
  ): R[];
433

434
  /**
435
   * Get the last tokens of the given node with comment options.
436
   */
437
  public getLastTokens<R extends AST.Token | AST.Comment>(
438
    node: YAMLSyntaxElement,
439
    options: CursorWithCountOptionsWithComment<AST.Token, AST.Comment, R>,
440
  ): R[];
441

442
  public getLastTokens(
443
    node: YAMLSyntaxElement,
444
    options?:
445
      | CursorWithCountOptionsWithoutFilter
446
      | CursorWithCountOptionsWithFilter<AST.Token>
447
      | CursorWithCountOptionsWithComment<AST.Token, AST.Comment>,
448
  ): (AST.Token | AST.Comment)[] {
NEW
449
    return this.tokenStore.getLastTokens(node, options as never);
×
450
  }
451

452
  /**
453
   * Gets the token that precedes a given node or token.
454
   */
455
  public getTokenBefore(
456
    node: YAMLSyntaxElement,
457
    options?: CursorWithSkipOptionsWithoutFilter,
458
  ): AST.Token | null;
459

460
  /**
461
   * Gets the token that precedes a given node or token with filter options.
462
   */
463
  public getTokenBefore<R extends AST.Token>(
464
    node: YAMLSyntaxElement,
465
    options: CursorWithSkipOptionsWithFilter<AST.Token, R>,
466
  ): R | null;
467

468
  /**
469
   * Gets the token that precedes a given node or token with comment options.
470
   */
471
  public getTokenBefore<R extends AST.Token | AST.Comment>(
472
    node: YAMLSyntaxElement,
473
    options: CursorWithSkipOptionsWithComment<AST.Token, AST.Comment, R>,
474
  ): R | null;
475

476
  public getTokenBefore(
477
    node: YAMLSyntaxElement,
478
    options?:
479
      | CursorWithSkipOptionsWithoutFilter
480
      | CursorWithSkipOptionsWithFilter<AST.Token>
481
      | CursorWithSkipOptionsWithComment<AST.Token, AST.Comment>,
482
  ): AST.Token | AST.Comment | null {
483
    return this.tokenStore.getTokenBefore(node, options as never);
22,077✔
484
  }
485

486
  /**
487
   * Gets the `count` tokens that precedes a given node or token.
488
   */
489
  public getTokensBefore(
490
    node: YAMLSyntaxElement,
491
    options?: CursorWithCountOptionsWithoutFilter,
492
  ): AST.Token[];
493

494
  /**
495
   * Gets the `count` tokens that precedes a given node or token with filter options.
496
   */
497
  public getTokensBefore<R extends AST.Token>(
498
    node: YAMLSyntaxElement,
499
    options: CursorWithCountOptionsWithFilter<AST.Token, R>,
500
  ): R[];
501

502
  /**
503
   * Gets the `count` tokens that precedes a given node or token with comment options.
504
   */
505
  public getTokensBefore<R extends AST.Token | AST.Comment>(
506
    node: YAMLSyntaxElement,
507
    options: CursorWithCountOptionsWithComment<AST.Token, AST.Comment, R>,
508
  ): R[];
509

510
  public getTokensBefore(
511
    node: YAMLSyntaxElement,
512
    options?:
513
      | CursorWithCountOptionsWithoutFilter
514
      | CursorWithCountOptionsWithFilter<AST.Token>
515
      | CursorWithCountOptionsWithComment<AST.Token, AST.Comment>,
516
  ): (AST.Token | AST.Comment)[] {
517
    return this.tokenStore.getTokensBefore(node, options as never);
96✔
518
  }
519

520
  /**
521
   * Gets the token that follows a given node or token.
522
   */
523
  public getTokenAfter(
524
    node: YAMLSyntaxElement,
525
    options?: CursorWithSkipOptionsWithoutFilter,
526
  ): AST.Token | null;
527

528
  /**
529
   * Gets the token that follows a given node or token with filter options.
530
   */
531
  public getTokenAfter<R extends AST.Token>(
532
    node: YAMLSyntaxElement,
533
    options: CursorWithSkipOptionsWithFilter<AST.Token, R>,
534
  ): R | null;
535

536
  /**
537
   * Gets the token that follows a given node or token with comment options.
538
   */
539
  public getTokenAfter<R extends AST.Token | AST.Comment>(
540
    node: YAMLSyntaxElement,
541
    options: CursorWithSkipOptionsWithComment<AST.Token, AST.Comment, R>,
542
  ): R | null;
543

544
  public getTokenAfter(
545
    node: YAMLSyntaxElement,
546
    options?:
547
      | CursorWithSkipOptionsWithoutFilter
548
      | CursorWithSkipOptionsWithFilter<AST.Token>
549
      | CursorWithSkipOptionsWithComment<AST.Token, AST.Comment>,
550
  ): AST.Token | AST.Comment | null {
551
    return this.tokenStore.getTokenAfter(node, options as never);
26,424✔
552
  }
553

554
  /**
555
   * Gets the `count` tokens that follows a given node or token.
556
   */
557
  public getTokensAfter(
558
    node: YAMLSyntaxElement,
559
    options?: CursorWithCountOptionsWithoutFilter,
560
  ): AST.Token[];
561

562
  /**
563
   * Gets the `count` tokens that follows a given node or token with filter options.
564
   */
565
  public getTokensAfter<R extends AST.Token>(
566
    node: YAMLSyntaxElement,
567
    options: CursorWithCountOptionsWithFilter<AST.Token, R>,
568
  ): R[];
569

570
  /**
571
   * Gets the `count` tokens that follows a given node or token with comment options.
572
   */
573
  public getTokensAfter<R extends AST.Token | AST.Comment>(
574
    node: YAMLSyntaxElement,
575
    options: CursorWithCountOptionsWithComment<AST.Token, AST.Comment, R>,
576
  ): R[];
577

578
  public getTokensAfter(
579
    node: YAMLSyntaxElement,
580
    options?:
581
      | CursorWithCountOptionsWithoutFilter
582
      | CursorWithCountOptionsWithFilter<AST.Token>
583
      | CursorWithCountOptionsWithComment<AST.Token, AST.Comment>,
584
  ): (AST.Token | AST.Comment)[] {
NEW
585
    return this.tokenStore.getTokensAfter(node, options as never);
×
586
  }
587

588
  /**
589
   * Gets the first token between two non-overlapping nodes.
590
   */
591
  public getFirstTokenBetween(
592
    left: YAMLSyntaxElement,
593
    right: YAMLSyntaxElement,
594
    options?: CursorWithSkipOptionsWithoutFilter,
595
  ): AST.Token | null;
596

597
  /**
598
   * Gets the first token between two non-overlapping nodes with filter options.
599
   */
600
  public getFirstTokenBetween<R extends AST.Token>(
601
    left: YAMLSyntaxElement,
602
    right: YAMLSyntaxElement,
603
    options: CursorWithSkipOptionsWithFilter<AST.Token, R>,
604
  ): R | null;
605

606
  /**
607
   * Gets the first token between two non-overlapping nodes with comment options.
608
   */
609
  public getFirstTokenBetween<R extends AST.Token | AST.Comment>(
610
    left: YAMLSyntaxElement,
611
    right: YAMLSyntaxElement,
612
    options: CursorWithSkipOptionsWithComment<AST.Token, AST.Comment, R>,
613
  ): R | null;
614

615
  public getFirstTokenBetween(
616
    left: YAMLSyntaxElement,
617
    right: YAMLSyntaxElement,
618
    options?:
619
      | CursorWithSkipOptionsWithoutFilter
620
      | CursorWithSkipOptionsWithFilter<AST.Token>
621
      | CursorWithSkipOptionsWithComment<AST.Token, AST.Comment>,
622
  ): AST.Token | AST.Comment | null {
NEW
623
    return this.tokenStore.getFirstTokenBetween(left, right, options as never);
×
624
  }
625

626
  /**
627
   * Gets the first tokens between two non-overlapping nodes.
628
   */
629
  public getFirstTokensBetween(
630
    left: YAMLSyntaxElement,
631
    right: YAMLSyntaxElement,
632
    options?: CursorWithCountOptionsWithoutFilter,
633
  ): AST.Token[];
634

635
  /**
636
   * Gets the first tokens between two non-overlapping nodes with filter options.
637
   */
638
  public getFirstTokensBetween<R extends AST.Token>(
639
    left: YAMLSyntaxElement,
640
    right: YAMLSyntaxElement,
641
    options: CursorWithCountOptionsWithFilter<AST.Token, R>,
642
  ): R[];
643

644
  /**
645
   * Gets the first tokens between two non-overlapping nodes with comment options.
646
   */
647
  public getFirstTokensBetween<R extends AST.Token | AST.Comment>(
648
    left: YAMLSyntaxElement,
649
    right: YAMLSyntaxElement,
650
    options: CursorWithCountOptionsWithComment<AST.Token, AST.Comment, R>,
651
  ): R[];
652

653
  public getFirstTokensBetween(
654
    left: YAMLSyntaxElement,
655
    right: YAMLSyntaxElement,
656
    options?:
657
      | CursorWithCountOptionsWithoutFilter
658
      | CursorWithCountOptionsWithFilter<AST.Token>
659
      | CursorWithCountOptionsWithComment<AST.Token, AST.Comment>,
660
  ): (AST.Token | AST.Comment)[] {
NEW
661
    return this.tokenStore.getFirstTokensBetween(left, right, options as never);
×
662
  }
663

664
  /**
665
   * Gets the last token between two non-overlapping nodes.
666
   */
667
  public getLastTokenBetween(
668
    left: YAMLSyntaxElement,
669
    right: YAMLSyntaxElement,
670
    options?: CursorWithSkipOptionsWithoutFilter,
671
  ): AST.Token | null;
672

673
  /**
674
   * Gets the last token between two non-overlapping nodes with filter options.
675
   */
676
  public getLastTokenBetween<R extends AST.Token>(
677
    left: YAMLSyntaxElement,
678
    right: YAMLSyntaxElement,
679
    options: CursorWithSkipOptionsWithFilter<AST.Token, R>,
680
  ): R | null;
681

682
  /**
683
   * Gets the last token between two non-overlapping nodes with comment options.
684
   */
685
  public getLastTokenBetween<R extends AST.Token | AST.Comment>(
686
    left: YAMLSyntaxElement,
687
    right: YAMLSyntaxElement,
688
    options: CursorWithSkipOptionsWithComment<AST.Token, AST.Comment, R>,
689
  ): R | null;
690

691
  public getLastTokenBetween(
692
    left: YAMLSyntaxElement,
693
    right: YAMLSyntaxElement,
694
    options?:
695
      | CursorWithSkipOptionsWithoutFilter
696
      | CursorWithSkipOptionsWithFilter<AST.Token>
697
      | CursorWithSkipOptionsWithComment<AST.Token, AST.Comment>,
698
  ): AST.Token | AST.Comment | null {
NEW
699
    return this.tokenStore.getLastTokenBetween(left, right, options as never);
×
700
  }
701

702
  /**
703
   * Gets the last tokens between two non-overlapping nodes.
704
   */
705
  public getLastTokensBetween(
706
    left: YAMLSyntaxElement,
707
    right: YAMLSyntaxElement,
708
    options?: CursorWithCountOptionsWithoutFilter,
709
  ): AST.Token[];
710

711
  /**
712
   * Gets the last tokens between two non-overlapping nodes with filter options.
713
   */
714
  public getLastTokensBetween<R extends AST.Token>(
715
    left: YAMLSyntaxElement,
716
    right: YAMLSyntaxElement,
717
    options: CursorWithCountOptionsWithFilter<AST.Token, R>,
718
  ): R[];
719

720
  /**
721
   * Gets the last tokens between two non-overlapping nodes with comment options.
722
   */
723
  public getLastTokensBetween<R extends AST.Token | AST.Comment>(
724
    left: YAMLSyntaxElement,
725
    right: YAMLSyntaxElement,
726
    options: CursorWithCountOptionsWithComment<AST.Token, AST.Comment, R>,
727
  ): R[];
728

729
  public getLastTokensBetween(
730
    left: YAMLSyntaxElement,
731
    right: YAMLSyntaxElement,
732
    options?:
733
      | CursorWithCountOptionsWithoutFilter
734
      | CursorWithCountOptionsWithFilter<AST.Token>
735
      | CursorWithCountOptionsWithComment<AST.Token, AST.Comment>,
736
  ): (AST.Token | AST.Comment)[] {
NEW
737
    return this.tokenStore.getLastTokensBetween(left, right, options as never);
×
738
  }
739

740
  /**
741
   * Gets all tokens that are related to the given node.
742
   */
743
  public getTokens(
744
    node: YAMLSyntaxElement,
745
    options?: CursorWithCountOptionsWithoutFilter,
746
  ): AST.Token[];
747

748
  /**
749
   * Gets all tokens that are related to the given node with filter options.
750
   */
751
  public getTokens<R extends AST.Token>(
752
    node: YAMLSyntaxElement,
753
    options: CursorWithCountOptionsWithFilter<AST.Token, R>,
754
  ): R[];
755

756
  /**
757
   * Gets all tokens that are related to the given node with comment options.
758
   */
759
  public getTokens<R extends AST.Token | AST.Comment>(
760
    node: YAMLSyntaxElement,
761
    options: CursorWithCountOptionsWithComment<AST.Token, AST.Comment, R>,
762
  ): R[];
763

764
  public getTokens(
765
    node: YAMLSyntaxElement,
766
    options?:
767
      | CursorWithCountOptionsWithoutFilter
768
      | CursorWithCountOptionsWithFilter<AST.Token>
769
      | CursorWithCountOptionsWithComment<AST.Token, AST.Comment>,
770
  ): (AST.Token | AST.Comment)[] {
771
    return this.tokenStore.getTokens(node, options as never);
196✔
772
  }
773

774
  /**
775
   * Gets all of the tokens between two non-overlapping nodes.
776
   */
777
  public getTokensBetween(
778
    left: YAMLSyntaxElement,
779
    right: YAMLSyntaxElement,
780
    options?: CursorWithCountOptionsWithoutFilter,
781
  ): AST.Token[];
782

783
  /**
784
   * Gets all of the tokens between two non-overlapping nodes with filter options.
785
   */
786
  public getTokensBetween<R extends AST.Token>(
787
    left: YAMLSyntaxElement,
788
    right: YAMLSyntaxElement,
789
    options: CursorWithCountOptionsWithFilter<AST.Token, R>,
790
  ): R[];
791

792
  /**
793
   * Gets all of the tokens between two non-overlapping nodes with comment options.
794
   */
795
  public getTokensBetween<R extends AST.Token | AST.Comment>(
796
    left: YAMLSyntaxElement,
797
    right: YAMLSyntaxElement,
798
    options: CursorWithCountOptionsWithComment<AST.Token, AST.Comment, R>,
799
  ): R[];
800

801
  public getTokensBetween(
802
    left: YAMLSyntaxElement,
803
    right: YAMLSyntaxElement,
804
    options?:
805
      | CursorWithCountOptionsWithoutFilter
806
      | CursorWithCountOptionsWithFilter<AST.Token>
807
      | CursorWithCountOptionsWithComment<AST.Token, AST.Comment>,
808
  ): (AST.Token | AST.Comment)[] {
809
    return this.tokenStore.getTokensBetween(left, right, options as never);
249✔
810
  }
811

812
  public getCommentsInside(nodeOrToken: YAMLSyntaxElement): AST.Comment[] {
NEW
813
    return this.tokenStore.getCommentsInside(nodeOrToken);
×
814
  }
815

816
  public getCommentsBefore(nodeOrToken: YAMLSyntaxElement): AST.Comment[] {
817
    return this.tokenStore.getCommentsBefore(nodeOrToken);
110✔
818
  }
819

820
  public getCommentsAfter(nodeOrToken: YAMLSyntaxElement): AST.Comment[] {
821
    return this.tokenStore.getCommentsAfter(nodeOrToken);
2✔
822
  }
823

824
  public isSpaceBetween(
825
    first: AST.Token | AST.Comment,
826
    second: AST.Token | AST.Comment,
827
  ): boolean {
828
    // Normalize order: ensure left comes before right
829
    const [left, right] =
830
      first.range[1] <= second.range[0] ? [first, second] : [second, first];
683!
831
    return this.tokenStore.isSpaceBetween(left, right);
683✔
832
  }
833

834
  /**
835
   * Compatibility for ESLint's SourceCode API
836
   * @deprecated YAML does not have scopes
837
   */
838
  public getScope(node?: AST.YAMLNode): Scope.Scope | null {
839
    if (node?.type !== "Program") {
39!
840
      return null;
×
841
    }
842
    return createFakeGlobalScope(this.ast);
39✔
843
  }
844

845
  /**
846
   * Compatibility for ESLint's SourceCode API
847
   * @deprecated YAML does not have scopes
848
   */
849
  public get scopeManager(): Scope.ScopeManager | null {
850
    return {
×
851
      scopes: [],
852
      globalScope: createFakeGlobalScope(this.ast),
853
      acquire: (node) => {
854
        if (node.type === "Program") {
×
855
          return createFakeGlobalScope(this.ast);
×
856
        }
857
        return null;
×
858
      },
859
      getDeclaredVariables: () => [],
×
860
      addGlobals: () => {
861
        // noop
862
      },
863
    };
864
  }
865

866
  /**
867
   * Compatibility for ESLint's SourceCode API
868
   * @deprecated
869
   */
870
  public isSpaceBetweenTokens(first: YAMLToken, second: YAMLToken): boolean {
871
    return this.isSpaceBetween(first, second);
×
872
  }
873

874
  private _getChildren(node: AST.YAMLNode) {
875
    const keys = this.visitorKeys[node.type] || [];
79!
876
    const children: AST.YAMLNode[] = [];
79✔
877
    for (const key of keys) {
79✔
878
      const value = (node as unknown as Record<string, unknown>)[key];
103✔
879
      if (Array.isArray(value)) {
103✔
880
        for (const element of value) {
55✔
881
          if (isNode(element)) {
61✔
882
            children.push(element);
61✔
883
          }
884
        }
885
      } else if (isNode(value)) {
48✔
886
        children.push(value);
48✔
887
      }
888
    }
889
    return children;
79✔
890
  }
891
}
892

893
/**
894
 * Determines whether the given value is a YAML AST node.
895
 */
896
function isNode(value: unknown): value is AST.YAMLNode {
1✔
897
  return (
109✔
898
    typeof value === "object" &&
654✔
899
    value !== null &&
900
    typeof (value as Record<string, unknown>).type === "string" &&
901
    Array.isArray((value as Record<string, unknown>).range) &&
902
    Boolean((value as Record<string, unknown>).loc) &&
903
    typeof (value as Record<string, unknown>).loc === "object"
904
  );
905
}
906

907
/**
908
 * Creates a fake global scope for YAML files.
909
 * @deprecated YAML does not have scopes
910
 */
911
function createFakeGlobalScope(node: AST.YAMLProgram): Scope.Scope {
2!
912
  const fakeGlobalScope: Scope.Scope = {
39✔
913
    type: "global",
914
    block: node as never,
915
    set: new Map(),
916
    through: [],
917
    childScopes: [],
918
    variableScope: null as never,
919
    variables: [],
920
    references: [],
921
    functionExpressionScope: false,
922
    isStrict: false,
923
    upper: null,
924
    implicit: {
925
      variables: [],
926
      set: new Map(),
927
    },
928
  };
929
  fakeGlobalScope.variableScope = fakeGlobalScope;
39✔
930
  return fakeGlobalScope;
39✔
931
}
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