Repository: vuejs/eslint-plugin-vue
Pull request #2158 (2023-05-12 07:52:48)

Added support for `defineOptions` to `vue/no-reserved-component-names` rule

related to https://github.com/vuejs/eslint-plugin-vue/issues/2118


Patch:
diff --git a/docs/rules/define-macros-order.md b/docs/rules/define-macros-order.md
index 6fe48b20..c4e36b25 100644
--- a/docs/rules/define-macros-order.md
+++ b/docs/rules/define-macros-order.md
@@ -25,7 +25,7 @@ This rule reports the `defineProps` and `defineEmits` compiler macros when they
 }
 ```
 
-- `order` (`string[]`) ... The order of defineEmits and defineProps macros
+- `order` (`string[]`) ... The order of defineEmits and defineProps macros. You can also add `"defineOptions"` and `"defineSlots"`.
 
 ### `{ "order": ["defineProps", "defineEmits"] }` (default)
 
@@ -66,6 +66,51 @@ defineEmits(/* ... */)
 
 </eslint-code-block>
 
+### `{ "order": ["defineOptions", "defineProps", "defineEmits", "defineSlots"] }` (default)
+
+<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']}]}">
+
+```vue
+<!-- ✓ GOOD -->
+<script setup>
+defineOptions({/* ... */})
+defineProps(/* ... */)
+defineEmits(/* ... */)
+const slots = defineSlots()
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']}]}">
+
+```vue
+<!-- ✗ BAD -->
+<script setup>
+defineEmits(/* ... */)
+const slots = defineSlots()
+defineProps(/* ... */)
+defineOptions({/* ... */})
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']}]}">
+
+```vue
+<!-- ✗ BAD -->
+<script setup>
+const bar = ref()
+defineOptions({/* ... */})
+defineProps(/* ... */)
+defineEmits(/* ... */)
+const slots = defineSlots()
+</script>
+```
+
+</eslint-code-block>
+
 ## :rocket: Version
 
 This rule was introduced in eslint-plugin-vue v8.7.0
diff --git a/lib/rules/define-macros-order.js b/lib/rules/define-macros-order.js
index baf03a40..5a678db4 100644
--- a/lib/rules/define-macros-order.js
+++ b/lib/rules/define-macros-order.js
@@ -8,7 +8,9 @@ const utils = require('../utils')
 
 const MACROS_EMITS = 'defineEmits'
 const MACROS_PROPS = 'defineProps'
-const ORDER = [MACROS_EMITS, MACROS_PROPS]
+const MACROS_OPTIONS = 'defineOptions'
+const MACROS_SLOTS = 'defineSlots'
+const ORDER_SCHEMA = [MACROS_EMITS, MACROS_PROPS, MACROS_OPTIONS, MACROS_SLOTS]
 const DEFAULT_ORDER = [MACROS_PROPS, MACROS_EMITS]
 
 /**
@@ -103,97 +105,69 @@ function create(context) {
       },
       onDefineEmitsExit(node) {
         macrosNodes.set(MACROS_EMITS, getDefineMacrosStatement(node))
+      },
+      onDefineOptionsExit(node) {
+        macrosNodes.set(MACROS_OPTIONS, getDefineMacrosStatement(node))
+      },
+      onDefineSlotsExit(node) {
+        macrosNodes.set(MACROS_SLOTS, getDefineMacrosStatement(node))
       }
     }),
     {
       'Program:exit'(program) {
-        const shouldFirstNode = macrosNodes.get(order[0])
-        const shouldSecondNode = macrosNodes.get(order[1])
+        /**
+         * @typedef {object} OrderedData
+         * @property {string} name
+         * @property {ASTNode} node
+         */
         const firstStatementIndex = getTargetStatementPosition(
           scriptSetup,
           program
         )
-        const firstStatement = program.body[firstStatementIndex]
+        const orderedList = order
+          .map((name) => ({ name, node: macrosNodes.get(name) }))
+          .filter(
+            /** @returns {data is OrderedData} */
+            (data) => utils.isDef(data.node)
+          )
 
-        // have both defineEmits and defineProps
-        if (shouldFirstNode && shouldSecondNode) {
-          const secondStatement = program.body[firstStatementIndex + 1]
+        for (const [index, should] of orderedList.entries()) {
+          const targetStatement = program.body[firstStatementIndex + index]
 
-          // need move only first
-          if (firstStatement === shouldSecondNode) {
-            reportNotOnTop(order[0], shouldFirstNode, firstStatement)
+          if (should.node !== targetStatement) {
+            let moveTargetNodes = orderedList
+              .slice(index)
+              .map(({ node }) => node)
+            const targetStatementIndex =
+              moveTargetNodes.indexOf(targetStatement)
+            if (targetStatementIndex >= 0) {
+              moveTargetNodes = moveTargetNodes.slice(0, targetStatementIndex)
+            }
+            reportNotOnTop(should.name, moveTargetNodes, targetStatement)
             return
           }
-
-          // need move both defineEmits and defineProps
-          if (firstStatement !== shouldFirstNode) {
-            reportBothNotOnTop(
-              shouldFirstNode,
-              shouldSecondNode,
-              firstStatement
-            )
-            return
-          }
-
-          // need move only second
-          if (secondStatement !== shouldSecondNode) {
-            reportNotOnTop(order[1], shouldSecondNode, secondStatement)
-          }
-
-          return
-        }
-
-        // have only first and need to move it
-        if (shouldFirstNode && firstStatement !== shouldFirstNode) {
-          reportNotOnTop(order[0], shouldFirstNode, firstStatement)
-          return
-        }
-
-        // have only second and need to move it
-        if (shouldSecondNode && firstStatement !== shouldSecondNode) {
-          reportNotOnTop(order[1], shouldSecondNode, firstStatement)
         }
       }
     }
   )
 
-  /**
-   * @param {ASTNode} shouldFirstNode
-   * @param {ASTNode} shouldSecondNode
-   * @param {ASTNode} before
-   */
-  function reportBothNotOnTop(shouldFirstNode, shouldSecondNode, before) {
-    context.report({
-      node: shouldFirstNode,
-      loc: shouldFirstNode.loc,
-      messageId: 'macrosNotOnTop',
-      data: {
-        macro: order[0]
-      },
-      fix(fixer) {
-        return [
-          ...moveNodeBefore(fixer, shouldFirstNode, before),
-          ...moveNodeBefore(fixer, shouldSecondNode, before)
-        ]
-      }
-    })
-  }
-
   /**
    * @param {string} macro
-   * @param {ASTNode} node
+   * @param {ASTNode[]} nodes
    * @param {ASTNode} before
    */
-  function reportNotOnTop(macro, node, before) {
+  function reportNotOnTop(macro, nodes, before) {
     context.report({
-      node,
-      loc: node.loc,
+      node: nodes[0],
+      loc: nodes[0].loc,
       messageId: 'macrosNotOnTop',
       data: {
         macro
       },
-      fix(fixer) {
-        return moveNodeBefore(fixer, node, before)
+      *fix(fixer) {
+        for (const node of nodes) {
+          yield* moveNodeBefore(fixer, node, before)
+        }
       }
     })
   }
@@ -288,7 +262,7 @@ module.exports = {
           order: {
             type: 'array',
             items: {
-              enum: Object.values(ORDER)
+              enum: ORDER_SCHEMA
             },
             uniqueItems: true,
             additionalItems: false
diff --git a/lib/rules/multi-word-component-names.js b/lib/rules/multi-word-component-names.js
index 38426bfb..7f8e7bb6 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/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js
index dafc70e0..835fb732 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 1698b23b..a63891bd 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/lib/utils/ts-ast-utils.js b/lib/utils/ts-ast-utils.js
index e7cc5242..330e6b98 100644
--- a/lib/utils/ts-ast-utils.js
+++ b/lib/utils/ts-ast-utils.js
@@ -10,6 +10,7 @@ const { findVariable } = require('@eslint-community/eslint-utils')
 /**
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit
+ * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownEmit} ComponentUnknownEmit
  */
 
 module.exports = {
@@ -147,22 +148,42 @@ function* extractRuntimeProps(context, node) {
 /**
  * @see https://github.com/vuejs/vue-next/blob/348c3b01e56383ffa70b180d1376fdf4ac12e274/packages/compiler-sfc/src/compileScript.ts#L1632
  * @param {TSTypeLiteral | TSInterfaceBody | TSFunctionType} node
- * @returns {IterableIterator<ComponentTypeEmit>}
+ * @returns {IterableIterator<ComponentTypeEmit | ComponentUnknownEmit>}
  */
 function* extractRuntimeEmits(node) {
-  if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {
-    const members = node.type === 'TSTypeLiteral' ? node.members : node.body
-    for (const t of members) {
-      if (t.type === 'TSCallSignatureDeclaration') {
-        yield* extractEventNames(
-          t.params[0],
-          /** @type {TSCallSignatureDeclaration} */ (t)
-        )
+  if (node.type === 'TSFunctionType') {
+    yield* extractEventNames(node.params[0], node)
+    return
+  }
+  const members = node.type === 'TSTypeLiteral' ? node.members : node.body
+  for (const member of members) {
+    if (member.type === 'TSCallSignatureDeclaration') {
+      yield* extractEventNames(
+        member.params[0],
+        /** @type {TSCallSignatureDeclaration} */ (member)
+      )
+    } else if (
+      member.type === 'TSPropertySignature' ||
+      member.type === 'TSMethodSignature'
+    ) {
+      if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
+        yield {
+          type: 'unknown',
+          node: member.key
+        }
+        continue
+      }
+      yield {
+        type: 'type',
+        key: /** @type {Identifier | Literal} */ (member.key),
+        emitName:
+          member.key.type === 'Identifier'
+            ? member.key.name
+            : `${member.key.value}`,
+        value: null,
+        node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
       }
     }
-    return
-  } else {
-    yield* extractEventNames(node.params[0], node)
   }
 }
 
diff --git a/typings/eslint-plugin-vue/util-types/utils.ts b/typings/eslint-plugin-vue/util-types/utils.ts
index d998761b..e16d8603 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)
@@ -152,13 +156,23 @@ export type ComponentUnknownEmit = {
   node: Expression | SpreadElement | null
 }
 
-export type ComponentTypeEmit = {
+export type ComponentTypeEmitCallSignature = {
   type: 'type'
   key: TSLiteralType
   emitName: string
   value: null
   node: TSCallSignatureDeclaration | TSFunctionType
 }
+export type ComponentTypeEmitPropertySignature = {
+  type: 'type'
+  key: Identifier | Literal
+  emitName: string
+  value: null
+  node: TSPropertySignature | TSMethodSignature
+}
+export type ComponentTypeEmit =
+  | ComponentTypeEmitCallSignature
+  | ComponentTypeEmitPropertySignature
 
 export type ComponentEmit =
   | ComponentArrayEmit


Test patch:
diff --git a/tests/lib/rules/define-macros-order.js b/tests/lib/rules/define-macros-order.js
index bd46ac3d..46e94eae 100644
--- a/tests/lib/rules/define-macros-order.js
+++ b/tests/lib/rules/define-macros-order.js
@@ -148,6 +148,28 @@ tester.run('define-macros-order', rule, {
         <script setup>
         </script>
       `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          import Foo from 'foo'
