diff --git a/docs/rules/README.md b/docs/rules/README.md
index b4c01e51c..7e0b1dd96 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -92,6 +92,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
| [vue/valid-v-for](./valid-v-for.md) | enforce valid `v-for` directives | |
| [vue/valid-v-html](./valid-v-html.md) | enforce valid `v-html` directives | |
| [vue/valid-v-if](./valid-v-if.md) | enforce valid `v-if` directives | |
+| [vue/valid-v-is](./valid-v-is.md) | enforce valid `v-is` directives | |
| [vue/valid-v-model](./valid-v-model.md) | enforce valid `v-model` directives | |
| [vue/valid-v-on](./valid-v-on.md) | enforce valid `v-on` directives | |
| [vue/valid-v-once](./valid-v-once.md) | enforce valid `v-once` directives | |
diff --git a/docs/rules/valid-v-is.md b/docs/rules/valid-v-is.md
new file mode 100644
index 000000000..c8b44d213
--- /dev/null
+++ b/docs/rules/valid-v-is.md
@@ -0,0 +1,63 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-is
+description: enforce valid `v-is` directives
+---
+# vue/valid-v-is
+> enforce valid `v-is` directives
+
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+
+This rule checks whether every `v-is` directive is valid.
+
+## :book: Rule Details
+
+This rule reports `v-is` directives in the following cases:
+
+- The directive has that argument. E.g. `
`
+- The directive has that modifier. E.g. ``
+- The directive does not have that attribute value. E.g. ``
+- The directive is on Vue-components. E.g. ``
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+::: warning Note
+This rule does not check syntax errors in directives because it's checked by [vue/no-parsing-error] rule.
+:::
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/no-parsing-error]
+
+[vue/no-parsing-error]: ./no-parsing-error.md
+
+## :books: Further Reading
+
+- [API - v-is](https://v3.vuejs.org/api/directives.html#v-is)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-is.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-is.js)
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index 5adbbcd23..8b2d6a131 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -60,6 +60,7 @@ module.exports = {
'vue/valid-v-for': 'error',
'vue/valid-v-html': 'error',
'vue/valid-v-if': 'error',
+ 'vue/valid-v-is': 'error',
'vue/valid-v-model': 'error',
'vue/valid-v-on': 'error',
'vue/valid-v-once': 'error',
diff --git a/lib/index.js b/lib/index.js
index 530c05c58..5f285be69 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -158,6 +158,7 @@ module.exports = {
'valid-v-for': require('./rules/valid-v-for'),
'valid-v-html': require('./rules/valid-v-html'),
'valid-v-if': require('./rules/valid-v-if'),
+ 'valid-v-is': require('./rules/valid-v-is'),
'valid-v-model': require('./rules/valid-v-model'),
'valid-v-on': require('./rules/valid-v-on'),
'valid-v-once': require('./rules/valid-v-once'),
diff --git a/lib/rules/valid-v-is.js b/lib/rules/valid-v-is.js
new file mode 100644
index 000000000..50fa1fb5e
--- /dev/null
+++ b/lib/rules/valid-v-is.js
@@ -0,0 +1,95 @@
+/**
+ * @fileoverview enforce valid `v-is` directives
+ * @author Yosuke Ota
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Check whether the given node is valid or not.
+ * @param {VElement} node The element node to check.
+ * @returns {boolean} `true` if the node is valid.
+ */
+function isValidElement(node) {
+ if (
+ utils.isHtmlElementNode(node) &&
+ !utils.isHtmlWellKnownElementName(node.rawName)
+ ) {
+ // Vue-component
+ return false
+ }
+ return true
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `v-is` directives',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-is.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ unexpectedArgument: "'v-is' directives require no argument.",
+ unexpectedModifier: "'v-is' directives require no modifier.",
+ expectedValue: "'v-is' directives require that attribute value.",
+ ownerMustBeHTMLElement:
+ "'v-is' directive must be owned by a native HTML element, but '{{name}}' is not."
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ "VAttribute[directive=true][key.name.name='is']"(node) {
+ if (node.key.argument) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'unexpectedArgument'
+ })
+ }
+ if (node.key.modifiers.length > 0) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'unexpectedModifier'
+ })
+ }
+ if (!node.value || utils.isEmptyValueDirective(node, context)) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'expectedValue'
+ })
+ }
+
+ const element = node.parent.parent
+
+ if (!isValidElement(element)) {
+ const name = element.name
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'ownerMustBeHTMLElement',
+ data: { name }
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/tests/lib/rules/valid-v-is.js b/tests/lib/rules/valid-v-is.js
new file mode 100644
index 000000000..1a3a45e5e
--- /dev/null
+++ b/tests/lib/rules/valid-v-is.js
@@ -0,0 +1,109 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/valid-v-is')
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2020 }
+})
+
+tester.run('valid-v-is', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ``
+ },
+ // parsing error
+ {
+ filename: 'parsing-error.vue',
+ code: ''
+ },
+ // comment value (parsing error)
+ {
+ filename: 'comment-value.vue',
+ code: ''
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: '',
+ errors: [
+ {
+ message: "'v-is' directives require no argument.",
+ column: 16,
+ endColumn: 28
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: '',
+ errors: [
+ {
+ message: "'v-is' directives require no modifier.",
+ column: 16,
+ endColumn: 28
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: '',
+ errors: [
+ {
+ message: "'v-is' directives require that attribute value.",
+ column: 16,
+ endColumn: 20
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: '',
+ errors: [
+ {
+ message: "'v-is' directives require that attribute value.",
+ column: 16,
+ endColumn: 23
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: '',
+ errors: [
+ {
+ message:
+ "'v-is' directive must be owned by a native HTML element, but 'mycomponent' is not.",
+ column: 24,
+ endColumn: 34
+ }
+ ]
+ }
+ ]
+})
diff --git a/typings/eslint-plugin-vue/util-types/ast/ast.ts b/typings/eslint-plugin-vue/util-types/ast/ast.ts
index 357b56400..d8f21315c 100644
--- a/typings/eslint-plugin-vue/util-types/ast/ast.ts
+++ b/typings/eslint-plugin-vue/util-types/ast/ast.ts
@@ -62,6 +62,8 @@ export type VNodeListenerMap = {
| (V.VExpressionContainer & { expression: ES.Expression | null })
| null
}
+ "VAttribute[directive=true][key.name.name='is']": V.VDirective
+ "VAttribute[directive=true][key.name.name='is']:exit": V.VDirective
"VAttribute[directive=true][key.name.name='model']": V.VDirective & {
value:
| (V.VExpressionContainer & { expression: ES.Expression | null })