123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- 'use strict'
- const utils = require('../utils')
- function getSlotDirectivesOnElement (node) {
- return node.startTag.attributes.filter(attribute =>
- attribute.directive &&
- attribute.key.name.name === 'slot'
- )
- }
- 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 }
- }, { groups: [], vIf: false })
- .groups
- .map(group =>
- group
- .map(childElement =>
- childElement.name === 'template'
- ? utils.getDirective(childElement, 'slot')
- : null
- )
- .filter(Boolean)
- )
- .filter(group => group.length >= 1)
- }
- function getNormalizedName (node, sourceCode) {
- return node.key.argument == null ? 'default' : sourceCode.getText(node.key.argument)
- }
- 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)
- }
- 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
- }
- 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
- }
- }
- }
- }
- module.exports = {
- meta: {
- type: 'problem',
- docs: {
- description: 'enforce valid `v-slot` directives',
- category: undefined,
-
-
- 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 '<template>' on a custom element.",
- defaultSlotMustBeOnTemplate: "Default slot must use '<template>' on a custom element when there are other named slots.",
- disallowDuplicateSlotsOnElement: "An element cannot have multiple 'v-slot' directives.",
- disallowDuplicateSlotsOnChildren: "An element cannot have multiple '<template>' elements which are distributed to the same slot.",
- disallowArgumentUseSlotParams: "Dynamic argument of 'v-slot' directive cannot use that slot parameter.",
- disallowAnyModifier: "'v-slot' directive doesn't support any modifier.",
- requireAttributeValue: "'v-slot' directive on a custom element requires that attribute value."
- }
- },
- create (context) {
- const sourceCode = context.getSourceCode()
- return utils.defineTemplateBodyVisitor(context, {
- "VAttribute[directive=true][key.name.name='slot']" (node) {
- const isDefaultSlot = node.key.argument == null || node.key.argument.name === 'default'
- const element = node.parent.parent
- const parentElement = element.parent
- const ownerElement = element.name === 'template' ? parentElement : element
- const vSlotsOnElement = getSlotDirectivesOnElement(element)
- const vSlotGroupsOnChildren = getSlotDirectivesOnChildren(ownerElement)
-
- if (!utils.isCustomComponent(ownerElement)) {
- context.report({
- node,
- messageId: 'ownerMustBeCustomElement',
- data: { name: ownerElement.rawName }
- })
- }
- if (!isDefaultSlot && element.name !== 'template') {
- context.report({
- node,
- messageId: 'namedSlotMustBeOnTemplate'
- })
- }
- if (ownerElement === element && vSlotGroupsOnChildren.length >= 1) {
- context.report({
- node,
- messageId: 'defaultSlotMustBeOnTemplate'
- })
- }
-
- if (vSlotsOnElement.length >= 2 && vSlotsOnElement[0] !== node) {
-
- context.report({
- node,
- messageId: 'disallowDuplicateSlotsOnElement'
- })
- }
- if (ownerElement === parentElement) {
- const vSlotGroupsOfSameSlot = filterSameSlot(vSlotGroupsOnChildren, node, sourceCode)
- const vFor = utils.getDirective(element, 'for')
- if (
- vSlotGroupsOfSameSlot.length >= 2 &&
- !vSlotGroupsOfSameSlot[0].includes(node)
- ) {
-
-
- context.report({
- node,
- messageId: 'disallowDuplicateSlotsOnChildren'
- })
- }
- if (vFor && !isUsingIterationVar(node.key.argument, element)) {
-
- context.report({
- node,
- messageId: 'disallowDuplicateSlotsOnChildren'
- })
- }
- }
-
- if (isUsingScopeVar(node)) {
- context.report({
- node,
- messageId: 'disallowArgumentUseSlotParams'
- })
- }
-
- if (node.key.modifiers.length >= 1) {
- context.report({
- node,
- messageId: 'disallowAnyModifier'
- })
- }
-
- if (ownerElement === element && isDefaultSlot && !utils.hasAttributeValue(node)) {
- context.report({
- node,
- messageId: 'requireAttributeValue'
- })
- }
- }
- })
- }
- }
|