+          /** options */
+          defineOptions({})
+          /** emits */
+          defineEmits(['update:foo'])
+          /** props */
+          const props = defineProps(['foo'])
+          /** slots */
+          const slots = defineSlots()
+          console.log('test1')
+        </script>
+      `,
+      options: [
+        {
+          order: ['defineOptions', 'defineEmits', 'defineProps', 'defineSlots']
+        }
+      ]
     }
   ],
   invalid: [
@@ -520,6 +542,86 @@ tester.run('define-macros-order', rule, {
           line: 5
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          import Foo from 'foo'
+          console.log('test1')
+          /** emits */
+          defineEmits(['update:foo'])
+          /** props */
+          const props = defineProps(['foo'])
+          /** slots */
+          const slots = defineSlots()
+          /** options */
+          defineOptions({})
+        </script>
+      `,
+      output: `
+        <script setup>
+          import Foo from 'foo'
+          /** options */
+          defineOptions({})
+          /** emits */
+          defineEmits(['update:foo'])
+          /** props */
+          const props = defineProps(['foo'])
+          /** slots */
+          const slots = defineSlots()
+          console.log('test1')
+        </script>
+      `,
+      options: [
+        {
+          order: ['defineOptions', 'defineEmits', 'defineProps', 'defineSlots']
+        }
+      ],
+      errors: [
+        {
+          message: message('defineOptions'),
+          line: 12
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          /** slots */
+          const slots = defineSlots()
+          /** options */
+          defineOptions({})
+          /** emits */
+          defineEmits(['update:foo'])
+          /** props */
+          const props = defineProps(['foo'])
+        </script>
+      `,
+      output: `
+        <script setup>
+          /** options */
+          defineOptions({})
+          /** emits */
+          defineEmits(['update:foo'])
+          /** props */
+          const props = defineProps(['foo'])
+          /** slots */
+          const slots = defineSlots()
+        </script>
+      `,
+      options: [
+        {
+          order: ['defineOptions', 'defineEmits', 'defineProps', 'defineSlots']
+        }
+      ],
+      errors: [
+        {
+          message: message('defineOptions'),
+          line: 6
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/multi-word-component-names.js b/tests/lib/rules/multi-word-component-names.js
index d11b11d3..96302eec 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, {
       <template>
         <AppButton />
       </template>
-      
+
       <script setup lang="ts">
       import AppButton from "@/components/AppButton.vue";
       </script>`
+    },
+    {
+      filename: 'Single.vue',
+      code: `
+      <script setup>
+      defineOptions({name: 'MultiWord'})
+      </script>
+      `
     }
   ],
   invalid: [
@@ -333,6 +341,20 @@ tester.run('multi-word-component-names', rule, {
           line: 1
         }
       ]
+    },
+    {
+      filename: 'MultiWord.vue',
+      code: `
+      <script setup>
+      defineOptions({name: 'Single'})
+      </script>
+      `,
+      errors: [
+        {
+          message: 'Component name "Single" should always be multi-word.',
+          line: 3
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js
index ead9cce6..d9114810 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: `<script setup> defineOptions({}) </script>`,
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `<script setup> defineOptions({ ...name }) </script>`,
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `<script setup> defineOptions({ name: 'Foo' }) </script>`,
+      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: `<script setup> defineOptions({name: '${name}'}) </script>`,
+      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/tests/lib/rules/require-explicit-emits.js b/tests/lib/rules/require-explicit-emits.js
index 9a22c74a..a5bc5114 100644
--- a/tests/lib/rules/require-explicit-emits.js
+++ b/tests/lib/rules/require-explicit-emits.js
@@ -593,6 +593,31 @@ tester.run('require-explicit-emits', rule, {
       </script>
       `,
       options: [{ allowProps: true }]
+    },
+    {
+      // new syntax in Vue 3.3
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      const emit = defineEmits<{foo: [], bar:[number]}>()
+      emit('foo')
+      emit('bar', 42)
+      </script>
+      `,
+      parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+    },
+    {
+      // new syntax in Vue 3.3
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      type Emits = {foo: [], bar:[number]}
+      const emit = defineEmits<Emits>()
+      emit('foo')
+      emit('bar', 42)
+      </script>
+      `,
+      parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
     }
   ],
   invalid: [
@@ -1886,6 +1911,45 @@ emits: {'foo': null}
           ]
         }
       ]
+    },
+    {
+      // new syntax in Vue 3.3
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      const emit = defineEmits<{foo: []}>()
+      emit('foo')
+      emit('bar')
+      </script>
+      `,
+      parserOptions: { parser: require.resolve('@typescript-eslint/parser') },
+      errors: [
+        {
+          message:
+            'The "bar" event has been triggered but not declared on `defineEmits`.',
+          line: 5
+        }
+      ]
+    },
+    {
+      // new syntax in Vue 3.3
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      type Emits = {foo: []}
+      const emit = defineEmits<Emits>()
+      emit('foo')
+      emit('bar')
+      </script>
+      `,
+      parserOptions: { parser: require.resolve('@typescript-eslint/parser') },
+      errors: [
+        {
+          message:
+            'The "bar" event has been triggered but not declared on `defineEmits`.',
+          line: 6
+        }
+      ]
     }
   ]
 })