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

ota-meshi / svelte-eslint-parser / 4581244271

01 Apr 2023 04:12AM UTC coverage: 90.693% (-0.1%) from 90.807%
4581244271

push

github

GitHub
feat: improved event handler type (#296)

880 of 1041 branches covered (84.53%)

Branch coverage included in aggregate %.

33 of 33 new or added lines in 4 files covered. (100.0%)

2053 of 2193 relevant lines covered (93.62%)

27060.16 hits per line

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

94.38
/src/context/script-let.ts
1
import type { ScopeManager, Scope } from "eslint-scope";
4,958✔
2
import type * as ESTree from "estree";
3
import type { TSESTree } from "@typescript-eslint/types";
4
import type { Context, ScriptsSourceCode } from ".";
5
import type {
6
  Comment,
7
  Locations,
8
  SvelteEachBlock,
9
  SvelteIfBlock,
10
  SvelteName,
11
  SvelteNode,
12
  Token,
13
} from "../ast";
14
import type { ESLintExtendedProgram } from "../parser";
15
import { getWithLoc } from "../parser/converts/common";
1✔
16
import {
17
  getInnermostScopeFromNode,
18
  getScopeFromNode,
19
  removeAllScopeAndVariableAndReference,
20
  removeReference,
21
  removeScope,
22
} from "../scope";
1✔
23
import { traverseNodes } from "../traverse";
1✔
24
import { UniqueIdGenerator } from "./unique";
1✔
25

26
type TSAsExpression = {
27
  type: "TSAsExpression";
28
  expression: ESTree.Expression;
29
  typeAnnotation: TSParenthesizedType | ESTree.Node;
30
};
31

32
// TS ESLint v4 Node
33
type TSParenthesizedType = {
34
  type: "TSParenthesizedType";
35
  typeAnnotation: ESTree.Node;
36
};
37

38
export type ScriptLetCallback<E extends ESTree.Node> = (
39
  es: E,
40
  options: ScriptLetCallbackOption
41
) => void;
42

43
export type ScriptLetCallbackOption = {
44
  getScope: (node: ESTree.Node) => Scope;
45
  getInnermostScope: (node: ESTree.Node) => Scope;
46
  registerNodeToScope: (node: any, scope: Scope) => void;
47
  scopeManager: ScopeManager;
48
  visitorKeys?: { [type: string]: string[] };
49
};
50
export type ScriptLetRestoreCallback = (
51
  node: ESTree.Node,
52
  tokens: Token[],
53
  comments: Comment[],
54
  options: ScriptLetRestoreCallbackOption
55
) => void;
56
type ScriptLetRestoreCallbackOption = {
57
  getScope: (node: ESTree.Node) => Scope;
58
  getInnermostScope: (node: ESTree.Node) => Scope;
59
  registerNodeToScope: (node: any, scope: Scope) => void;
60
  scopeManager: ScopeManager;
61
  visitorKeys?: { [type: string]: string[] };
62
  addPostProcess: (callback: () => void) => void;
63
};
64

65
/**
66
 * Get node range
67
 */
68
function getNodeRange(
69
  node:
70
    | ESTree.Node
71
    | {
72
        start: number;
73
        end: number;
74
        leadingComments?: Comment[];
75
        trailingComments?: Comment[];
76
      }
77
    | {
78
        range: [number, number];
79
        leadingComments?: Comment[];
80
        trailingComments?: Comment[];
81
      }
82
): [number, number] {
83
  let start = null;
16,190✔
84
  let end = null;
16,190✔
85
  if (node.leadingComments) {
16,190✔
86
    start = getWithLoc(node.leadingComments[0]).start;
28✔
87
  }
88
  if (node.trailingComments) {
16,190✔
89
    end = getWithLoc(
28✔
90
      node.trailingComments[node.trailingComments.length - 1]
91
    ).end;
92
  }
93

94
  const loc =
95
    "range" in node
16,190✔
96
      ? { start: node.range![0], end: node.range![1] }
97
      : getWithLoc(node);
98

99
  return [
16,190✔
100
    start ? Math.min(start, loc.start) : loc.start,
16,190✔
101
    end ? Math.max(end, loc.end) : loc.end,
16,190✔
102
  ];
103
}
104

105
type RestoreCallback = {
106
  start: number;
107
  end: number;
108
  callback: ScriptLetRestoreCallback;
109
};
110

111
type TypeGenHelper = { generateUniqueId: (base: string) => string };
112

113
type ObjectShorthandProperty = ESTree.Property & {
114
  key: ESTree.Identifier;
115
  value: ESTree.Identifier;
116
};
117

118
export type ScriptLetBlockParam = {
119
  node: ESTree.Pattern | SvelteName;
120
  parent: SvelteNode;
121
  typing: string;
122
  callback: (node: ESTree.Pattern, options: ScriptLetCallbackOption) => void;
123
};
124
/**
125
 * A class that handles script fragments.
126
 * The script fragment AST node remaps and connects to the original directive AST node.
127
 */
128
export class ScriptLetContext {
129
  private readonly script: ScriptsSourceCode;
130

131
  private readonly ctx: Context;
132

133
  private readonly restoreCallbacks: RestoreCallback[] = [];
4,955✔
134

135
  private readonly programRestoreCallbacks: ScriptLetRestoreCallback[] = [];
4,955✔
136

137
  private readonly closeScopeCallbacks: (() => void)[] = [];
4,955✔
138

139
  private readonly unique = new UniqueIdGenerator();
4,955✔
140

141
  public constructor(ctx: Context) {
142
    this.script = ctx.sourceCode.scripts;
4,955✔
143
    this.ctx = ctx;
4,955✔
144
  }
145

146
  public addExpression<E extends ESTree.Expression>(
147
    expression: E | SvelteName,
148
    parent: SvelteNode,
149
    typing?: string | null,
150
    ...callbacks: ScriptLetCallback<E>[]
151
  ): ScriptLetCallback<E>[] {
152
    const range = getNodeRange(expression);
13,204✔
153
    const part = this.ctx.code.slice(...range);
13,204✔
154
    const isTS = typing && this.ctx.isTypeScript();
13,204✔
155
    this.appendScript(
13,204✔
156
      `(${part})${isTS ? `as (${typing})` : ""};`,
13,204✔
157
      range[0] - 1,
158
      (st, tokens, comments, result) => {
159
        const exprSt = st as ESTree.ExpressionStatement;
13,103✔
160
        const tsAs: TSAsExpression | null = isTS
13,103✔
161
          ? (exprSt.expression as any)
162
          : null;
163
        const node: ESTree.Expression = tsAs?.expression || exprSt.expression;
13,103✔
164
        // Process for nodes
165
        for (const callback of callbacks) {
13,103✔
166
          callback(node as E, result);
15,662✔
167
        }
168

169
        if (isTS) {
13,091✔
170
          const blockNode =
171
            tsAs!.typeAnnotation.type === "TSParenthesizedType"
114!
172
              ? tsAs!.typeAnnotation.typeAnnotation
173
              : tsAs!.typeAnnotation;
174
          const targetScopes = [result.getScope(blockNode)];
114✔
175
          let targetBlockNode: TSESTree.Node | TSParenthesizedType =
176
            blockNode as any;
114✔
177
          while (
114✔
178
            targetBlockNode.type === "TSConditionalType" ||
300✔
179
            targetBlockNode.type === "TSParenthesizedType"
180
          ) {
181
            if (targetBlockNode.type === "TSParenthesizedType") {
72!
182
              targetBlockNode = targetBlockNode.typeAnnotation as any;
×
183
              continue;
×
184
            }
185
            // TSConditionalType's `falseType` may not be a child scope.
186
            const falseType: TSESTree.TypeNode = targetBlockNode.falseType;
72✔
187
            const falseTypeScope = result.getScope(falseType as any);
72✔
188
            if (!targetScopes.includes(falseTypeScope)) {
72!
189
              targetScopes.push(falseTypeScope);
×
190
            }
191
            targetBlockNode = falseType;
72✔
192
          }
193
          for (const scope of targetScopes) {
114✔
194
            removeScope(result.scopeManager, scope);
114✔
195
          }
196
          this.remapNodes(
114✔
197
            [
198
              {
199
                offset: range[0] - 1,
200
                range,
201
                newNode: node,
202
              },
203
            ],
204
            tokens,
205
            comments,
206
            result.visitorKeys
207
          );
208
        }
209

210
        (node as any).parent = parent;
13,091✔
211

212
        tokens.shift(); // (
13,091✔
213
        tokens.pop(); // )
13,091✔
214
        tokens.pop(); // ;
13,091✔
215

216
        // Disconnect the tree structure.
217
        exprSt.expression = null as never;
13,091✔
218
      }
219
    );
220
    return callbacks;
13,204✔
221
  }
222

223
  public addObjectShorthandProperty(
224
    identifier: SvelteName,
225
    parent: SvelteNode,
226
    ...callbacks: ScriptLetCallback<ObjectShorthandProperty>[]
227
  ): void {
228
    const range = getNodeRange(identifier);
155✔
229
    const part = this.ctx.code.slice(...range);
155✔
230
    this.appendScript(
155✔
231
      `({${part}});`,
232
      range[0] - 2,
233
      (st, tokens, _comments, result) => {
234
        const exprSt = st as ESTree.ExpressionStatement;
154✔
235
        const objectExpression: ESTree.ObjectExpression =
236
          exprSt.expression as ESTree.ObjectExpression;
154✔
237
        const node = objectExpression.properties[0] as ObjectShorthandProperty;
154✔
238
        // Process for nodes
239
        for (const callback of callbacks) {
154✔
240
          callback(node, result);
154✔
241
        }
242
        (node.key as any).parent = parent;
154✔
243
        (node.value as any).parent = parent;
154✔
244

245
        tokens.shift(); // (
154✔
246
        tokens.shift(); // {
154✔
247
        tokens.pop(); // }
154✔
248
        tokens.pop(); // )
154✔
249
        tokens.pop(); // ;
154✔
250

251
        // Disconnect the tree structure.
252
        exprSt.expression = null as never;
154✔
253
      }
254
    );
255
  }
256

257
  public addVariableDeclarator(
258
    expression: ESTree.AssignmentExpression,
259
    parent: SvelteNode,
260
    ...callbacks: ScriptLetCallback<ESTree.VariableDeclarator>[]
261
  ): ScriptLetCallback<ESTree.VariableDeclarator>[] {
262
    const range = getNodeRange(expression);
28✔
263
    const part = this.ctx.code.slice(...range);
28✔
264
    this.appendScript(
28✔
265
      `const ${part};`,
266
      range[0] - 6,
267
      (st, tokens, _comments, result) => {
268
        const decl = st as ESTree.VariableDeclaration;
28✔
269
        const node = decl.declarations[0];
28✔
270
        // Process for nodes
271
        for (const callback of callbacks) {
28✔
272
          callback(node, result);
28✔
273
        }
274

275
        const scope = result.getScope(decl);
28✔
276
        for (const variable of scope.variables) {
28✔
277
          for (const def of variable.defs) {
42✔
278
            if (def.parent === decl) {
42✔
279
              (def as any).parent = parent;
28✔
280
            }
281
          }
282
        }
283

284
        (node as any).parent = parent;
28✔
285

286
        tokens.shift(); // const
28✔
287
        tokens.pop(); // ;
28✔
288

289
        // Disconnect the tree structure.
290
        decl.declarations = [];
28✔
291
      }
292
    );
293
    return callbacks;
28✔
294
  }
295

296
  public nestIfBlock(
297
    expression: ESTree.Expression,
298
    ifBlock: SvelteIfBlock,
299
    callback: ScriptLetCallback<ESTree.Expression>
300
  ): void {
301
    const range = getNodeRange(expression);
738✔
302
    const part = this.ctx.code.slice(...range);
738✔
303
    const restore = this.appendScript(
738✔
304
      `if(${part}){`,
305
      range[0] - 3,
306
      (st, tokens, _comments, result) => {
307
        const ifSt = st as ESTree.IfStatement;
732✔
308
        const node = ifSt.test;
732✔
309
        const scope = result.getScope(ifSt.consequent);
732✔
310

311
        // Process for nodes
312
        callback(node, result);
732✔
313
        (node as any).parent = ifBlock;
732✔
314

315
        // Process for scope
316
        result.registerNodeToScope(ifBlock, scope);
732✔
317

318
        tokens.shift(); // if
732✔
319
        tokens.shift(); // (
732✔
320
        tokens.pop(); // )
732✔
321
        tokens.pop(); // {
732✔
322
        tokens.pop(); // }
732✔
323

324
        // Disconnect the tree structure.
325
        ifSt.test = null as never;
732✔
326
        ifSt.consequent = null as never;
732✔
327
      }
328
    );
329
    this.pushScope(restore, "}");
738✔
330
  }
331

332
  public nestEachBlock(
333
    expression: ESTree.Expression,
334
    context: ESTree.Pattern,
335
    indexRange: { start: number; end: number } | null,
336
    eachBlock: SvelteEachBlock,
337
    callback: (
338
      expr: ESTree.Expression,
339
      ctx: ESTree.Pattern,
340
      index: ESTree.Identifier | null
341
    ) => void
342
  ): void {
343
    const exprRange = getNodeRange(expression);
635✔
344
    const ctxRange = getNodeRange(context);
635✔
345
    let source = "Array.from(";
635✔
346
    const exprOffset = source.length;
635✔
347
    source += `${this.ctx.code.slice(...exprRange)}).forEach((`;
635✔
348
    const ctxOffset = source.length;
635✔
349
    source += this.ctx.code.slice(...ctxRange);
635✔
350
    let idxOffset: number | null = null;
635✔
351
    if (indexRange) {
635✔
352
      source += ",";
170✔
353
      idxOffset = source.length;
170✔
354
      source += this.ctx.code.slice(indexRange.start, indexRange.end);
170✔
355
    }
356
    source += ")=>{";
635✔
357
    const restore = this.appendScript(
635✔
358
      source,
359
      exprRange[0] - exprOffset,
360
      (st, tokens, comments, result) => {
361
        const expSt = st as ESTree.ExpressionStatement;
632✔
362
        const call = expSt.expression as ESTree.CallExpression;
632✔
363
        const fn = call.arguments[0] as ESTree.ArrowFunctionExpression;
632✔
364
        const callArrayFrom = (call.callee as ESTree.MemberExpression)
632✔
365
          .object as ESTree.CallExpression;
366
        const expr = callArrayFrom.arguments[0] as ESTree.Expression;
632✔
367
        const ctx = fn.params[0]!;
632✔
368
        const idx = (fn.params[1] ?? null) as ESTree.Identifier | null;
632✔
369
        const scope = result.getScope(fn.body);
632✔
370

371
        // Process for nodes
372
        callback(expr, ctx, idx);
632✔
373

374
        // Process for scope
375
        result.registerNodeToScope(eachBlock, scope);
632✔
376
        for (const v of scope.variables) {
632✔
377
          for (const def of v.defs) {
870✔
378
            if (def.node === fn) {
870✔
379
              def.node = eachBlock;
856✔
380
            }
381
          }
382
        }
383
        // remove Array reference
384
        const arrayId = (callArrayFrom.callee as ESTree.MemberExpression)
632✔
385
          .object;
386
        const ref = scope.upper!.references.find(
632✔
387
          (r) => r.identifier === arrayId
2,360✔
388
        );
389
        if (ref) {
632!
390
          removeReference(ref, scope.upper!);
632✔
391
        }
392

393
        (expr as any).parent = eachBlock;
632✔
394
        (ctx as any).parent = eachBlock;
632✔
395
        if (idx) {
632✔
396
          (idx as any).parent = eachBlock;
168✔
397
        }
398

399
        tokens.shift(); // Array
632✔
400
        tokens.shift(); // .
632✔
401
        tokens.shift(); // from
632✔
402
        tokens.shift(); // (
632✔
403

404
        tokens.pop(); // )
632✔
405
        tokens.pop(); // =>
632✔
406
        tokens.pop(); // {
632✔
407
        tokens.pop(); // }
632✔
408
        tokens.pop(); // )
632✔
409
        tokens.pop(); // ;
632✔
410

411
        const map = [
632✔
412
          {
413
            offset: exprOffset,
414
            range: exprRange,
415
            newNode: expr,
416
          },
417
          {
418
            offset: ctxOffset,
419
            range: ctxRange,
420
            newNode: ctx,
421
          },
422
        ];
423
        if (indexRange) {
632✔
424
          map.push({
168✔
425
            offset: idxOffset!,
426
            range: [indexRange.start, indexRange.end],
427
            newNode: idx!,
428
          });
429
        }
430
        this.remapNodes(map, tokens, comments, result.visitorKeys);
632✔
431

432
        // Disconnect the tree structure.
433
        expSt.expression = null as never;
632✔
434
      }
435
    );
436
    this.pushScope(restore, "});");
635✔
437
  }
438

439
  public nestBlock(
440
    block: SvelteNode,
441
    params?:
442
      | ScriptLetBlockParam[]
443
      | ((helper: TypeGenHelper | null) => {
444
          param: ScriptLetBlockParam;
445
          preparationScript?: string[];
446
        })
447
  ): void {
448
    if (!params) {
1,537✔
449
      const restore = this.appendScript(
784✔
450
        `{`,
451
        block.range[0],
452
        (st, tokens, _comments, result) => {
453
          const blockSt = st as ESTree.BlockStatement;
770✔
454

455
          // Process for scope
456
          const scope = result.getScope(blockSt);
770✔
457
          result.registerNodeToScope(block, scope);
770✔
458

459
          tokens.length = 0; // clear tokens
770✔
460

461
          // Disconnect the tree structure.
462
          blockSt.body = null as never;
770✔
463
        }
464
      );
465
      this.pushScope(restore, "}");
784✔
466
    } else {
467
      let resolvedParams;
468
      if (typeof params === "function") {
753✔
469
        if (this.ctx.isTypeScript()) {
366✔
470
          const generatedTypes = params({
226✔
471
            generateUniqueId: (base) => this.generateUniqueId(base),
361✔
472
          });
473
          resolvedParams = [generatedTypes.param];
226✔
474
          if (generatedTypes.preparationScript) {
226✔
475
            for (const preparationScript of generatedTypes.preparationScript) {
211✔
476
              this.appendScriptWithoutOffset(
361✔
477
                preparationScript,
478
                (node, tokens, comments, result) => {
479
                  tokens.length = 0;
337✔
480
                  comments.length = 0;
337✔
481
                  removeAllScopeAndVariableAndReference(node, result);
337✔
482
                }
483
              );
484
            }
485
          }
486
        } else {
487
          const generatedTypes = params(null);
140✔
488
          resolvedParams = [generatedTypes.param];
140✔
489
        }
490
      } else {
491
        resolvedParams = params;
387✔
492
      }
493
      const sortedParams = [...resolvedParams]
753✔
494
        .map((d) => {
495
          return {
795✔
496
            ...d,
497
            range: getNodeRange(d.node),
498
          };
499
        })
500
        .sort((a, b) => {
501
          const [pA] = a.range;
42✔
502
          const [pB] = b.range;
42✔
503
          return pA - pB;
42✔
504
        });
505

506
      const maps: {
507
        index: number;
508
        offset: number;
509
        range: readonly [number, number];
510
      }[] = [];
753✔
511

512
      let source = "";
753✔
513
      for (let index = 0; index < sortedParams.length; index++) {
753✔
514
        const param = sortedParams[index];
795✔
515
        const range = param.range;
795✔
516
        if (source) {
795✔
517
          source += ",";
42✔
518
        }
519
        const offset = source.length + 1; /* ( */
795✔
520
        source += this.ctx.code.slice(...range);
795✔
521
        maps.push({
795✔
522
          index: maps.length,
523
          offset,
524
          range,
525
        });
526
        if (this.ctx.isTypeScript()) {
795✔
527
          source += `: (${param.typing})`;
361✔
528
        }
529
      }
530
      const restore = this.appendScript(
753✔
531
        `(${source})=>{`,
532
        maps[0].range[0] - 1,
533
        (st, tokens, comments, result) => {
534
          const exprSt = st as ESTree.ExpressionStatement;
729✔
535
          const fn = exprSt.expression as ESTree.ArrowFunctionExpression;
729✔
536
          const scope = result.getScope(fn.body);
729✔
537

538
          // Process for nodes
539
          for (let index = 0; index < fn.params.length; index++) {
729✔
540
            const p = fn.params[index];
771✔
541
            sortedParams[index].callback(p, result);
771✔
542
            if (this.ctx.isTypeScript()) {
771✔
543
              const typeAnnotation = (p as any).typeAnnotation;
337✔
544
              delete (p as any).typeAnnotation;
337✔
545

546
              p.range![1] = typeAnnotation.range[0];
337✔
547
              p.loc!.end = {
337✔
548
                line: typeAnnotation.loc.start.line,
549
                column: typeAnnotation.loc.start.column,
550
              };
551

552
              removeAllScopeAndVariableAndReference(typeAnnotation, result);
337✔
553
            }
554
            (p as any).parent = sortedParams[index].parent;
771✔
555
          }
556

557
          // Process for scope
558
          result.registerNodeToScope(block, scope);
729✔
559
          for (const v of scope.variables) {
729✔
560
            for (const def of v.defs) {
771✔
561
              if (def.node === fn) {
771!
562
                def.node = block;
771✔
563
              }
564
            }
565
          }
566

567
          tokens.shift(); // (
729✔
568
          tokens.pop(); // )
729✔
569
          tokens.pop(); // =>
729✔
570
          tokens.pop(); // {
729✔
571
          tokens.pop(); // }
729✔
572
          tokens.pop(); // ;
729✔
573

574
          this.remapNodes(
729✔
575
            maps.map((m) => {
576
              return {
771✔
577
                offset: m.offset,
578
                range: m.range,
579
                newNode: fn.params[m.index],
580
              };
581
            }),
582
            tokens,
583
            comments,
584
            result.visitorKeys
585
          );
586

587
          // Disconnect the tree structure.
588
          exprSt.expression = null as never;
729✔
589
        }
590
      );
591
      this.pushScope(restore, "};");
753✔
592
    }
593
  }
594

595
  public closeScope(): void {
596
    this.closeScopeCallbacks.pop()!();
2,910✔
597
  }
598

599
  public addProgramRestore(callback: ScriptLetRestoreCallback): void {
600
    this.programRestoreCallbacks.push(callback);
4,955✔
601
  }
602

603
  private appendScript(
604
    text: string,
605
    offset: number,
606
    callback: (
607
      node: ESTree.Node,
608
      tokens: Token[],
609
      comments: Comment[],
610
      options: ScriptLetCallbackOption
611
    ) => void
612
  ) {
613
    const resultCallback = this.appendScriptWithoutOffset(
16,297✔
614
      text,
615
      (node, tokens, comments, result) => {
616
        this.fixLocations(
16,148✔
617
          node,
618
          tokens,
619
          comments,
620
          offset - resultCallback.start,
621
          result.visitorKeys
622
        );
623
        callback(node, tokens, comments, result);
16,148✔
624
      }
625
    );
626
    return resultCallback;
16,297✔
627
  }
628

629
  private appendScriptWithoutOffset(
630
    text: string,
631
    callback: (
632
      node: ESTree.Node,
633
      tokens: Token[],
634
      comments: Comment[],
635
      options: ScriptLetCallbackOption
636
    ) => void
637
  ) {
638
    const { start: startOffset, end: endOffset } = this.script.addLet(text);
16,658✔
639

640
    const restoreCallback: RestoreCallback = {
16,658✔
641
      start: startOffset,
642
      end: endOffset,
643
      callback,
644
    };
645
    this.restoreCallbacks.push(restoreCallback);
16,658✔
646
    return restoreCallback;
16,658✔
647
  }
648

649
  private pushScope(restoreCallback: RestoreCallback, closeToken: string) {
650
    this.closeScopeCallbacks.push(() => {
2,910✔
651
      this.script.addLet(closeToken);
2,910✔
652
      restoreCallback.end = this.script.getCurrentVirtualCodeLength();
2,910✔
653
    });
654
  }
655

656
  /**
657
   * Restore AST nodes
658
   */
659
  public restore(result: ESLintExtendedProgram): void {
660
    const nodeToScope = getNodeToScope(result.scopeManager!);
4,928✔
661
    const postprocessList: (() => void)[] = [];
4,928✔
662

663
    const callbackOption: ScriptLetRestoreCallbackOption = {
4,928✔
664
      getScope,
665
      getInnermostScope,
666
      registerNodeToScope,
667
      scopeManager: result.scopeManager!,
668
      visitorKeys: result.visitorKeys,
669
      addPostProcess: (cb) => postprocessList.push(cb),
4,916✔
670
    };
671

672
    this.restoreNodes(result, callbackOption);
4,928✔
673
    this.restoreProgram(result, callbackOption);
4,916✔
674
    postprocessList.forEach((p) => p());
17,611✔
675

676
    // Helpers
677
    /** Get scope */
678
    function getScope(node: ESTree.Node) {
679
      return getScopeFromNode(result.scopeManager!, node);
3,077✔
680
    }
681

682
    /** Get innermost scope */
683
    function getInnermostScope(node: ESTree.Node) {
684
      return getInnermostScopeFromNode(result.scopeManager!, node);
1,691✔
685
    }
686

687
    /** Register node to scope */
688
    function registerNodeToScope(node: any, scope: Scope): void {
689
      // If we replace the `scope.block` at this time,
690
      // the scope restore calculation will not work, so we will replace the `scope.block` later.
691
      postprocessList.push(() => {
12,695✔
692
        scope.block = node;
12,695✔
693
      });
694

695
      const scopes = nodeToScope.get(node);
12,695✔
696
      if (scopes) {
12,695✔
697
        scopes.push(scope);
4,916✔
698
      } else {
699
        nodeToScope.set(node, [scope]);
7,779✔
700
      }
701
    }
702
  }
703

704
  /**
705
   * Restore AST nodes
706
   */
707
  private restoreNodes(
708
    result: ESLintExtendedProgram,
709
    callbackOption: ScriptLetRestoreCallbackOption
710
  ): void {
711
    let orderedRestoreCallback = this.restoreCallbacks.shift();
4,928✔
712
    if (!orderedRestoreCallback) {
4,928✔
713
      return;
782✔
714
    }
715
    const separateIndexes = this.script.separateIndexes;
4,146✔
716
    const tokens = result.ast.tokens;
4,146✔
717
    const processedTokens = [];
4,146✔
718
    const comments = result.ast.comments;
4,146✔
719
    const processedComments = [];
4,146✔
720

721
    let tok;
722
    while ((tok = tokens.shift())) {
4,146✔
723
      if (separateIndexes.includes(tok.range[0]) && tok.value === ";") {
101,225✔
724
        break;
4,146✔
725
      }
726
      if (orderedRestoreCallback.start <= tok.range[0]) {
97,079!
727
        tokens.unshift(tok);
×
728
        break;
×
729
      }
730
      processedTokens.push(tok);
97,079✔
731
    }
732
    while ((tok = comments.shift())) {
4,146✔
733
      if (orderedRestoreCallback.start <= tok.range[0]) {
323✔
734
        comments.unshift(tok);
70✔
735
        break;
70✔
736
      }
737
      processedComments.push(tok);
253✔
738
    }
739

740
    const targetNodes = new Map<ESTree.Node, RestoreCallback>();
4,146✔
741
    const removeStatements: ESTree.Statement[] = [];
4,146✔
742

743
    traverseNodes(result.ast, {
4,146✔
744
      visitorKeys: result.visitorKeys,
745
      enterNode: (node) => {
746
        while (node.range && separateIndexes.includes(node.range[1] - 1)) {
157,999✔
747
          node.range[1]--;
8,520✔
748
          node.loc!.end.column--;
8,520✔
749
        }
750
        if (node.loc!.end.column < 0) {
157,999✔
751
          node.loc!.end = this.ctx.getLocFromIndex(node.range![1]);
4,260✔
752
        }
753
        if (
157,999✔
754
          (node as any).parent === result.ast &&
181,629✔
755
          separateIndexes[0] <= node.range![0]
756
        ) {
757
          removeStatements.push(node as any);
16,285✔
758
        }
759
        if (!orderedRestoreCallback) {
157,999✔
760
          return;
9,842✔
761
        }
762
        if (
148,157✔
763
          orderedRestoreCallback.start <= node.range![0] &&
164,646✔
764
          node.range![1] <= orderedRestoreCallback.end
765
        ) {
766
          targetNodes.set(node, orderedRestoreCallback);
16,489✔
767
          orderedRestoreCallback = this.restoreCallbacks.shift();
16,489✔
768
        }
769
        //
770
      },
771
      leaveNode(node) {
772
        const restoreCallback = targetNodes.get(node);
157,977✔
773
        if (!restoreCallback) {
157,977✔
774
          return;
141,492✔
775
        }
776
        const startIndex = {
16,485✔
777
          token: tokens.findIndex((t) => restoreCallback.start <= t.range[0]),
62,136✔
778
          comment: comments.findIndex(
779
            (t) => restoreCallback.start <= t.range[0]
126✔
780
          ),
781
        };
782
        if (startIndex.comment === -1) {
16,485✔
783
          startIndex.comment = comments.length;
16,401✔
784
        }
785
        const endIndex = {
16,485✔
786
          token: tokens.findIndex(
787
            (t) => restoreCallback.end < t.range[1],
172,653✔
788
            startIndex.token
789
          ),
790
          comment: comments.findIndex(
791
            (t) => restoreCallback.end < t.range[1],
140✔
792
            startIndex.comment
793
          ),
794
        };
795
        if (endIndex.token === -1) {
16,485✔
796
          endIndex.token = tokens.length;
4,137✔
797
        }
798
        if (endIndex.comment === -1) {
16,485✔
799
          endIndex.comment = comments.length;
16,471✔
800
        }
801
        const targetTokens = tokens.splice(
16,485✔
802
          startIndex.token,
803
          endIndex.token - startIndex.token
804
        );
805
        const targetComments = comments.splice(
16,485✔
806
          startIndex.comment,
807
          endIndex.comment - startIndex.comment
808
        );
809
        restoreCallback.callback(
16,485✔
810
          node,
811
          targetTokens,
812
          targetComments,
813
          callbackOption
814
        );
815

816
        processedTokens.push(...targetTokens);
16,473✔
817
        processedComments.push(...targetComments);
16,473✔
818
      },
819
    });
820
    for (const st of removeStatements) {
4,134✔
821
      const index = result.ast.body.indexOf(st);
16,263✔
822
      result.ast.body.splice(index, 1);
16,263✔
823
    }
824

825
    result.ast.tokens = processedTokens;
4,134✔
826
    result.ast.comments = processedComments;
4,134✔
827
  }
828

829
  /**
830
   * Restore program node
831
   */
832
  private restoreProgram(
833
    result: ESLintExtendedProgram,
834
    callbackOption: ScriptLetRestoreCallbackOption
835
  ): void {
836
    for (const callback of this.programRestoreCallbacks) {
4,916✔
837
      callback(
4,916✔
838
        result.ast,
839
        result.ast.tokens,
840
        result.ast.comments,
841
        callbackOption
842
      );
843
    }
844
  }
845

846
  private remapNodes(
847
    maps: {
848
      offset: number;
849
      range: readonly [number, number];
850
      newNode: ESTree.Node;
851
    }[],
852
    tokens: Token[],
853
    comments: Comment[],
854
    visitorKeys?: { [type: string]: string[] }
855
  ) {
856
    const targetMaps = [...maps];
1,475✔
857
    const first = targetMaps.shift()!;
1,475✔
858
    let tokenIndex = 0;
1,475✔
859
    for (; tokenIndex < tokens.length; tokenIndex++) {
1,475✔
860
      const token = tokens[tokenIndex];
4,029✔
861
      if (first.range[1] <= token.range[0]) {
4,029✔
862
        break;
1,111✔
863
      }
864
    }
865

866
    for (const map of targetMaps) {
1,475✔
867
      const startOffset = map.offset - first.offset + first.range[0];
842✔
868
      const endOffset = startOffset + (map.range[1] - map.range[0]);
842✔
869

870
      let removeCount = 0;
842✔
871
      let target = tokens[tokenIndex];
842✔
872
      while (target && target.range[1] <= startOffset) {
842✔
873
        removeCount++;
3,370✔
874
        target = tokens[tokenIndex + removeCount];
3,370✔
875
      }
876
      if (removeCount) {
842!
877
        tokens.splice(tokenIndex, removeCount);
842✔
878
      }
879

880
      const bufferTokens: Token[] = [];
842✔
881
      for (; tokenIndex < tokens.length; tokenIndex++) {
842✔
882
        const token = tokens[tokenIndex];
1,248✔
883
        if (endOffset <= token.range[0]) {
1,248✔
884
          break;
182✔
885
        }
886
        bufferTokens.push(token);
1,066✔
887
      }
888
      this.fixLocations(
842✔
889
        map.newNode,
890
        bufferTokens,
891
        comments.filter(
892
          (t) => startOffset <= t.range[0] && t.range[1] <= endOffset
28!
893
        ),
894
        map.range[0] - startOffset,
895
        visitorKeys
896
      );
897
    }
898
    tokens.splice(tokenIndex);
1,475✔
899
  }
900

901
  /** Fix locations */
902
  private fixLocations(
903
    node: ESTree.Node,
904
    tokens: Token[],
905
    comments: Comment[],
906
    offset: number,
907
    visitorKeys?: { [type: string]: string[] }
908
  ) {
909
    if (offset === 0) {
16,990!
910
      return;
×
911
    }
912
    const traversed = new Set<any>();
16,990✔
913
    traverseNodes(node, {
16,990✔
914
      visitorKeys,
915
      enterNode: (n) => {
916
        if (traversed.has(n)) {
69,164!
917
          return;
×
918
        }
919
        traversed.add(n);
69,164✔
920
        if (traversed.has(n.range)) {
69,164!
921
          if (!traversed.has(n.loc)) {
×
922
            // However, `Node#loc` may not be shared.
923
            const locs = this.ctx.getConvertLocation({
×
924
              start: n.range![0],
925
              end: n.range![1],
926
            });
927
            applyLocs(n, locs);
×
928
            traversed.add(n.loc);
×
929
          }
930
        } else {
931
          const start = n.range![0] + offset;
69,164✔
932
          const end = n.range![1] + offset;
69,164✔
933
          const locs = this.ctx.getConvertLocation({ start, end });
69,164✔
934
          applyLocs(n, locs);
69,164✔
935
          traversed.add(n.range);
69,164✔
936
          traversed.add(n.loc);
69,164✔
937
        }
938
      },
939
      leaveNode: Function.prototype as any,
940
    });
941
    for (const t of tokens) {
16,990✔
942
      const start = t.range[0] + offset;
103,907✔
943
      const end = t.range[1] + offset;
103,907✔
944
      const locs = this.ctx.getConvertLocation({ start, end });
103,907✔
945
      applyLocs(t, locs);
103,907✔
946
    }
947
    for (const t of comments) {
16,990✔
948
      const start = t.range[0] + offset;
84✔
949
      const end = t.range[1] + offset;
84✔
950
      const locs = this.ctx.getConvertLocation({ start, end });
84✔
951
      applyLocs(t, locs);
84✔
952
    }
953
  }
954

955
  private generateUniqueId(base: string) {
956
    return this.unique.generate(
361✔
957
      base,
958
      this.ctx.code,
959
      this.script.getCurrentVirtualCode()
960
    );
961
  }
962
}
963

964
/**
965
 * applyLocs
966
 */
967
function applyLocs(target: Locations | ESTree.Node, locs: Locations) {
968
  target.loc = locs.loc;
173,155✔
969
  target.range = locs.range;
173,155✔
970
  if (typeof (target as any).start === "number") {
173,155✔
971
    delete (target as any).start;
127,840✔
972
  }
973
  if (typeof (target as any).end === "number") {
173,155✔
974
    delete (target as any).end;
127,840✔
975
  }
976
}
977

978
/** Get the node to scope map from given scope manager  */
979
function getNodeToScope(
980
  scopeManager: ScopeManager
981
): WeakMap<ESTree.Node, Scope[]> {
982
  if ("__nodeToScope" in scopeManager) {
4,928✔
983
    return (scopeManager as any).__nodeToScope;
4,523✔
984
  }
985

986
  // transform scopeManager
987
  const nodeToScope = new WeakMap<ESTree.Node, Scope[]>();
405✔
988
  for (const scope of scopeManager.scopes) {
405✔
989
    const scopes = nodeToScope.get(scope.block);
3,651✔
990
    if (scopes) {
3,651✔
991
      scopes.push(scope);
405✔
992
    } else {
993
      nodeToScope.set(scope.block, [scope]);
3,246✔
994
    }
995
  }
996
  scopeManager.acquire = function (node, inner) {
405✔
997
    /**
998
     * predicate
999
     */
1000
    function predicate(testScope: Scope) {
1001
      if (testScope.type === "function" && testScope.functionExpressionScope) {
459!
1002
        return false;
×
1003
      }
1004
      return true;
459✔
1005
    }
1006

1007
    const scopes = nodeToScope.get(node as any);
28,901✔
1008

1009
    if (!scopes || scopes.length === 0) {
28,901✔
1010
      return null;
22,855✔
1011
    }
1012

1013
    // Heuristic selection from all scopes.
1014
    // If you would like to get all scopes, please use ScopeManager#acquireAll.
1015
    if (scopes.length === 1) {
6,046✔
1016
      return scopes[0];
5,587✔
1017
    }
1018

1019
    if (inner) {
459✔
1020
      for (let i = scopes.length - 1; i >= 0; --i) {
10✔
1021
        const scope = scopes[i];
10✔
1022

1023
        if (predicate(scope)) {
10!
1024
          return scope;
10✔
1025
        }
1026
      }
1027
    } else {
1028
      for (let i = 0, iz = scopes.length; i < iz; ++i) {
449✔
1029
        const scope = scopes[i];
449✔
1030

1031
        if (predicate(scope)) {
449!
1032
          return scope;
449✔
1033
        }
1034
      }
1035
    }
1036

1037
    return null;
×
1038
  };
1039

1040
  return nodeToScope;
405✔
1041
}
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