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

Issue:
disallow v-text/v-html on components
<!--
  Before proposing new rule, 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
-->

**Please describe what the rule should do:**
Components can have internal html structure that will be destroyed if you use `v-html` or `v-text` on them. Here's codepen example  

<https://codepen.io/jkarczm/pen/MWvRLYa> 

```html
    <v-btn>foo</v-btn>
    <v-btn v-text="'foo'" />
```

and the generated markup:

```html
    <button type="button" class="...">
      <span class="v-btn__content">foo</span>
    </button>
    <button type="button" class="...">
      foo
    </button>
```

As you see the second one misses the `v-btn__content` div, which in this particular case doesn't seem to break anything (EDIT: actually it does <https://github.com/vuetifyjs/vuetify/issues/11792#issuecomment-658767874>) but it could potentially

<!-- A clear and concise description of what the rule should do. -->

**What category should the rule belong to?**
<!-- (place an "X" next to just one item) -->
[ ] Enforces code style (layout)
[X] Warns about a potential error (problem)
[X] Suggests an alternate way of doing something (suggestion)
[ ] Other (please specify:)

**Provide 2-3 code examples that this rule should warn about:**

```vue
<template>
  <!-- warn -->
  <some-component v-text="'content'" />
  <some-component v-html="'content'" />

  <!-- ok -->
  <div v-text="'content'" />

  <!-- not sure, should probably warn too, could be configurable -->
  <web-component v-text="'content'" />
```

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Initially I thought about a new rule but I think that it could be just a configuration option for `vue/no-v-text` and `vue/no-v-html` rules (`{ allowNativeTags: true }`)



Discussion:
Thank you for your suggestion. I think it's good to add options to the two existing rules, but I think it's better to add new rule to clearly separate the goals.
> but I think it's better to add new rule to clearly separate the goals

I don't agree, the goal is the same - prevent from using v-text/v-html, the only difference is that the existing rules prevents it everywhere and proposed one prevents it only for components, so just adding the configuration seems a valid solution for me
I think those goals are different. Also, I think each rule has a different category.

- `no-v-html` is a rule to prevent XSS attacks. In most cases it is better not to use v-html, so the rule is included in the category `recommended`.
- `no-v-text` is a rule for users who do not want to use v-text. Users can turn ON the rule according to your preference.
- The new rule is to prevent unexpected component content corruption. This is a problem for everyone, so I think it's a rule that should be included in the category `essential` in the future.


Fair enough

>  This is a problem for everyone, so I think it's a rule that should be included in the category essential in the future

