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

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

pending completion
14774400750

Pull #392

github

web-flow
Merge bc68239aa into 8e9b9c3d4
Pull Request #392: chore(deps): update dependency eslint-plugin-node-dependencies to v1

1454 of 1669 branches covered (87.12%)

Branch coverage included in aggregate %.

2778 of 2917 relevant lines covered (95.23%)

406.21 hits per line

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

84.65
/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
    const loc = {
3,487✔
178
      start: getESLintLineAndColumnFromPostCSSSelectorParserNode(
179
        offsetLocation,
180
        node,
181
        "start",
182
      ),
183
      end: getESLintLineAndColumnFromPostCSSSelectorParserNode(
184
        offsetLocation,
185
        node,
186
        "end",
187
      ),
188
    };
189
    const start = sourceCode.getIndexFromLoc(loc.start);
3,487✔
190
    const end = sourceCode.getIndexFromLoc(loc.end);
3,487✔
191

192
    const astNode = this[typeToConvertMethodName(node.type)](
3,487✔
193
      node as never,
194
      loc,
195
      start,
196
      end,
197
      parent as never,
198
    );
199

200
    if (astNode == null) {
3,487✔
201
      return null;
72✔
202
    }
203

204
    this.parseRawsSpaces(astNode, node, parent);
3,415✔
205

206
    if (isPostCSSSPContainer(node)) {
3,415✔
207
      if (astNode.type === "VCSSSelectorPseudo") {
1,314✔
208
        astNode.nodes = normalizePseudoParams(
179✔
209
          astNode,
210
          this._postcssSelectorParserNodeChiildrenToASTNodes(
211
            offsetLocation,
212
            node as PostCSSSPPseudoNode,
213
            astNode,
214
          ),
215
        );
216
      } else if (astNode.type === "VCSSSelector") {
1,135✔
217
        astNode.nodes = this._postcssSelectorParserNodeChiildrenToASTNodes(
1,135✔
218
          offsetLocation,
219
          node,
220
          astNode,
221
        );
222
      }
223
    }
224

225
    return astNode;
3,415✔
226
  }
227

228
  protected parseRawsSpaces(
229
    astNode: VCSSSelectorNode,
230
    node: PostCSSSPNode,
231
    parent: VCSSStyleRule | VCSSAtRule | VCSSSelector | VCSSSelectorPseudo,
232
  ): void {
233
    if (!hasRaws(node) || !node.raws.spaces) {
3,415✔
234
      return;
2,978✔
235
    }
236
    const { after, before } = node.raws.spaces;
437✔
237
    if (after?.trim()) {
437✔
238
      const selectorParserRoot = this.parseCommentsInternal(after);
12✔
239
      selectorParserRoot.walkComments((comment) => {
12✔
240
        this._postcssSelectorParserNodeToASTNode(
12✔
241
          astNode.loc.end,
242
          comment,
243
          parent,
244
        );
245
      });
246
    }
247
    if (before?.trim()) {
437✔
248
      const startLoc = this.sourceCode.getLocFromIndex(
16✔
249
        astNode.range[0] - before.length,
250
      );
251
      const selectorParserRoot = this.parseCommentsInternal(before);
16✔
252
      selectorParserRoot.walkComments((comment) => {
16✔
253
        this._postcssSelectorParserNodeToASTNode(startLoc, comment, parent);
16✔
254
      });
255
    }
256
  }
257

258
  /**
259
   * Convert selector Node
260
   * @param  {object} node  The node.
261
   * @param  {SourceLocation} loc  The location.
262
   * @param  {number} start  The index of start.
263
   * @param  {number} end  The index of end.
264
   * @param  {Node} parent  The parent node.
265
   * @returns {VCSSSelector}
266
   */
