Repository: vuejs/eslint-plugin-vue
Pull request #2297 (2023-10-19 09:21:11)

Add `externalIgnores` option for `vue/singleline-html-element-content-newline`
## What to be solved

When users want to customize `ignores` option for `vue/singleline-html-element-content-newline` rule and don't want to lose the ignored elements which are specified by default, it's a hard work.

A long list is defined in `utils/inline-non-void-elements.json`, and user need to define it again in their config.


Patch:
diff --git a/docs/rules/define-emits-declaration.md b/docs/rules/define-emits-declaration.md
index 2fd4b478..7a11524c 100644
--- a/docs/rules/define-emits-declaration.md
+++ b/docs/rules/define-emits-declaration.md
@@ -5,13 +5,15 @@ title: vue/define-emits-declaration
 description: enforce declaration style of `defineEmits`
 since: v9.5.0
 ---
+
 # vue/define-emits-declaration
 
 > enforce declaration style of `defineEmits`
 
 ## :book: Rule Details
 
-This rule enforces `defineEmits` typing style which you should use `type-based` or `runtime` declaration.
+This rule enforces `defineEmits` typing style which you should use `type-based`, strict `type-literal`
+(introduced in Vue 3.3), or `runtime` declaration.
 
 This rule only works in setup script and `lang="ts"`.
 
@@ -25,6 +27,12 @@ const emit = defineEmits<{
   (e: 'update', value: string): void
 }>()
 
