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

future-architect / eslint-plugin-vue-scoped-css / 16537023885

26 Jul 2025 06:29AM UTC coverage: 92.289%. Remained the same
16537023885

Pull #360

github

web-flow
Merge 222eb64ab into ad0808d98
Pull Request #360: chore(deps): update dependency rimraf to v6

1455 of 1670 branches covered (87.13%)

Branch coverage included in aggregate %.

2782 of 2921 relevant lines covered (95.24%)

410.45 hits per line

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

84.96
/lib/styles/parser/selector/css-selector-parser.ts
1
import selectorParser from "postcss-selector-parser";
1✔
2
import type {
3
  VCSSCommentNode,
4
  VCSSStyleRule,
5
  VCSSAtRule,
6
  VCSSSelectorNode,
7
  VCSSNode,
8
  VCSSContainerNode,
9
  VCSSSelectorValueNode,
10
} from "../../ast";
11
import {
1✔
12
  VCSSSelector,
13
  VCSSTypeSelector,
14
  VCSSIDSelector,
15
  VCSSClassSelector,
16
  VCSSNestingSelector,
17
  VCSSUniversalSelector,
18
  VCSSAttributeSelector,
19
  VCSSSelectorPseudo,
20
  VCSSSelectorCombinator,
21
  VCSSUnknownSelector,
22
  VCSSComment,
23
} from "../../ast";
24
import {
1✔
25
  isSelectorCombinator,
26
  isDescendantCombinator,
27
  isVueSpecialPseudo,
28
  normalizePseudoParams,
29
  isVDeepPseudoV2,
30
} from "../../utils/selectors";
31
import type {
32
  SourceCode,
33
  LineAndColumnData,
34
  SourceLocation,
35
  PostCSSSPNode,
36
  PostCSSSPContainer,
37
  PostCSSSPStringNode,
38
  PostCSSSPSelector,
39
  PostCSSSPTypeNode,
40
  PostCSSSPClassNameNode,
41
  PostCSSSPNestingNode,
42
  PostCSSSPUniversalNode,
43
  PostCSSSPAttributeNode,
44
  PostCSSSPPseudoNode,
45
  PostCSSSPCombinatorNode,
46
  PostCSSSPCommentNode,
47
  PostCSSSPIDNode,
48
  PostCSSSPRootNode,
49
} from "../../../types";
50
import { isPostCSSSPContainer } from "../utils";
1✔
51
import { isDefined } from "../../../utils/utils";
1✔
52