267
  protected convertSelectorNode(
268
    node: PostCSSSPSelector,
269
    loc: SourceLocation,
270
    start: number,
271
    end: number,
272
    parent: VCSSNode,
273
  ): VCSSSelectorNode | null {
274
    const sourceCode = this.sourceCode;
1,135✔
275
    const code = sourceCode.text;
1,135✔
276
    let source = code.slice(start, end);
1,135✔
277
    const beforeSpaces = /^\s+/u.exec(source);
1,135✔
278
    if (beforeSpaces?.[0]) {
1,135✔
279
      // adjust before spaces
280
      // `.foo, .bar`
281
      //       ^
282

283
      // eslint-disable-next-line no-param-reassign -- ignore
284
      start = start + beforeSpaces[0].length;
125✔
285
      loc.start = this.sourceCode.getLocFromIndex(start);
125✔
286
      source = source.slice(beforeSpaces[0].length);
125✔
287
    }
288
    const afterSpaces = /\s+$/u.exec(source);
1,135✔
289
    if (afterSpaces?.[0]) {
1,135✔
290
      // adjust after spaces
291

292
      // eslint-disable-next-line no-param-reassign -- ignore
293
      end = end - afterSpaces[0].length;
2✔
294
      loc.end = this.sourceCode.getLocFromIndex(end);
2✔
295
      source = source.slice(0, -afterSpaces[0].length);
2✔
296
    }
297

298
    adjustBeforeParenLocation();
1,135✔
299
    adjustAfterParenLocation();
1,135✔
300

301
    return new VCSSSelector(node, loc, start, end, {
1,135✔
302
      parent: parent as VCSSStyleRule | VCSSAtRule,
303
    });
304

305
    /**
306
     * Adjust before paren token
307
     * `:not(.bar)`
308
     *      ^
309
     */
310
    function adjustBeforeParenLocation() {
311
      const beforeTrivials = /^\(\s*/u.exec(source);
1,135✔
312
      if (!beforeTrivials?.[0]) return;
1,135!
313
      let withinParen = false;
×
314

315
      for (
×
316
        // Search from `end - 1` since it may be in the current source.
317
        let index = end - 1;
×
318
        index < code.length;
319
        index++
320
      ) {
321
        const ch = code[index];
×
322
        if (ch === ")") {
×
323
          withinParen = true;
×
324
          break;
×
325
        } else if (ch?.trim() && index !== end - 1) {
×
326
          return;
×
327
        }
328
      }
329
      if (!withinParen) return;
×
330
      // eslint-disable-next-line no-param-reassign -- ignore
331
      start = start + beforeTrivials[0].length;
×
332
      loc.start = sourceCode.getLocFromIndex(start);
×
333
      source = source.slice(beforeTrivials[0].length);
×
334
    }
335

336
    /**
337
     * Adjust after paren token
338
     * `:not(.bar)`
339
     *           ^
340
     */
341
    function adjustAfterParenLocation() {
342
      const afterTrivials = /\s*\)$/u.exec(source);
1,135✔
343
      if (!afterTrivials?.[0]) return;
1,135✔
344
      let withinParen = false;
218✔
345
      for (let index = start - 1; index >= 0; index--) {
218✔
346
        const ch = code[index];
936✔
347
        if (ch === "(") {
936✔
348
          withinParen = true;
112✔
349
          break;
112✔
350
        } else if (ch?.trim()) {
824!
351
          return;
106✔
352
        }
353
      }
354
      if (!withinParen) return;
112!
355
      // eslint-disable-next-line no-param-reassign -- ignore
356
      end = end - afterTrivials[0].length;
112✔
357
      loc.end = sourceCode.getLocFromIndex(end);
112✔
358
      source = source.slice(0, -afterTrivials[0].length);
112✔
359
    }
360
  }
361

362
  /**
363
   * Convert tag Node
364
   * @param  {object} node  The node.
365
   * @param  {SourceLocation} loc  The location.
366
   * @param  {number} start  The index of start.
367
   * @param  {number} end  The index of end.
368
   * @param  {Node} parent  The parent node.
369
   * @returns {VCSSTypeSelector}
370
   */
371
  protected convertTagNode(
372
    node: PostCSSSPTypeNode,
373
    loc: SourceLocation,
374
    start: number,
375
    end: number,
376
    parent: VCSSSelector,
377
  ): VCSSSelectorNode | null {
378
    return new VCSSTypeSelector(node, loc, start, end, {
414✔
379
      parent,
380
    });
381
  }
382

383
  /**
384
   * Convert id Node
385
   * @param  {object} node  The node.
386
   * @param  {SourceLocation} loc  The location.
387
   * @param  {number} start  The index of start.
388
   * @param  {number} end  The index of end.
389
   * @param  {Node} parent  The parent node.
390
   * @returns {VCSSIDSelector}
391
   */
