Skip to content

Commit e6d952f

Browse files
authored
Fix false positive for v-for in no-unused-selector (#180)
1 parent 7d0f30e commit e6d952f

File tree

3 files changed

+192
-95
lines changed

3 files changed

+192
-95
lines changed

lib/styles/selectors/query/index.ts

Lines changed: 136 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -443,105 +443,118 @@ function* genChildElements(
443443
/**
444444
* Get the parent elements.
445445
*/
446-
function* genParentElements(
446+
function genParentElements(
447447
elements: AST.VElement[],
448448
): IterableIterator<AST.VElement> {
449-
const found = new Set<AST.VElement>()
450-
for (const element of elements) {
451-
const parent = getParentElement(element)
452-
if (parent) {
453-
if (!found.has(parent)) {
454-
yield parent
455-
found.add(parent)
456-
}
449+
return iterateUnique(function* () {
450+
for (const element of elements) {
451+
yield getParentElement(element)
457452
}
458-
}
453+
})
459454
}
460455

461456
/**
462457
* Get the adjacent sibling elements.
463458
*/
464-
function* genAdjacentSiblingElements(
459+
function genAdjacentSiblingElements(
465460
elements: AST.VElement[],
466461
): IterableIterator<AST.VElement> {
467-
for (const element of elements) {
468-
const parent = getParentElement(element)
469-
if (parent == null) {
470-
continue
471-
}
472-
const children = [...genChildElements([parent])]
473-
const index = children.indexOf(element)
474-
const e = children[index + 1]
475-
if (e) {
476-
yield e
462+
return iterateUnique(function* () {
463+
for (const element of elements) {
464+
if (hasVFor(element)) {
465+
yield element
466+
}
467+
const vForTemplate = getVForTemplate(element)
468+
if (vForTemplate) {
469+
const children = [...genChildElements([vForTemplate])]
470+
const index = children.indexOf(element)
471+
yield children[index + 1] || children[0]
472+
}
473+
const parent = getParentElement(element)
474+
if (parent) {
475+
const children = [...genChildElements([parent])]
476+
const index = children.indexOf(element)
477+
yield children[index + 1]
478+
}
477479
}
478-
}
480+
})
479481
}
480482

481483
/**
482484
* Gets the previous adjacent sibling elements.
483485
*/
484-
function* genPrevAdjacentSiblingElements(
486+
function genPrevAdjacentSiblingElements(
485487
elements: AST.VElement[],
486488
): IterableIterator<AST.VElement> {
487-
for (const element of elements) {
488-
const parent = getParentElement(element)
489-
if (parent == null) {
490-
continue
491-
}
492-
const children = [...genChildElements([parent])]
493-
const index = children.indexOf(element)
494-
const e = children[index - 1]
495-
if (e) {
496-
yield e
489+
return iterateUnique(function* () {
490+
for (const element of elements) {
491+
if (hasVFor(element)) {
492+
yield element
493+
}
494+
const vForTemplate = getVForTemplate(element)
495+
if (vForTemplate) {
496+
const children = [...genChildElements([vForTemplate])]
497+
const index = children.indexOf(element)
498+
yield children[index - 1] || children[children.length - 1]
499+
}
500+
const parent = getParentElement(element)
501+
if (parent) {
502+
const children = [...genChildElements([parent])]
503+
const index = children.indexOf(element)
504+
yield children[index - 1]
505+
}
497506
}
498-
}
507+
})
499508
}
500509

501510
/**
502511
* Gets the general sibling elements.
503512
*/
504-
function* genGeneralSiblingElements(
513+
function genGeneralSiblingElements(
505514
elements: AST.VElement[],
506515
): IterableIterator<AST.VElement> {
507-
const found = new Set<AST.VElement>()
508-
for (const element of elements) {
509-
const parent = getParentElement(element)
510-
if (parent == null) {
511-
continue
512-
}
513-
const children = [...genChildElements([parent])]
514-
const index = children.indexOf(element)
515-
for (const n of children.slice(index + 1)) {
516-
if (!found.has(n)) {
517-
yield n
518-
found.add(n)
516+
return iterateUnique(function* () {
517+
for (const element of elements) {
518+
if (hasVFor(element)) {
519+
yield element
520+
}
521+
const vForTemplate = getVForTemplate(element)
522+
if (vForTemplate) {
523+
yield* genChildElements([vForTemplate])
524+
}
525+
const parent = getParentElement(element)
526+
if (parent) {
527+
const children = [...genChildElements([parent])]
528+
const index = children.indexOf(element)
529+
yield* children.slice(index + 1)
519530
}
520531
}
521-
}
532+
})
522533
}
523534

524535
/**
525536
* Gets the previous general sibling elements.
526537
*/
527-
function* genPrevGeneralSiblingElements(
538+
function genPrevGeneralSiblingElements(
528539
elements: AST.VElement[],
529540
): IterableIterator<AST.VElement> {
530-
const found = new Set<AST.VElement>()
531-
for (const element of elements) {
532-
const parent = getParentElement(element)
533-
if (parent == null) {
534-
continue
535-
}
536-
const children = [...genChildElements([parent])]
537-
const index = children.indexOf(element)
538-
for (const p of children.slice(0, index)) {
539-
if (!found.has(p)) {
540-
yield p
541-
found.add(p)
541+
return iterateUnique(function* () {
542+
for (const element of elements) {
543+
if (hasVFor(element)) {
544+
yield element
545+
}
546+
const vForTemplate = getVForTemplate(element)
547+
if (vForTemplate) {
548+
yield* genChildElements([vForTemplate])
549+
}
550+
const parent = getParentElement(element)
551+
if (parent) {
552+
const children = [...genChildElements([parent])]
553+
const index = children.indexOf(element)
554+
yield* children.slice(0, index)
542555
}
543556
}
544-
}
557+
})
545558
}
546559

547560
/**
@@ -556,16 +569,11 @@ function* genVDeepElements(
556569
) => AST.VElement[],
557570
): IterableIterator<AST.VElement> {
558571
if (params.length) {
559-
const found = new Set<AST.VElement>()
560-
for (const node of params) {
561-
const els = query(elements, node.nodes)
562-
for (const e of els) {
563-
if (!found.has(e)) {
564-
yield e
565-
found.add(e)
566-
}
572+
yield* iterateUnique(function* () {
573+
for (const node of params) {
574+
yield* query(elements, node.nodes)
567575
}
568-
}
576+
})
569577
} else {
570578
yield* elements
571579
}
@@ -574,33 +582,30 @@ function* genVDeepElements(
574582
/**
575583
* Gets the v-slotted elements
576584
*/
577-
function* genVSlottedElements(
585+
function genVSlottedElements(
578586
elements: AST.VElement[],
579587
params: VCSSSelector[],
580588
query: (
581589
els: AST.VElement[],
582590
selList: VCSSSelectorValueNode[],
583591
) => AST.VElement[],
584592
): IterableIterator<AST.VElement> {
585-
const found = new Set<AST.VElement>()
586-
for (const element of elements) {
587-
if (isSlotElement(element)) {
588-
if (!found.has(element)) {
593+
return iterateUnique(function* () {
594+
for (const element of elements) {
595+
if (isSlotElement(element)) {
589596
yield element
590-
found.add(element)
591597
}
592598
}
593-
}
594599

595-
for (const node of params) {
596-
const els = query(elements, node.nodes)
597-
for (const e of els) {
598-
if (!found.has(e) && inSlot(e)) {
599-
yield e
600-
found.add(e)
600+
for (const node of params) {
601+
const els = query(elements, node.nodes)
602+
for (const e of els) {
603+
if (inSlot(e)) {
604+
yield e
605+
}
601606
}
602607
}
603-
}
608+
})
604609

605610
/**
606611
* Checks if givin element within slot
@@ -616,7 +621,7 @@ function* genVSlottedElements(
616621
/**
617622
* Gets the v-global elements
618623
*/
619-
function* genVGlobalElements(
624+
function genVGlobalElements(
620625
_elements: AST.VElement[],
621626
params: VCSSSelector[],
622627
document: VueDocumentQueryContext,
@@ -625,16 +630,11 @@ function* genVGlobalElements(
625630
selList: VCSSSelectorValueNode[],
626631
) => AST.VElement[],
627632
): IterableIterator<AST.VElement> {
628-
const found = new Set<AST.VElement>()
629-
for (const node of params) {
630-
const els = query(document.elements, node.nodes)
631-
for (const e of els) {
632-
if (!found.has(e)) {
633-
yield e
634-
found.add(e)
635-
}
633+
return iterateUnique(function* () {
634+
for (const node of params) {
635+
yield* query(document.elements, node.nodes)
636636
}
637-
}
637+
})
638638
}
639639

640640
/**
@@ -1035,3 +1035,44 @@ function withinTemplate(
10351035
templateRange[0] <= expr.range[0] && expr.range[1] <= templateRange[1]
10361036
)
10371037
}
1038+
1039+
/**
1040+
* Iterate unique items
1041+
*/
1042+
function* iterateUnique<T>(
1043+
gen: () => IterableIterator<T | null>,
1044+
): IterableIterator<T> {
1045+
const found = new Set<T>()
1046+
for (const e of gen()) {
1047+
if (e != null && !found.has(e)) {
1048+
yield e
1049+
found.add(e)
1050+
}
1051+
}
1052+
}
1053+
1054+
/**
1055+
* Checks if givin element has v-for
1056+
*/
1057+
function hasVFor(element: AST.VElement) {
1058+
return element.startTag.attributes.some(
1059+
(attr) => attr.directive && attr.key.name.name === "for",
1060+
)
1061+
}
1062+
1063+
/**
1064+
* Get the <template v-for> from givin element within
1065+
*/
1066+
function getVForTemplate(element: AST.VElement) {
1067+
let parent = element.parent
1068+
while (parent) {
1069+
if (parent.type !== "VElement" || parent.name !== "template") {
1070+
return null
1071+
}
1072+
if (hasVFor(parent)) {
1073+
return parent
1074+
}
1075+
parent = parent.parent
1076+
}
1077+
return null
1078+
}

tests/lib/rules/no-unused-selector.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,34 @@ tester.run("no-unused-selector", rule as any, {
443443
.unknown ::v-global(.bar > .baz) .qux {}
444444
</style>
445445
`,
446+
// with v-for
447+
`
448+
<template>
449+
<div>
450+
<div v-for="item in list" class="foo">{{ item }}</div>
451+
</div>
452+
</template>
453+
<style lang="scss" scoped>
454+
.foo {
455+
& + .foo {}
456+
}
457+
</style>
458+
`,
459+
`
460+
<template>
461+
<div>
462+
<template v-for="item in list" >
463+
<div class="foo">{{ item }}</div>
464+
<div class="bar">{{ item }}</div>
465+
</template>
466+
</div>
467+
</template>
468+
<style lang="scss" scoped>
469+
.foo {
470+
& ~ .foo {}
471+
}
472+
</style>
473+
`,
446474
],
447475
invalid: [
448476
{

tests/lib/rules/require-selector-used-inside.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,34 @@ tester.run("require-selector-used-inside", rule as any, {
372372
.unknown ::v-global(.bar > .baz) .qux {}
373373
</style>
374374
`,
375+
// with v-for
376+
`
377+
<template>
378+
<div>
379+
<div v-for="item in list"class="foo">{{ item }}</div>
380+
</div>
381+
</template>
382+
<style lang="scss" scoped>
383+
.foo {
384+
& + .foo {}
385+
}
386+
</style>
387+
`,
388+
`
389+
<template>
390+
<div>
391+
<template v-for="item in list" >
392+
<div class="foo">{{ item }}</div>
393+
<div class="bar">{{ item }}</div>
394+
</template>
395+
</div>
396+
</template>
397+
<style lang="scss" scoped>
398+
.foo {
399+
& ~ .foo {}
400+
}
401+
</style>
402+
`,
375403
],
376404
invalid: [
377405
{

0 commit comments

Comments
 (0)