53
export class CSSSelectorParser {
1✔
54
  private readonly sourceCode: SourceCode;
55

56
  protected commentContainer: VCSSCommentNode[];
57

58
  protected lang = "css";
444✔
59

60
  /**
61
   * constructor.
62
   * @param {SourceCode} sourceCode the SourceCode object that you can use to work with the source that was passed to ESLint.
63
   * @param {Node[]} commentContainer comment nodes container
64
   */
65
  public constructor(
66
    sourceCode: SourceCode,
67
    commentContainer: VCSSCommentNode[],
68
  ) {
69
    this.sourceCode = sourceCode;
444✔
70
    this.commentContainer = commentContainer;
444✔
71
  }
72

73
  /**
74
   * Parse CSS selector.
75
   * @param {string} rawSelector `<style>` node
76
   * @param {LineAndColumnData} offsetLocation start location of selector.
77
   * @param {Node} parent parent node
78
   * @return {Node[]} parsed result
79
   */
80
  public parse(
81
    rawSelector: string,
82
    offsetLocation: LineAndColumnData,
83
    parent: VCSSStyleRule | VCSSAtRule,
84
  ): VCSSSelectorNode[] {
85
    const selectorParserRoot = this.parseInternal(rawSelector);
888✔
86

87
    return this._postcssSelectorParserNodeChiildrenToASTNodes(
888✔
88
      offsetLocation,
89
      selectorParserRoot,
90
      parent,
91
    );
92
  }
93

94
  protected parseInternal(selector: string): PostCSSSPRootNode {
95
    return selectorParser().astSync(selector);
643✔
96
  }
97

98
  protected parseCommentsInternal(selector: string): PostCSSSPRootNode {
99
    return selectorParser().astSync(selector);
20✔
100
  }
101

102
  /**
103
   * Convert `postcss-selector-parser` node to node that can be handled by ESLint.
104
   * @param {LineAndColumnData} offsetLocation start location of selector.
105
   * @param {object} node the `postcss-selector-parser` node to comvert
106
   * @param {Node} parent parent node
107
   * @return {Node[]} converted nodes.
108
   */
109
  private _postcssSelectorParserNodeChiildrenToASTNodes(
110
    offsetLocation: LineAndColumnData,
111
    node: PostCSSSPRootNode,
112
    parent: VCSSStyleRule | VCSSAtRule,
113
  ): VCSSSelector[];
114

115
  private _postcssSelectorParserNodeChiildrenToASTNodes(
116
    offsetLocation: LineAndColumnData,
117
    node: PostCSSSPContainer,
118
    parent: VCSSSelector,
119
  ): VCSSSelectorValueNode[];
120

121
  private _postcssSelectorParserNodeChiildrenToASTNodes(
122
    offsetLocation: LineAndColumnData,
123
    node: PostCSSSPPseudoNode,
124
    parent: VCSSSelectorPseudo,
125
  ): VCSSSelector[];
126

127
  private _postcssSelectorParserNodeChiildrenToASTNodes(
128
    offsetLocation: LineAndColumnData,
129
    node: PostCSSSPContainer | PostCSSSPPseudoNode,
130
    parent: VCSSStyleRule | VCSSAtRule | VCSSSelector | VCSSSelectorPseudo,
131
  ): VCSSSelectorNode[] {
132
    const astNodes = removeInvalidDescendantCombinator(
2,202✔
133
      node.nodes
134
        .map((child) =>
135
          this._postcssSelectorParserNodeToASTNode(
3,459✔
136
            offsetLocation,
137
            child,
138
            parent,
139
          ),
140
        )
141
        .filter(isDefined),
142
    );
143
    if (astNodes.length !== node.nodes.length) {
2,202✔
144
      // adjust locations
145
      if (node.type === "selector") {
49✔
146
        // adjust start location
147
        const firstAstNode = astNodes[0];
49✔
148
        parent.loc.start = { ...firstAstNode.loc.start };
49✔
149
        parent.start = firstAstNode.start;
49✔
150
        parent.range = [firstAstNode.start, parent.range[1]];
49✔
151
      }
152
      if (node.type === "selector") {
49✔
153
        // adjust end location
154
        const lastAstNode = astNodes[astNodes.length - 1];
49✔
155
        parent.loc.end = { ...lastAstNode.loc.end };
49✔
156
        parent.end = lastAstNode.end;
49✔
157
        parent.range = [parent.range[0], lastAstNode.end];
49✔
158
      }
159
    }
160
    return astNodes;
2,202✔
161
  }
162

163
  /**
164
   * Convert `postcss-selector-parser` node to node that can be handled by ESLint.
165
   * @param {LineAndColumnData} offsetLocation start location of selector.
166
   * @param {object} node the `postcss-selector-parser` node to convert
167
   * @param {Node} parent parent node
168
   * @return {Node} converted node.
169
   */
170
  private _postcssSelectorParserNodeToASTNode(
171
    offsetLocation: LineAndColumnData,
172
    node: PostCSSSPNode,
173
    parent: VCSSStyleRule | VCSSAtRule | VCSSSelector | VCSSSelectorPseudo,
174
  ): VCSSSelectorNode | null {
175
    const { sourceCode } = this;
3,487✔
176

177
    /**
178
     * Get index from location
179
     */
180
    function getIndexFromLoc(loc: LineAndColumnData): number {
181
      if (loc.column >= 0) {
6,974✔
182
        return sourceCode.getIndexFromLoc(loc);
6,911✔
183
      }
184
      // A column index can be negative if the starting position contains a newline.
185
      const index = sourceCode.getIndexFromLoc({
63✔
186
        line: loc.line,
187
        column: 0,
188
      });
189
      return index + loc.column;
63✔
190
    }
191

192
    const loc = {
3,487✔
193
      start: getESLintLineAndColumnFromPostCSSSelectorParserNode(
194
        offsetLocation,
195
        node,
196
        "start",
197
      ),
198
      end: getESLintLineAndColumnFromPostCSSSelectorParserNode(
199
        offsetLocation,
200
        node,
201
        "end",
202
      ),
203
    };
204
    const start = getIndexFromLoc(loc.start);
3,487✔
205
    const end = getIndexFromLoc(loc.end);
3,487✔
206

207
    const astNode = this[typeToConvertMethodName(node.type)](
3,487✔
208
      node as never,
209
      loc,
210
      start,
211
      end,
212
      parent as never,
213
    );
214

215
    if (astNode == null) {
3,487✔
216
      return null;
72✔
217
    }
218

219
    this.parseRawsSpaces(astNode, node, parent);
3,415✔
220

221
    if (isPostCSSSPContainer(node)) {
3,415✔
222
      if (astNode.type === "VCSSSelectorPseudo") {
1,314✔
223
        astNode.nodes = normalizePseudoParams(
179✔
224
          astNode,
225
          this._postcssSelectorParserNodeChiildrenToASTNodes(
226
            offsetLocation,
227
            node as PostCSSSPPseudoNode,
228
            astNode,
229
          ),
230
        );
231
      } else if (astNode.type === "VCSSSelector") {
1,135✔
232
        astNode.nodes = this._postcssSelectorParserNodeChiildrenToASTNodes(
1,135✔
233
          offsetLocation,
234
          node,
235
          astNode,
236
        );
237
      }
238
    }
239

240
    return astNode;
3,415✔
241
  }
242

243
  protected parseRawsSpaces(
244
    astNode: VCSSSelectorNode,
245
    node: PostCSSSPNode,
246
    parent: VCSSStyleRule | VCSSAtRule | VCSSSelector | VCSSSelectorPseudo,
247
  ): void {
248
    if (!hasRaws(node) || !node.raws.spaces) {
3,415✔
249
      return;
2,978✔
250
    }
251
    const { after, before } = node.raws.spaces;
437✔
252
    if (after?.trim()) {
437✔
253
      const selectorParserRoot = this.parseCommentsInternal(after);
12✔
254
      selectorParserRoot.walkComments((comment) => {
12✔
255
        this._postcssSelectorParserNodeToASTNode(
12✔
256
          astNode.loc.end,
257
          comment,
258
          parent,
259
        );
260
      });
261
    }
262
    if (before?.trim()) {
437✔
263
      const startLoc = this.sourceCode.getLocFromIndex(
16✔
264
        astNode.range[0] - before.length,
265
      );
266
      const selectorParserRoot = this.parseCommentsInternal(before);
16✔
267
      selectorParserRoot.walkComments((comment) => {
16✔
268
        this._postcssSelectorParserNodeToASTNode(startLoc, comment, parent);
16✔
269
      });
270
    }
271
  }
272

273
  /**
274
   * Convert selector Node
275
   * @param  {object} node  The node.
276
   * @param  {SourceLocation} loc  The location.
277
   * @param  {number} start  The index of start.
278
   * @param  {number} end  The index of end.
279
   * @param  {Node} parent  The parent node.
280
   * @returns {VCSSSelector}
281
   */
282
  protected convertSelectorNode(
283
    node: PostCSSSPSelector,
284
    loc: SourceLocation,
285
    start: number,
286
    end: number,
287
    parent: VCSSNode,
288
  ): VCSSSelectorNode | null {
289
    const sourceCode = this.sourceCode;
1,135✔
290
    const code = sourceCode.text;
1,135✔
291
    let source = code.slice(start, end);
1,135✔
292
    const beforeSpaces = /^\s+/u.exec(source);
1,135✔
293
    if (beforeSpaces?.[0]) {
1,135✔
294
      // adjust before spaces
295
      // `.foo, .bar`
296
      //       ^
297

298
      // eslint-disable-next-line no-param-reassign -- ignore
299
      start = start + beforeSpaces[0].length;
125✔
300
      loc.start = this.sourceCode.getLocFromIndex(start);
125✔
301
      source = source.slice(beforeSpaces[0].length);
125✔
302
    }
303
    const afterSpaces = /\s+$/u.exec(source);
1,135✔
304
    if (afterSpaces?.[0]) {
1,135✔
305
      // adjust after spaces
306

307
      // eslint-disable-next-line no-param-reassign -- ignore
308
      end = end - afterSpaces[0].length;
2✔
309
      loc.end = this.sourceCode.getLocFromIndex(end);
2✔
310
      source = source.slice(0, -afterSpaces[0].length);
2✔
311
    }
312

313
    adjustBeforeParenLocation();
1,135✔
314
    adjustAfterParenLocation();
1,135✔
315

316
    return new VCSSSelector(node, loc, start, end, {
1,135✔
317
      parent: parent as VCSSStyleRule | VCSSAtRule,
318
    });
319

320
    /**
321
     * Adjust before paren token
322
     * `:not(.bar)`
323
     *      ^
324
     */
325
    function adjustBeforeParenLocation() {
326
      const beforeTrivials = /^\(\s*/u.exec(source);
1,135✔
327
      if (!beforeTrivials?.[0]) return;
1,135!
328
      let withinParen = false;
×
329

330
      for (
×
331
        // Search from `end - 1` since it may be in the current source.
332
        let index = end - 1;
×
333
        index < code.length;
334
        index++
335
      ) {
336
        const ch = code[index];
×
337
        if (ch === ")") {
×
338
          withinParen = true;
×
339
          break;
×
340
        } else if (ch?.trim() && index !== end - 1) {
×
341
          return;
×
342
        }
343
      }
344
      if (!withinParen) return;
×
345
      // eslint-disable-next-line no-param-reassign -- ignore
346
      start = start + beforeTrivials[0].length;
×
347
      loc.start = sourceCode.getLocFromIndex(start);
×
348
      source = source.slice(beforeTrivials[0].length);
×
349
    }
350

351
    /**
352
     * Adjust after paren token
353
     * `:not(.bar)`
354
     *           ^
355
     */
356
    function adjustAfterParenLocation() {
357
      const afterTrivials = /\s*\)$/u.exec(source);
1,135✔
358
      if (!afterTrivials?.[0]) return;
1,135✔
359
      let withinParen = false;
218✔
360
      for (let index = start - 1; index >= 0; index--) {
218✔
361
        const ch = code[index];
936✔
362
        if (ch === "(") {
936✔
363
          withinParen = true;
112✔
364
          break;
112✔
365
        } else if (ch?.trim()) {
824!
366
          return;
106✔
367
        }
368
      }
369
      if (!withinParen) return;
112!
370
      // eslint-disable-next-line no-param-reassign -- ignore
371
      end = end - afterTrivials[0].length;
112✔
372
      loc.end = sourceCode.getLocFromIndex(end);
112✔
373
      source = source.slice(0, -afterTrivials[0].length);
112✔
374
    }
375
  }
376

377
  /**
378
   * Convert tag Node
379
   * @param  {object} node  The node.
380
   * @param  {SourceLocation} loc  The location.
381
   * @param  {number} start  The index of start.
382
   * @param  {number} end  The index of end.
383
   * @param  {Node} parent  The parent node.
384
   * @returns {VCSSTypeSelector}
385
   */
386
  protected convertTagNode(
387
    node: PostCSSSPTypeNode,
388
    loc: SourceLocation,
389
    start: number,
390
    end: number,
391
    parent: VCSSSelector,
392
  ): VCSSSelectorNode | null {
393
    return new VCSSTypeSelector(node, loc, start, end, {
414✔
394
      parent,
395
    });
396
  }
397

398
  /**
399
   * Convert id Node
400
   * @param  {object} node  The node.
401
   * @param  {SourceLocation} loc  The location.
402
   * @param  {number} start  The index of start.
403
   * @param  {number} end  The index of end.
404
   * @param  {Node} parent  The parent node.
405
   * @returns {VCSSIDSelector}
406
   */
407
  protected convertIdNode(
408
    node: PostCSSSPIDNode,
409
    loc: SourceLocation,
410
    start: number,
411
    end: number,
412
    parent: VCSSSelector,
413
  ): VCSSSelectorNode | null {
414
    return new VCSSIDSelector(node, loc, start, end, {
51✔
415
      parent,
416
    });
417
  }
418

419
  /**
420
   * Convert class Node
421
   * @param  {object} node  The node.
422
   * @param  {SourceLocation} loc  The location.
423
   * @param  {number} start  The index of start.
424
   * @param  {number} end  The index of end.
425
   * @param  {Node} parent  The parent node.
426
   * @returns {VCSSClassSelector}
427
   */
428
  protected convertClassNode(
429
    node: PostCSSSPClassNameNode,
430
    loc: SourceLocation,
431
    start: number,
432
    end: number,
433
    parent: VCSSSelector,
434
  ): VCSSSelectorNode | null {
435
    return new VCSSClassSelector(node, loc, start, end, {
920✔
436
      parent,
437
    });
438
  }
439

440
  /**
441
   * Convert nesting Node
442
   * @param  {object} node  The node.
443
   * @param  {SourceLocation} loc  The location.
444
   * @param  {number} start  The index of start.
445
   * @param  {number} end  The index of end.
446
   * @param  {Node} parent  The parent node.
447
   * @returns {VCSSNestingSelector}
448
   */
449
  protected convertNestingNode(
450
    node: PostCSSSPNestingNode,
451
    loc: SourceLocation,
452
    start: number,
453
    end: number,
454
    parent: VCSSSelector,
455
  ): VCSSSelectorNode | null {
456
    return new VCSSNestingSelector(node, loc, start, end, {
201✔
457
      parent,
458
    });
459
  }
460

461
  /**
462
   * Convert universal Node
463
   * @param  {object} node  The node.
464
   * @param  {SourceLocation} loc  The location.
465
   * @param  {number} start  The index of start.
466
   * @param  {number} end  The index of end.
467
   * @param  {Node} parent  The parent node.
468
   * @returns {VCSSUniversalSelector}
469
   */
470
  protected convertUniversalNode(
471
    node: PostCSSSPUniversalNode,
472
    loc: SourceLocation,
473
    start: number,
474
    end: number,
475
    parent: VCSSSelector,
476
  ): VCSSSelectorNode | null {
477
    return new VCSSUniversalSelector(node, loc, start, end, {
6✔
478
      parent,
479
    });
480
  }
481

482
  /**
483
   * Convert attribute Node
484
   * @param  {object} node  The node.
485
   * @param  {SourceLocation} loc  The location.
486
   * @param  {number} start  The index of start.
487
   * @param  {number} end  The index of end.
488
   * @param  {Node} parent  The parent node.
489
   * @returns {VCSSAttributeSelector}
490
   */
491
  protected convertAttributeNode(
492
    node: PostCSSSPAttributeNode,
493
    loc: SourceLocation,
494
    start: number,
495
    end: number,
496
    parent: VCSSSelector,
497
  ): VCSSSelectorNode | null {
498
    return new VCSSAttributeSelector(node, loc, start, end, {
34✔
499
      parent,
500
    });
501
  }
502

503
  /**
504
   * Convert pseudo Node
505
   * @param  {object} node  The node.
506
   * @param  {SourceLocation} loc  The location.
507
   * @param  {number} start  The index of start.
508
   * @param  {number} end  The index of end.
509
   * @param  {Node} parent  The parent node.
510
   * @returns {VCSSSelectorPseudo}
511
   */
512
  protected convertPseudoNode(
513
    node: PostCSSSPPseudoNode,
514
    loc: SourceLocation,
515
    start: number,
516
    end: number,
517
    parent: VCSSSelector,
518
  ): VCSSSelectorNode | null {
519
    return new VCSSSelectorPseudo(node, loc, start, end, {
179✔
520
      parent,
521
    });
522
  }
523

524
  /**
525
   * Convert combinator Node
526
   * @param  {object} node  The node.
527
   * @param  {SourceLocation} loc  The location.
528
   * @param  {number} start  The index of start.
529
   * @param  {number} end  The index of end.
530
   * @param  {Node} parent  The parent node.
531
   * @returns {VCSSSelectorCombinator}
532
   */
533
  protected convertCombinatorNode(
534
    node: PostCSSSPCombinatorNode,
535
    loc: SourceLocation,
536
    start: number,
537
    end: number,
538
    parent: VCSSSelector,
539
  ): VCSSSelectorNode | null {
540
    const astNode = new VCSSSelectorCombinator(node, loc, start, end, {
469✔
541
      parent,
542
    });
543
    // The end index of Deep Combinator may be invalid, so adjust it.
544
    adjustEndLocation(astNode, start + astNode.value.length, this.sourceCode);
469✔
545
    return astNode;
469✔
546
  }
547

548
  /**
549
   * Convert string Node
550
   * @param  {object} node  The node.
551
   * @param  {SourceLocation} loc  The location.
552
   * @param  {number} start  The index of start.
553
   * @param  {number} end  The index of end.
554
   * @param  {Node} parent  The parent node.
555
   * @returns {VCSSSelectorCombinator}
556
   */
557
  protected convertStringNode(
558
    node: PostCSSSPStringNode,
559
    loc: SourceLocation,
560
    start: number,
561
    end: number,
562
    parent: VCSSSelector,
563
  ): VCSSSelectorNode | null {
564
    // unknown string
565
    const astNode = new VCSSUnknownSelector(node, loc, start, end, {
6✔
566
      parent,
567
    });
568
    // The end index may be invalid, so adjust it.
569
    adjustEndLocation(astNode, start + astNode.value.length, this.sourceCode);
6✔
570
    return astNode;
6✔
571
  }
572

573
  /**
574
   * Convert comment Node
575
   * @param  {object} node  The node.
576
   * @param  {SourceLocation} loc  The location.
577
   * @param  {number} start  The index of start.
578
   * @param  {number} end  The index of end.
579
   * @param  {Node} parent  The parent node.
580
   * @returns {null}
581
   */
582
  protected convertCommentNode(
583
    node: PostCSSSPCommentNode,
584
    loc: SourceLocation,
585
    start: number,
586
    end: number,
587
    parent: VCSSSelector,
588
  ): VCSSSelectorNode | null {
589
    const text = node.value.replace(/^\s*\/\*/u, "").replace(/\*\/\s*$/u, "");
60✔
590
    this.commentContainer.push(
60✔
591
      new VCSSComment(node, text, loc, start, end, {
592
        parent,
593
      }),
594
    );
595

596
    return null;
60✔
597
  }
598

599
  /**
600
   * Convert unknown Node
601
   * @param  {object} node  The node.
602
   * @param  {SourceLocation} loc  The location.
603
   * @param  {number} start  The index of start.
604
   * @param  {number} end  The index of end.
605
   * @param  {Node} parent  The parent node.
606
   * @returns {Node}
607
   */
608
  protected convertUnknownTypeNode(
609
    node: PostCSSSPNode,
610
    loc: SourceLocation,
611
    start: number,
612
    end: number,
613
    parent: VCSSSelector,
614
  ): VCSSUnknownSelector {
615
    return new VCSSUnknownSelector(node, loc, start, end, {
×
616
      parent,
617
    });
618
  }
619
}
620