392
  protected convertIdNode(
393
    node: PostCSSSPIDNode,
394
    loc: SourceLocation,
395
    start: number,
396
    end: number,
397
    parent: VCSSSelector,
398
  ): VCSSSelectorNode | null {
399
    return new VCSSIDSelector(node, loc, start, end, {
51✔
400
      parent,
401
    });
402
  }
403

404
  /**
405
   * Convert class Node
406
   * @param  {object} node  The node.
407
   * @param  {SourceLocation} loc  The location.
408
   * @param  {number} start  The index of start.
409
   * @param  {number} end  The index of end.
410
   * @param  {Node} parent  The parent node.
411
   * @returns {VCSSClassSelector}
412
   */
413
  protected convertClassNode(
414
    node: PostCSSSPClassNameNode,
415
    loc: SourceLocation,
416
    start: number,
417
    end: number,
418
    parent: VCSSSelector,
419
  ): VCSSSelectorNode | null {
420
    return new VCSSClassSelector(node, loc, start, end, {
920✔
421
      parent,
422
    });
423
  }
424

425
  /**
426
   * Convert nesting Node
427
   * @param  {object} node  The node.
428
   * @param  {SourceLocation} loc  The location.
429
   * @param  {number} start  The index of start.
430
   * @param  {number} end  The index of end.
431
   * @param  {Node} parent  The parent node.
432
   * @returns {VCSSNestingSelector}
433
   */
434
  protected convertNestingNode(
435
    node: PostCSSSPNestingNode,
436
    loc: SourceLocation,
437
    start: number,
438
    end: number,
439
    parent: VCSSSelector,
440
  ): VCSSSelectorNode | null {
441
    return new VCSSNestingSelector(node, loc, start, end, {
201✔
442
      parent,
443
    });
444
  }
445

446
  /**
447
   * Convert universal Node
448
   * @param  {object} node  The node.
449
   * @param  {SourceLocation} loc  The location.
450
   * @param  {number} start  The index of start.
451
   * @param  {number} end  The index of end.
452
   * @param  {Node} parent  The parent node.
453
   * @returns {VCSSUniversalSelector}
454
   */
455
  protected convertUniversalNode(
456
    node: PostCSSSPUniversalNode,
457
    loc: SourceLocation,
458
    start: number,
459
    end: number,
460
    parent: VCSSSelector,
461
  ): VCSSSelectorNode | null {
462
    return new VCSSUniversalSelector(node, loc, start, end, {
6✔
463
      parent,
464
    });
465
  }
466

467
  /**
468
   * Convert attribute Node
469
   * @param  {object} node  The node.
470
   * @param  {SourceLocation} loc  The location.
471
   * @param  {number} start  The index of start.
472
   * @param  {number} end  The index of end.
473
   * @param  {Node} parent  The parent node.
474
   * @returns {VCSSAttributeSelector}
475
   */
476
  protected convertAttributeNode(
477
    node: PostCSSSPAttributeNode,
478
    loc: SourceLocation,
479
    start: number,
480
    end: number,
481
    parent: VCSSSelector,
482
  ): VCSSSelectorNode | null {
483
    return new VCSSAttributeSelector(node, loc, start, end, {
34✔
484
      parent,
485
    });
486
  }
487

488
  /**
489
   * Convert pseudo Node
490
   * @param  {object} node  The node.
491
   * @param  {SourceLocation} loc  The location.
492
   * @param  {number} start  The index of start.
493
   * @param  {number} end  The index of end.
494
   * @param  {Node} parent  The parent node.
495
   * @returns {VCSSSelectorPseudo}
496
   */
497
  protected convertPseudoNode(
498
    node: PostCSSSPPseudoNode,
499
    loc: SourceLocation,
500
    start: number,
501
    end: number,
502
    parent: VCSSSelector,
503
  ): VCSSSelectorNode | null {
504
    return new VCSSSelectorPseudo(node, loc, start, end, {
179✔
505
      parent,
506
    });
507
  }
508

509
  /**
510
   * Convert combinator Node
511
   * @param  {object} node  The node.
512
   * @param  {SourceLocation} loc  The location.
513
   * @param  {number} start  The index of start.
514
   * @param  {number} end  The index of end.
515
   * @param  {Node} parent  The parent node.
516
   * @returns {VCSSSelectorCombinator}
517
   */
