diff --git a/lib/rules/multi-word-component-names.js b/lib/rules/multi-word-component-names.js
index 38426bfb0..7f8e7bb65 100644
--- a/lib/rules/multi-word-component-names.js
+++ b/lib/rules/multi-word-component-names.js
@@ -98,6 +98,17 @@ module.exports = {
hasName = true
validateName(node.value)
}),
+ utils.defineScriptSetupVisitor(context, {
+ onDefineOptionsEnter(node) {
+ if (node.arguments.length === 0) return
+ const define = node.arguments[0]
+ if (define.type !== 'ObjectExpression') return
+ const nameNode = utils.findProperty(define, 'name')
+ if (!nameNode) return
+ hasName = true
+ validateName(nameNode.value)
+ }
+ }),
{
/** @param {Program} node */
'Program:exit'(node) {
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 1698b23ba..a63891bd9 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -1148,6 +1148,10 @@ module.exports = {
* - `onDefinePropsExit` ... Event when defineProps visit ends.
* - `onDefineEmitsEnter` ... Event when defineEmits is found.
* - `onDefineEmitsExit` ... Event when defineEmits visit ends.
+ * - `onDefineOptionsEnter` ... Event when defineOptions is found.
+ * - `onDefineOptionsExit` ... Event when defineOptions visit ends.
+ * - `onDefineSlotsEnter` ... Event when defineSlots is found.
+ * - `onDefineSlotsExit` ... Event when defineSlots visit ends.
*
* @param {RuleContext} context The ESLint rule context object.
* @param {ScriptSetupVisitor} visitor The visitor to traverse the AST nodes.
@@ -1186,11 +1190,58 @@ module.exports = {
scriptSetupVisitor[key] = (node) => callVisitor(key, node)
}
- const hasPropsEvent =
- visitor.onDefinePropsEnter || visitor.onDefinePropsExit
- const hasEmitsEvent =
- visitor.onDefineEmitsEnter || visitor.onDefineEmitsExit
- if (hasPropsEvent || hasEmitsEvent) {
+ class MacroListener {
+ /**
+ * @param {string} name
+ * @param {string} enterName
+ * @param {string} exitName
+ * @param {(candidateMacro: Expression | null, node: CallExpression) => boolean} isMacroNode
+ * @param {(context: RuleContext, node: CallExpression) => unknown} buildParam
+ */
+ constructor(name, enterName, exitName, isMacroNode, buildParam) {
+ this.name = name
+ this.enterName = enterName
+ this.exitName = exitName
+ this.isMacroNode = isMacroNode
+ this.buildParam = buildParam
+ this.hasListener = Boolean(
+ visitor[this.enterName] || visitor[this.exitName]
+ )
+ this.paramsMap = new Map()
+ }
+ }
+ const macroListenerList = [
+ new MacroListener(
+ 'defineProps',
+ 'onDefinePropsEnter',
+ 'onDefinePropsExit',
+ (candidateMacro, node) =>
+ candidateMacro === node || candidateMacro === getWithDefaults(node),
+ getComponentPropsFromDefineProps
+ ),
+ new MacroListener(
+ 'defineEmits',
+ 'onDefineEmitsEnter',
+ 'onDefineEmitsExit',
+ (candidateMacro, node) => candidateMacro === node,
+ getComponentEmitsFromDefineEmits
+ ),
+ new MacroListener(
+ 'defineOptions',
+ 'onDefineOptionsEnter',
+ 'onDefineOptionsExit',
+ (candidateMacro, node) => candidateMacro === node,
+ () => undefined
+ ),
+ new MacroListener(
+ 'defineSlots',
+ 'onDefineSlotsEnter',
+ 'onDefineSlotsExit',
+ (candidateMacro, node) => candidateMacro === node,
+ () => undefined
+ )
+ ].filter((m) => m.hasListener)
+ if (macroListenerList.length > 0) {
/** @type {Expression | null} */
let candidateMacro = null
/** @param {VariableDeclarator|ExpressionStatement} node */
@@ -1213,8 +1264,6 @@ module.exports = {
candidateMacro = null
}
}
- const definePropsMap = new Map()
- const defineEmitsMap = new Map()
/**
* @param {CallExpression} node
*/
@@ -1224,40 +1273,32 @@ module.exports = {
inScriptSetup(node) &&
node.callee.type === 'Identifier'
) {
- if (
- hasPropsEvent &&
- (candidateMacro === node ||
- candidateMacro === getWithDefaults(node)) &&
- node.callee.name === 'defineProps'
- ) {
- /** @type {ComponentProp[]} */
- const props = getComponentPropsFromDefineProps(context, node)
-
- callVisitor('onDefinePropsEnter', node, props)
- definePropsMap.set(node, props)
- } else if (
- hasEmitsEvent &&
- candidateMacro === node &&
- node.callee.name === 'defineEmits'
- ) {
- /** @type {ComponentEmit[]} */
- const emits = getComponentEmitsFromDefineEmits(context, node)
-
- callVisitor('onDefineEmitsEnter', node, emits)
- defineEmitsMap.set(node, emits)
+ for (const macroListener of macroListenerList) {
+ if (
+ node.callee.name !== macroListener.name ||
+ !macroListener.isMacroNode(candidateMacro, node)
+ ) {
+ continue
+ }
+ const param = macroListener.buildParam(context, node)
+ callVisitor(macroListener.enterName, node, param)
+ macroListener.paramsMap.set(node, param)
+ break
}
}
callVisitor('CallExpression', node)
}
scriptSetupVisitor['CallExpression:exit'] = (node) => {
callVisitor('CallExpression:exit', node)
- if (definePropsMap.has(node)) {
- callVisitor('onDefinePropsExit', node, definePropsMap.get(node))
- definePropsMap.delete(node)
- }
- if (defineEmitsMap.has(node)) {
- callVisitor('onDefineEmitsExit', node, defineEmitsMap.get(node))
- defineEmitsMap.delete(node)
+ for (const macroListener of macroListenerList) {
+ if (macroListener.paramsMap.has(node)) {
+ callVisitor(
+ macroListener.exitName,
+ node,
+ macroListener.paramsMap.get(node)
+ )
+ macroListener.paramsMap.delete(node)
+ }
}
}
}
diff --git a/tests/lib/rules/multi-word-component-names.js b/tests/lib/rules/multi-word-component-names.js
index d11b11d3f..96302eec8 100644
--- a/tests/lib/rules/multi-word-component-names.js
+++ b/tests/lib/rules/multi-word-component-names.js
@@ -209,10 +209,18 @@ tester.run('multi-word-component-names', rule, {
-
+
`
+ },
+ {
+ filename: 'Single.vue',
+ code: `
+
+ `
}
],
invalid: [
@@ -333,6 +341,20 @@ tester.run('multi-word-component-names', rule, {
line: 1
}
]
+ },
+ {
+ filename: 'MultiWord.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Component name "Single" should always be multi-word.',
+ line: 3
+ }
+ ]
}
]
})
diff --git a/typings/eslint-plugin-vue/util-types/utils.ts b/typings/eslint-plugin-vue/util-types/utils.ts
index d998761be..e355456dc 100644
--- a/typings/eslint-plugin-vue/util-types/utils.ts
+++ b/typings/eslint-plugin-vue/util-types/utils.ts
@@ -40,6 +40,10 @@ export interface ScriptSetupVisitor extends ScriptSetupVisitorBase {
onDefinePropsExit?(node: CallExpression, props: ComponentProp[]): void
onDefineEmitsEnter?(node: CallExpression, emits: ComponentEmit[]): void
onDefineEmitsExit?(node: CallExpression, emits: ComponentEmit[]): void
+ onDefineOptionsEnter?(node: CallExpression): void
+ onDefineOptionsExit?(node: CallExpression): void
+ onDefineSlotsEnter?(node: CallExpression): void
+ onDefineSlotsExit?(node: CallExpression): void
[query: string]:
| ((node: VAST.ParamNode) => void)
| ((node: CallExpression, props: ComponentProp[]) => void)