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

Issue:
 Maximum depth of template block
**Please describe what the rule should do:**
The rule should allow developers to set a maximum depth for templates for Vue components in a project.
In my experience a high depth of a template correlates with very complex components, so forcing a developer to split up a component into subcomponents via an optional linting rule, could aid in keeping complexity down in a codebase.
Therefore, I think it would be a good addition to the vue eslint rule set, to give developers the option to enforce this rule.

**What category should the rule belong to?**
<!-- (place an "X" next to just one item) -->
[x] Enforces code style (layout)
[ ] Warns about a potential error (problem)
[ ] Suggests an alternate way of doing something (suggestion)
[ ] Other (please specify:)

**Provide 2-3 code examples that this rule should warn about:**
```vue
<!-- ✗ BAD -->
<template>
  <main-container>
    <child-component>
      <div />
    </child-component>
    <child-component>
      <nested-component>
        <div>
          <div />
        </div>
      </nested-component>
    </child-component>
  </main-container>
</template>
```

```vue
<!-- ✓ GOOD -->
<template>
  <main-container>
    <child-component>
      <div />
    </child-component>
  </main-container>
</template>
```

**Additional context**
So far I haven't found a similar rule proposal.
But given the ruleset includes the vue/max-len rule and vue/max-props, which similarly help control the complexity of components, I think it would be a good addition.




Discussion:

Patch:
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 113ff416b..476f266ce 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -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 000000000..94bd8b5ec
--- /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/lib/index.js b/lib/index.js
index ff3f69c6e..6dbe5107b 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/max-template-depth.js b/lib/rules/max-template-depth.js
new file mode 100644
index 000000000..899ec75ce
--- /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)
+      }
+    }
+  }
+}


Test patch:
diff --git a/tests/lib/rules/max-template-depth.js b/tests/lib/rules/max-template-depth.js
new file mode 100644
index 000000000..4ef9e95d1
--- /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
+        }
+      ]
+    }
+  ]
+})