621
/**
622
 * Convert `postcss-selector-parser` location to ESLint location.
623
 * @param {LineAndColumnData} offsetLocation start location of selector.
624
 * @param {object} node the `postcss-selector-parser` node to comvert
625
 * @param {"start"|"end"} locName the name of location
626
 * @return {LineAndColumnData} converted location.
627
 */
628
function getESLintLineAndColumnFromPostCSSSelectorParserNode(
629
  offsetLocation: LineAndColumnData,
630
  node: PostCSSSPNode,
631
  locName: "start" | "end",
632
) {
633
  const sourceLoc = (node.source && node.source[locName]) || {
6,974✔
634
    line: 0,
635
    column: 1,
636
  };
637
  let { line } = sourceLoc;
6,974✔
638
  let column = sourceLoc.column - 1; // Change to 0 base.
6,974✔
639
  if (line === 1) {
6,974✔
640
    line = offsetLocation.line;
6,298✔
641
    column = offsetLocation.column + column;
6,298✔
642
  } else {
643
    line = offsetLocation.line + line - 1;
676✔
644
  }
645
  if (locName === "end") {
6,974✔
646
    // End column is shifted by one.
647
    column++;
3,487✔
648
  }
649
  return { line, column };
6,974✔
650
}
651

652
/**
653
 * Adjust end location
654
 */