518
  protected convertCombinatorNode(
519
    node: PostCSSSPCombinatorNode,
520
    loc: SourceLocation,
521
    start: number,
522
    end: number,
523
    parent: VCSSSelector,
524
  ): VCSSSelectorNode | null {
525
    const astNode = new VCSSSelectorCombinator(node, loc, start, end, {
469✔
526
      parent,
527
    });
528
    // The end index of Deep Combinator may be invalid, so adjust it.
529
    adjustEndLocation(astNode, start + astNode.value.length, this.sourceCode);
469✔
530
    return astNode;
469✔
531
  }
532

533
  /**
534
   * Convert string Node
535
   * @param  {object} node  The node.
536
   * @param  {SourceLocation} loc  The location.
537
   * @param  {number} start  The index of start.
538
   * @param  {number} end  The index of end.
539
   * @param  {Node} parent  The parent node.
540
   * @returns {VCSSSelectorCombinator}
541
   */
542
  protected convertStringNode(
543
    node: PostCSSSPStringNode,
544
    loc: SourceLocation,
545
    start: number,
546
    end: number,
547
    parent: VCSSSelector,
548
  ): VCSSSelectorNode | null {
549
    // unknown string
550
    const astNode = new VCSSUnknownSelector(node, loc, start, end, {
6✔
551
      parent,
552
    });
553
    // The end index may be invalid, so adjust it.
554
    adjustEndLocation(astNode, start + astNode.value.length, this.sourceCode);
6✔
555
    return astNode;
6✔
556
  }
557

558
  /**
559
   * Convert comment Node
560
   * @param  {object} node  The node.
561
   * @param  {SourceLocation} loc  The location.
562
   * @param  {number} start  The index of start.
563
   * @param  {number} end  The index of end.
564
   * @param  {Node} parent  The parent node.
565
   * @returns {null}
566
   */
567
  protected convertCommentNode(
568
    node: PostCSSSPCommentNode,
569
    loc: SourceLocation,
570
    start: number,
571
    end: number,
572
    parent: VCSSSelector,
573
  ): VCSSSelectorNode | null {
574
    const text = node.value.replace(/^\s*\/\*/u, "").replace(/\*\/\s*$/u, "");
60✔
575
    this.commentContainer.push(
60✔
576
      new VCSSComment(node, text, loc, start, end, {
577
        parent,
578
      }),
579
    );
580

581
    return null;
60✔
582
  }
583

584
  /**
585
   * Convert unknown Node
586
   * @param  {object} node  The node.
587
   * @param  {SourceLocation} loc  The location.
588
   * @param  {number} start  The index of start.
589
   * @param  {number} end  The index of end.
590
   * @param  {Node} parent  The parent node.
591
   * @returns {Node}
592
   */
593
  protected convertUnknownTypeNode(
594
    node: PostCSSSPNode,
595
    loc: SourceLocation,
596
    start: number,
597
    end: number,
598
    parent: VCSSSelector,
599
  ): VCSSUnknownSelector {
600
    return new VCSSUnknownSelector(node, loc, start, end, {
×
601
      parent,
602
    });
603
  }
604
}
605

606
/**
607
 * Convert `postcss-selector-parser` location to ESLint location.
608
 * @param {LineAndColumnData} offsetLocation start location of selector.
609
 * @param {object} node the `postcss-selector-parser` node to comvert
610
 * @param {"start"|"end"} locName the name of location
611
 * @return {LineAndColumnData} converted location.
612
 */
613
function getESLintLineAndColumnFromPostCSSSelectorParserNode(
614
  offsetLocation: LineAndColumnData,
615
  node: PostCSSSPNode,
616
  locName: "start" | "end",
617
) {
618
  const sourceLoc = (node.source && node.source[locName]) || {
6,974✔
619
    line: 0,
620
    column: 1,
621
  };
622
  let { line } = sourceLoc;
6,974✔
623
  let column = sourceLoc.column - 1; // Change to 0 base.
6,974✔
624
  if (line === 1) {
6,974✔
625
    line = offsetLocation.line;
6,298✔
626
    column = offsetLocation.column + column;
6,298✔
627
  } else {
628
    line = offsetLocation.line + line - 1;
676✔
629
  }
630
  if (locName === "end") {
6,974✔
631
    // End column is shifted by one.
632
    column++;
3,487✔
633
  }
634
  return { line, column };
6,974✔
635
}
636

