Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 28dec65

Browse files
authoredJun 25, 2021
Fix that vue/no-deprecated-slot-attribute and vue/no-deprecated-slot-scope-attribute rules had wrong auto-fix. (#1521)
1 parent f2b9ccc commit 28dec65

File tree

7 files changed

+527
-50
lines changed

7 files changed

+527
-50
lines changed
 

‎lib/rules/syntaxes/slot-attribute.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,26 @@
33
* See LICENSE file in root directory for full license.
44
*/
55
'use strict'
6+
7+
const canConvertToVSlot = require('./utils/can-convert-to-v-slot')
8+
69
module.exports = {
710
deprecated: '2.6.0',
811
supported: '<3.0.0',
912
/** @param {RuleContext} context @returns {TemplateListener} */
1013
createTemplateBodyVisitor(context) {
1114
const sourceCode = context.getSourceCode()
15+
const tokenStore =
16+
context.parserServices.getTemplateBodyTokenStore &&
17+
context.parserServices.getTemplateBodyTokenStore()
1218

1319
/**
1420
* Checks whether the given node can convert to the `v-slot`.
1521
* @param {VAttribute} slotAttr node of `slot`
1622
* @returns {boolean} `true` if the given node can convert to the `v-slot`
1723
*/
1824
function canConvertFromSlotToVSlot(slotAttr) {
19-
if (slotAttr.parent.parent.name !== 'template') {
25+
if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
2026
return false
2127
}
2228
if (!slotAttr.value) {
@@ -33,7 +39,7 @@ module.exports = {
3339
* @returns {boolean} `true` if the given node can convert to the `v-slot`
3440
*/
3541
function canConvertFromVBindSlotToVSlot(slotAttr) {
36-
if (slotAttr.parent.parent.name !== 'template') {
42+
if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
3743
return false
3844
}
3945

‎lib/rules/syntaxes/slot-scope-attribute.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
* See LICENSE file in root directory for full license.
44
*/
55
'use strict'
6+
7+
const canConvertToVSlotForElement = require('./utils/can-convert-to-v-slot')
8+
69
module.exports = {
710
deprecated: '2.6.0',
811
supported: '>=2.5.0 <3.0.0',
@@ -14,14 +17,19 @@ module.exports = {
1417
*/
1518
createTemplateBodyVisitor(context, { fixToUpgrade } = {}) {
1619
const sourceCode = context.getSourceCode()
20+
const tokenStore =
21+
context.parserServices.getTemplateBodyTokenStore &&
22+
context.parserServices.getTemplateBodyTokenStore()
1723

1824
/**
1925
* Checks whether the given node can convert to the `v-slot`.
2026
* @param {VStartTag} startTag node of `<element v-slot ... >`
2127
* @returns {boolean} `true` if the given node can convert to the `v-slot`
2228
*/
2329
function canConvertToVSlot(startTag) {
24-
if (startTag.parent.name !== 'template') {
30+
if (
31+
!canConvertToVSlotForElement(startTag.parent, sourceCode, tokenStore)
32+
) {
2533
return false
2634
}
2735

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../../../utils')
8+
/**
9+
* @typedef {object} SlotVForVariables
10+
* @property {VForExpression} expr
11+
* @property {VVariable[]} variables
12+
*/
13+
/**
14+
* @typedef {object} SlotContext
15+
* @property {VElement} element
16+
* @property {VAttribute | VDirective | null} slot
17+
* @property {VDirective | null} vFor
18+
* @property {SlotVForVariables | null} slotVForVars
19+
* @property {string} normalizedName
20+
*/
21+
/**
22+
* Checks whether the given element can use v-slot.
23+
* @param {VElement} element
24+
* @param {SourceCode} sourceCode
25+
* @param {ParserServices.TokenStore} tokenStore
26+
*/
27+
module.exports = function canConvertToVSlot(element, sourceCode, tokenStore) {
28+
if (element.name !== 'template') {
29+
return false
30+
}
31+
const ownerElement = element.parent
32+
if (
33+
ownerElement.type === 'VDocumentFragment' ||
34+
!utils.isCustomComponent(ownerElement)
35+
) {
36+
return false
37+
}
38+
const slot = getSlotContext(element, sourceCode)
39+
if (slot.vFor && !slot.slotVForVars) {
40+
// E.g., <template v-for="x of xs" #one></template>
41+
return false
42+
}
43+
if (hasSameSlotDirective(ownerElement, slot, sourceCode, tokenStore)) {
44+
return false
45+
}
46+
return true
47+
}
48+
/**
49+
* @param {VElement} element
50+
* @param {SourceCode} sourceCode
51+
* @returns {SlotContext}
52+
*/
53+
function getSlotContext(element, sourceCode) {
54+
const slot =
55+
utils.getAttribute(element, 'slot') ||
56+
utils.getDirective(element, 'bind', 'slot')
57+
const vFor = utils.getDirective(element, 'for')
58+
const slotVForVars = getSlotVForVariableIfUsingIterationVars(slot, vFor)
59+
60+
return {
61+
element,
62+
slot,
63+
vFor,
64+
slotVForVars,
65+
normalizedName: getNormalizedName(slot, sourceCode)
66+
}
67+
}
68+
69+
/**
70+
* Gets the `v-for` directive and variable that provide the variables used by the given `slot` attribute.
71+
* @param {VAttribute | VDirective | null} slot The current `slot` attribute node.
72+
* @param {VDirective | null} [vFor] The current `v-for` directive node.
73+
* @returns { SlotVForVariables | null } The SlotVForVariables.
74+
*/
75+
function getSlotVForVariableIfUsingIterationVars(slot, vFor) {
76+
if (!slot || !slot.directive) {
77+
return null
78+
}
79+
const expr =
80+
vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression)
81+
const variables =
82+
expr && getUsingIterationVars(slot.value, slot.parent.parent)
83+
return expr && variables && variables.length ? { expr, variables } : null
84+
}
85+
86+
/**
87+
* Gets iterative variables if a given expression node is using iterative variables that the element defined.
88+
* @param {VExpressionContainer|null} expression The expression node to check.
89+
* @param {VElement} element The element node which has the expression.
90+
* @returns {VVariable[]} The expression node is using iteration variables.
91+
*/
92+
function getUsingIterationVars(expression, element) {
93+
const vars = []
94+
if (expression && expression.type === 'VExpressionContainer') {
95+
for (const { variable } of expression.references) {
96+
if (
97+
variable != null &&
98+
variable.kind === 'v-for' &&
99+
variable.id.range[0] > element.startTag.range[0] &&
100+
variable.id.range[1] < element.startTag.range[1]
101+
) {
102+
vars.push(variable)
103+
}
104+
}
105+
}
106+
return vars
107+
}
108+
109+
/**
110+
* Get the normalized name of a given `slot` attribute node.
111+
* @param {VAttribute | VDirective | null} slotAttr node of `slot`
112+
* @param {SourceCode} sourceCode The source code.
113+
* @returns {string} The normalized name.
114+
*/
115+
function getNormalizedName(slotAttr, sourceCode) {
116+
if (!slotAttr) {
117+
return 'default'
118+
}
119+
if (!slotAttr.directive) {
120+
return slotAttr.value ? slotAttr.value.value : 'default'
121+
}
122+
return slotAttr.value ? `[${sourceCode.getText(slotAttr.value)}]` : '[null]'
123+
}
124+
125+
/**
126+
* Checks whether parent element has the same slot as the given slot.
127+
* @param {VElement} ownerElement The parent element.
128+
* @param {SlotContext} targetSlot The SlotContext with a slot to check if they are the same.
129+
* @param {SourceCode} sourceCode
130+
* @param {ParserServices.TokenStore} tokenStore
131+
*/
132+
function hasSameSlotDirective(
133+
ownerElement,
134+
targetSlot,
135+
sourceCode,
136+
tokenStore
137+
) {
138+
for (const group of utils.iterateChildElementsChains(ownerElement)) {
139+
if (group.includes(targetSlot.element)) {
140+
continue
141+
}
142+
for (const childElement of group) {
143+
const slot = getSlotContext(childElement, sourceCode)
144+
if (!targetSlot.slotVForVars || !slot.slotVForVars) {
145+
if (
146+
!targetSlot.slotVForVars &&
147+
!slot.slotVForVars &&
148+
targetSlot.normalizedName === slot.normalizedName
149+
) {
150+
return true
151+
}
152+
continue
153+
}
154+
if (
155+
equalSlotVForVariables(
156+
targetSlot.slotVForVars,
157+
slot.slotVForVars,
158+
tokenStore
159+
)
160+
) {
161+
return true
162+
}
163+
}
164+
}
165+
return false
166+
}
167+
168+
/**
169+
* Determines whether the two given `v-slot` variables are considered to be equal.
170+
* @param {SlotVForVariables} a First element.
171+
* @param {SlotVForVariables} b Second element.
172+
* @param {ParserServices.TokenStore} tokenStore The token store.
173+
* @returns {boolean} `true` if the elements are considered to be equal.
174+
*/
175+
function equalSlotVForVariables(a, b, tokenStore) {
176+
if (a.variables.length !== b.variables.length) {
177+
return false
178+
}
179+
if (!equal(a.expr.right, b.expr.right)) {
180+
return false
181+
}
182+
183+
const checkedVarNames = new Set()
184+
const len = Math.min(a.expr.left.length, b.expr.left.length)
185+
for (let index = 0; index < len; index++) {
186+
const aPtn = a.expr.left[index]
187+
const bPtn = b.expr.left[index]
188+
189+
const aVar = a.variables.find(
190+
(v) => aPtn.range[0] <= v.id.range[0] && v.id.range[1] <= aPtn.range[1]
191+
)
192+
const bVar = b.variables.find(
193+
(v) => bPtn.range[0] <= v.id.range[0] && v.id.range[1] <= bPtn.range[1]
194+
)
195+
if (aVar && bVar) {
196+
if (aVar.id.name !== bVar.id.name) {
197+
return false
198+
}
199+
if (!equal(aPtn, bPtn)) {
200+
return false
201+
}
202+
checkedVarNames.add(aVar.id.name)
203+
} else if (aVar || bVar) {
204+
return false
205+
}
206+
}
207+
for (const v of a.variables) {
208+
if (!checkedVarNames.has(v.id.name)) {
209+
if (b.variables.every((bv) => v.id.name !== bv.id.name)) {
210+
return false
211+
}
212+
}
213+
}
214+
return true
215+
216+
/**
217+
* Determines whether the two given nodes are considered to be equal.
218+
* @param {ASTNode} a First node.
219+
* @param {ASTNode} b Second node.
220+
* @returns {boolean} `true` if the nodes are considered to be equal.
221+
*/
222+
function equal(a, b) {
223+
if (a.type !== b.type) {
224+
return false
225+
}
226+
return utils.equalTokens(a, b, tokenStore)
227+
}
228+
}

‎lib/rules/valid-v-slot.js

Lines changed: 15 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -28,54 +28,22 @@ function getSlotDirectivesOnElement(node) {
2828
* by `v-if`/`v-else-if`/`v-else`.
2929
*/
3030
function getSlotDirectivesOnChildren(node) {
31-
return node.children
32-
.reduce(
33-
({ groups, vIf }, childNode) => {
34-
if (childNode.type === 'VElement') {
35-
let connected
36-
if (utils.hasDirective(childNode, 'if')) {
37-
connected = false
38-
vIf = true
39-
} else if (utils.hasDirective(childNode, 'else-if')) {
40-
connected = vIf
41-
vIf = true
42-
} else if (utils.hasDirective(childNode, 'else')) {
43-
connected = vIf
44-
vIf = false
45-
} else {
46-
connected = false
47-
vIf = false
48-
}
31+
/** @type {VDirective[][]} */
32+
const groups = []
33+
for (const group of utils.iterateChildElementsChains(node)) {
34+
const slotDirs = group
35+
.map((childElement) =>
36+
childElement.name === 'template'
37+
? utils.getDirective(childElement, 'slot')
38+
: null
39+
)
40+
.filter(utils.isDef)
41+
if (slotDirs.length > 0) {
42+
groups.push(slotDirs)
43+
}
44+
}
4945

50-
if (connected) {
51-
groups[groups.length - 1].push(childNode)
52-
} else {
53-
groups.push([childNode])
54-
}
55-
} else if (
56-
childNode.type !== 'VText' ||
57-
childNode.value.trim() !== ''
58-
) {
59-
vIf = false
60-
}
61-
return { groups, vIf }
62-
},
63-
{
64-
/** @type {VElement[][]} */
65-
groups: [],
66-
vIf: false
67-
}
68-
)
69-
.groups.map((group) =>
70-
group
71-
.map((childElement) =>
72-
childElement.name === 'template'
73-
? utils.getDirective(childElement, 'slot')
74-
: null
75-
)
76-
.filter(utils.isDef)
77-
)
78-
.filter((group) => group.length >= 1)
46+
return groups
7947
}
8048

8149
/**

‎lib/utils/index.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,49 @@ module.exports = {
624624
)
625625
},
626626

627+
/**
628+
* Returns a generator with all child element v-if chains of the given element.
629+
* @param {VElement} node The element node to check.
630+
* @returns {IterableIterator<VElement[]>}
631+
*/
632+
*iterateChildElementsChains(node) {
633+
let vIf = false
634+
/** @type {VElement[]} */
635+
let elementChain = []
636+
for (const childNode of node.children) {
637+
if (childNode.type === 'VElement') {
638+
let connected
639+
if (this.hasDirective(childNode, 'if')) {
640+
connected = false
641+
vIf = true
642+
} else if (this.hasDirective(childNode, 'else-if')) {
643+
connected = vIf
644+
vIf = true
645+
} else if (this.hasDirective(childNode, 'else')) {
646+
connected = vIf
647+
vIf = false
648+
} else {
649+
connected = false
650+
vIf = false
651+
}
652+
653+
if (connected) {
654+
elementChain.push(childNode)
655+
} else {
656+
if (elementChain.length) {
657+
yield elementChain
658+
}
659+
elementChain = [childNode]
660+
}
661+
} else if (childNode.type !== 'VText' || childNode.value.trim() !== '') {
662+
vIf = false
663+
}
664+
}
665+
if (elementChain.length) {
666+
yield elementChain
667+
}
668+
},
669+
627670
/**
628671
* Check whether the given node is a custom component or not.
629672
* @param {VElement} node The start tag node to check.

‎tests/lib/rules/no-deprecated-slot-attribute.js

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,205 @@ tester.run('no-deprecated-slot-attribute', rule, {
405405
</MyComponent>
406406
</template>`,
407407
errors: ['`slot` attributes are deprecated.']
408+
},
409+
{
410+
// https://github.com/vuejs/eslint-plugin-vue/issues/1499
411+
code: `
412+
<template>
413+
<some-component>
414+
<template slot="some-slot">
415+
This works 1
416+
</template>
417+
418+
<template v-if="true"> <!-- some arbitrary conditional -->
419+
<template slot="some-slot">
420+
This works 2
421+
</template>
422+
</template>
423+
</some-component>
424+
</template>`,
425+
output: `
426+
<template>
427+
<some-component>
428+
<template v-slot:some-slot>
429+
This works 1
430+
</template>
431+
432+
<template v-if="true"> <!-- some arbitrary conditional -->
433+
<template slot="some-slot">
434+
This works 2
435+
</template>
436+
</template>
437+
</some-component>
438+
</template>`,
439+
errors: [
440+
{
441+
message: '`slot` attributes are deprecated.',
442+
line: 4
443+
},
444+
{
445+
message: '`slot` attributes are deprecated.',
446+
line: 9
447+
}
448+
]
449+
},
450+
{
451+
code: `
452+
<template>
453+
<my-component>
454+
<template v-for="x in xs" slot="one">
455+
A
456+
</template>
457+
<template v-for="x in xs" :slot="x">
458+
B
459+
</template>
460+
</my-component>
461+
</template>`,
462+
output: `
463+
<template>
464+
<my-component>
465+
<template v-for="x in xs" slot="one">
466+
A
467+
</template>
468+
<template v-for="x in xs" v-slot:[x]>
469+
B
470+
</template>
471+
</my-component>
472+
</template>`,
473+
errors: [
474+
'`slot` attributes are deprecated.',
475+
'`slot` attributes are deprecated.'
476+
]
477+
},
478+
{
479+
code: `
480+
<template>
481+
<my-component>
482+
<template slot="one">
483+
A
484+
</template>
485+
<template slot="one">
486+
B
487+
</template>
488+
</my-component>
489+
</template>`,
490+
output: `
491+
<template>
492+
<my-component>
493+
<template slot="one">
494+
A
495+
</template>
496+
<template slot="one">
497+
B
498+
</template>
499+
</my-component>
500+
</template>`,
501+
errors: [
502+
'`slot` attributes are deprecated.',
503+
'`slot` attributes are deprecated.'
504+
]
505+
},
506+
{
507+
code: `
508+
<template>
509+
<my-component>
510+
<template v-if="c" slot="one">
511+
A
512+
</template>
513+
<template v-else slot="one">
514+
B
515+
</template>
516+
</my-component>
517+
</template>`,
518+
output: `
519+
<template>
520+
<my-component>
521+
<template v-if="c" v-slot:one>
522+
A
523+
</template>
524+
<template v-else v-slot:one>
525+
B
526+
</template>
527+
</my-component>
528+
</template>`,
529+
errors: [
530+
'`slot` attributes are deprecated.',
531+
'`slot` attributes are deprecated.'
532+
]
533+
},
534+
{
535+
code: `
536+
<template>
537+
<my-component>
538+
<template v-for="x in xs" :slot="x">
539+
A
540+
</template>
541+
<template v-for="x in xs" :slot="x">
542+
B
543+
</template>
544+
</my-component>
545+
</template>`,
546+
output: null,
547+
errors: [
548+
'`slot` attributes are deprecated.',
549+
'`slot` attributes are deprecated.'
550+
]
551+
},
552+
{
553+
code: `
554+
<template>
555+
<my-component>
556+
<template v-for="x in ys" :slot="x">
557+
A
558+
</template>
559+
<template v-for="x in xs" :slot="x">
560+
B
561+
</template>
562+
</my-component>
563+
</template>`,
564+
output: `
565+
<template>
566+
<my-component>
567+
<template v-for="x in ys" v-slot:[x]>
568+
A
569+
</template>
570+
<template v-for="x in xs" v-slot:[x]>
571+
B
572+
</template>
573+
</my-component>
574+
</template>`,
575+
errors: [
576+
'`slot` attributes are deprecated.',
577+
'`slot` attributes are deprecated.'
578+
]
579+
},
580+
{
581+
code: `
582+
<template>
583+
<my-component>
584+
<template v-for="(x,y) in xs" :slot="x+y">
585+
A
586+
</template>
587+
<template v-for="x in xs" :slot="x">
588+
B
589+
</template>
590+
</my-component>
591+
</template>`,
592+
output: `
593+
<template>
594+
<my-component>
595+
<template v-for="(x,y) in xs" :slot="x+y">
596+
A
597+
</template>
598+
<template v-for="x in xs" v-slot:[x]>
599+
B
600+
</template>
601+
</my-component>
602+
</template>`,
603+
errors: [
604+
'`slot` attributes are deprecated.',
605+
'`slot` attributes are deprecated.'
606+
]
408607
}
409608
]
410609
})

‎tests/lib/rules/no-deprecated-slot-scope-attribute.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,31 @@ tester.run('no-deprecated-slot-scope-attribute', rule, {
138138
line: 4
139139
}
140140
]
141+
},
142+
{
143+
code: `
144+
<template>
145+
<my-component>
146+
<template v-for="x in xs" slot-scope="{a}" >
147+
{{a}}
148+
</template>
149+
</my-component>
150+
</template>`,
151+
output: null,
152+
errors: ['`slot-scope` are deprecated.']
153+
},
154+
{
155+
code: `
156+
<template>
157+
<my-component>
158+
<template slot-scope="{a}" >
159+
{{a}}
160+
</template>
161+
<div />
162+
</my-component>
163+
</template>`,
164+
output: null,
165+
errors: ['`slot-scope` are deprecated.']
141166
}
142167
]
143168
})

0 commit comments

Comments
 (0)
Please sign in to comment.