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

Issue:
Allow custom toggles in vue/require-toggle-inside-transition 
<!--
  Before proposing rule changes, please make sure it hasn't been posted already.
  You can see all open propositions here:
  https://github.com/vuejs/eslint-plugin-vue/issues?q=is%3Aopen+is%3Aissue+label%3A%22new+rule+proposition%22
-->

**What rule do you want to change?**

[vue/require-toggle-inside-transition](https://eslint.vuejs.org/rules/require-toggle-inside-transition.html) 

**Does this change cause the rule to produce more or fewer warnings?**

If option is configured, fewer warnings.

**How will the change be implemented? (New option, new default behavior, etc.)?**

I would add a new option to the rule which allows adding additional custom "toggle" directives which will satisfy this check in addition to `v-show` or `v-if`.

**Please provide some example code that this change will affect:**

<!-- Put your code examples here -->
```vue
<transition>
  <dialog v-dialog="visible">...</dialog>
</transition>
```

**What does the rule currently do for this code?**

It flags it as an error, because there is no `v-show` or `v-if` on the `<dialog>`

**What will the rule do after it's changed?**

If the rule is configured to include `v-dialog` as a toggle directive, it will no longer flag this case as an error.

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

In the example above, `v-dialog` is a custom directive which shows/hides the element using `close()` and `showModal()`. Transitions are also applied in the directive.



Pull request:
feat(require-toggle-inside-transition): add additionalDirectives option (#2535)
Resolves #2535

Patch:
diff --git a/docs/rules/define-macros-order.md b/docs/rules/define-macros-order.md
index 5d8dda56..6dbe5ef0 100644
--- a/docs/rules/define-macros-order.md
+++ b/docs/rules/define-macros-order.md
@@ -2,20 +2,20 @@
 pageClass: rule-details
 sidebarDepth: 0
 title: vue/define-macros-order
-description: enforce order of `defineEmits` and `defineProps` compiler macros
+description: enforce order of compiler macros (`defineProps`, `defineEmits`, etc.)
 since: v8.7.0
 ---
 
 # vue/define-macros-order
 
-> enforce order of `defineEmits` and `defineProps` compiler macros
+> enforce order of compiler macros (`defineProps`, `defineEmits`, etc.)
 
 - :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
 - :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
 
 ## :book: Rule Details
 
-This rule reports the `defineProps` and `defineEmits` compiler macros when they are not the first statements in `<script setup>` (after any potential import statements or type definitions) or when they are not in the correct order.
+This rule reports compiler macros (like `defineProps` or `defineEmits` but also custom ones) when they are not the first statements in `<script setup>` (after any potential import statements or type definitions) or when they are not in the correct order.
 
 ## :wrench: Options
 
@@ -28,7 +28,7 @@ This rule reports the `defineProps` and `defineEmits` compiler macros when they
 }
 ```
 
-- `order` (`string[]`) ... The order of defineEmits and defineProps macros. You can also add `"defineOptions"`, `"defineSlots"`, and `"defineModel"`.
+- `order` (`string[]`) ... The order in which the macros should appear. The default is `["defineProps", "defineEmits"]`.
 - `defineExposeLast` (`boolean`) ... Force `defineExpose` at the end.
 
 ### `{ "order": ["defineProps", "defineEmits"] }` (default)
@@ -118,6 +118,39 @@ const slots = defineSlots()
 
 </eslint-code-block>
 
+### `{ "order": ["definePage", "defineModel", "defineCustom", "defineEmits", "defineSlots"] }`
+
+<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['definePage', 'defineModel', 'defineCustom', 'defineEmits', 'defineSlots']}]}">
+
+```vue
+<!-- ✓ GOOD -->
+<script setup>
+definePage()
+const model = defineModel()
+defineCustom()
+defineEmits(/* ... */)
+const slots = defineSlots()
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['definePage', 'defineModel', 'defineCustom', 'defineEmits', 'defineSlots']}]}">
+
+```vue
+<!-- ✗ BAD -->
+<script setup>
+defineEmits(/* ... */)
+const slots = defineSlots()
+defineProps(/* ... */)
+defineCustom({/* ... */})
+const model = defineModel()
+definePage()
+</script>
+```
+
+</eslint-code-block>
+
 ### `{ "defineExposeLast": true }`
 
 <eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {defineExposeLast: true}]}">
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 113ff416..9624111c 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -213,7 +213,7 @@ For example:
 | [vue/component-options-name-casing](./component-options-name-casing.md) | enforce the casing of component name in `components` options | :wrench::bulb: | :hammer: |
 | [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce specific casing for custom event name |  | :hammer: |
 | [vue/define-emits-declaration](./define-emits-declaration.md) | enforce declaration style of `defineEmits` |  | :hammer: |
-| [vue/define-macros-order](./define-macros-order.md) | enforce order of `defineEmits` and `defineProps` compiler macros | :wrench::bulb: | :lipstick: |
+| [vue/define-macros-order](./define-macros-order.md) | enforce order of compiler macros (`defineProps`, `defineEmits`, etc.) | :wrench::bulb: | :lipstick: |
 | [vue/define-props-declaration](./define-props-declaration.md) | enforce declaration style of `defineProps` |  | :hammer: |
 | [vue/enforce-style-attribute](./enforce-style-attribute.md) | enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags |  | :hammer: |
 | [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute |  | :hammer: |
@@ -224,6 +224,7 @@ For example:
 | [vue/match-component-import-name](./match-component-import-name.md) | require the registered component name to match the imported component name |  | :warning: |
 | [vue/max-lines-per-block](./max-lines-per-block.md) | enforce maximum number of lines in Vue SFC blocks |  | :warning: |
 | [vue/max-props](./max-props.md) | enforce maximum number of props in Vue component |  | :warning: |
+| [vue/max-template-depth](./max-template-depth.md) | enforce maximum depth of template |  | :warning: |
 | [vue/new-line-between-multi-line-property](./new-line-between-multi-line-property.md) | enforce new lines between multi-line properties in Vue components | :wrench: | :lipstick: |
 | [vue/next-tick-style](./next-tick-style.md) | enforce Promise or callback style in `nextTick` | :wrench: | :hammer: |
 | [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in `<template>` |  | :hammer: |
diff --git a/docs/rules/max-template-depth.md b/docs/rules/max-template-depth.md
new file mode 100644
index 00000000..94bd8b5e
--- /dev/null
+++ b/docs/rules/max-template-depth.md
@@ -0,0 +1,67 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/max-template-depth
+description: enforce maximum depth of template
+---
+
+# vue/max-template-depth
+
+> enforce maximum depth of template
+
+- :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 enforces a maximum depth of the template in a Vue SFC, in order to aid in maintainability and reduce complexity.
+
+## :wrench: Options
+
+This rule takes an object, where you can specify the maximum depth allowed in a Vue SFC template block.
+There is one property that can be specified for the object.
+
+- `maxDepth` ... Specify the maximum template depth `template` block.
+
+### `{ maxDepth: 3 }`
+
+<eslint-code-block :rules="{'vue/max-template-depth': ['error', { maxDepth: 3 }]}">
+
+```vue
+<!-- ✗ BAD -->
+<template>
+  <main-container>
+    <child-component>
+      <div />
+    </child-component>
+    <child-component>
+      <nested-component>
+        <div>
+          <div />
+        </div>
+      </nested-component>
+    </child-component>
+  </main-container>
+</template>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/max-template-depth': ['error', { maxDepth: 3 }]}">
+
+```vue
+<!-- ✓ GOOD -->
+<template>
+  <main-container>
+    <child-component>
+      <div />
+    </child-component>
+  </main-container>
+</template>
+```
+
+</eslint-code-block>
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/max-template-depth.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/max-template-depth.js)
diff --git a/docs/rules/require-toggle-inside-transition.md b/docs/rules/require-toggle-inside-transition.md
index c62de95c..c0760e38 100644
--- a/docs/rules/require-toggle-inside-transition.md
+++ b/docs/rules/require-toggle-inside-transition.md
@@ -33,7 +33,34 @@ This rule reports elements inside `<transition>` that do not control the display
 
 ## :wrench: Options
 
-Nothing.
+```json
+{
+  "vue/require-toggle-inside-transition": ["error", {
+    "additionalDirectives": []
+  }]
+}
+```
+
+- `additionalDirectives` (`string[]`) ... Custom directives which will satisfy this rule in addition to `v-show` and `v-if`. Should be added without the `v-` prefix.
+
+### `additionalDirectives: ["dialog"]`
+
+<eslint-code-block :rules="{'vue/require-toggle-inside-transition': ['error', {additionalDirectives: ['dialog']}]}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <transition><div v-if="show" /></transition>
+  <transition><div v-show="show" /></transition>
+  <transition><dialog v-dialog="show" /></transition>
+
+  <!-- ✗ BAD -->
+  <transition><div /></transition>
+  <transition><div v-custom="show" /></transition>
+<template>
+```
+
+</eslint-code-block>
 
 ## :books: Further Reading
 
diff --git a/lib/index.js b/lib/index.js
index ff3f69c6..6dbe5107 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -80,6 +80,7 @@ const plugin = {
     'max-len': require('./rules/max-len'),
     'max-lines-per-block': require('./rules/max-lines-per-block'),
     'max-props': require('./rules/max-props'),
+    'max-template-depth': require('./rules/max-template-depth'),
     'multi-word-component-names': require('./rules/multi-word-component-names'),
     'multiline-html-element-content-newline': require('./rules/multiline-html-element-content-newline'),
     'multiline-ternary': require('./rules/multiline-ternary'),
diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js
index 0ef2a042..35519d23 100644
--- a/lib/rules/attribute-hyphenation.js
+++ b/lib/rules/attribute-hyphenation.js
@@ -18,7 +18,7 @@ function getAttributeName(node) {
   }
 
   if (
-    node.key.name.name === 'bind' &&
+    (node.key.name.name === 'bind' || node.key.name.name === 'model') &&
     node.key.argument &&
     node.key.argument.type === 'VIdentifier'
   ) {
@@ -101,6 +101,10 @@ module.exports = {
             return null
           }
 
+          if (text.endsWith('.sync')) {
+            return null
+          }
+
           if (/^[A-Z]/.test(name)) {
             return null
           }
diff --git a/lib/rules/define-macros-order.js b/lib/rules/define-macros-order.js
index f24c58af..920b5c29 100644
--- a/lib/rules/define-macros-order.js
+++ b/lib/rules/define-macros-order.js
@@ -11,13 +11,15 @@ const MACROS_PROPS = 'defineProps'
 const MACROS_OPTIONS = 'defineOptions'
 const MACROS_SLOTS = 'defineSlots'
 const MACROS_MODEL = 'defineModel'
-const ORDER_SCHEMA = [
+const MACROS_EXPOSE = 'defineExpose'
+const KNOWN_MACROS = new Set([
   MACROS_EMITS,
   MACROS_PROPS,
   MACROS_OPTIONS,
   MACROS_SLOTS,
-  MACROS_MODEL
-]
+  MACROS_MODEL,
+  MACROS_EXPOSE
+])
 const DEFAULT_ORDER = [MACROS_PROPS, MACROS_EMITS]
 
 /**
@@ -109,6 +111,12 @@ function create(context) {
   /** @type {ASTNode} */
   let defineExposeNode
 
+  if (order.includes(MACROS_EXPOSE) && defineExposeLast) {
+    throw new Error(
+      "`defineExpose` macro can't be in the `order` array if `defineExposeLast` is true."
+    )
+  }
+
   return utils.compositingVisitors(
     utils.defineScriptSetupVisitor(context, {
       onDefinePropsExit(node) {
@@ -130,6 +138,20 @@ function create(context) {
       },
       onDefineExposeExit(node) {
         defineExposeNode = getDefineMacrosStatement(node)
+      },
+
+      /** @param {CallExpression} node */
+      'Program > ExpressionStatement > CallExpression, Program > VariableDeclaration > VariableDeclarator > CallExpression'(
+        node
+      ) {
+        if (
+          node.callee &&
+          node.callee.type === 'Identifier' &&
+          order.includes(node.callee.name) &&
+          !KNOWN_MACROS.has(node.callee.name)
+        ) {
+          macrosNodes.set(node.callee.name, [getDefineMacrosStatement(node)])
+        }
       }
     }),
     {
@@ -333,7 +355,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description:
-        'enforce order of `defineEmits` and `defineProps` compiler macros',
+        'enforce order of compiler macros (`defineProps`, `defineEmits`, etc.)',
       categories: undefined,
       url: 'https://eslint.vuejs.org/rules/define-macros-order.html'
     },
@@ -346,7 +368,8 @@ module.exports = {
           order: {
             type: 'array',
             items: {
-              enum: ORDER_SCHEMA
+              type: 'string',
+              minLength: 1
             },
             uniqueItems: true,
             additionalItems: false
diff --git a/lib/rules/max-template-depth.js b/lib/rules/max-template-depth.js
new file mode 100644
index 00000000..899ec75c
--- /dev/null
+++ b/lib/rules/max-template-depth.js
@@ -0,0 +1,82 @@
+/**
+ * @author kevsommer Kevin Sommer
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'enforce maximum depth of template',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/max-template-depth.html'
+    },
+    fixable: null,
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          maxDepth: {
+            type: 'integer',
+            minimum: 1
+          }
+        },
+        additionalProperties: false,
+        minProperties: 1
+      }
+    ],
+    messages: {
+      templateTooDeep:
+        'Element is nested too deeply (depth of {{depth}}, maximum allowed is {{limit}}).'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    const option = context.options[0] || {}
+
+    /**
+     * @param {VElement} element
+     * @param {number} curDepth
+     */
+    function checkMaxDepth(element, curDepth) {
+      if (curDepth > option.maxDepth) {
+        context.report({
+          node: element,
+          messageId: 'templateTooDeep',
+          data: {
+            depth: curDepth,
+            limit: option.maxDepth
+          }
+        })
+      }
+
+      if (!element.children) {
+        return
+      }
+
+      for (const child of element.children) {
+        if (child.type === 'VElement') {
+          checkMaxDepth(child, curDepth + 1)
+        }
+      }
+    }
+
+    return {
+      /** @param {Program} program */
+      Program(program) {
+        const element = program.templateBody
+
+        if (element == null) {
+          return
+        }
+
+        if (element.type !== 'VElement') {
+          return
+        }
+
+        checkMaxDepth(element, 0)
+      }
+    }
+  }
+}
diff --git a/lib/rules/require-toggle-inside-transition.js b/lib/rules/require-toggle-inside-transition.js
index 423d4beb..bfb267ca 100644
--- a/lib/rules/require-toggle-inside-transition.js
+++ b/lib/rules/require-toggle-inside-transition.js
@@ -20,6 +20,25 @@ function isValidBindAppear(vBindAppear) {
   return true
 }
 
+/**
+ * @param {string[]} directives
+ */
+function createDirectiveList(directives) {
+  let str = ''
+
+  for (const [index, directive] of directives.entries()) {
+    if (index === 0) {
+      str += `\`v-${directive}\``
+    } else if (index < directives.length - 1) {
+      str += `, \`v-${directive}\``
+    } else {
+      str += ` or \`v-${directive}\``
+    }
+  }
+
+  return str
+}
+
 module.exports = {
   meta: {
     type: 'problem',
@@ -30,14 +49,34 @@ module.exports = {
       url: 'https://eslint.vuejs.org/rules/require-toggle-inside-transition.html'
     },
     fixable: null,
-    schema: [],
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          additionalDirectives: {
+            type: 'array',
+            items: {
+              type: 'string'
+            },
+            uniqueItems: true
+          }
+        },
+        additionalProperties: false
+      }
+    ],
     messages: {
       expected:
-        'The element inside `<transition>` is expected to have a `v-if` or `v-show` directive.'
+        'The element inside `<transition>` is expected to have a {{allowedDirectives}} directive.'
     }
   },
   /** @param {RuleContext} context */
   create(context) {
+    /** @type {Array<string>} */
+    const additionalDirectives =
+      (context.options[0] && context.options[0].additionalDirectives) || []
+    const allowedDirectives = ['if', 'show', ...additionalDirectives]
+    const allowedDirectivesString = createDirectiveList(allowedDirectives)
+
     /**
      * Check if the given element has display control.
      * @param {VElement} element The element node to check.
@@ -59,14 +98,18 @@ module.exports = {
 
       if (
         element.name !== 'slot' &&
-        !utils.hasDirective(element, 'if') &&
-        !utils.hasDirective(element, 'show') &&
+        !allowedDirectives.some((directive) =>
+          utils.hasDirective(element, directive)
+        ) &&
         !utils.hasDirective(element, 'bind', 'key')
       ) {
         context.report({
           node: element.startTag,
           loc: element.startTag.loc,
-          messageId: 'expected'
+          messageId: 'expected',
+          data: {
+            allowedDirectives: allowedDirectivesString
+          }
         })
       }
     }
diff --git a/lib/rules/syntaxes/slot-attribute.js b/lib/rules/syntaxes/slot-attribute.js
index 61891df3..27087cb3 100644
--- a/lib/rules/syntaxes/slot-attribute.js
+++ b/lib/rules/syntaxes/slot-attribute.js
@@ -56,10 +56,8 @@ module.exports = {
         // parse error or empty expression
         return false
       }
-      const slotName = sourceCode.getText(slotAttr.value.expression).trim()
-      // If non-Latin characters are included it can not be converted.
-      // It does not check the space only because `a>b?c:d` should be rejected.
-      return !/[^a-z]/i.test(slotName)
+
+      return slotAttr.value.expression.type === 'Identifier'
     }
 
     /**
@@ -102,7 +100,18 @@ module.exports = {
           yield fixer.remove(scopeAttr)
         }
 
-        yield fixer.insertTextBefore(element, `<template ${replaceText}>\n`)
+        const vFor = startTag.attributes.find(
+          (attr) => attr.directive && attr.key.name.name === 'for'
+        )
+        const vForText = vFor ? `${sourceCode.getText(vFor)} ` : ''
+        if (vFor) {
+          yield fixer.remove(vFor)
+        }
+
+        yield fixer.insertTextBefore(
+          element,
+          `<template ${vForText}${replaceText}>\n`
+        )
         yield fixer.insertTextAfter(element, `\n</template>`)
       }
     }
diff --git a/lib/rules/syntaxes/utils/can-convert-to-v-slot.js b/lib/rules/syntaxes/utils/can-convert-to-v-slot.js
index f4e66778..e0e51053 100644
--- a/lib/rules/syntaxes/utils/can-convert-to-v-slot.js
+++ b/lib/rules/syntaxes/utils/can-convert-to-v-slot.js
@@ -28,7 +28,8 @@ module.exports = function canConvertToVSlot(element, sourceCode, tokenStore) {
   const ownerElement = element.parent
   if (
     ownerElement.type === 'VDocumentFragment' ||
-    !utils.isCustomComponent(ownerElement)
+    !utils.isCustomComponent(ownerElement) ||
+    ownerElement.name === 'component'
   ) {
     return false
   }


Test patch:
diff --git a/tests/lib/rules/attribute-hyphenation.js b/tests/lib/rules/attribute-hyphenation.js
index 4d867d15..18d60e19 100644
--- a/tests/lib/rules/attribute-hyphenation.js
+++ b/tests/lib/rules/attribute-hyphenation.js
@@ -26,6 +26,11 @@ ruleTester.run('attribute-hyphenation', rule, {
       code: '<template><div><custom data-id="foo" aria-test="bar" slot-scope="{ data }" my-prop="prop"></custom></div></template>',
       options: ['always']
     },
+    {
+      filename: 'test.vue',
+      code: '<template><div><custom :my-prop="prop" v-model:foo-bar="fooBar"></custom></div></template>',
+      options: ['always']
+    },
     {
       filename: 'test.vue',
       code: '<template><div><custom data-id="foo" aria-test="bar" slot-scope="{ data }" myProp="prop"></custom></div></template>',
@@ -70,6 +75,16 @@ ruleTester.run('attribute-hyphenation', rule, {
       filename: 'test.vue',
       code: '<template><div><custom :attr_ff="prop"></custom></div></template>',
       options: ['never']
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><custom :my-name.sync="prop"></custom></div></template>',
+      options: ['always']
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><custom :myName.sync="prop"></custom></div></template>',
+      options: ['never']
     }
   ],
 
@@ -209,6 +224,48 @@ ruleTester.run('attribute-hyphenation', rule, {
         }
       ]
     },
+    {
+      // https://github.com/vuejs/eslint-plugin-vue/issues/2510
+      filename: 'test.vue',
+      code: '<template><div><custom v-model:my-prop="prop"></custom></div></template>',
+      output:
+        '<template><div><custom v-model:myProp="prop"></custom></div></template>',
+      options: ['never'],
+      errors: [
+        {
+          message: "Attribute 'v-model:my-prop' can't be hyphenated.",
+          type: 'VDirectiveKey',
+          line: 1
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><custom v-model:myProp="prop"></custom></div></template>',
+      output:
+        '<template><div><custom v-model:my-prop="prop"></custom></div></template>',
+      options: ['always'],
+      errors: [
+        {
+          message: "Attribute 'v-model:myProp' must be hyphenated.",
+          type: 'VDirectiveKey',
+          line: 1
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><custom v-model:MyProp="prop"></custom></div></template>',
+      output: null,
+      options: ['always'],
+      errors: [
+        {
+          message: "Attribute 'v-model:MyProp' must be hyphenated.",
+          type: 'VDirectiveKey',
+          line: 1
+        }
+      ]
+    },
     {
       filename: 'test.vue',
       code: `
@@ -367,6 +424,32 @@ ruleTester.run('attribute-hyphenation', rule, {
           line: 1
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><custom :myAge.sync="prop"></custom></div></template>',
+      output: null,
+      options: ['always'],
+      errors: [
+        {
+          message: "Attribute ':myAge.sync' must be hyphenated.",
+          type: 'VDirectiveKey',
+          line: 1
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><div><custom :my-age.sync="prop"></custom></div></template>',
+      output: null,
+      options: ['never'],
+      errors: [
+        {
+          message: "Attribute ':my-age.sync' can't be hyphenated.",
+          type: 'VDirectiveKey',
+          line: 1
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/define-macros-order.js b/tests/lib/rules/define-macros-order.js
index 3aa2e434..72b35986 100644
--- a/tests/lib/rules/define-macros-order.js
+++ b/tests/lib/rules/define-macros-order.js
@@ -195,6 +195,65 @@ tester.run('define-macros-order', rule, {
         }
       ]
     },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          import Foo from 'foo'
+          /** Page */
+          definePage()
+          /** model */
+          const model = defineModel()
+          /** emits */
+          defineEmits(['update:foo'])
+        </script>
+      `,
+      options: [
+        {
+          order: ['definePage', 'defineModel', 'defineEmits']
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          import Foo from 'foo'
+          /** model */
+          const model = defineModel()
+          /** emits */
+          defineEmits(['update:foo'])
+
+          function fn() {
+            definePage()
+          }
+        </script>
+      `,
+      options: [
+        {
+          order: ['definePage', 'defineModel', 'defineEmits']
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          import Foo from 'foo'
+          /** model */
+          const model = defineModel()
+          /** emits */
+          defineEmits(['update:foo'])
+
+          const val = () => definePage()
+        </script>
+      `,
+      options: [
+        {
+          order: ['definePage', 'defineModel', 'defineEmits']
+        }
+      ]
+    },
     {
       filename: 'test.vue',
       code: `
@@ -254,6 +313,22 @@ tester.run('define-macros-order', rule, {
           order: ['defineModel', 'defineSlots']
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+        const page = definePage()
+
+        const first = defineModel('first')
+        const second = defineModel('second')
+        </script>
+      `,
+      options: [
+        {
+          order: ['definePage', 'defineModel']
+        }
+      ]
     }
   ],
   invalid: [
@@ -382,6 +457,40 @@ tester.run('define-macros-order', rule, {
         }
       ]
     },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          console.log('test1')
+          const props = defineProps({
+            test: Boolean
+          })
+          console.log('test2')
+          const page = definePage({
+            name: 'hello'
+          })
+        </script>
+      `,
+      output: `
+        <script setup>
+          const page = definePage({
+            name: 'hello'
+          })
+          const props = defineProps({
+            test: Boolean
+          })
+          console.log('test1')
+          console.log('test2')
+        </script>
+      `,
+      options: [{ order: ['definePage', 'defineProps'] }],
+      errors: [
+        {
+          message: message('definePage'),
+          line: 8
+        }
+      ]
+    },
     {
       filename: 'test.vue',
       code: `
@@ -425,6 +534,61 @@ tester.run('define-macros-order', rule, {
         }
       ]
     },
+    {
+      filename: 'test.vue',
+      code: `
+        <script lang="ts" setup>
+          interface Props {
+            msg?: string
+            labels?: string[]
+          }
+
+          const props = defineProps<{
+            msg?: string
+            labels?: string[]
+          }>()
+          defineCustom()
+          const emit = defineEmits<{(e: 'update:test'): void}>()
+
+          const page = definePage({
+            name: 'hello'
+          })
+        </script>
+      `,
+      output: `
+        <script lang="ts" setup>
+          interface Props {
+            msg?: string
+            labels?: string[]
+          }
+
+          const page = definePage({
+            name: 'hello'
+          })
+          defineCustom()
+          const props = defineProps<{
+            msg?: string
+            labels?: string[]
+          }>()
+          const emit = defineEmits<{(e: 'update:test'): void}>()
+
+        </script>
+      `,
+      options: [
+        { order: ['definePage', 'defineCustom', 'defineProps', 'defineEmits'] }
+      ],
+      languageOptions: {
+        parserOptions: {
+          parser: require.resolve('@typescript-eslint/parser')
+        }
+      },
+      errors: [
+        {
+          message: message('definePage'),
+          line: 15
+        }
+      ]
+    },
     {
       filename: 'test.vue',
       code: `
@@ -537,6 +701,25 @@ tester.run('define-macros-order', rule, {
         }
       ]
     },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          const props = defineProps({ test: Boolean });definePage({name: 'hello'})
+        </script>
+      `,
+      output: `
+        <script setup>
+          definePage({name: 'hello'});const props = defineProps({ test: Boolean });        </script>
+      `,
+      options: [{ order: ['definePage', 'defineProps'] }],
+      errors: [
+        {
+          message: message('definePage'),
+          line: 3
+        }
+      ]
+    },
     {
       filename: 'test.vue',
       code: `
@@ -633,6 +816,54 @@ tester.run('define-macros-order', rule, {
         }
       ]
     },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          definePage({name: 'hello'})
+          console.log('test1')
+          defineCustom({ test: Boolean })
+        </script>
+      `,
+      output: `
+        <script setup>
+          defineCustom({ test: Boolean })
+          definePage({name: 'hello'})
+          console.log('test1')
+        </script>
+      `,
+      options: [{ order: ['defineCustom', 'definePage'] }],
+      errors: [
+        {
+          message: message('defineCustom'),
+          line: 5
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          definePage({name: 'hello'})
+          console.log('test1')
+          const custom = defineCustom({ test: Boolean })
+        </script>
+      `,
+      output: `
+        <script setup>
+          const custom = defineCustom({ test: Boolean })
+          definePage({name: 'hello'})
+          console.log('test1')
+        </script>
+      `,
+      options: [{ order: ['defineCustom', 'definePage'] }],
+      errors: [
+        {
+          message: message('defineCustom'),
+          line: 5
+        }
+      ]
+    },
     {
       filename: 'test.vue',
       code: `
@@ -895,6 +1126,44 @@ tester.run('define-macros-order', rule, {
         }
       ]
     },
+    {
+      filename: 'test.vue',
+      code: `
+        <script setup>
+          defineOptions({})
+          defineCustom('second')
+          const something = defineSomething('first')
+          definePage()
+          const model = defineModel('second')
+        </script>
+      `,
+      output: `
+        <script setup>
+          const something = defineSomething('first')
+          defineCustom('second')
+          const model = defineModel('second')
+          defineOptions({})
+          definePage()
+        </script>
+      `,
+      options: [
+        {
+          order: [
+            'defineSomething',
+            'defineCustom',
+            'defineModel',
+            'defineOptions',
+            'definePage'
+          ]
+        }
+      ],
+      errors: [
+        {
+          message: message('defineSomething'),
+          line: 5
+        }
+      ]
+    },
     {
       filename: 'test.vue',
       code: `
diff --git a/tests/lib/rules/max-template-depth.js b/tests/lib/rules/max-template-depth.js
new file mode 100644
index 00000000..4ef9e95d
--- /dev/null
+++ b/tests/lib/rules/max-template-depth.js
@@ -0,0 +1,168 @@
+/**
+ * @author kevsommer Kevin Sommer
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('../../eslint-compat').RuleTester
+const rule = require('../../../lib/rules/max-template-depth')
+
+const tester = new RuleTester({
+  languageOptions: {
+    parser: require('vue-eslint-parser'),
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('max-template-depth', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+      <main-container>
+        <child-component>
+          <div />
+        </child-component>
+      </main-container>
+      </template>
+      `,
+      options: [{ maxDepth: 3 }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+      <main-container>
+        <ul>
+          <li>Item 1</li>
+        </ul>
+      </main-container>
+      </template>
+      `,
+      options: [{ maxDepth: 4 }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+      <main-container>
+        <child-component>
+          <sub-child-component>
+            <div />
+          </sub-child-component>
+        </child-component>
+      </main-container>
+      </template>
+      `,
+      options: [{ maxDepth: 4 }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+      <div>
+      </div>
+      <main-container>
+        <child-component>
+          <ul>
+            <li />
+          </ul>
+        </child-component>
+      </main-container>
+      </template>
+      `,
+      options: [{ maxDepth: 4 }]
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+      <div>
+        <div>
+          <div>
+            <div />
+          </div>
+        </div>
+      </div>
+      </template>
+      `,
+      options: [{ maxDepth: 3 }],
+      errors: [
+        {
+          message:
+            'Element is nested too deeply (depth of 4, maximum allowed is 3).',
+          line: 6
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+      <side-container>
+        <child-component />
+      </side-container>
+      <main-container>
+        <child-component>
+          <nested-component>
+            <h1 />
+          </nested-component>
+        </child-component>
+      </main-container>
+      </template>
+      `,
+      options: [{ maxDepth: 3 }],
+      errors: [
+        {
+          message:
+            'Element is nested too deeply (depth of 4, maximum allowed is 3).',
+          line: 9
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+      <main-container>
+        <nav-bar>
+          <div />
+        </nav-bar>
+        <child-component>
+          <nested-component>
+            <div>
+              <div />
+            <div>
+          </nested-component>
+        </child-component>
+      </main-container>
+      </template>
+      `,
+      options: [{ maxDepth: 3 }],
+      errors: [
+        {
+          message:
+            'Element is nested too deeply (depth of 4, maximum allowed is 3).',
+          line: 9,
+          endLine: 12
+        },
+        {
+          message:
+            'Element is nested too deeply (depth of 5, maximum allowed is 3).',
+          line: 10,
+          endLine: 10
+        },
+        {
+          message:
+            'Element is nested too deeply (depth of 5, maximum allowed is 3).',
+          line: 11,
+          endLine: 12
+        }
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/no-deprecated-slot-attribute.js b/tests/lib/rules/no-deprecated-slot-attribute.js
index 51d10935..2fd5fa40 100644
--- a/tests/lib/rules/no-deprecated-slot-attribute.js
+++ b/tests/lib/rules/no-deprecated-slot-attribute.js
@@ -643,6 +643,44 @@ tester.run('no-deprecated-slot-attribute', rule, {
         }
       ],
       errors: ['`slot` attributes are deprecated.']
+    },
+    {
+      code: `
+      <template>
+        <my-component>
+          <slot
+            v-for="slot in Object.keys($slots)"
+            :slot="slot"
+            :name="slot"
+          ></slot>
+        </my-component>
+      </template>`,
+      output: `
+      <template>
+        <my-component>
+          <template v-for="slot in Object.keys($slots)" v-slot:[slot]>
+<slot
+            
+            
+            :name="slot"
+          ></slot>
+</template>
+        </my-component>
+      </template>`,
+      errors: ['`slot` attributes are deprecated.']
+    },
+    {
+      code: `
+      <template>
+        <component :is="toggle ? 'my-component' : 'div'">
+          <div slot="named">
+            Passing in a named slot to a div worked with old syntax
+            But not with new syntax
+          </div>
+        </component>
+      </template>`,
+      output: null,
+      errors: ['`slot` attributes are deprecated.']
     }
   ]
 })
diff --git a/tests/lib/rules/require-toggle-inside-transition.js b/tests/lib/rules/require-toggle-inside-transition.js
index d40ad474..cdd48c49 100644
--- a/tests/lib/rules/require-toggle-inside-transition.js
+++ b/tests/lib/rules/require-toggle-inside-transition.js
@@ -77,6 +77,15 @@ tester.run('require-toggle-inside-transition', rule, {
     {
       filename: 'test.vue',
       code: '<template><transition :appear="true"><div /></transition></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><transition><dialog v-dialog="show" /></transition></template>',
+      options: [
+        {
+          additionalDirectives: ['dialog']
+        }
+      ]
     }
   ],
   invalid: [
@@ -132,6 +141,36 @@ tester.run('require-toggle-inside-transition', rule, {
       filename: 'test.vue',
       code: '<template><transition @appear="isLoaded"><div /></transition></template>',
       errors: [{ messageId: 'expected' }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><transition><dialog v-dialog="show" /></transition></template>',
+      options: [
+        {
+          additionalDirectives: []
+        }
+      ],
+      errors: [
+        {
+          messageId: 'expected',
+          data: { allowedDirectives: '`v-if` or `v-show`' }
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><transition><div v-custom="show" /></transition></template>',
+      options: [
+        {
+          additionalDirectives: ['dialog']
+        }
+      ],
+      errors: [
+        {
+          messageId: 'expected',
+          data: { allowedDirectives: '`v-if`, `v-show` or `v-dialog`' }
+        }
+      ]
     }
   ]
 })