637
/**
638
 * Adjust end location
639
 */
640
function adjustEndLocation(
641
  astNode: VCSSSelectorCombinator | VCSSUnknownSelector,
642
  endIndex: number,
643
  sourceCode: SourceCode,
644
) {
645
  if (astNode.range[1] === endIndex) {
475✔
646
    return;
437✔
647
  }
648
  astNode.range[1] = endIndex;
38✔
649
  astNode.end = endIndex;
38✔
650
  astNode.loc.end = sourceCode.getLocFromIndex(endIndex);
38✔
651

652
  // update parent locations
653
  let p: VCSSContainerNode | VCSSSelector | VCSSSelectorPseudo = astNode.parent;
38✔
654
  while (p && p.end < endIndex) {
38✔
655
    p.end = endIndex;
6✔
656
    p.range[1] = endIndex;
6✔
657
    p.loc.end = { ...astNode.loc.end };
6✔
658

659
    p = p.parent;
6✔
660
  }
661
}
662

663
/**
664
 * Remove invalid descendant combinators
665
 */
666
function removeInvalidDescendantCombinator(
667
  nodes: VCSSSelectorNode[],
668
): VCSSSelectorNode[] {
669
  const results = [];
2,202✔
670

671
  let prev = null;
2,202✔
672
  for (let index = 0; index < nodes.length; index++) {
2,202✔
673
    const node = nodes[index];
3,415✔
674
    if (isDescendantCombinator(node)) {
3,415✔
675
      if (results.length === 0) {
220!
676
        continue;
×
677
      }
678
      if (isSelectorCombinator(prev) || isVDeepPseudoV2(prev)) {
220✔
679
        continue;
27✔
680
      }
681
      const next = nodes[index + 1];
193✔
682
      if (isSelectorCombinator(next)) {
193!
683
        continue;
×
684
      }
685
    } else if (isVueSpecialPseudo(node)) {
3,195✔
686
      if (prev && !isSelectorCombinator(prev)) {
119!
687
        results.push(
×
688
          new VCSSSelectorCombinator(
689
            node.node as never,
690
            node.loc,
691
            node.start,
692
            node.end,
693
            { parent: node.parent, value: " " },
694
          ),
695
        );
696
      }
697
    }
698
    results.push(node);
3,388✔
699
    prev = node;
3,388✔
700
  }
701

702
  return results;
2,202✔
703
}
704

705
interface ConvertNodeTypes {
706
  tag: "convertTagNode";
707
  string: "convertStringNode";
708
  selector: "convertSelectorNode";
709
  pseudo: "convertPseudoNode";
710
  nesting: "convertNestingNode";
711
  id: "convertIdNode";
712
  comment: "convertCommentNode";
713
  combinator: "convertCombinatorNode";
714
  class: "convertClassNode";
715
  attribute: "convertAttributeNode";
716
  universal: "convertUniversalNode";
717
}
718
const convertNodeTypes: ConvertNodeTypes = {
1✔
719
  tag: "convertTagNode",
720
  string: "convertStringNode",
721
  selector: "convertSelectorNode",
722
  pseudo: "convertPseudoNode",
723
  nesting: "convertNestingNode",
724
  id: "convertIdNode",
725
  comment: "convertCommentNode",
726
  combinator: "convertCombinatorNode",
727
  class: "convertClassNode",
728
  attribute: "convertAttributeNode",
729
  universal: "convertUniversalNode",
730
};
731

732
/**
733
 * Get convert method name from given type
734
 */
735
function typeToConvertMethodName(
736
  type: keyof ConvertNodeTypes | "root",
737
): "convertUnknownTypeNode" | ConvertNodeTypes[keyof ConvertNodeTypes] {
738
  if (type === "root") {
3,487!
739
    return "convertUnknownTypeNode";
×
740
  }
741
  return convertNodeTypes[type] || "convertUnknownTypeNode";
3,487!
742
}
743

744
/**
745
 * Checks whether has raws
746
 */
747
function hasRaws(
748
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- check to prop
749
  node: any,
750
): node is { raws: { spaces: { after?: string; before?: string } } } {
751
  return node.raws != null;
3,415✔
752
}
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