Repository: vuejs/eslint-plugin-vue (js)

Issue:
`valid-v-slot` to accept custom components within SVG
**What rule do you want to change?**
I'd like `vue/valid-v-slot` to accept custom components within an `<svg>`

**Does this change cause the rule to produce more or fewer warnings?**
Fewer

**How will the change be implemented? (New option, new default behavior, etc.)?**
New default behavior.

Would adding `|| this.isSvgElementNode(node)` to [isCustomComponent](https://github.com/vuejs/eslint-plugin-vue/blob/80b8983118cc08cf473642c05413bcb930c93613/lib/utils/index.js#L589) would solve the problem?

`MyComponent` template:
```vue
<g>
  <slot></slot>
</g>
```

Currently gives error:
```vue
<svg>
  <MyComponent v-slot="slotProps">    <!-- 'v-slot' directive must be owned by a custom element, but 'MyComponent' is not -->
    <MyChildComponent :thing="slotProps.thing" />
  </MyComponent>
</svg>
```

Currently doesn't give error:
```vue
<div>
  <MyComponent v-slot="slotProps">
    <MyChildComponent :thing="slotProps.thing" />
  </MyComponent>
</div>
```
(I wouldn't actually use a `g` within a `div`, it's just to illustrate how to get rid of the error)

**What does the rule currently do for this code?**
Gives an error:
`'v-slot' directive must be owned by a custom element, but 'MyComponent' is not`

**What will the rule do after it's changed?**
Consider the code valid

**Additional context**
My use case is drawing charts with D3



Pull request description:
Fix false positives when using <svg> in `vue/valid-v-slot` and `vue/valid-v-model` rule
fixes #1346

Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index f2d9b847..fe113336 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -39,7 +39,6 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 
 | Rule ID | Description |    |
 |:--------|:------------|:---|
-| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce custom event names always use "kebab-case" |  |
 | [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher |  |
 | [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties |  |
 | [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: |
@@ -172,7 +171,6 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 
 | Rule ID | Description |    |
 |:--------|:------------|:---|
-| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce custom event names always use "kebab-case" |  |
 | [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher |  |
 | [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties |  |
 | [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component |  |
@@ -290,6 +288,7 @@ For example:
 |:--------|:------------|:---|
 | [vue/block-tag-newline](./block-tag-newline.md) | enforce line breaks after opening and before closing block-level tags | :wrench: |
 | [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
+| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce specific casing for custom event name |  |
 | [vue/html-comment-content-newline](./html-comment-content-newline.md) | enforce unified line brake in HTML comments | :wrench: |
 | [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: |
 | [vue/html-comment-indent](./html-comment-indent.md) | enforce consistent indentation in HTML comments | :wrench: |
diff --git a/docs/rules/custom-event-name-casing.md b/docs/rules/custom-event-name-casing.md
index 6890c771..3a5f339c 100644
--- a/docs/rules/custom-event-name-casing.md
+++ b/docs/rules/custom-event-name-casing.md
@@ -2,22 +2,30 @@
 pageClass: rule-details
 sidebarDepth: 0
 title: vue/custom-event-name-casing
-description: enforce custom event names always use "kebab-case"
+description: enforce specific casing for custom event name
 ---
 # vue/custom-event-name-casing
-> enforce custom event names always use "kebab-case"
+> enforce specific casing for custom event name
 
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+Define a style for custom event name casing for consistency purposes.
 
 ## :book: Rule Details
 
-This rule enforces using kebab-case custom event names.
+This rule aims to warn the custom event names other than the configured casing.
+
+Vue 2 recommends using kebab-case for custom event names.
 
 > Event names will never be used as variable or property names in JavaScript, so there’s no reason to use camelCase or PascalCase. Additionally, `v-on` event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so `v-on:myEvent` would become `v-on:myevent` – making `myEvent` impossible to listen to.
 >
 > For these reasons, we recommend you **always use kebab-case for event names**.
 
-See [Guide - Custom Events] for more details.
+See [Guide (for v2) - Custom Events] for more details.
+
+Vue 3 recommends using camelCase for custom event names.
+
+See [vuejs/docs-next#656](https://github.com/vuejs/docs-next/issues/656) for more details.
+
+This rule enforces kebab-case by default.
 
 <eslint-code-block :rules="{'vue/custom-event-name-casing': ['error']}">
 
@@ -51,14 +59,77 @@ export default {
 
 ```json
 {
-  "vue/custom-event-name-casing": ["error", {
-    "ignores": []
-  }]
+  "vue/custom-event-name-casing": ["error",
+    "kebab-case" | "camelCase",
+    {
+      "ignores": []
+    }
+  ]
 }
 ```
 
+- `"kebab-case"` (default) ... Enforce custom event names to kebab-case.
+- `"camelCase"` ... Enforce custom event names to camelCase.
 - `ignores` (`string[]`) ... The event names to ignore. Sets the event name to allow. For example, custom event names, Vue components event with special name, or Vue library component event name. You can set the regexp by writing it like `"/^name/"` or `click:row` or `fooBar`.
 
+### `"kebab-case"`
+
+<eslint-code-block :rules="{'vue/custom-event-name-casing': ['error', 'kebab-case']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <button @click="$emit('my-event')" />
+
+  <!-- ✗ BAD -->
+  <button @click="$emit('myEvent')" />
+</template>
+<script>
+export default {
+  methods: {
+    onClick () {
+      /* ✓ GOOD */
+      this.$emit('my-event')
+
+      /* ✗ BAD */
+      this.$emit('myEvent')
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+### `"camelCase"`
+
+<eslint-code-block :rules="{'vue/custom-event-name-casing': ['error', 'camelCase']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <button @click="$emit('myEvent')" />
+
+  <!-- ✗ BAD -->
+  <button @click="$emit('my-event')" />
+</template>
+<script>
+export default {
+  methods: {
+    onClick () {
+      /* ✓ GOOD */
+      this.$emit('myEvent')
+
+      /* ✗ BAD */
+      this.$emit('my-event')
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
 ### `"ignores": ["fooBar", "/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u"]`
 
 <eslint-code-block :rules="{'vue/custom-event-name-casing': ['error', { ignores: ['fooBar', '/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }]}">
@@ -93,6 +164,7 @@ export default {
 ## :books: Further Reading
 
 - [Guide - Custom Events]
+- [Guide (for v2) - Custom Events]
 
 [Guide - Custom Events]: https://v3.vuejs.org/guide/component-custom-events.html
 [Guide (for v2) - Custom Events]: https://vuejs.org/v2/guide/components-custom-events.html
diff --git a/docs/rules/no-deprecated-filter.md b/docs/rules/no-deprecated-filter.md
index 13a1aa9d..c128e5f8 100644
--- a/docs/rules/no-deprecated-filter.md
+++ b/docs/rules/no-deprecated-filter.md
@@ -39,6 +39,22 @@ See [Migration Guide - Filters](https://v3.vuejs.org/guide/migration/filters.htm
 
 </eslint-code-block>
 
+:::warning
+Do not disable [`"parserOptions.vueFeatures.filter"`](https://github.com/vuejs/vue-eslint-parser#parseroptionsvuefeaturesfilter) to use this rule.
+
+```json5
+{
+  "parser": "vue-eslint-parser",
+  "parserOptions": {
+    "vueFeatures": {
+      "filter": false // Don't!!
+    }
+  }
+}
+```
+
+:::
+
 ### :wrench: Options
 
 Nothing.
diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md
index 552a1bdf..f3ff5eec 100644
--- a/docs/user-guide/README.md
+++ b/docs/user-guide/README.md
@@ -154,6 +154,25 @@ For example:
 
 If you want to disallow `eslint-disable` functionality in `<template>`, disable the [vue/comment-directive](../rules/comment-directive.md) rule.
 
+### Parser Options
+
+This plugin uses [vue-eslint-parser](https://www.npmjs.com/package/vue-eslint-parser).
+For `parserOptions`, you can use the `vueFeatures` options of `vue-eslint-parser`.
+
+```json
+{
+  "parser": "vue-eslint-parser",
+  "parserOptions": {
+    "vueFeatures": {
+      "filter": true,
+      "interpolationAsNonHTML": false,
+    }
+  }
+}
+```
+
+See the [`parserOptions.vueFeatures` documentation for `vue-eslint-parser`](https://github.com/vuejs/vue-eslint-parser#parseroptionsvuefeatures) for more details.
+
 ## :computer: Editor integrations
 
 ### Visual Studio Code
diff --git a/lib/configs/essential.js b/lib/configs/essential.js
index 7b7a5873..dca31cd2 100644
--- a/lib/configs/essential.js
+++ b/lib/configs/essential.js
@@ -6,7 +6,6 @@
 module.exports = {
   extends: require.resolve('./base'),
   rules: {
-    'vue/custom-event-name-casing': 'error',
     'vue/no-arrow-functions-in-watch': 'error',
     'vue/no-async-in-computed-properties': 'error',
     'vue/no-custom-modifiers-on-v-model': 'error',
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index d1e2d176..802cb62e 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -6,7 +6,6 @@
 module.exports = {
   extends: require.resolve('./base'),
   rules: {
-    'vue/custom-event-name-casing': 'error',
     'vue/no-arrow-functions-in-watch': 'error',
     'vue/no-async-in-computed-properties': 'error',
     'vue/no-deprecated-data-object-declaration': 'error',
diff --git a/lib/rules/custom-event-name-casing.js b/lib/rules/custom-event-name-casing.js
index 7ed9335a..39d9436e 100644
--- a/lib/rules/custom-event-name-casing.js
+++ b/lib/rules/custom-event-name-casing.js
@@ -10,21 +10,15 @@
 
 const { findVariable } = require('eslint-utils')
 const utils = require('../utils')
-const { isKebabCase } = require('../utils/casing')
+const casing = require('../utils/casing')
 const { toRegExp } = require('../utils/regexp')
 
 // ------------------------------------------------------------------------------
 // Helpers
 // ------------------------------------------------------------------------------
 
-/**
- * Check whether the given event name is valid.
- * @param {string} name The name to check.
- * @returns {boolean} `true` if the given event name is valid.
- */
-function isValidEventName(name) {
-  return isKebabCase(name) || name.startsWith('update:')
-}
+const ALLOWED_CASE_OPTIONS = ['kebab-case', 'camelCase']
+const DEFAULT_CASE = 'kebab-case'
 
 /**
  * Get the name param node from the given CallExpression
@@ -64,53 +58,87 @@ function getCalleeMemberNode(node) {
 // Rule Definition
 // ------------------------------------------------------------------------------
 
+const OBJECT_OPTION_SCHEMA = {
+  type: 'object',
+  properties: {
+    ignores: {
+      type: 'array',
+      items: { type: 'string' },
+      uniqueItems: true,
+      additionalItems: false
+    }
+  },
+  additionalProperties: false
+}
 module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
-      description: 'enforce custom event names always use "kebab-case"',
-      categories: ['vue3-essential', 'essential'],
+      description: 'enforce specific casing for custom event name',
+      categories: undefined,
       url: 'https://eslint.vuejs.org/rules/custom-event-name-casing.html'
     },
     fixable: null,
-    schema: [
-      {
-        type: 'object',
-        properties: {
-          ignores: {
-            type: 'array',
-            items: { type: 'string' },
-            uniqueItems: true,
-            additionalItems: false
-          }
+    schema: {
+      anyOf: [
+        {
+          type: 'array',
+          items: [
+            {
+              enum: ALLOWED_CASE_OPTIONS
+            },
+            OBJECT_OPTION_SCHEMA
+          ]
         },
-        additionalProperties: false
-      }
-    ],
+        // For backward compatibility
+        {
+          type: 'array',
+          items: [OBJECT_OPTION_SCHEMA]
+        }
+      ]
+    },
     messages: {
-      unexpected: "Custom event name '{{name}}' must be kebab-case."
+      unexpected: "Custom event name '{{name}}' must be {{caseType}}."
     }
   },
   /** @param {RuleContext} context */
   create(context) {
+    /** @type {Map<ObjectExpression, {contextReferenceIds:Set<Identifier>,emitReferenceIds:Set<Identifier>}>} */
     const setupContexts = new Map()
-    const options = context.options[0] || {}
+    const options =
+      context.options.length === 1 && typeof context.options[0] !== 'string'
+        ? // For backward compatibility
+          [undefined, context.options[0]]
+        : context.options
+    const caseType = options[0] || DEFAULT_CASE
+    const objectOption = options[1] || {}
+    const caseChecker = casing.getChecker(caseType)
     /** @type {RegExp[]} */
-    const ignores = (options.ignores || []).map(toRegExp)
+    const ignores = (objectOption.ignores || []).map(toRegExp)
+
+    /**
+     * Check whether the given event name is valid.
+     * @param {string} name The name to check.
+     * @returns {boolean} `true` if the given event name is valid.
+     */
+    function isValidEventName(name) {
+      return caseChecker(name) || name.startsWith('update:')
+    }
 
     /**
      * @param { Literal & { value: string } } nameLiteralNode
      */
     function verify(nameLiteralNode) {
       const name = nameLiteralNode.value
-      if (ignores.some((re) => re.test(name)) || isValidEventName(name)) {
+      if (isValidEventName(name) || ignores.some((re) => re.test(name))) {
         return
       }
       context.report({
         node: nameLiteralNode,
         messageId: 'unexpected',
         data: {
-          name
+          name,
+          caseType
         }
       })
     }
@@ -190,7 +218,10 @@ module.exports = {
             const setupContext = setupContexts.get(vueNode)
             if (setupContext) {
               const { contextReferenceIds, emitReferenceIds } = setupContext
-              if (emitReferenceIds.has(node.callee)) {
+              if (
+                node.callee.type === 'Identifier' &&
+                emitReferenceIds.has(node.callee)
+              ) {
                 // verify setup(props,{emit}) {emit()}
                 verify(nameLiteralNode)
               } else {
@@ -198,6 +229,7 @@ module.exports = {
                 if (
                   emit &&
                   emit.name === 'emit' &&
+                  emit.member.object.type === 'Identifier' &&
                   contextReferenceIds.has(emit.member.object)
                 ) {
                   // verify setup(props,context) {context.emit()}
diff --git a/lib/rules/require-toggle-inside-transition.js b/lib/rules/require-toggle-inside-transition.js
index 93a074f2..12b77d74 100644
--- a/lib/rules/require-toggle-inside-transition.js
+++ b/lib/rules/require-toggle-inside-transition.js
@@ -10,26 +10,6 @@
 
 const utils = require('../utils')
 
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Check whether the given node is an well-known element or not.
- * @param {VElement} node The element node to check.
- * @returns {boolean} `true` if the name is an well-known element name.
- */
-function isWellKnownElement(node) {
-  if (
-    (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
-    utils.isHtmlWellKnownElementName(node.rawName) ||
-    utils.isSvgWellKnownElementName(node.rawName)
-  ) {
-    return true
-  }
-  return false
-}
-
 // ------------------------------------------------------------------------------
 // Rule Definition
 // ------------------------------------------------------------------------------
@@ -61,9 +41,6 @@ module.exports = {
       if (utils.isCustomComponent(element)) {
         return
       }
-      if (!isWellKnownElement(element)) {
-        return
-      }
       if (
         !utils.hasDirective(element, 'if') &&
         !utils.hasDirective(element, 'show')
diff --git a/lib/rules/valid-v-bind-sync.js b/lib/rules/valid-v-bind-sync.js
index d6ab658e..945ab0a7 100644
--- a/lib/rules/valid-v-bind-sync.js
+++ b/lib/rules/valid-v-bind-sync.js
@@ -20,11 +20,7 @@ const utils = require('../utils')
  * @returns {boolean} `true` if the node is valid.
  */
 function isValidElement(node) {
-  if (
-    (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
-    utils.isHtmlWellKnownElementName(node.rawName) ||
-    utils.isSvgWellKnownElementName(node.rawName)
-  ) {
+  if (!utils.isCustomComponent(node)) {
     // non Vue-component
     return false
   }
@@ -82,22 +78,6 @@ function maybeNullObjectMemberExpression(node) {
   return false
 }
 
-function isValidIs(node) {
-  const { attributes } = node
-  const isAttribute = attributes.some((attr) => {
-    // check for `VAttribute`
-    if (attr.type === 'VAttribute') {
-      // check for `is` attribute
-      if (attr.key.type === 'VIdentifier' && attr.key.name === 'is') return true
-
-      // check for `:is` `bind` attribute
-      if (attr.key.type === 'VDirectiveKey' && attr.key.argument.name === 'is')
-        return true
-    }
-  })
-  return isAttribute
-}
-
 // ------------------------------------------------------------------------------
 // Rule Definition
 // ------------------------------------------------------------------------------
@@ -136,7 +116,7 @@ module.exports = {
         const element = node.parent.parent
         const name = element.name
 
-        if (!isValidElement(element) && !isValidIs(node.parent)) {
+        if (!isValidElement(element)) {
           context.report({
             node,
             loc: node.loc,
diff --git a/lib/rules/valid-v-slot.js b/lib/rules/valid-v-slot.js
index 07025f5e..f5ff3a0e 100644
--- a/lib/rules/valid-v-slot.js
+++ b/lib/rules/valid-v-slot.js
@@ -6,6 +6,10 @@
 
 const utils = require('../utils')
 
+/**
+ * @typedef { { expr: VForExpression, variables: VVariable[] } } VSlotVForVariables
+ */
+
 /**
  * Get all `v-slot` directives on a given element.
  * @param {VElement} node The VElement node to check.
@@ -93,27 +97,128 @@ function getNormalizedName(node, sourceCode) {
  * Get all `v-slot` directives which are distributed to the same slot as a given `v-slot` directive node.
  * @param {VDirective[][]} vSlotGroups The result of `getAllNamedSlotElements()`.
  * @param {VDirective} currentVSlot The current `v-slot` directive node.
+ * @param {VSlotVForVariables | null} currentVSlotVForVars The current `v-for` variables.
  * @param {SourceCode} sourceCode The source code.
+ * @param {ParserServices.TokenStore} tokenStore The token store.
  * @returns {VDirective[][]} The array of the group of `v-slot` directives.
  */
-function filterSameSlot(vSlotGroups, currentVSlot, sourceCode) {
+function filterSameSlot(
+  vSlotGroups,
+  currentVSlot,
+  currentVSlotVForVars,
+  sourceCode,
+  tokenStore
+) {
   const currentName = getNormalizedName(currentVSlot, sourceCode)
   return vSlotGroups
     .map((vSlots) =>
-      vSlots.filter(
-        (vSlot) => getNormalizedName(vSlot, sourceCode) === currentName
-      )
+      vSlots.filter((vSlot) => {
+        if (getNormalizedName(vSlot, sourceCode) !== currentName) {
+          return false
+        }
+        const vForExpr = getVSlotVForVariableIfUsingIterationVars(
+          vSlot,
+          utils.getDirective(vSlot.parent.parent, 'for')
+        )
+        if (!currentVSlotVForVars || !vForExpr) {
+          return !currentVSlotVForVars && !vForExpr
+        }
+        if (
+          !equalVSlotVForVariables(currentVSlotVForVars, vForExpr, tokenStore)
+        ) {
+          return false
+        }
+        //
+        return true
+      })
     )
     .filter((slots) => slots.length >= 1)
 }
 
 /**
- * Check whether a given argument node is using an iteration variable that the element defined.
+ * Determines whether the two given `v-slot` variables are considered to be equal.
+ * @param {VSlotVForVariables} a First element.
+ * @param {VSlotVForVariables} b Second element.
+ * @param {ParserServices.TokenStore} tokenStore The token store.
+ * @returns {boolean} `true` if the elements are considered to be equal.
+ */
+function equalVSlotVForVariables(a, b, tokenStore) {
+  if (a.variables.length !== b.variables.length) {
+    return false
+  }
+  if (!equal(a.expr.right, b.expr.right)) {
+    return false
+  }
+
+  const checkedVarNames = new Set()
+  const len = Math.min(a.expr.left.length, b.expr.left.length)
+  for (let index = 0; index < len; index++) {
+    const aPtn = a.expr.left[index]
+    const bPtn = b.expr.left[index]
+
+    const aVar = a.variables.find(
+      (v) => aPtn.range[0] <= v.id.range[0] && v.id.range[1] <= aPtn.range[1]
+    )
+    const bVar = b.variables.find(
+      (v) => bPtn.range[0] <= v.id.range[0] && v.id.range[1] <= bPtn.range[1]
+    )
+    if (aVar && bVar) {
+      if (aVar.id.name !== bVar.id.name) {
+        return false
+      }
+      if (!equal(aPtn, bPtn)) {
+        return false
+      }
+      checkedVarNames.add(aVar.id.name)
+    } else if (aVar || bVar) {
+      return false
+    }
+  }
+  for (const v of a.variables) {
+    if (!checkedVarNames.has(v.id.name)) {
+      if (b.variables.every((bv) => v.id.name !== bv.id.name)) {
+        return false
+      }
+    }
+  }
+  return true
+
+  /**
+   * Determines whether the two given nodes are considered to be equal.
+   * @param {ASTNode} a First node.
+   * @param {ASTNode} b Second node.
+   * @returns {boolean} `true` if the nodes are considered to be equal.
+   */
+  function equal(a, b) {
+    if (a.type !== b.type) {
+      return false
+    }
+    return utils.equalTokens(a, b, tokenStore)
+  }
+}
+
+/**
+ * Gets the `v-for` directive and variable that provide the variables used by the given` v-slot` directive.
+ * @param {VDirective} vSlot The current `v-slot` directive node.
+ * @param {VDirective | null} [vFor] The current `v-for` directive node.
+ * @returns { VSlotVForVariables | null } The VSlotVForVariable.
+ */
+function getVSlotVForVariableIfUsingIterationVars(vSlot, vFor) {
+  const expr =
+    vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression)
+  const variables =
+    expr && getUsingIterationVars(vSlot.key.argument, vSlot.parent.parent)
+  return expr && variables && variables.length ? { expr, variables } : null
+}
+
+/**
+ * Gets iterative variables if a given argument node is using iterative variables that the element defined.
  * @param {VExpressionContainer|VIdentifier|null} argument The argument node to check.
  * @param {VElement} element The element node which has the argument.
- * @returns {boolean} `true` if the argument node is using the iteration variable.
+ * @returns {VVariable[]} The argument node is using iteration variables.
  */
-function isUsingIterationVar(argument, element) {
+function getUsingIterationVars(argument, element) {
+  const vars = []
   if (argument && argument.type === 'VExpressionContainer') {
     for (const { variable } of argument.references) {
       if (
@@ -122,11 +227,11 @@ function isUsingIterationVar(argument, element) {
         variable.id.range[0] > element.startTag.range[0] &&
         variable.id.range[1] < element.startTag.range[1]
       ) {
-        return true
+        vars.push(variable)
       }
     }
   }
-  return false
+  return vars
 }
 
 /**
@@ -206,6 +311,9 @@ module.exports = {
   /** @param {RuleContext} context */
   create(context) {
     const sourceCode = context.getSourceCode()
+    const tokenStore =
+      context.parserServices.getTemplateBodyTokenStore &&
+      context.parserServices.getTemplateBodyTokenStore()
     const options = context.options[0] || {}
     const allowModifiers = options.allowModifiers === true
 
@@ -256,12 +364,18 @@ module.exports = {
           })
         }
         if (ownerElement === parentElement) {
+          const vFor = utils.getDirective(element, 'for')
+          const vSlotVForVar = getVSlotVForVariableIfUsingIterationVars(
+            node,
+            vFor
+          )
           const vSlotGroupsOfSameSlot = filterSameSlot(
             vSlotGroupsOnChildren,
             node,
-            sourceCode
+            vSlotVForVar,
+            sourceCode,
+            tokenStore
           )
-          const vFor = utils.getDirective(element, 'for')
           if (
             vSlotGroupsOfSameSlot.length >= 2 &&
             !vSlotGroupsOfSameSlot[0].includes(node)
@@ -273,7 +387,7 @@ module.exports = {
               messageId: 'disallowDuplicateSlotsOnChildren'
             })
           }
-          if (vFor && !isUsingIterationVar(node.key.argument, element)) {
+          if (vFor && !vSlotVForVar) {
             // E.g., <template v-for="x of xs" #one></template>
             context.report({
               node,
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 24b9a1bb..793d3440 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -590,6 +590,8 @@ module.exports = {
     return (
       (this.isHtmlElementNode(node) &&
         !this.isHtmlWellKnownElementName(node.rawName)) ||
+      (this.isSvgElementNode(node) &&
+        !this.isSvgWellKnownElementName(node.rawName)) ||
       this.hasAttribute(node, 'is') ||
       this.hasDirective(node, 'bind', 'is') ||
       this.hasDirective(node, 'is')
diff --git a/package.json b/package.json
index eb2ab025..64f56d2c 100644
--- a/package.json
+++ b/package.json
@@ -55,7 +55,7 @@
     "eslint-utils": "^2.1.0",
     "natural-compare": "^1.4.0",
     "semver": "^7.3.2",
-    "vue-eslint-parser": "^7.1.1"
+    "vue-eslint-parser": "^7.2.0"
   },
   "devDependencies": {
     "@types/eslint": "^7.2.0",


Test patch:
diff --git a/tests/lib/rules/custom-event-name-casing.js b/tests/lib/rules/custom-event-name-casing.js
index ed0ba0eb..cd857b4c 100644
--- a/tests/lib/rules/custom-event-name-casing.js
+++ b/tests/lib/rules/custom-event-name-casing.js
@@ -191,7 +191,7 @@ tester.run('custom-event-name-casing', rule, {
       }
       </script>
       `,
-      options: [{ ignores: ['fooBar', 'barBaz', 'bazQux'] }]
+      options: ['kebab-case', { ignores: ['fooBar', 'barBaz', 'bazQux'] }]
     },
     {
       filename: 'test.vue',
@@ -225,7 +225,50 @@ tester.run('custom-event-name-casing', rule, {
       }
       </script>
       `,
-      options: [{ ignores: ['/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }]
+      options: [
+        'kebab-case',
+        { ignores: ['/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }
+      ]
+    },
+
+    // For backward compatibility
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('fooBar')">
+      </template>
+      `,
+      options: [{ ignores: ['fooBar'] }]
+    },
+
+    // camelCase
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('fooBar')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('barBaz')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('bazQux')
+          }
+        }
+      }
+      </script>
+      `,
+      options: ['camelCase']
     }
   ],
   invalid: [
@@ -361,7 +404,10 @@ tester.run('custom-event-name-casing', rule, {
       }
       </script>
       `,
-      options: [{ ignores: ['/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }],
+      options: [
+        'kebab-case',
+        { ignores: ['/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }
+      ],
       errors: [
         "Custom event name 'input/update' must be kebab-case.",
         "Custom event name 'search/update' must be kebab-case.",
@@ -392,12 +438,46 @@ tester.run('custom-event-name-casing', rule, {
       }
       </script>
       `,
-      options: [{ ignores: ['input:update', 'search:update', 'click:row'] }],
+      options: [
+        'kebab-case',
+        { ignores: ['input:update', 'search:update', 'click:row'] }
+      ],
       errors: [
         "Custom event name 'input/update' must be kebab-case.",
         "Custom event name 'search/update' must be kebab-case.",
         "Custom event name 'click/row' must be kebab-case."
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('foo-bar')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('bar-baz')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('baz-qux')
+          }
+        }
+      }
+      </script>
+      `,
+      options: ['camelCase'],
+      errors: [
+        "Custom event name 'foo-bar' must be camelCase.",
+        "Custom event name 'bar-baz' must be camelCase.",
+        "Custom event name 'baz-qux' must be camelCase."
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/no-deprecated-filter.js b/tests/lib/rules/no-deprecated-filter.js
index 7d951281..a6158ab9 100644
--- a/tests/lib/rules/no-deprecated-filter.js
+++ b/tests/lib/rules/no-deprecated-filter.js
@@ -29,6 +29,13 @@ ruleTester.run('no-deprecated-filter', rule, {
     {
       filename: 'test.vue',
       code: '<template>{{ method(msg) }}</template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template>{{ msg | filter }}</template>',
+      parserOptions: {
+        vueFeatures: { filter: false }
+      }
     }
   ],
 
diff --git a/tests/lib/rules/valid-v-bind-sync.js b/tests/lib/rules/valid-v-bind-sync.js
index 6ff2c462..763ae582 100644
--- a/tests/lib/rules/valid-v-bind-sync.js
+++ b/tests/lib/rules/valid-v-bind-sync.js
@@ -428,6 +428,18 @@ tester.run('valid-v-bind-sync', rule, {
       filename: 'test.vue',
       code: '<template><MyComponent :foo.sync="(a?.b).c.d" /></template>',
       errors: ["'.sync' modifier has potential null object property access."]
+    },
+    {
+      filename: 'test.vue',
+      code:
+        '<template><tr v-on:is="myRow" :some-prop.sync="somePropValue"></template>',
+      errors: ["'.sync' modifiers aren't supported on <tr> non Vue-components."]
+    },
+    {
+      filename: 'test.vue',
+      code:
+        '<template><tr v-bind="myRow" :some-prop.sync="somePropValue"></template>',
+      errors: ["'.sync' modifiers aren't supported on <tr> non Vue-components."]
     }
   ]
 })
diff --git a/tests/lib/rules/valid-v-model.js b/tests/lib/rules/valid-v-model.js
index 2369fe4d..8c3e384a 100644
--- a/tests/lib/rules/valid-v-model.js
+++ b/tests/lib/rules/valid-v-model.js
@@ -151,6 +151,15 @@ tester.run('valid-v-model', rule, {
       code:
         '<template><MyComponent v-model.modifier.modifierTwo="a"></MyComponent></template>'
     },
+    // svg
+    {
+      code: `
+      <template>
+        <svg>
+          <MyComponent v-model="slotProps"></MyComponent>
+        </svg>
+      </template>`
+    },
     // parsing error
     {
       filename: 'parsing-error.vue',
diff --git a/tests/lib/rules/valid-v-slot.js b/tests/lib/rules/valid-v-slot.js
index 08b55224..99dc4b39 100644
--- a/tests/lib/rules/valid-v-slot.js
+++ b/tests/lib/rules/valid-v-slot.js
@@ -84,6 +84,30 @@ tester.run('valid-v-slot', rule, {
         <template v-for="(key, value) in xxxx" #[key]>{{value}}</template>
       </MyComponent>
     </template>`,
+    `<template>
+      <MyComponent>
+        <template v-for="(key, value) in xxxx" #[key]>{{value}}</template>
+        <template v-for="(key, value) in yyyy" #[key]>{{value}}</template>
+      </MyComponent>
+    </template>`,
+    `<template>
+      <MyComponent>
+        <template #[key]>{{value}}</template>
+        <template v-for="(key, value) in yyyy" #[key]>{{value}}</template>
+      </MyComponent>
+    </template>`,
+    `<template>
+      <MyComponent>
+        <template v-for="(value, key) in xxxx" #[key]>{{value}}</template>
+        <template v-for="(key, value) in xxxx" #[key]>{{value}}</template>
+      </MyComponent>
+    </template>`,
+    `<template>
+      <MyComponent>
+        <template v-for="(key) in xxxx" #[key+value]>{{value}}</template>
+        <template v-for="(key, value) in xxxx" #[key+value]>{{value}}</template>
+      </MyComponent>
+    </template>`,
     {
       code: `
         <template>
@@ -107,6 +131,17 @@ tester.run('valid-v-slot', rule, {
       `,
       options: [{ allowModifiers: true }]
     },
+    // svg
+    {
+      code: `
+      <template>
+        <svg>
+          <MyComponent v-slot="slotProps">
+            <MyChildComponent :thing="slotProps.thing" />
+          </MyComponent>
+        </svg>
+      </template>`
+    },
     // parsing error
     {
       filename: 'parsing-error.vue',
@@ -282,6 +317,43 @@ tester.run('valid-v-slot', rule, {
       `,
       errors: [{ messageId: 'disallowDuplicateSlotsOnChildren' }]
     },
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-for="(key, value) in xxxx" v-slot:key>{{value}}</template>
+            <template v-for="(key, value) in yyyy" v-slot:key>{{value}}</template>
+          </MyComponent>
+        </template>
+      `,
+      errors: [
+        { messageId: 'disallowDuplicateSlotsOnChildren' },
+        { messageId: 'disallowDuplicateSlotsOnChildren' },
+        { messageId: 'disallowDuplicateSlotsOnChildren' }
+      ]
+    },
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-for="(key, value) in xxxx" v-slot:[key]>{{value}}</template>
+            <template v-for="(key, value) in xxxx" v-slot:[key]>{{value}}</template>
+          </MyComponent>
+        </template>
+      `,
+      errors: [{ messageId: 'disallowDuplicateSlotsOnChildren' }]
+    },
+    {
+      code: `
+        <template>
+          <MyComponent>
+            <template v-for="(key) in xxxx" v-slot:[key]>{{value}}</template>
+            <template v-for="(key, value) in xxxx" v-slot:[key]>{{value}}</template>
+          </MyComponent>
+        </template>
+      `,
+      errors: [{ messageId: 'disallowDuplicateSlotsOnChildren' }]
+    },
     {
       code: `
         <template>