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

ota-meshi / astro-eslint-parser / 27489752680

14 Jun 2026 05:42AM UTC coverage: 81.805% (+0.1%) from 81.66%
27489752680

push

github

web-flow
chore(deps): update dependency typescript to v6 (#420)

* chore(deps): update dependency typescript to v6

* fix

* fix

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: yosuke ota <otameshiyo23@gmail.com>

759 of 993 branches covered (76.44%)

Branch coverage included in aggregate %.

1462 of 1722 relevant lines covered (84.9%)

22187.22 hits per line

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

96.61
/src/parser/process-template.ts
1
import type { ParseResult } from "@astrojs/compiler";
4,460!
2
import { AST_TOKEN_TYPES, AST_NODE_TYPES } from "@typescript-eslint/types";
1✔
3
import type { TSESTree } from "@typescript-eslint/types";
4
import {
5
  calcAttributeEndOffset,
6
  calcAttributeValueStartOffset,
7
  getSelfClosingTag,
8
  isTag,
9
  walkElements,
10
  getEndTag,
11
  calcContentEndOffset,
12
  getEndOffset,
13
} from "../astro";
1✔
14
import type { Context } from "../context";
15
import { VirtualScriptContext } from "../context/script";
1✔
16
import type {
17
  AstroDoctype,
18
  AstroFragment,
19
  AstroHTMLComment,
20
  AstroProgram,
21
  AstroRawText,
22
  AstroShorthandAttribute,
23
  AstroTemplateLiteralAttribute,
24
  JSXElement,
25
} from "../ast";
26
import type { AttributeNode } from "@astrojs/compiler/types";
27
import { removeAllScopeAndVariableAndReference } from "./scope";
1✔
28

29
/**
30
 * Process the template to generate a ScriptContext.
31
 */
32
export function processTemplate(
2!
33
  ctx: Context,
34
  resultTemplate: ParseResult,
35
): VirtualScriptContext {
36
  let uniqueIdSeq = 0;
1,480✔
37
  const usedUniqueIds = new Set<string>();
1,480✔
38

39
  const script = new VirtualScriptContext(ctx);
1,480✔
40

41
  let fragmentOpened = false;
1,480✔
42

43
  /** Open astro root fragment */
44
  function openRootFragment(startOffset: number) {
1,480✔
45
    script.appendVirtualScript("<>");
1,492✔
46
    fragmentOpened = true;
1,492✔
47
    script.restoreContext.addRestoreNodeProcess((scriptNode, { result }) => {
1,492✔
48
      if (
15,810✔
49
        scriptNode.type === AST_NODE_TYPES.ExpressionStatement &&
20,328✔
50
        scriptNode.expression.type === AST_NODE_TYPES.JSXFragment &&
51
        scriptNode.range[0] === startOffset &&
52
        result.ast.body.includes(scriptNode)
53
      ) {
54
        const index = result.ast.body.indexOf(scriptNode);
1,491✔
55
        const rootFragment = ((result.ast as AstroProgram).body[index] =
1,491✔
56
          scriptNode.expression as unknown as AstroFragment);
57
        delete (rootFragment as any).closingFragment;
1,491✔
58
        delete (rootFragment as any).openingFragment;
1,491✔
59
        rootFragment.type = "AstroFragment";
1,491✔
60

61
        return true;
1,491✔
62
      }
63
      return false;
14,319✔
64
    });
65
  }
66

67
  walkElements(
68
    resultTemplate.ast,
69
    ctx.code,
70
    // eslint-disable-next-line complexity -- X(
71
    (node, [parent]) => {
72
      if (node.type === "frontmatter") {
16,475✔
73
        const start = node.position!.start.offset;
1,044✔
74
        if (fragmentOpened) {
1,044✔
75
          script.appendVirtualScript("</>;");
45✔
76
          fragmentOpened = false;
45✔
77
        }
78
        script.appendOriginal(start);
1,044✔
79
        script.skipOriginalOffset(3);
1,044✔
80
        const end = getEndOffset(node, ctx);
1,044✔
81
        const scriptStart = start + 3;
1,044✔
82
        let scriptEnd = end - 3;
1,044✔
83
        let endChar: string;
84
        while (
1,044✔
85
          scriptStart < scriptEnd - 1 &&
6,254✔
86
          (endChar = ctx.code[scriptEnd - 1]) &&
87
          !endChar.trim()
88
        ) {
89
          scriptEnd--;
1,102✔
90
        }
91
        script.appendOriginal(scriptEnd);
1,044✔
92

93
        script.appendVirtualScript("\n;");
1,044✔
94
        script.skipOriginalOffset(end - scriptEnd);
1,044✔
95

96
        script.restoreContext.addRestoreNodeProcess(
1,044✔
97
          (_scriptNode, { result }) => {
98
            for (let index = 0; index < result.ast.body.length; index++) {
1,043✔
99
              const st = result.ast.body[index] as TSESTree.Node;
2,944✔
100
              if (st.type === AST_NODE_TYPES.EmptyStatement) {
2,944✔
101
                if (st.range[0] === scriptEnd && st.range[1] === scriptEnd) {
708✔
102
                  result.ast.body.splice(index, 1);
708✔
103
                  break;
708✔
104
                }
105
              }
106
            }
107
            return true;
1,043✔
108
          },
109
        );
110

111
        script.restoreContext.addToken(AST_TOKEN_TYPES.Punctuator, [
1,044✔
112
          node.position!.start.offset,
113
          node.position!.start.offset + 3,
114
        ]);
115
        script.restoreContext.addToken(AST_TOKEN_TYPES.Punctuator, [
1,044✔
116
          end - 3,
117
          end,
118
        ]);
119
      } else if (isTag(node)) {
15,431✔
120
        // Process for multiple tag
121
        if (parent.type === "expression") {
5,057✔
122
          const siblings = parent.children.filter(
257✔
123
            (n) => n.type !== "text" || n.value.trim(),
936✔
124
          );
125
          const index = siblings.indexOf(node);
257✔
126
          const before = siblings[index - 1];
257✔
127
          if (!before || !isTag(before)) {
257✔
128
            const after = siblings[index + 1];
227✔
129
            if (after && (isTag(after) || after.type === "comment")) {
227✔
130
              const start = node.position!.start.offset;
45✔
131
              script.appendOriginal(start);
45✔
132
              script.appendVirtualScript("<>");
45✔
133
              script.restoreContext.addRestoreNodeProcess((scriptNode) => {
45✔
134
                if (
540✔
135
                  scriptNode.range[0] === start &&
585✔
136
                  scriptNode.type === AST_NODE_TYPES.JSXFragment
137
                ) {
138
                  delete (scriptNode as any).openingFragment;
45✔
139
                  delete (scriptNode as any).closingFragment;
45✔
140
                  const fragmentNode = scriptNode as unknown as AstroFragment;
45✔
141
                  fragmentNode.type = "AstroFragment";
45✔
142
                  const last =
143
                    fragmentNode.children[fragmentNode.children.length - 1];
45✔
144
                  if (fragmentNode.range[1] < last.range[1]) {
45!
145
                    fragmentNode.range[1] = last.range[1];
×
146
                    fragmentNode.loc.end = ctx.getLocFromIndex(
×
147
                      fragmentNode.range[1],
148
                    );
149
                  }
150
                  return true;
45✔
151
                }
152
                return false;
495✔
153
              });
154
            }
155
          }
156
        }
157

158
        const start = node.position!.start.offset;
5,057✔
159
        script.appendOriginal(start);
5,057✔
160
        if (!fragmentOpened) {
5,057✔
161
          openRootFragment(start);
1,157✔
162
        }
163

164
        // Process for attributes
165
        for (const attr of node.attributes) {
5,057✔
166
          if (
3,265✔
167
            attr.kind === "quoted" ||
5,257✔
168
            attr.kind === "empty" ||
169
            attr.kind === "expression" ||
170
            attr.kind === "template-literal"
171
          ) {
172
            const needPunctuatorsProcess =
173
              node.type === "component" || node.type === "fragment"
3,130✔
174
                ? /[.:@]/u.test(attr.name)
175
                : /[.@]/u.test(attr.name) || attr.name.startsWith(":");
4,516✔
176

177
            if (needPunctuatorsProcess) {
3,130✔
178
              processAttributePunctuators(attr);
272✔
179
            }
180
          }
181
          if (attr.kind === "quoted") {
3,265✔
182
            if (
2,209✔
183
              attr.raw &&
4,493✔
184
              !attr.raw.startsWith('"') &&
185
              !attr.raw.startsWith("'")
186
            ) {
187
              const attrStart = attr.position!.start.offset;
45✔
188
              const start = calcAttributeValueStartOffset(attr, ctx);
45✔
189
              const end = calcAttributeEndOffset(attr, ctx);
45✔
190
              script.appendOriginal(start);
45✔
191
              script.appendVirtualScript('"');
45✔
192
              script.appendOriginal(end);
45✔
193
              script.appendVirtualScript('"');
45✔
194

195
              script.restoreContext.addRestoreNodeProcess(
45✔
196
                (scriptNode, context) => {
197
                  if (
555✔
198
                    scriptNode.type === AST_NODE_TYPES.JSXAttribute &&
615✔
199
                    scriptNode.range[0] === attrStart
200
                  ) {
201
                    const attrNode = scriptNode;
45✔
202
                    if (
45✔
203
                      attrNode.value?.type === "Literal" &&
90✔
204
                      typeof attrNode.value.value === "string"
205
                    ) {
206
                      const raw = ctx.code.slice(start, end);
45✔
207
                      attrNode.value.raw = raw;
45✔
208
                      context.findToken(start)!.value = raw;
45✔
209
                      return true;
45✔
210
                    }
211
                  }
212
                  return false;
510✔
213
                },
214
              );
215
            }
216
          } else if (attr.kind === "shorthand") {
1,056✔
217
            const start = attr.position!.start.offset;
75✔
218
            script.appendOriginal(start);
75✔
219
            const jsxName = /[\s"'[\]{}]/u.test(attr.name)
75✔
220
              ? generateUniqueId(attr.name)
221
              : attr.name;
222
            script.appendVirtualScript(`${jsxName}=`);
75✔
223

224
            script.restoreContext.addRestoreNodeProcess((scriptNode) => {
75✔
225
              if (
3,885✔
226
                scriptNode.type === AST_NODE_TYPES.JSXAttribute &&
4,020✔
227
                scriptNode.range[0] === start
228
              ) {
229
                const attrNode =
230
                  scriptNode as unknown as AstroShorthandAttribute;
75✔
231
                attrNode.type = "AstroShorthandAttribute";
75✔
232

233
                const locs = ctx.getLocations(
75✔
234
                  ...attrNode.value.expression.range,
235
                );
236
                if (jsxName !== attr.name) {
75✔
237
                  attrNode.name.name = attr.name;
15✔
238
                }
239
                attrNode.name.range = locs.range;
75✔
240
                attrNode.name.loc = locs.loc;
75✔
241
                return true;
75✔
242
              }
243
              return false;
3,810✔
244
            });
245
          } else if (attr.kind === "template-literal") {
981✔
246
            const attrStart = attr.position!.start.offset;
15✔
247
            const start = calcAttributeValueStartOffset(attr, ctx);
15✔
248
            const end = calcAttributeEndOffset(attr, ctx);
15✔
249
            script.appendOriginal(start);
15✔
250
            script.appendVirtualScript("{");
15✔
251
            script.appendOriginal(end);
15✔
252
            script.appendVirtualScript("}");
15✔
253

254
            script.restoreContext.addRestoreNodeProcess((scriptNode) => {
15✔
255
              if (
315✔
256
                scriptNode.type === AST_NODE_TYPES.JSXAttribute &&
345✔
257
                scriptNode.range[0] === attrStart
258
              ) {
259
                const attrNode =
260
                  scriptNode as unknown as AstroTemplateLiteralAttribute;
15✔
261
                attrNode.type = "AstroTemplateLiteralAttribute";
15✔
262
                return true;
15✔
263
              }
264
              return false;
300✔
265
            });
266
          }
267
        }
268

269
        // Process for start tag close
270
        const closing = getSelfClosingTag(node, ctx);
5,057✔
271
        if (closing && closing.end === ">") {
5,057✔
272
          script.appendOriginal(closing.offset - 1);
244✔
273
          script.appendVirtualScript("/");
244✔
274
        }
275

276
        // Process for raw text
277
        if (
5,057✔
278
          node.name === "script" ||
14,196✔
279
          node.name === "style" ||
280
          node.attributes.some((attr) => attr.name === "is:raw")
2,980✔
281
        ) {
282
          const text = node.children[0];
690✔
283
          if (text && text.type === "text") {
690✔
284
            const styleNodeStart = node.position!.start.offset;
645✔
285
            const start = text.position!.start.offset;
645✔
286
            script.appendOriginal(start);
645✔
287
            script.skipOriginalOffset(text.value.length);
645✔
288

289
            script.restoreContext.addRestoreNodeProcess((scriptNode) => {
645✔
290
              if (
40,980✔
291
                scriptNode.type === AST_NODE_TYPES.JSXElement &&
44,835✔
292
                scriptNode.range[0] === styleNodeStart
293
              ) {
294
                const textNode: AstroRawText = {
645✔
295
                  type: "AstroRawText",
296
                  value: text.value,
297
                  raw: text.value,
298
                  parent: scriptNode as JSXElement,
299
                  ...ctx.getLocations(start, start + text.value.length),
300
                };
301
                scriptNode.children = [textNode as unknown as TSESTree.JSXText];
645✔
302
                return true;
645✔
303
              }
304
              return false;
40,335✔
305
            });
306
            script.restoreContext.addToken(AST_TOKEN_TYPES.JSXText, [
645✔
307
              start,
308
              start + text.value.length,
309
            ]);
310
          }
311
        }
312
      } else if (node.type === "comment") {
10,374✔
313
        const start = node.position!.start.offset;
390✔
314
        const end = getEndOffset(node, ctx);
390✔
315
        const length = end - start;
390✔
316
        script.appendOriginal(start);
390✔
317
        if (!fragmentOpened) {
390✔
318
          openRootFragment(start);
150✔
319
        }
320
        script.appendOriginal(start + 1);
390✔
321
        script.appendVirtualScript(`></`);
390✔
322
        script.skipOriginalOffset(length - 2);
390✔
323
        script.appendOriginal(end);
390✔
324

325
        script.restoreContext.addRestoreNodeProcess((scriptNode, context) => {
390✔
326
          if (
13,335✔
327
            scriptNode.range[0] === start &&
14,175✔
328
            scriptNode.type === AST_NODE_TYPES.JSXFragment
329
          ) {
330
            delete (scriptNode as any).children;
390✔
331
            delete (scriptNode as any).openingFragment;
390✔
332
            delete (scriptNode as any).closingFragment;
390✔
333
            delete (scriptNode as any).expression;
390✔
334
            const commentNode = scriptNode as unknown as AstroHTMLComment;
390✔
335
            commentNode.type = "AstroHTMLComment";
390✔
336
            commentNode.value = node.value;
390✔
337

338
            context.addRemoveToken(
390✔
339
              (token: TSESTree.Token) =>
340
                token.value === "<" && token.range[0] === scriptNode.range[0],
12,030✔
341
            );
342
            context.addRemoveToken(
390✔
343
              (token: TSESTree.Token) =>
344
                token.value === ">" && token.range[1] === scriptNode.range[1],
11,640✔
345
            );
346
            return true;
390✔
347
          }
348
          return false;
12,945✔
349
        });
350
        script.restoreContext.addToken("HTMLComment" as AST_TOKEN_TYPES, [
390✔
351
          start,
352
          start + length,
353
        ]);
354
      } else if (node.type === "doctype") {
9,984✔
355
        const start = node.position!.start.offset;
91✔
356
        const end = getEndOffset(node, ctx);
91✔
357
        const length = end - start;
91✔
358
        script.appendOriginal(start);
91✔
359
        if (!fragmentOpened) {
91✔
360
          openRootFragment(start);
61✔
361
        }
362
        script.appendOriginal(start + 1);
91✔
363
        script.appendVirtualScript(`></`);
91✔
364
        script.skipOriginalOffset(length - 2);
91✔
365
        script.appendOriginal(end);
91✔
366

367
        script.restoreContext.addRestoreNodeProcess((scriptNode, context) => {
91✔
368
          if (
630✔
369
            scriptNode.range[0] === start &&
900✔
370
            scriptNode.type === AST_NODE_TYPES.JSXFragment
371
          ) {
372
            delete (scriptNode as any).children;
90✔
373
            delete (scriptNode as any).openingFragment;
90✔
374
            delete (scriptNode as any).closingFragment;
90✔
375
            delete (scriptNode as any).expression;
90✔
376
            const doctypeNode = scriptNode as unknown as AstroDoctype;
90✔
377
            doctypeNode.type = "AstroDoctype";
90✔
378

379
            context.addRemoveToken(
90✔
380
              (token: TSESTree.Token) =>
381
                token.value === "<" && token.range[0] === scriptNode.range[0],
2,895✔
382
            );
383
            context.addRemoveToken(
90✔
384
              (token: TSESTree.Token) =>
385
                token.value === ">" && token.range[1] === scriptNode.range[1],
2,805✔
386
            );
387
            return true;
90✔
388
          }
389
          return false;
540✔
390
        });
391
        script.restoreContext.addToken("HTMLDocType" as AST_TOKEN_TYPES, [
91✔
392
          start,
393
          end,
394
        ]);
395
      } else {
396
        const start = node.position!.start.offset;
9,893✔
397
        script.appendOriginal(start);
9,893✔
398
        if (!fragmentOpened) {
9,893✔
399
          openRootFragment(start);
124✔
400
        }
401
      }
402
    },
403
    (node, [parent]) => {
404
      if (isTag(node)) {
16,475✔
405
        const closing = getSelfClosingTag(node, ctx);
5,057✔
406
        if (!closing) {
5,057✔
407
          const end = getEndTag(node, ctx);
3,680✔
408
          if (!end) {
3,680✔
409
            const offset = calcContentEndOffset(node, ctx);
15✔
410
            script.appendOriginal(offset);
15✔
411
            script.appendVirtualScript(`</${node.name}>`);
15✔
412
            script.restoreContext.addRestoreNodeProcess(
15✔
413
              (scriptNode, context) => {
414
                const parent = context.getParent(scriptNode)!;
300✔
415
                if (
300✔
416
                  scriptNode.range[0] === offset &&
330✔
417
                  scriptNode.type === AST_NODE_TYPES.JSXClosingElement &&
418
                  parent.type === AST_NODE_TYPES.JSXElement
419
                ) {
15✔
420
                  removeAllScopeAndVariableAndReference(scriptNode, {
421
                    visitorKeys: context.result.visitorKeys,
422
                    scopeManager: context.result.scopeManager!,
423
                  });
424
                  parent.closingElement = null;
15✔
425
                  return true;
15✔
426
                }
427
                return false;
285✔
428
              },
429
            );
430
          }
431
        }
432
      }
433
      // Process for multiple tag
434
      if (
16,475✔
435
        (isTag(node) || node.type === "comment") &&
436
        parent.type === "expression"
437
      ) {
438
        const siblings = parent.children.filter(
287✔
439
          (n) => n.type !== "text" || n.value.trim(),
1,086✔
440
        );
441
        const index = siblings.indexOf(node);
287✔
442
        const after = siblings[index + 1];
287✔
443
        if (!after || (!isTag(after) && after.type !== "comment")) {
287✔
444
          const before = siblings[index - 1];
242✔
445
          if (before && (isTag(before) || before.type === "comment")) {
242✔
446
            const end = getEndOffset(node, ctx);
45✔
447
            script.appendOriginal(end);
45✔
448
            script.appendVirtualScript("</>");
45✔
449
          }
450
        }
451
      }
452
    },
453
  );
454
  if (fragmentOpened) {
1,480✔
455
    const last =
456
      resultTemplate.ast.children[resultTemplate.ast.children.length - 1];
1,447✔
457
    const end = getEndOffset(last, ctx);
1,447✔
458
    script.appendOriginal(end);
1,447✔
459
    script.appendVirtualScript("</>;");
1,447✔
460
  }
461

462
  script.appendOriginal(ctx.code.length);
1,480✔
463

464
  return script;
1,480✔
465

466
  /**
467
   * Process for attribute punctuators
468
   */
469
  function processAttributePunctuators(attr: AttributeNode) {
×
470
    const start = attr.position!.start.offset;
272✔
471
    let targetIndex = start;
272✔
472
    let colonOffset: number | undefined;
473
    for (let index = 0; index < attr.name.length; index++) {
272✔
474
      const char = attr.name[index];
3,121✔
475
      if (char !== ":" && char !== "." && char !== "@") {
3,121✔
476
        continue;
2,789✔
477
      }
478
      if (index === 0) {
332✔
479
        targetIndex++;
60✔
480
      }
481
      const punctuatorIndex = start + index;
332✔
482
      script.appendOriginal(punctuatorIndex);
332✔
483
      script.skipOriginalOffset(1);
332✔
484
      script.appendVirtualScript(`_`);
332✔
485

486
      if (char === ":" && index !== 0 && colonOffset == null) {
332✔
487
        colonOffset = index;
212✔
488
      }
489
    }
490
    if (colonOffset != null) {
272✔
491
      const punctuatorIndex = start + colonOffset;
212✔
492
      script.restoreContext.addToken(AST_TOKEN_TYPES.JSXIdentifier, [
212✔
493
        start,
494
        punctuatorIndex,
495
      ]);
496
      script.restoreContext.addToken(AST_TOKEN_TYPES.Punctuator, [
212✔
497
        punctuatorIndex,
498
        punctuatorIndex + 1,
499
      ]);
500
      script.restoreContext.addToken(AST_TOKEN_TYPES.JSXIdentifier, [
212✔
501
        punctuatorIndex + 1,
502
        start + attr.name.length,
503
      ]);
504
    } else {
505
      script.restoreContext.addToken(AST_TOKEN_TYPES.JSXIdentifier, [
60✔
506
        start,
507
        start + attr.name.length,
508
      ]);
509
    }
510
    script.restoreContext.addRestoreNodeProcess((scriptNode, context) => {
272✔
511
      if (
6,806✔
512
        scriptNode.type === AST_NODE_TYPES.JSXAttribute &&
7,318✔
513
        scriptNode.range[0] === targetIndex
514
      ) {
515
        const baseNameNode = scriptNode.name;
272✔
516
        if (colonOffset != null) {
272✔
517
          const nameNode = baseNameNode as TSESTree.JSXNamespacedName;
212✔
518
          nameNode.type = AST_NODE_TYPES.JSXNamespacedName;
212✔
519
          nameNode.namespace = {
212✔
520
            type: AST_NODE_TYPES.JSXIdentifier,
521
            name: attr.name.slice(0, colonOffset),
522
            ...ctx.getLocations(
523
              baseNameNode.range[0],
524
              baseNameNode.range[0] + colonOffset,
525
            ),
526
          };
527
          nameNode.name = {
212✔
528
            type: AST_NODE_TYPES.JSXIdentifier,
529
            name: attr.name.slice(colonOffset + 1),
530
            ...ctx.getLocations(
531
              baseNameNode.range[0] + colonOffset + 1,
532
              baseNameNode.range[1],
533
            ),
534
          };
535
          scriptNode.name = nameNode;
212✔
536
          nameNode.namespace.parent = nameNode;
212✔
537
          nameNode.name.parent = nameNode;
212✔
538
        } else {
539
          if (baseNameNode.type === AST_NODE_TYPES.JSXIdentifier) {
60!
540
            const nameNode = baseNameNode;
60✔
541
            nameNode.name = attr.name;
60✔
542
            scriptNode.name = nameNode;
60✔
543
          } else {
544
            const nameNode = baseNameNode;
×
545
            nameNode.namespace.name = attr.name.slice(
×
546
              baseNameNode.namespace.range[0] - start,
547
              baseNameNode.namespace.range[1] - start,
548
            );
549
            nameNode.name.name = attr.name.slice(
×
550
              baseNameNode.name.range[0] - start,
551
              baseNameNode.name.range[1] - start,
552
            );
553
            scriptNode.name = nameNode;
×
554
            nameNode.namespace.parent = nameNode;
×
555
            nameNode.name.parent = nameNode;
×
556
          }
557
        }
558
        context.addRemoveToken(
272✔
559
          (token) =>
560
            token.range[0] === baseNameNode.range[0] &&
4,736✔
561
            token.range[1] === baseNameNode.range[1],
562
        );
563
        return true;
272✔
564
      }
565
      return false;
6,534✔
566
    });
567
  }
568

569
  /**
570
   * Generate unique id
571
   */
572
  function generateUniqueId(base: string) {
×
573
    let candidate = `$_${base.replace(/\W/g, "_")}${uniqueIdSeq++}`;
15✔
574
    while (usedUniqueIds.has(candidate) || ctx.code.includes(candidate)) {
15✔
575
      candidate = `$_${base.replace(/\W/g, "_")}${uniqueIdSeq++}`;
×
576
    }
577
    usedUniqueIds.add(candidate);
15✔
578
    return candidate;
15✔
579
  }
580
}
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