/** * @author Toru Nagashima * See LICENSE file in root directory for full license. */ 'use strict' const utils = require('../utils') /** * Get all `v-slot` directives on a given element. * @param {VElement} node The VElement node to check. * @returns {VDirective[]} The array of `v-slot` directives. */ function getSlotDirectivesOnElement(node) { return utils.getDirectives(node, 'slot') } /** * Get all `v-slot` directives on the children of a given element. * @param {VElement} node The VElement node to check. * @returns {VDirective[][]} * The array of the group of `v-slot` directives. * The group bundles `v-slot` directives of element sequence which is connected * 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 } 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) } /** * Get the normalized name of a given `v-slot` directive node. * @param {VDirective} node The `v-slot` directive node. * @param {SourceCode} sourceCode The source code. * @returns {string} The normalized name. */ function getNormalizedName(node, sourceCode) { return node.key.argument == null ? 'default' : sourceCode.getText(node.key.argument) } /** * Get all `v-slot` directives which are distributed to the same slot as a given `v-slot` directive node. * @param {VDirective[][]} vSlotGroups The result of `getAllNamedSlotElements()`. * @param {VDirective} currentVSlot The current `v-slot` directive node. * @param {SourceCode} sourceCode The source code. * @returns {VDirective[][]} The array of the group of `v-slot` directives. */ function filterSameSlot(vSlotGroups, currentVSlot, sourceCode) { const currentName = getNormalizedName(currentVSlot, sourceCode) return vSlotGroups .map((vSlots) => vSlots.filter( (vSlot) => getNormalizedName(vSlot, sourceCode) === currentName ) ) .filter((slots) => slots.length >= 1) } /** * Check whether a given argument node is using an iteration variable that the element defined. * @param {VExpressionContainer|VIdentifier|null} argument The argument node to check. * @param {VElement} element The element node which has the argument. * @returns {boolean} `true` if the argument node is using the iteration variable. */ function isUsingIterationVar(argument, element) { if (argument && argument.type === 'VExpressionContainer') { for (const { variable } of argument.references) { if ( variable != null && variable.kind === 'v-for' && variable.id.range[0] > element.startTag.range[0] && variable.id.range[1] < element.startTag.range[1] ) { return true } } } return false } /** * Check whether a given argument node is using an scope variable that the directive defined. * @param {VDirective} vSlot The `v-slot` directive to check. * @returns {boolean} `true` if that argument node is using a scope variable the directive defined. */ function isUsingScopeVar(vSlot) { const argument = vSlot.key.argument const value = vSlot.value if (argument && value && argument.type === 'VExpressionContainer') { for (const { variable } of argument.references) { if ( variable != null && variable.kind === 'scope' && variable.id.range[0] > value.range[0] && variable.id.range[1] < value.range[1] ) { return true } } } return false } module.exports = { meta: { type: 'problem', docs: { description: 'enforce valid `v-slot` directives', categories: ['vue3-essential', 'essential'], url: 'https://eslint.vuejs.org/rules/valid-v-slot.html' }, fixable: null, schema: [], messages: { ownerMustBeCustomElement: "'v-slot' directive must be owned by a custom element, but '{{name}}' is not.", namedSlotMustBeOnTemplate: "Named slots must use '