diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js
index dafc70e0d..835fb7325 100644
--- a/lib/rules/no-reserved-component-names.js
+++ b/lib/rules/no-reserved-component-names.js
@@ -147,8 +147,7 @@ module.exports = {
})
}
- return Object.assign(
- {},
+ return utils.compositingVisitors(
utils.executeOnCallVueComponent(context, (node) => {
if (node.arguments.length === 2) {
const argument = node.arguments[0]
@@ -171,6 +170,17 @@ module.exports = {
if (!node) return
if (!canVerify(node.value)) return
reportIfInvalid(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
+ if (!canVerify(nameNode.value)) return
+ reportIfInvalid(nameNode.value)
+ }
})
)
}
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/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js
index ead9cce65..d9114810b 100644
--- a/tests/lib/rules/no-reserved-component-names.js
+++ b/tests/lib/rules/no-reserved-component-names.js
@@ -555,7 +555,25 @@ ruleTester.run('no-reserved-component-names', rule, {
`,
parserOptions,
options: [{ disallowVueBuiltInComponents: true }]
- }))
+ })),
+ {
+ filename: 'test.vue',
+ code: ``,
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions
+ },
+ {
+ filename: 'test.vue',
+ code: ``,
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions
+ }
],
invalid: [
@@ -657,6 +675,21 @@ ruleTester.run('no-reserved-component-names', rule, {
}
]
})),
+ ...invalidElements.map((name) => ({
+ filename: `${name}.vue`,
+ code: ``,
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions,
+ errors: [
+ {
+ messageId: RESERVED_NAMES_IN_HTML.has(name)
+ ? 'reservedInHtml'
+ : 'reserved',
+ data: { name },
+ line: 1
+ }
+ ]
+ })),
...vue2BuiltInComponents.map((name) => ({
filename: `${name}.vue`,
code: `
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)