655
function adjustEndLocation(
656
  astNode: VCSSSelectorCombinator | VCSSUnknownSelector,
657
  endIndex: number,
658
  sourceCode: SourceCode,
659
) {
660
  if (astNode.range[1] === endIndex) {
475✔
661
    return;
437✔
662
  }
663
  astNode.range[1] = endIndex;
38✔
664
  astNode.end = endIndex;
38✔
665
  astNode.loc.end = sourceCode.getLocFromIndex(endIndex);
38✔
666

667
  // update parent locations
668
  let p: VCSSContainerNode | VCSSSelector | VCSSSelectorPseudo = astNode.parent;
38✔
669
  while (p && p.end < endIndex) {
38✔
670
    p.end = endIndex;
6✔
671
    p.range[1] = endIndex;
6✔
672
    p.loc.end = { ...astNode.loc.end };
6✔
673

674
    p = p.parent;
6✔
675
  }
676
}
677

678
/**
679
 * Remove invalid descendant combinators
680
 */
681
function removeInvalidDescendantCombinator(
682
  nodes: VCSSSelectorNode[],
683
): VCSSSelectorNode[] {
684
  const results = [];
2,202✔
685

686
  let prev = null;
2,202✔
687
  for (let index = 0; index < nodes.length; index++) {
2,202✔
688
    const node = nodes[index];
3,415✔
689
    if (isDescendantCombinator(node)) {
3,415✔
690
      if (results.length === 0) {
220!
691
        continue;
×
692
      }
693
      if (isSelectorCombinator(prev) || isVDeepPseudoV2(prev)) {
220✔
694
        continue;
27✔
695
      }
696
      const next = nodes[index + 1];
193✔
697
      if (isSelectorCombinator(next)) {
193!
698
        continue;
×
699
      }
700
    } else if (isVueSpecialPseudo(node)) {
3,195✔
701
      if (prev && !isSelectorCombinator(prev)) {
119!
702
        results.push(
×
703
          new VCSSSelectorCombinator(
704
            node.node as never,
705
            node.loc,
706
            node.start,
707
            node.end,
708
            { parent: node.parent, value: " " },
709
          ),
710
        );
711
      }
712
    }
713
    results.push(node);
3,388✔
714
    prev = node;
3,388✔
715
  }
716

717
  return results;
2,202✔
718
}
719