+/* ✓ GOOD */
+const emit = defineEmits<{
+  change: [id: number]
+  update: [value: string]
+}>()
+
 /* ✗ BAD */
 const emit = defineEmits({
   change: (id) => typeof id == 'number',
@@ -41,10 +49,11 @@ const emit = defineEmits(['change', 'update'])
 ## :wrench: Options
 
 ```json
-  "vue/define-emits-declaration": ["error", "type-based" | "runtime"]
+  "vue/define-emits-declaration": ["error", "type-based" | "type-literal" | "runtime"]
 ```
 
-- `type-based` (default) enforces type-based declaration
+- `type-based` (default) enforces type based declaration
+- `type-literal` enforces strict "type literal" type based declaration
 - `runtime` enforces runtime declaration
 
 ### `runtime`
@@ -72,6 +81,37 @@ const emit = defineEmits(['change', 'update'])
 
 </eslint-code-block>
 
+### `type-literal`
+
+<eslint-code-block :rules="{'vue/define-emits-declaration': ['error', 'type-literal']}">
+
+```vue
+<script setup lang="ts">
+/* ✗ BAD */
+const emit = defineEmits(['change', 'update'])
+
+/* ✗ BAD */
+const emit = defineEmits({
+  change: (id) => typeof id == 'number',
+  update: (value) => typeof value == 'string'
+})
+
+/* ✗ BAD */
+const emit = defineEmits<{
+  (e: 'change', id: number): void
+  (e: 'update', value: string): void
+}>()
+
+/* ✓ GOOD */
+const emit = defineEmits<{
+  change: [id: number]
+  update: [value: string]
+}>()
+</script>
+```
+
+</eslint-code-block>
+
 ## :couple: Related Rules
 
 - [vue/define-props-declaration](./define-props-declaration.md)
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 224cddc7..a7d3047a 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -251,6 +251,7 @@ For example:
 | [vue/no-undef-components](./no-undef-components.md) | disallow use of undefined components in `<template>` |  | :hammer: |
 | [vue/no-undef-properties](./no-undef-properties.md) | disallow undefined properties |  | :hammer: |
 | [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: | :hammer: |
+| [vue/no-unused-emit-declarations](./no-unused-emit-declarations.md) | disallow unused emit declarations |  | :hammer: |
 | [vue/no-unused-properties](./no-unused-properties.md) | disallow unused properties |  | :hammer: |
 | [vue/no-unused-refs](./no-unused-refs.md) | disallow unused refs |  | :hammer: |
 | [vue/no-use-v-else-with-v-for](./no-use-v-else-with-v-for.md) | disallow using `v-else-if`/`v-else` on the same element as `v-for` |  | :hammer: |
diff --git a/docs/rules/no-unused-emit-declarations.md b/docs/rules/no-unused-emit-declarations.md
new file mode 100644
index 00000000..a6c93862
--- /dev/null
+++ b/docs/rules/no-unused-emit-declarations.md
@@ -0,0 +1,59 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-unused-emit-declarations
+description: disallow unused emit declarations
+---
+# vue/no-unused-emit-declarations
+
+> disallow unused emit declarations
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+
+## :book: Rule Details
+
+This rule is aimed at eliminating unused emit declarations.
+
+<eslint-code-block :rules="{'vue/no-unused-emit-declarations': ['error']}">
+
+```vue
+<!-- ✗ BAD -->
+<script>
+  export default {
+    emits: ['foo'],
+  }
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/no-unused-emit-declarations': ['error']}">
+
+```vue
+<!-- ✓ GOOD -->
+<script>
+  export default {
+    emits: ['foo'],
+    methods: {
+      foo() {
+        this.$emit('foo')
+      },
+    },
+  }
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/require-explicit-emits](./require-explicit-emits.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unused-emit-declarations.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-unused-emit-declarations.js)
diff --git a/docs/rules/require-explicit-emits.md b/docs/rules/require-explicit-emits.md
index 6d681c55..f7ce60dd 100644
--- a/docs/rules/require-explicit-emits.md
+++ b/docs/rules/require-explicit-emits.md
@@ -109,6 +109,10 @@ export default {
 
 </eslint-code-block>
 
+## :couple: Related Rules
+
+- [vue/no-unused-emit-declarations](./no-unused-emit-declarations.md)
+
 ## :books: Further Reading
 
 - [Guide - Custom Events / Defining Custom Events](https://v3.vuejs.org/guide/component-custom-events.html#defining-custom-events)
diff --git a/docs/rules/singleline-html-element-content-newline.md b/docs/rules/singleline-html-element-content-newline.md
index 8e6dadf3..2cc64f39 100644
--- a/docs/rules/singleline-html-element-content-newline.md
+++ b/docs/rules/singleline-html-element-content-newline.md
@@ -56,7 +56,8 @@ This rule enforces a line break before and after the contents of a singleline el
   "vue/singleline-html-element-content-newline": ["error", {
     "ignoreWhenNoAttributes": true,
     "ignoreWhenEmpty": true,
-    "ignores": ["pre", "textarea", ...INLINE_ELEMENTS]
+    "ignores": ["pre", "textarea", ...INLINE_ELEMENTS],
+    "externalIgnores": []
   }]
 }
 ```
@@ -66,7 +67,9 @@ This rule enforces a line break before and after the contents of a singleline el
 - `ignoreWhenEmpty` ... disables reporting when element has no content.
     default `true`
 - `ignores` ... the configuration for element names to ignore line breaks style.
-    default `["pre", "textarea", ...INLINE_ELEMENTS]`.
+    default `["pre", "textarea", ...INLINE_ELEMENTS]`
+- `externalIgnores` ... the configuration for external element names to ignore line breaks style, it allows avoiding overwrite the default value of ignores.
+    default `[]`
 
 ::: info
   All inline non void elements can be found [here](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/utils/inline-non-void-elements.json).
diff --git a/lib/configs/vue3-strongly-recommended.js b/lib/configs/vue3-strongly-recommended.js
index 9da67f39..564e386b 100644
--- a/lib/configs/vue3-strongly-recommended.js
+++ b/lib/configs/vue3-strongly-recommended.js
@@ -28,7 +28,12 @@ module.exports = {
     'vue/require-prop-types': 'warn',
     'vue/singleline-html-element-content-newline': 'warn',
     'vue/v-bind-style': 'warn',
-    'vue/v-on-event-hyphenation': 'warn',
+    'vue/v-on-event-hyphenation': [
+      'warn',
+      {
+        autofix: true
+      }
+    ],
     'vue/v-on-style': 'warn',
     'vue/v-slot-style': 'warn'
   }
diff --git a/lib/index.js b/lib/index.js
index edbe34a2..0f3dcba2 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -147,6 +147,7 @@ module.exports = {
     'no-undef-properties': require('./rules/no-undef-properties'),
     'no-unsupported-features': require('./rules/no-unsupported-features'),
     'no-unused-components': require('./rules/no-unused-components'),
+    'no-unused-emit-declarations': require('./rules/no-unused-emit-declarations'),
     'no-unused-properties': require('./rules/no-unused-properties'),
     'no-unused-refs': require('./rules/no-unused-refs'),
     'no-unused-vars': require('./rules/no-unused-vars'),
diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js
index fc1849b1..487af771 100644
--- a/lib/rules/component-name-in-template-casing.js
+++ b/lib/rules/component-name-in-template-casing.js
@@ -123,7 +123,8 @@ module.exports = {
       if (
         (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
         utils.isHtmlWellKnownElementName(node.rawName) ||
-        utils.isSvgWellKnownElementName(node.rawName)
+        utils.isSvgWellKnownElementName(node.rawName) ||
+        utils.isVueBuiltInElementName(node.rawName)
       ) {
         return false
       }
diff --git a/lib/rules/define-emits-declaration.js b/lib/rules/define-emits-declaration.js
index 478e1d84..3d12e0bc 100644
--- a/lib/rules/define-emits-declaration.js
+++ b/lib/rules/define-emits-declaration.js
@@ -6,6 +6,11 @@
 
 const utils = require('../utils')
 
+/**
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSTypeLiteral
+ *
+ */
+
 module.exports = {
   meta: {
     type: 'suggestion',
@@ -17,12 +22,14 @@ module.exports = {
     fixable: null,
     schema: [
       {
-        enum: ['type-based', 'runtime']
+        enum: ['type-based', 'type-literal', 'runtime']
       }
     ],
     messages: {
-      hasArg: 'Use type-based declaration instead of runtime declaration.',
-      hasTypeArg: 'Use runtime declaration instead of type-based declaration.'
+      hasArg: 'Use type based declaration instead of runtime declaration.',
+      hasTypeArg: 'Use runtime declaration instead of type based declaration.',
+      hasTypeCallArg:
+        'Use new type literal declaration instead of the old call signature declaration.'
     }
   },
   /** @param {RuleContext} context */
@@ -46,8 +53,31 @@ module.exports = {
             break
           }
 
-          case 'runtime': {
+          case 'type-literal': {
+            if (node.arguments.length > 0) {
+              context.report({
+                node,
+                messageId: 'hasArg'
+              })
+              return
+            }
+
             const typeArguments = node.typeArguments || node.typeParameters
+            const param = /** @type {TSTypeLiteral} */ (typeArguments.params[0])
+            for (const memberNode of param.members) {
+              if (memberNode.type !== 'TSPropertySignature') {
+                context.report({
+                  node: memberNode,
+                  messageId: 'hasTypeCallArg'
+                })
+              }
+            }
+            break
+          }
+
+          case 'runtime': {
+            const typeArguments =
+              'typeArguments' in node ? node.typeArguments : node.typeParameters
             if (typeArguments && typeArguments.params.length > 0) {
               context.report({
                 node,
diff --git a/lib/rules/define-props-declaration.js b/lib/rules/define-props-declaration.js
index d355bb2f..3862bb22 100644
--- a/lib/rules/define-props-declaration.js
+++ b/lib/rules/define-props-declaration.js
@@ -47,7 +47,8 @@ module.exports = {
           }
 
           case 'runtime': {
-            const typeArguments = node.typeArguments || node.typeParameters
+            const typeArguments =
+              'typeArguments' in node ? node.typeArguments : node.typeParameters
             if (typeArguments && typeArguments.params.length > 0) {
               context.report({
                 node,
diff --git a/lib/rules/no-unused-emit-declarations.js b/lib/rules/no-unused-emit-declarations.js
new file mode 100644
index 00000000..1414b50c
--- /dev/null
+++ b/lib/rules/no-unused-emit-declarations.js
@@ -0,0 +1,330 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const { findVariable } = require('@eslint-community/eslint-utils')
+const utils = require('../utils')
+
+/**
+ * @typedef {import('../utils').ComponentEmit} ComponentEmit
+ * @typedef {import('../utils').VueObjectData} VueObjectData
+ */
+
+/**
+ * @typedef {object} SetupContext
+ * @property {Set<Identifier>} contextReferenceIds
+ * @property {Set<Identifier>} emitReferenceIds
+ */
+
+/**
+ * @typedef {object} NameWithLoc
+ * @property {string} name
+ * @property {SourceLocation} loc
+ * @property {Range} range
+ */
+
+/**
+ * Get the name param node from the given CallExpression
+ * @param {CallExpression} node CallExpression
+ * @returns {NameWithLoc | null}
+ */
+function getNameParamNode(node) {
+  const nameLiteralNode = node.arguments[0]
+  if (nameLiteralNode && utils.isStringLiteral(nameLiteralNode)) {
+    const name = utils.getStringLiteralValue(nameLiteralNode)
+    if (name != null) {
+      return { name, loc: nameLiteralNode.loc, range: nameLiteralNode.range }
+    }
+  }
+
+  return null
+}
+
+/**
+ * Check if the given node is a reference of setup context
+ * @param {Expression | Super | SpreadElement} value
+ * @param {SetupContext} setupContext
+ * @returns {boolean}
+ * */
+function hasReferenceId(value, setupContext) {
+  const { contextReferenceIds, emitReferenceIds } = setupContext
+  return (
+    value.type === 'Identifier' &&
+    (emitReferenceIds.has(value) || contextReferenceIds.has(value))
+  )
+}
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'disallow unused emit declarations',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/no-unused-emit-declarations.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      unused: '`{{name}}` is defined as emit but never used.'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    /** @type {Map<string, ComponentEmit>} */
+    const emitDeclarations = new Map()
+    /** @type {Map<string, Expression>} */
+    const emitCalls = new Map()
+    /** @type {Map<ObjectExpression | Program, SetupContext>} */
+    const setupContexts = new Map()
+    const programNode = context.getSourceCode().ast
+
+    /**
+     * @param {CallExpression} node
+     * */
+    function addEmitCall(node) {
+      const nameParamNode = getNameParamNode(node)
+      if (nameParamNode) {
+        emitCalls.set(nameParamNode.name, node)
+      }
+    }
+    function clearEmits() {
+      emitCalls.clear()
+      emitDeclarations.clear()
+    }
+
+    /**
+     * @param {Expression | SpreadElement} expression
+     * @param {SetupContext} setupContext
+     * @returns {boolean}
+     * */
+    function checkExpressionReference(expression, setupContext) {
+      if (expression.type === 'MemberExpression') {
+        const memObject = utils.skipChainExpression(expression.object)
+        if (hasReferenceId(memObject, setupContext)) {
+          clearEmits()
+          return true
+        }
+      }
+      if (hasReferenceId(expression, setupContext)) {
+        clearEmits()
+        return true
+      }
+      return false
+    }
+
+    /**
+     *
+     * @param {Array<Expression | SpreadElement>} args
+     * @param {SetupContext} setupContext
+     * @returns {boolean}
+     */
+    function verifyArgumentsReferences(args, setupContext) {
+      for (const argument of args) {
+        if (argument.type === 'ObjectExpression') {
+          for (const property of argument.properties) {
+            if (
+              property.type === 'Property' &&
+              checkExpressionReference(property.value, setupContext)
+            ) {
+              return true
+            }
+          }
+        }
+
+        if (argument.type === 'ArrayExpression') {
+          for (const element of argument.elements) {
+            if (!element) {
+              continue
+            }
+            if (checkExpressionReference(element, setupContext)) {
+              return true
+            }
+          }
+        }
+
+        if (checkExpressionReference(argument, setupContext)) {
+          return true
+        }
+      }
+      return false
+    }
+
+    /**
+     * @param {Expression | Super} callee
+     * @param {Set<Identifier>} referenceIds
+     * @param {CallExpression} node
+     * */
+    function addEmitCallByReference(callee, referenceIds, node) {
+      if (callee.type === 'Identifier' && referenceIds.has(callee)) {
+        addEmitCall(node)
+      }
+    }
+
+    const callVisitor = {
+      /**
+       * @param {CallExpression} node
+       * @param {VueObjectData} [info]
+       */
+      CallExpression(node, info) {
+        const callee = utils.skipChainExpression(node.callee)
+
+        let emit = null
+        if (callee.type === 'MemberExpression') {
+          const name = utils.getStaticPropertyName(callee)
+          if (name === 'emit' || name === '$emit') {
+            emit = { name, member: callee }
+          }
+        }
+
+        const vueDefineNode = info ? info.node : programNode
+        const setupContext = setupContexts.get(vueDefineNode)
+        if (setupContext) {
+          if (
+            callee.parent.type === 'CallExpression' &&
+            callee.parent.arguments &&
+            verifyArgumentsReferences(callee.parent.arguments, setupContext)
+          ) {
+            // skip if the emit passed as argument
+            return
+          }
+
+          const { contextReferenceIds, emitReferenceIds } = setupContext
+          // verify setup(props,{emit}) {emit()}
+          addEmitCallByReference(callee, emitReferenceIds, node)
+          if (emit && emit.name === 'emit') {
+            const memObject = utils.skipChainExpression(emit.member.object)
+            // verify setup(props,context) {context.emit()}
+            addEmitCallByReference(memObject, contextReferenceIds, node)
+          }
+        }
+
+        if (emit && emit.name === '$emit') {
+          const memObject = utils.skipChainExpression(emit.member.object)
+          // verify this.$emit()
+          if (utils.isThis(memObject, context)) {
+            addEmitCall(node)
+          }
+        }
+
+        // verify $emit() in template
+        if (callee.type === 'Identifier' && callee.name === '$emit') {
+          addEmitCall(node)
+        }
+      }
+    }
+
+    return utils.compositingVisitors(
+      utils.defineTemplateBodyVisitor(context, callVisitor),
+      utils.defineVueVisitor(context, {
+        ...callVisitor,
+        onVueObjectEnter(node) {
+          for (const emit of utils.getComponentEmitsFromOptions(node)) {
+            if (emit.emitName) {
+              emitDeclarations.set(emit.emitName, emit)
+            }
+          }
+        },
+        onSetupFunctionEnter(node, { node: vueNode }) {
+          const contextParam = node.params[1]
+          if (
+            !contextParam ||
+            contextParam.type === 'RestElement' ||
+            contextParam.type === 'ArrayPattern'
+          ) {
+            // no arguments or cannot check
+            return
+          }
+
+          /** @type {Set<Identifier>} */
+          const contextReferenceIds = new Set()
+          /** @type {Set<Identifier>} */
+          const emitReferenceIds = new Set()
+          if (contextParam.type === 'ObjectPattern') {
+            const emitProperty = utils.findAssignmentProperty(
+              contextParam,
+              'emit'
+            )
+            if (!emitProperty) {
+              return
+            }
+            const emitParam = emitProperty.value
+            // `setup(props, {emit})`
+            const variable =
+              emitParam.type === 'Identifier'
+                ? findVariable(context.getScope(), emitParam)
+                : null
+            if (!variable) {
+              return
+            }
+            for (const reference of variable.references) {
+              emitReferenceIds.add(reference.identifier)
+            }
+          } else if (contextParam.type === 'Identifier') {
+            // `setup(props, context)`
+            const variable = findVariable(context.getScope(), contextParam)
+            for (const reference of variable.references) {
+              contextReferenceIds.add(reference.identifier)
+            }
+          }
+
+          setupContexts.set(vueNode, {
+            contextReferenceIds,
+            emitReferenceIds
+          })
+        }
+      }),
+      utils.defineScriptSetupVisitor(context, {
+        onDefineEmitsEnter(node, emits) {
+          for (const emit of emits) {
+            if (emit.emitName) {
+              emitDeclarations.set(emit.emitName, emit)
+            }
+          }
+
+          if (
+            !node.parent ||
+            node.parent.type !== 'VariableDeclarator' ||
+            node.parent.init !== node
+          ) {
+            return
+          }
+
+          const emitParam = node.parent.id
+          const variable =
+            emitParam.type === 'Identifier'
+              ? findVariable(context.getScope(), emitParam)
+              : null
+          if (!variable) {
+            return
+          }
+          /** @type {Set<Identifier>} */
+          const emitReferenceIds = new Set()
+          for (const reference of variable.references) {
+            emitReferenceIds.add(reference.identifier)
+          }
+          setupContexts.set(programNode, {
+            contextReferenceIds: new Set(),
+            emitReferenceIds
+          })
+        },
+        ...callVisitor
+      }),
+      {
+        'Program:exit'() {
+          for (const [name, emit] of emitDeclarations) {
+            if (!emitCalls.has(name) && emit.node) {
+              context.report({
+                node: emit.node,
+                loc: emit.node.loc,
+                messageId: 'unused',
+                data: { name }
+              })
+            }
+          }
+        }
+      }
+    )
+  }
+}
diff --git a/lib/rules/require-toggle-inside-transition.js b/lib/rules/require-toggle-inside-transition.js
index 365fbc71..fc46f75f 100644
--- a/lib/rules/require-toggle-inside-transition.js
+++ b/lib/rules/require-toggle-inside-transition.js
@@ -32,9 +32,18 @@ module.exports = {
       if (utils.isCustomComponent(element)) {
         return
       }
+
+      /** @type VElement */ // @ts-expect-error
+      const parent = element.parent
+      if (utils.hasAttribute(parent, 'appear')) {
+        return
+      }
+
       if (
+        element.name !== 'slot' &&
         !utils.hasDirective(element, 'if') &&
-        !utils.hasDirective(element, 'show')
+        !utils.hasDirective(element, 'show') &&
+        !utils.hasDirective(element, 'bind', 'key')
       ) {
         context.report({
           node: element.startTag,
@@ -47,9 +56,11 @@ module.exports = {
     return utils.defineTemplateBodyVisitor(context, {
       /** @param {VElement} node */
       "VElement[name='transition'] > VElement"(node) {
-        if (node.parent.children[0] !== node) {
+        const child = node.parent.children.find(utils.isVElement)
+        if (child !== node) {
           return
         }
+
         verifyInsideElement(node)
       }
     })
diff --git a/lib/rules/require-typed-ref.js b/lib/rules/require-typed-ref.js
index 2f691fec..e26ac133 100644
--- a/lib/rules/require-typed-ref.js
+++ b/lib/rules/require-typed-ref.js
@@ -84,7 +84,9 @@ module.exports = {
           }
 
           const typeArguments =
-            ref.node.typeArguments || ref.node.typeParameters
+            'typeArguments' in ref.node
+              ? ref.node.typeArguments
+              : ref.node.typeParameters
           if (typeArguments == null) {
             if (
               ref.node.parent.type === 'VariableDeclarator' &&
diff --git a/lib/rules/singleline-html-element-content-newline.js b/lib/rules/singleline-html-element-content-newline.js
index 1f8b9ef2..d89fd940 100644
--- a/lib/rules/singleline-html-element-content-newline.js
+++ b/lib/rules/singleline-html-element-content-newline.js
@@ -22,6 +22,7 @@ function parseOptions(options) {
   return Object.assign(
     {
       ignores: ['pre', 'textarea', ...INLINE_ELEMENTS],
+      externalIgnores: [],
       ignoreWhenNoAttributes: true,
       ignoreWhenEmpty: true
     },
@@ -67,6 +68,12 @@ module.exports = {
             items: { type: 'string' },
             uniqueItems: true,
             additionalItems: false
+          },
+          externalIgnores: {
+            type: 'array',
+            items: { type: 'string' },
+            uniqueItems: true,
+            additionalItems: false
           }
         },
         additionalProperties: false
@@ -82,7 +89,7 @@ module.exports = {
   /** @param {RuleContext} context */
   create(context) {
     const options = parseOptions(context.options[0])
-    const ignores = options.ignores
+    const ignores = new Set([...options.ignores, ...options.externalIgnores])
     const ignoreWhenNoAttributes = options.ignoreWhenNoAttributes
     const ignoreWhenEmpty = options.ignoreWhenEmpty
     const template =
@@ -96,9 +103,9 @@ module.exports = {
     /** @param {VElement} node */
     function isIgnoredElement(node) {
       return (
-        ignores.includes(node.name) ||
-        ignores.includes(casing.pascalCase(node.rawName)) ||
-        ignores.includes(casing.kebabCase(node.rawName))
+        ignores.has(node.name) ||
+        ignores.has(casing.pascalCase(node.rawName)) ||
+        ignores.has(casing.kebabCase(node.rawName))
       )
     }
 
diff --git a/lib/rules/v-on-event-hyphenation.js b/lib/rules/v-on-event-hyphenation.js
index 5a3e023f..b797b3b7 100644
--- a/lib/rules/v-on-event-hyphenation.js
+++ b/lib/rules/v-on-event-hyphenation.js
@@ -10,7 +10,10 @@ module.exports = {
       description:
         'enforce v-on event naming style on custom components in template',
       categories: ['vue3-strongly-recommended'],
-      url: 'https://eslint.vuejs.org/rules/v-on-event-hyphenation.html'
+      url: 'https://eslint.vuejs.org/rules/v-on-event-hyphenation.html',
+      defaultOptions: {
+        vue3: [{ autofix: true }]
+      }
     },
     fixable: 'code',
     schema: [
diff --git a/lib/rules/valid-define-emits.js b/lib/rules/valid-define-emits.js
index 994302c6..2c83faa2 100644
--- a/lib/rules/valid-define-emits.js
+++ b/lib/rules/valid-define-emits.js
@@ -47,7 +47,8 @@ module.exports = {
         onDefineEmitsEnter(node) {
           defineEmitsNodes.push(node)
 
-          const typeArguments = node.typeArguments || node.typeParameters
+          const typeArguments =
+            'typeArguments' in node ? node.typeArguments : node.typeParameters
           if (node.arguments.length > 0) {
             if (typeArguments && typeArguments.params.length > 0) {
               // `defineEmits` has both a literal type and an argument.
diff --git a/lib/rules/valid-define-options.js b/lib/rules/valid-define-options.js
index c1cd5b99..fad893b4 100644
--- a/lib/rules/valid-define-options.js
+++ b/lib/rules/valid-define-options.js
@@ -74,7 +74,8 @@ module.exports = {
             })
           }
 
-          const typeArguments = node.typeArguments || node.typeParameters
+          const typeArguments =
+            'typeArguments' in node ? node.typeArguments : node.typeParameters
           if (typeArguments) {
             context.report({
               node: typeArguments,
diff --git a/lib/rules/valid-define-props.js b/lib/rules/valid-define-props.js
index ecde56f1..7e3b0a34 100644
--- a/lib/rules/valid-define-props.js
+++ b/lib/rules/valid-define-props.js
@@ -48,7 +48,8 @@ module.exports = {
         onDefinePropsEnter(node) {
           definePropsNodes.push(node)
 
-          const typeArguments = node.typeArguments || node.typeParameters
+          const typeArguments =
+            'typeArguments' in node ? node.typeArguments : node.typeParameters
           if (node.arguments.length > 0) {
             if (typeArguments && typeArguments.params.length > 0) {
               // `defineProps` has both a literal type and an argument.
diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js
index 6119406e..bacf43e1 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -1168,7 +1168,8 @@ module.exports.defineVisitor = function create(
     },
     /** @param {CallExpression} node */
     CallExpression(node) {
-      const typeArguments = node.typeArguments || node.typeParameters
+      const typeArguments =
+        'typeArguments' in node ? node.typeArguments : node.typeParameters
       const firstToken = tokenStore.getFirstToken(node)
       const rightToken = tokenStore.getLastToken(node)
       const leftToken = /** @type {Token} */ (
@@ -1695,7 +1696,8 @@ module.exports.defineVisitor = function create(
     },
     /** @param {NewExpression} node */
     NewExpression(node) {
-      const typeArguments = node.typeArguments || node.typeParameters
+      const typeArguments =
+        'typeArguments' in node ? node.typeArguments : node.typeParameters
       const newToken = tokenStore.getFirstToken(node)
       const calleeToken = tokenStore.getTokenAfter(newToken)
       const rightToken = tokenStore.getLastToken(node)
diff --git a/lib/utils/indent-ts.js b/lib/utils/indent-ts.js
index a4323b38..f021e8a1 100644
--- a/lib/utils/indent-ts.js
+++ b/lib/utils/indent-ts.js
@@ -332,7 +332,10 @@ function defineVisitor({
      * @param {TSTypeReference | TSInstantiationExpression} node
      */
     'TSTypeReference, TSInstantiationExpression'(node) {
-      const typeArguments = node.typeArguments || node.typeParameters
+      const typeArguments =
+        'typeArguments' in node
+          ? node.typeArguments
+          : /** @type {any} typescript-eslint v5 */ (node).typeParameters
       if (typeArguments) {
         const firstToken = tokenStore.getFirstToken(node)
         setOffset(tokenStore.getFirstToken(typeArguments), 1, firstToken)
@@ -1183,8 +1186,9 @@ function defineVisitor({
         setOffset([dotToken, propertyToken], 1, firstToken)
       }
       const typeArguments =
-        node.typeArguments ||
-        /** @type {any} typescript-eslint v5 */ (node).typeParameters
+        'typeArguments' in node
+          ? node.typeArguments
+          : /** @type {any} typescript-eslint v5 */ (node).typeParameters
       if (typeArguments) {
         setOffset(tokenStore.getFirstToken(typeArguments), 1, firstToken)
       }
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 8702c626..4073b691 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -54,6 +54,7 @@ const VUE2_BUILTIN_COMPONENT_NAMES = new Set(
 const VUE3_BUILTIN_COMPONENT_NAMES = new Set(
   require('./vue3-builtin-components')
 )
+const VUE_BUILTIN_ELEMENT_NAMES = new Set(require('./vue-builtin-elements'))
 const path = require('path')
 const vueEslintParser = require('vue-eslint-parser')
 const { traverseNodes, getFallbackKeys, NS } = vueEslintParser.AST
@@ -867,6 +868,15 @@ module.exports = {
     )
   },
 
+  /**
+   * Check whether the given name is Vue builtin element name or not.
+   * @param {string} name The name to check.
+   * @returns {boolean} `true` if the name is a builtin Vue element name
+   */
+  isVueBuiltInElementName(name) {
+    return VUE_BUILTIN_ELEMENT_NAMES.has(name.toLowerCase())
+  },
+
   /**
    * Check whether the given name is Vue builtin directive name or not.
    * @param {string} name The name to check.
@@ -2990,7 +3000,8 @@ function getComponentPropsFromDefineProps(context, node) {
       }
     ]
   }
-  const typeArguments = node.typeArguments || node.typeParameters
+  const typeArguments =
+    'typeArguments' in node ? node.typeArguments : node.typeParameters
   if (typeArguments && typeArguments.params.length > 0) {
     return getComponentPropsFromTypeDefine(context, typeArguments.params[0])
   }
@@ -3023,7 +3034,8 @@ function getComponentEmitsFromDefineEmits(context, node) {
       }
     ]
   }
-  const typeArguments = node.typeArguments || node.typeParameters
+  const typeArguments =
+    'typeArguments' in node ? node.typeArguments : node.typeParameters
   if (typeArguments && typeArguments.params.length > 0) {
     return getComponentEmitsFromTypeDefine(context, typeArguments.params[0])
   }
diff --git a/lib/utils/ts-utils/ts-ast.js b/lib/utils/ts-utils/ts-ast.js
index 8a95dd9b..3a7356a2 100644
--- a/lib/utils/ts-utils/ts-ast.js
+++ b/lib/utils/ts-utils/ts-ast.js
@@ -430,7 +430,10 @@ function inferRuntimeType(context, node, checked = new Set()) {
             return ['Array']
           }
           case 'NonNullable': {
-            const typeArguments = node.typeArguments || node.typeParameters
+            const typeArguments =
+              'typeArguments' in node
+                ? node.typeArguments
+                : /** @type {any} typescript-eslint v5 */ (node).typeParameters
             if (typeArguments && typeArguments.params[0]) {
               return inferRuntimeType(
                 context,
@@ -441,7 +444,10 @@ function inferRuntimeType(context, node, checked = new Set()) {
             break
           }
           case 'Extract': {
-            const typeArguments = node.typeArguments || node.typeParameters
+            const typeArguments =
+              'typeArguments' in node
+                ? node.typeArguments
+                : /** @type {any} typescript-eslint v5 */ (node).typeParameters
             if (typeArguments && typeArguments.params[1]) {
               return inferRuntimeType(context, typeArguments.params[1], checked)
             }
@@ -449,7 +455,10 @@ function inferRuntimeType(context, node, checked = new Set()) {
           }
           case 'Exclude':
           case 'OmitThisParameter': {
-            const typeArguments = node.typeArguments || node.typeParameters
+            const typeArguments =
+              'typeArguments' in node
+                ? node.typeArguments
+                : /** @type {any} typescript-eslint v5 */ (node).typeParameters
             if (typeArguments && typeArguments.params[0]) {
               return inferRuntimeType(context, typeArguments.params[0], checked)
             }
diff --git a/lib/utils/vue-builtin-elements.js b/lib/utils/vue-builtin-elements.js
new file mode 100644
index 00000000..70ddb6b7
--- /dev/null
+++ b/lib/utils/vue-builtin-elements.js
@@ -0,0 +1,1 @@
+module.exports = ['template', 'slot', 'component']
diff --git a/lib/utils/vue3-export-names.json b/lib/utils/vue3-export-names.json
index 15b72e2f..af9cda39 100644
--- a/lib/utils/vue3-export-names.json
+++ b/lib/utils/vue3-export-names.json
@@ -75,6 +75,15 @@
   "Suspense",
   "SuspenseBoundary",
   "RootHydrateFunction",
+  "BaseTransitionProps",
+  "TransitionHooks",
+  "TransitionState",
+  "useTransitionState",
+  "BaseTransitionPropsValidators",
+  "BaseTransition",
+  "resolveTransitionHooks",
+  "setTransitionHooks",
+  "getTransitionRawChildren",
   "Renderer",
   "HydrationRenderer",
   "RootRenderFunction",
@@ -135,15 +144,6 @@
   "AppContext",
   "Plugin",
   "CreateAppFunction",
-  "BaseTransitionProps",
-  "TransitionHooks",
-  "TransitionState",
-  "useTransitionState",
-  "BaseTransitionPropsValidators",
-  "BaseTransition",
-  "resolveTransitionHooks",
-  "setTransitionHooks",
-  "getTransitionRawChildren",
   "TeleportProps",
   "Teleport",
   "resolveComponent",
@@ -276,6 +276,7 @@
   "IframeHTMLAttributes",
   "ImgHTMLAttributes",
   "InsHTMLAttributes",
+  "InputTypeHTMLAttribute",
   "InputHTMLAttributes",
   "KeygenHTMLAttributes",
   "LabelHTMLAttributes",
diff --git a/package.json b/package.json
index eb3750f4..35236e9f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-vue",
-  "version": "9.17.0",
+  "version": "9.18.1",
   "description": "Official ESLint plugin for Vue.js",
   "main": "lib/index.js",
   "scripts": {


Test patch:
diff --git a/tests/lib/rules/component-name-in-template-casing.js b/tests/lib/rules/component-name-in-template-casing.js
index 3aa4ee43..99206380 100644
--- a/tests/lib/rules/component-name-in-template-casing.js
+++ b/tests/lib/rules/component-name-in-template-casing.js
@@ -86,6 +86,10 @@ tester.run('component-name-in-template-casing', rule, {
       code: '<template><circle cx="0" cy="0" :d="radius"></template>',
       options: ['PascalCase', { registeredComponentsOnly: false }]
     },
+    {
+      code: '<template><component is="div" /></template>',
+      options: ['PascalCase', { registeredComponentsOnly: false }]
+    },
 
     // kebab-case
     {
@@ -108,6 +112,10 @@ tester.run('component-name-in-template-casing', rule, {
       code: '<template><math><mspace/></math></template>',
       options: ['kebab-case', { registeredComponentsOnly: false }]
     },
+    {
+      code: '<template><component is="div" /></template>',
+      options: ['kebab-case', { registeredComponentsOnly: false }]
+    },
 
     // ignores
     {
@@ -859,7 +867,7 @@ tester.run('component-name-in-template-casing', rule, {
       `,
       output: `
         <template>
-          <Component />
+          <component />
           <Suspense />
           <Teleport />
           <ClientOnly />
@@ -868,7 +876,6 @@ tester.run('component-name-in-template-casing', rule, {
       `,
       options: ['PascalCase', { registeredComponentsOnly: false }],
       errors: [
-        'Component name "component" is not PascalCase.',
         'Component name "suspense" is not PascalCase.',
         'Component name "teleport" is not PascalCase.',
         'Component name "client-only" is not PascalCase.',
@@ -1025,7 +1032,7 @@ tester.run('component-name-in-template-casing', rule, {
                 <HelloWorld3 />
                 <HelloWorld4 />
                 <HelloWorld5 />
-                <Component />
+                <component />
               </template>
             `,
             errors: [
@@ -1058,11 +1065,6 @@ tester.run('component-name-in-template-casing', rule, {
                 message: 'Component name "hello-world5" is not PascalCase.',
                 line: 18,
                 column: 17
-              },
-              {
-                message: 'Component name "component" is not PascalCase.',
-                line: 19,
-                column: 17
               }
             ]
           }
diff --git a/tests/lib/rules/define-emits-declaration.js b/tests/lib/rules/define-emits-declaration.js
index 372fa1a4..43ecb385 100644
--- a/tests/lib/rules/define-emits-declaration.js
+++ b/tests/lib/rules/define-emits-declaration.js
@@ -63,6 +63,36 @@ tester.run('define-emits-declaration', rule, {
        `,
       options: ['runtime']
     },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup lang="ts">
+        const emit = defineEmits<{
+          change: [id: number]
+          update: [value: string]
+        }>()
+        </script>
+       `,
+      options: ['type-based'],
+      parserOptions: {
+        parser: require.resolve('@typescript-eslint/parser')
+      }
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup lang="ts">
+        const emit = defineEmits<{
+          change: [id: number]
+          update: [value: string]
+        }>()
+        </script>
+       `,
+      options: ['type-literal'],
+      parserOptions: {
+        parser: require.resolve('@typescript-eslint/parser')
+      }
+    },
     {
       filename: 'test.vue',
       // ignore code without defineEmits
@@ -82,7 +112,7 @@ tester.run('define-emits-declaration', rule, {
       code: `
          <script lang="ts">
          import { PropType } from 'vue'
- 
+
          export default {
            props: {
              kind: { type: String as PropType<'primary' | 'secondary'> },
@@ -106,7 +136,7 @@ tester.run('define-emits-declaration', rule, {
        `,
       errors: [
         {
-          message: 'Use type-based declaration instead of runtime declaration.',
+          message: 'Use type based declaration instead of runtime declaration.',
           line: 3
         }
       ]
@@ -121,7 +151,25 @@ tester.run('define-emits-declaration', rule, {
       options: ['type-based'],
       errors: [
         {
-          message: 'Use type-based declaration instead of runtime declaration.',
+          message: 'Use type based declaration instead of runtime declaration.',
+          line: 3
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+       <script setup lang="ts">
+       const emit = defineEmits(['change', 'update'])
+       </script>
+       `,
+      options: ['type-literal'],
+      parserOptions: {
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      errors: [
+        {
+          message: 'Use type based declaration instead of runtime declaration.',
           line: 3
         }
       ]
@@ -142,10 +190,59 @@ tester.run('define-emits-declaration', rule, {
       },
       errors: [
         {
-          message: 'Use runtime declaration instead of type-based declaration.',
+          message: 'Use runtime declaration instead of type based declaration.',
           line: 3
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup lang="ts">
+        const emit = defineEmits<{
+          (e: 'change', id: number): void
+          (e: 'update', value: string): void
+        }>()
+        </script>
+       `,
+      options: ['type-literal'],
+      parserOptions: {
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      errors: [
+        {
+          message:
+            'Use new type literal declaration instead of the old call signature declaration.',
+          line: 4
+        },
+        {
+          message:
+            'Use new type literal declaration instead of the old call signature declaration.',
+          line: 5
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup lang="ts">
+        const emit = defineEmits<{
+          'change': [id: number]
+          (e: 'update', value: string): void
+        }>()
+        </script>
+       `,
+      options: ['type-literal'],
+      parserOptions: {
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      errors: [
+        {
+          message:
+            'Use new type literal declaration instead of the old call signature declaration.',
+          line: 5
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/no-unused-emit-declarations.js b/tests/lib/rules/no-unused-emit-declarations.js
new file mode 100644
index 00000000..0667ad46
--- /dev/null
+++ b/tests/lib/rules/no-unused-emit-declarations.js
@@ -0,0 +1,693 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-unused-emit-declarations')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('no-unused-emit-declarations', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['count', 'foo'],
+          methods: {
+              setCount () {
+                  this.$emit('count', newCount)
+              },
+              setFoo () {
+                  this.$emit('foo', newFoo)
+              }
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        emits: ['foo'],
+        methods: {
+          click () {
+            const vm = this
+            vm?.$emit?.('foo')
+          }
+        }
+      }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <button @click="$emit('bar')">Bar</button>
+      </template>
+      <script>
+        export default {
+          emits: ['foo', 'bar'],
+          methods: {
+              setFoo () {
+                  this.$emit('foo', newFoo)
+              }
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <button @click="this.$emit('bar')">Bar</button>
+      </template>
+      <script>
+        export default {
+          emits: ['bar'],
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <button @click="$emit('bar')">Bar</button>
+      </template>
+      <script setup>
+        const emit = defineEmits(['foo', 'bar'])
+        const setFoo = () => {
+          emit('foo', newFoo)
+        }
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, { emit }) {
+            const setFoo = () => {
+              emit('foo', newFoo)
+            }
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, { emit: customName }) {
+            const setFoo = () => customName('foo')
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: { foo: null },
+          setup(_, { emit }) {
+            const setFoo = () => {
+              emit('foo', newFoo)
+            }
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          setup(_, { emit }) {
+            const setFoo = () => {
+              emit('foo', newFoo)
+            }
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <button @click="$emit('bar')">Bar</button>
+      </template>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, context) {
+            const setFoo = () => {
+              context.emit('foo', newFoo)
+            }
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, { emit }) {
+            useCustomComposable(emit)
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, context) {
+            useCustomComposable(context.emit)
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, { emit }) {
+            useCustomComposable([emit])
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, context) {
+            useCustomComposable([context.emit])
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, context) {
+            useCustomComposable({ emit: context.emit }) 
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: [...foo],
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        const emits = ['foo'];
+        export default {
+          emits: [...emits],
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        const emits = ['foo'];
+        export default {
+          emits: emits,
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, { emit }) {
+            useCustomComposable({ emit: emit }) 
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo', 'bar'],
+          setup(_, { emit }) {
+            const emitBar = () => {
+              emit('bar', newBar)
+            }
+          },
+          methods: {
+            setFoo() {
+              this.$emit('foo', newFoo)
+            }
+          }
+        }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+        defineEmits(...foo)
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+        const emits = ['foo'];
+        defineEmits([...emits])
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      const emit = defineEmits<{
+        (e: 'foo'): void
+      }>()
+      const change = () => emit('foo');
+      `,
+      parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      const emit = defineEmits<{
+        foo: [string, number]
+      }>()
+      const change = () => emit('foo');
+      `,
+      ...getTypeScriptFixtureTestOptions()
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo', 'count'],
+          methods: {
+            setFoo () {
+              this.$emit('foo', newFoo)
+            }
+          }
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 26,
+          endColumn: 33
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        emits: ['foo', 'bar'],
+        methods: {
+          click () {
+            const vm = this
+            vm?.$emit?.('foo')
+          }
+        }
+      }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 24,
+          endColumn: 29
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <button @click="$emit('bar')">Foo</button>
+      </template>
+      <script>
+        export default {
+          emits: ['foo', 'bar'],
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 7,
+          column: 19,
+          endColumn: 24
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: { foo: null },
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 20,
+          endColumn: 29
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <button @click="$emit('bar')">Bar</button>
+      </template>
+      <script setup>
+        const emit = defineEmits(['foo', 'bar'])
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 6,
+          column: 35,
+          endColumn: 40
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          methods: {
+              setFoo () {
+                  custom.$emit('foo', newFoo)
+              }
+          }
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 19,
+          endColumn: 24
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo', 'bar'],
+          setup(_, { emit }) {
+            const setFoo = () => {
+              emit('foo', newFoo)
+            }
+          }
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 26,
+          endColumn: 31
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <button @click="$emit('bar')">Bar</button>
+      </template>
+      <script>
+        // @vue/component
+        const Foo = {
+          emits: ['foo', 'bar'],
+        }
+        export default Foo
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 8,
+          column: 19,
+          endColumn: 24
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo', 'bar'],
+          setup(_, { emit: customName }) {
+            const setFoo = () => customName('foo')
+          }
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 26,
+          endColumn: 31
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup() {
+            const setFoo = () => foo.$emit('foo')
+          }
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 19,
+          endColumn: 24
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo', 'bar'],
+          setup(_, context) {
+            const setFoo = () => {
+              context.emit('foo', newFoo)
+            }
+          }
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 26,
+          endColumn: 31
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo', 'bar', 'baz'],
+          setup(_, { emit }) {
+            const emitBar = () => {
+              emit('bar', newBar)
+            }
+          },
+          methods: {
+            setFoo() {
+              this.$emit('foo', newFoo)
+            }
+          }
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          endLine: 4,
+          column: 33,
+          endColumn: 38
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+        const emit = defineEmits<{
+          (e: 'foo' | 'bar'): void;
+        }>()
+        const change = () => emit('foo');
+      `,
+      parserOptions: { parser: require.resolve('@typescript-eslint/parser') },
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 11,
+          endColumn: 36
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+        const emits = ['foo'];
+        defineEmits(emits)
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 3,
+          column: 24,
+          endColumn: 29
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+        defineEmits<{(e: 'foo'): void}>()
+      </script>
+      `,
+      parserOptions: { parser: require.resolve('@typescript-eslint/parser') },
+      errors: [
+        {
+          messageId: 'unused',
+          line: 3,
+          column: 22,
+          endColumn: 38
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      const emit = defineEmits<{
+        foo: [string, number],
+        bar: []
+      }>()
+      const change = () => emit('foo');
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 5,
+          column: 9,
+          endColumn: 16
+        }
+      ],
+      ...getTypeScriptFixtureTestOptions()
+    }
+  ]
+})
diff --git a/tests/lib/rules/require-toggle-inside-transition.js b/tests/lib/rules/require-toggle-inside-transition.js
index 73ec4720..18a33aa3 100644
--- a/tests/lib/rules/require-toggle-inside-transition.js
+++ b/tests/lib/rules/require-toggle-inside-transition.js
@@ -57,6 +57,18 @@ tester.run('require-toggle-inside-transition', rule, {
     {
       filename: 'test.vue',
       code: '<template><transition><template v-if="show"><div /></template></transition></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><transition><slot /></transition></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><transition><div :key="k" /></transition></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><transition appear><div /></transition></template>'
     }
   ],
   invalid: [
@@ -97,6 +109,11 @@ tester.run('require-toggle-inside-transition', rule, {
       filename: 'test.vue',
       code: '<template><transition><template v-for="e in list"><div /></template></transition></template>',
       errors: [{ messageId: 'expected' }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><Transition>  <div /></Transition></template>',
+      errors: [{ messageId: 'expected' }]
     }
   ]
 })
diff --git a/tests/lib/rules/singleline-html-element-content-newline.js b/tests/lib/rules/singleline-html-element-content-newline.js
index 5bf2c939..c93e96e4 100644
--- a/tests/lib/rules/singleline-html-element-content-newline.js
+++ b/tests/lib/rules/singleline-html-element-content-newline.js
@@ -204,7 +204,35 @@ tester.run('singleline-html-element-content-newline', rule, {
         <div
           id=
           ""
-    `
+    `,
+    {
+      code: `
+        <template>
+          <pre>content</pre>
+          <IgnoreTag>content</IgnoreTag>
+          <IgnoreTag attr>content</IgnoreTag>
+          <IgnoreTag><span attr>content</span></IgnoreTag>
+        </template>`,
+      options: [
+        {
+          externalIgnores: ['IgnoreTag']
+        }
+      ]
+    },
+    {
+      code: `
+        <template>
+        <pre>content</pre>
+          <ignore-tag>content</ignore-tag>
+          <ignore-tag attr>content</ignore-tag>
+          <ignore-tag><span attr>content</span></ignore-tag>
+        </template>`,
+      options: [
+        {
+          externalIgnores: ['IgnoreTag']
+        }
+      ]
+    }
   ],
   invalid: [
     {