Skip to content

Commit

Permalink
fix(require-explicit-slots): ignore attribute binding (#2591)
Browse files Browse the repository at this point in the history
  • Loading branch information
waynzh authored Nov 11, 2024
1 parent 54a99c5 commit e13089e
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 10 deletions.
55 changes: 45 additions & 10 deletions lib/rules/require-explicit-slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ function getSlotsName(node) {
return null
}

/**
* @param {VElement} node
* @return {VAttribute | VDirective | undefined}
*/
function getSlotNameNode(node) {
return node.startTag.attributes.find(
(node) =>
(!node.directive && node.key.name === 'name') ||
(node.directive &&
node.key.name.name === 'bind' &&
node.key.argument?.type === 'VIdentifier' &&
node.key.argument?.name === 'name')
)
}

module.exports = {
meta: {
type: 'problem',
Expand Down Expand Up @@ -68,6 +83,19 @@ module.exports = {
}
const slotsDefined = new Set()

/**
* @param {VElement} node
* @param {string | undefined} slotName
*/
function reportMissingSlot(node, slotName) {
if (!slotsDefined.has(slotName)) {
context.report({
node,
messageId: 'requireExplicitSlots'
})
}
}

return utils.compositingVisitors(
utils.defineScriptSetupVisitor(context, {
onDefineSlotsEnter(node) {
Expand Down Expand Up @@ -137,20 +165,27 @@ module.exports = {
}
}),
utils.defineTemplateBodyVisitor(context, {
/** @param {VElement} node */
"VElement[name='slot']"(node) {
let slotName = 'default'

const slotNameAttr = utils.getAttribute(node, 'name')
const nameNode = getSlotNameNode(node)

if (slotNameAttr?.value) {
slotName = slotNameAttr.value.value
// if no slot name is declared, default to 'default'
if (!nameNode) {
reportMissingSlot(node, 'default')
return
}

if (!slotsDefined.has(slotName)) {
context.report({
node,
messageId: 'requireExplicitSlots'
})
if (nameNode.directive) {
const expression = nameNode.value?.expression
// ignore attribute binding except string literal
if (!expression || !utils.isStringLiteral(expression)) {
return
}

const name = utils.getStringLiteralValue(expression) || undefined
reportMissingSlot(node, name)
} else {
reportMissingSlot(node, nameNode.value?.value)
}
}
})
Expand Down
51 changes: 51 additions & 0 deletions tests/lib/rules/require-explicit-slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,37 @@ tester.run('require-explicit-slots', rule, {
parser: null
}
}
},
// attribute binding
{
filename: 'test.vue',
code: `
<template>
<div>
<slot :name="'foo'"></slot>
<slot :name="\`bar\`"></slot>
</div>
</template>
<script setup lang="ts">
defineSlots<{
foo(props: { msg: string }): any
bar(props: { msg: string }): any
}>()
</script>`
},
{
filename: 'test.vue',
code: `
<template>
<div>
<slot :name="bar"></slot>
</div>
</template>
<script setup lang="ts">
defineSlots<{
default(props: { msg: string }): any
}>()
</script>`
}
],
invalid: [
Expand Down Expand Up @@ -291,6 +322,26 @@ tester.run('require-explicit-slots', rule, {
}
]
},
{
// ignore attribute binding except string literal
filename: 'test.vue',
code: `
<template>
<div>
<slot :name="'foo'"></slot>
</div>
</template>
<script setup lang="ts">
defineSlots<{
default(props: { msg: string }): any
}>()
</script>`,
errors: [
{
message: 'Slots must be explicitly defined.'
}
]
},
{
filename: 'test.vue',
code: `
Expand Down

0 comments on commit e13089e

Please sign in to comment.