Skip to content

Commit

Permalink
Fix that vue/no-deprecated-slot-attribute and `vue/no-deprecated-sl…
Browse files Browse the repository at this point in the history
…ot-scope-attribute` rules had wrong auto-fix. (#1521)
  • Loading branch information
ota-meshi committed Jun 25, 2021
1 parent f2b9ccc commit 28dec65
Show file tree
Hide file tree
Showing 7 changed files with 527 additions and 50 deletions.
10 changes: 8 additions & 2 deletions lib/rules/syntaxes/slot-attribute.js
Expand Up @@ -3,20 +3,26 @@
* See LICENSE file in root directory for full license.
*/
'use strict'

const canConvertToVSlot = require('./utils/can-convert-to-v-slot')

module.exports = {
deprecated: '2.6.0',
supported: '<3.0.0',
/** @param {RuleContext} context @returns {TemplateListener} */
createTemplateBodyVisitor(context) {
const sourceCode = context.getSourceCode()
const tokenStore =
context.parserServices.getTemplateBodyTokenStore &&
context.parserServices.getTemplateBodyTokenStore()

/**
* Checks whether the given node can convert to the `v-slot`.
* @param {VAttribute} slotAttr node of `slot`
* @returns {boolean} `true` if the given node can convert to the `v-slot`
*/
function canConvertFromSlotToVSlot(slotAttr) {
if (slotAttr.parent.parent.name !== 'template') {
if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
return false
}
if (!slotAttr.value) {
Expand All @@ -33,7 +39,7 @@ module.exports = {
* @returns {boolean} `true` if the given node can convert to the `v-slot`
*/
function canConvertFromVBindSlotToVSlot(slotAttr) {
if (slotAttr.parent.parent.name !== 'template') {
if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
return false
}

Expand Down
10 changes: 9 additions & 1 deletion lib/rules/syntaxes/slot-scope-attribute.js
Expand Up @@ -3,6 +3,9 @@
* See LICENSE file in root directory for full license.
*/
'use strict'

const canConvertToVSlotForElement = require('./utils/can-convert-to-v-slot')

module.exports = {
deprecated: '2.6.0',
supported: '>=2.5.0 <3.0.0',
Expand All @@ -14,14 +17,19 @@ module.exports = {
*/
createTemplateBodyVisitor(context, { fixToUpgrade } = {}) {
const sourceCode = context.getSourceCode()
const tokenStore =
context.parserServices.getTemplateBodyTokenStore &&
context.parserServices.getTemplateBodyTokenStore()

/**
* Checks whether the given node can convert to the `v-slot`.
* @param {VStartTag} startTag node of `<element v-slot ... >`
* @returns {boolean} `true` if the given node can convert to the `v-slot`
*/
function canConvertToVSlot(startTag) {
if (startTag.parent.name !== 'template') {
if (
!canConvertToVSlotForElement(startTag.parent, sourceCode, tokenStore)
) {
return false
}

Expand Down
228 changes: 228 additions & 0 deletions lib/rules/syntaxes/utils/can-convert-to-v-slot.js
@@ -0,0 +1,228 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../../../utils')
/**
* @typedef {object} SlotVForVariables
* @property {VForExpression} expr
* @property {VVariable[]} variables
*/
/**
* @typedef {object} SlotContext
* @property {VElement} element
* @property {VAttribute | VDirective | null} slot
* @property {VDirective | null} vFor
* @property {SlotVForVariables | null} slotVForVars
* @property {string} normalizedName
*/
/**
* Checks whether the given element can use v-slot.
* @param {VElement} element
* @param {SourceCode} sourceCode
* @param {ParserServices.TokenStore} tokenStore
*/
module.exports = function canConvertToVSlot(element, sourceCode, tokenStore) {
if (element.name !== 'template') {
return false
}
const ownerElement = element.parent
if (
ownerElement.type === 'VDocumentFragment' ||
!utils.isCustomComponent(ownerElement)
) {
return false
}
const slot = getSlotContext(element, sourceCode)
if (slot.vFor && !slot.slotVForVars) {
// E.g., <template v-for="x of xs" #one></template>
return false
}
if (hasSameSlotDirective(ownerElement, slot, sourceCode, tokenStore)) {
return false
}
return true
}
/**
* @param {VElement} element
* @param {SourceCode} sourceCode
* @returns {SlotContext}
*/
function getSlotContext(element, sourceCode) {
const slot =
utils.getAttribute(element, 'slot') ||
utils.getDirective(element, 'bind', 'slot')
const vFor = utils.getDirective(element, 'for')
const slotVForVars = getSlotVForVariableIfUsingIterationVars(slot, vFor)

return {
element,
slot,
vFor,
slotVForVars,
normalizedName: getNormalizedName(slot, sourceCode)
}
}

/**
* Gets the `v-for` directive and variable that provide the variables used by the given `slot` attribute.
* @param {VAttribute | VDirective | null} slot The current `slot` attribute node.
* @param {VDirective | null} [vFor] The current `v-for` directive node.
* @returns { SlotVForVariables | null } The SlotVForVariables.
*/
function getSlotVForVariableIfUsingIterationVars(slot, vFor) {
if (!slot || !slot.directive) {
return null
}
const expr =
vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression)
const variables =
expr && getUsingIterationVars(slot.value, slot.parent.parent)
return expr && variables && variables.length ? { expr, variables } : null
}

/**
* Gets iterative variables if a given expression node is using iterative variables that the element defined.
* @param {VExpressionContainer|null} expression The expression node to check.
* @param {VElement} element The element node which has the expression.
* @returns {VVariable[]} The expression node is using iteration variables.
*/
function getUsingIterationVars(expression, element) {
const vars = []
if (expression && expression.type === 'VExpressionContainer') {
for (const { variable } of expression.references) {
if (
variable != null &&
variable.kind === 'v-for' &&
variable.id.range[0] > element.startTag.range[0] &&
variable.id.range[1] < element.startTag.range[1]
) {
vars.push(variable)
}
}
}
return vars
}

/**
* Get the normalized name of a given `slot` attribute node.
* @param {VAttribute | VDirective | null} slotAttr node of `slot`
* @param {SourceCode} sourceCode The source code.
* @returns {string} The normalized name.
*/
function getNormalizedName(slotAttr, sourceCode) {
if (!slotAttr) {
return 'default'
}
if (!slotAttr.directive) {
return slotAttr.value ? slotAttr.value.value : 'default'
}
return slotAttr.value ? `[${sourceCode.getText(slotAttr.value)}]` : '[null]'
}

/**
* Checks whether parent element has the same slot as the given slot.
* @param {VElement} ownerElement The parent element.
* @param {SlotContext} targetSlot The SlotContext with a slot to check if they are the same.
* @param {SourceCode} sourceCode
* @param {ParserServices.TokenStore} tokenStore
*/
function hasSameSlotDirective(
ownerElement,
targetSlot,
sourceCode,
tokenStore
) {
for (const group of utils.iterateChildElementsChains(ownerElement)) {
if (group.includes(targetSlot.element)) {
continue
}
for (const childElement of group) {
const slot = getSlotContext(childElement, sourceCode)
if (!targetSlot.slotVForVars || !slot.slotVForVars) {
if (
!targetSlot.slotVForVars &&
!slot.slotVForVars &&
targetSlot.normalizedName === slot.normalizedName
) {
return true
}
continue
}
if (
equalSlotVForVariables(
targetSlot.slotVForVars,
slot.slotVForVars,
tokenStore
)
) {
return true
}
}
}
return false
}

/**
* Determines whether the two given `v-slot` variables are considered to be equal.
* @param {SlotVForVariables} a First element.
* @param {SlotVForVariables} b Second element.
* @param {ParserServices.TokenStore} tokenStore The token store.
* @returns {boolean} `true` if the elements are considered to be equal.
*/
function equalSlotVForVariables(a, b, tokenStore) {
if (a.variables.length !== b.variables.length) {
return false
}
if (!equal(a.expr.right, b.expr.right)) {
return false
}

const checkedVarNames = new Set()
const len = Math.min(a.expr.left.length, b.expr.left.length)
for (let index = 0; index < len; index++) {
const aPtn = a.expr.left[index]
const bPtn = b.expr.left[index]

const aVar = a.variables.find(
(v) => aPtn.range[0] <= v.id.range[0] && v.id.range[1] <= aPtn.range[1]
)
const bVar = b.variables.find(
(v) => bPtn.range[0] <= v.id.range[0] && v.id.range[1] <= bPtn.range[1]
)
if (aVar && bVar) {
if (aVar.id.name !== bVar.id.name) {
return false
}
if (!equal(aPtn, bPtn)) {
return false
}
checkedVarNames.add(aVar.id.name)
} else if (aVar || bVar) {
return false
}
}
for (const v of a.variables) {
if (!checkedVarNames.has(v.id.name)) {
if (b.variables.every((bv) => v.id.name !== bv.id.name)) {
return false
}
}
}
return true

/**
* Determines whether the two given nodes are considered to be equal.
* @param {ASTNode} a First node.
* @param {ASTNode} b Second node.
* @returns {boolean} `true` if the nodes are considered to be equal.
*/
function equal(a, b) {
if (a.type !== b.type) {
return false
}
return utils.equalTokens(a, b, tokenStore)
}
}
62 changes: 15 additions & 47 deletions lib/rules/valid-v-slot.js
Expand Up @@ -28,54 +28,22 @@ function getSlotDirectivesOnElement(node) {
* by `v-if`/`v-else-if`/`v-else`.
*/
function getSlotDirectivesOnChildren(node) {
return node.children
.reduce(
({ groups, vIf }, childNode) => {
if (childNode.type === 'VElement') {
let connected
if (utils.hasDirective(childNode, 'if')) {
connected = false
vIf = true
} else if (utils.hasDirective(childNode, 'else-if')) {
connected = vIf
vIf = true
} else if (utils.hasDirective(childNode, 'else')) {
connected = vIf
vIf = false
} else {
connected = false
vIf = false
}
/** @type {VDirective[][]} */
const groups = []
for (const group of utils.iterateChildElementsChains(node)) {
const slotDirs = group
.map((childElement) =>
childElement.name === 'template'
? utils.getDirective(childElement, 'slot')
: null
)
.filter(utils.isDef)
if (slotDirs.length > 0) {
groups.push(slotDirs)
}
}

if (connected) {
groups[groups.length - 1].push(childNode)
} else {
groups.push([childNode])
}
} else if (
childNode.type !== 'VText' ||
childNode.value.trim() !== ''
) {
vIf = false
}
return { groups, vIf }
},
{
/** @type {VElement[][]} */
groups: [],
vIf: false
}
)
.groups.map((group) =>
group
.map((childElement) =>
childElement.name === 'template'
? utils.getDirective(childElement, 'slot')
: null
)
.filter(utils.isDef)
)
.filter((group) => group.length >= 1)
return groups
}

/**
Expand Down

0 comments on commit 28dec65

Please sign in to comment.