Skip to content

Commit 28dec65

Browse files
authored
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

7 files changed

+527
-50
lines changed

lib/rules/syntaxes/slot-attribute.js

+8-2
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

+9-1
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

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

+15-47
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
/**

0 commit comments

Comments
 (0)