Was about to suggest it as well, completely agree



Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 735dc87d0..49a6c6389 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -349,6 +349,7 @@ For example:
 | [vue/no-use-computed-property-like-method](./no-use-computed-property-like-method.md) | disallow use computed property like method |  |
 | [vue/no-useless-mustaches](./no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: |
 | [vue/no-useless-v-bind](./no-useless-v-bind.md) | disallow unnecessary `v-bind` directives | :wrench: |
+| [vue/no-v-text-v-html-on-component](./no-v-text-v-html-on-component.md) | disallow v-text / v-html on component |  |
 | [vue/no-v-text](./no-v-text.md) | disallow use of v-text |  |
 | [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
 | [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: |
diff --git a/docs/rules/no-v-text-v-html-on-component.md b/docs/rules/no-v-text-v-html-on-component.md
new file mode 100644
index 000000000..5d8c8c434
--- /dev/null
+++ b/docs/rules/no-v-text-v-html-on-component.md
@@ -0,0 +1,43 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-v-text-v-html-on-component
+description: disallow v-text / v-html on component
+---
+# vue/no-v-text-v-html-on-component
+
+> disallow v-text / v-html on component
+
+- :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 disallows the use of v-text / v-html on component.
+
+If you use v-text / v-html on a component, it will overwrite the component's content and may break the component.
+
+<eslint-code-block :rules="{'vue/no-v-text-v-html-on-component': ['error']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <div v-text="content"></div>
+  <div v-html="html"></div>
+  <MyComponent>{{content}}</MyComponent>
+
+  <!-- ✗ BAD -->
+  <MyComponent v-text="content"></MyComponent>
+  <MyComponent v-html="html"></MyComponent>
+</template>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-v-text-v-html-on-component.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-v-text-v-html-on-component.js)
diff --git a/lib/index.js b/lib/index.js
index 3c628c559..021710984 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -145,6 +145,7 @@ module.exports = {
     'no-v-for-template-key': require('./rules/no-v-for-template-key'),
     'no-v-html': require('./rules/no-v-html'),
     'no-v-model-argument': require('./rules/no-v-model-argument'),
+    'no-v-text-v-html-on-component': require('./rules/no-v-text-v-html-on-component'),
     'no-v-text': require('./rules/no-v-text'),
     'no-watch-after-await': require('./rules/no-watch-after-await'),
     'object-curly-newline': require('./rules/object-curly-newline'),
diff --git a/lib/rules/no-v-text-v-html-on-component.js b/lib/rules/no-v-text-v-html-on-component.js
new file mode 100644
index 000000000..4a208627d
--- /dev/null
+++ b/lib/rules/no-v-text-v-html-on-component.js
@@ -0,0 +1,59 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'disallow v-text / v-html on component',
+      // TODO We will change it in the next major version.
+      // categories: ['essential', 'vue3-essential'],
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/no-v-text-v-html-on-component.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      disallow:
+        "Using {{directiveName}} on component may break component's content."
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    /**
+     * Verify for v-text and v-html directive
+     * @param {VDirective} node
+     */
+    function verify(node) {
+      const element = node.parent.parent
+      if (utils.isCustomComponent(element)) {
+        context.report({
+          node,
+          loc: node.loc,
+          messageId: 'disallow',
+          data: {
+            directiveName: `v-${node.key.name.name}`
+          }
+        })
+      }
+    }
+
+    return utils.defineTemplateBodyVisitor(context, {
+      "VAttribute[directive=true][key.name.name='text']": verify,
+      "VAttribute[directive=true][key.name.name='html']": verify
+    })
+  }
+}


Test patch:
diff --git a/tests/lib/rules/no-v-text-v-html-on-component.js b/tests/lib/rules/no-v-text-v-html-on-component.js
new file mode 100644
index 000000000..c50071346
--- /dev/null
+++ b/tests/lib/rules/no-v-text-v-html-on-component.js
@@ -0,0 +1,137 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-v-text-v-html-on-component')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('no-v-text-v-html-on-component', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div v-text="content" />
+      </template>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div v-html="content" />
+      </template>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <MyComponent v-if="content" />
+      </template>
+      `
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <MyComponent v-text="content" />
+      </template>
+      `,
+      errors: [
+        {
+          message: "Using v-text on component may break component's content.",
+          line: 3,
+          column: 22
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <MyComponent v-html="content" />
+      </template>
+      `,
+      errors: [
+        {
+          message: "Using v-html on component may break component's content.",
+          line: 3,
+          column: 22
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <component :is="component" v-text="content" />
+      </template>
+      `,
+      errors: [
+        {
+          message: "Using v-text on component may break component's content.",
+          line: 3,
+          column: 36
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <component :is="component" v-html="content" />
+      </template>
+      `,
+      errors: [
+        {
+          message: "Using v-html on component may break component's content.",
+          line: 3,
+          column: 36
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :is="component" v-text="content" />
+      </template>
+      `,
+      errors: [
+        {
+          message: "Using v-text on component may break component's content.",
+          line: 3,
+          column: 30
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :is="component" v-html="content" />
+      </template>
+      `,
+      errors: [
+        {
+          message: "Using v-html on component may break component's content.",
+          line: 3,
+          column: 30
+        }
+      ]
+    }
+  ]
+})