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/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)