720
interface ConvertNodeTypes {
721
  tag: "convertTagNode";
722
  string: "convertStringNode";
723
  selector: "convertSelectorNode";
724
  pseudo: "convertPseudoNode";
725
  nesting: "convertNestingNode";
726
  id: "convertIdNode";
727
  comment: "convertCommentNode";
728
  combinator: "convertCombinatorNode";
729
  class: "convertClassNode";
730
  attribute: "convertAttributeNode";
731
  universal: "convertUniversalNode";
732
}
733
const convertNodeTypes: ConvertNodeTypes = {
1✔
734
  tag: "convertTagNode",
735
  string: "convertStringNode",
736
  selector: "convertSelectorNode",
737
  pseudo: "convertPseudoNode",
738
  nesting: "convertNestingNode",
739
  id: "convertIdNode",
740
  comment: "convertCommentNode",
741
  combinator: "convertCombinatorNode",
742
  class: "convertClassNode",
743
  attribute: "convertAttributeNode",
744
  universal: "convertUniversalNode",
745
};
746

747
/**
748
 * Get convert method name from given type
749
 */
750
function typeToConvertMethodName(
751
  type: keyof ConvertNodeTypes | "root",
752
): "convertUnknownTypeNode" | ConvertNodeTypes[keyof ConvertNodeTypes] {
753
  if (type === "root") {
3,487!
754
    return "convertUnknownTypeNode";
×
755
  }
756
  return convertNodeTypes[type] || "convertUnknownTypeNode";
3,487!
757
}
758

759
/**
760
 * Checks whether has raws
761
 */
762
function hasRaws(
763
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- check to prop
764
  node: any,
765
): node is { raws: { spaces: { after?: string; before?: string } } } {
766
  return node.raws != null;
3,415✔
767
}
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

© 2025 Coveralls, Inc