diff --git a/docs/rules/no-unsupported-features.md b/docs/rules/no-unsupported-features.md
index dfb73e851..8665e9b83 100644
--- a/docs/rules/no-unsupported-features.md
+++ b/docs/rules/no-unsupported-features.md
@@ -29,6 +29,8 @@ This rule reports unsupported Vue.js syntax on the specified version.
- `version` ... The `version` option accepts [the valid version range of `node-semver`](https://github.com/npm/node-semver#range-grammar). Set the version of Vue.js you are using. This option is required.
- `ignores` ... You can use this `ignores` option to ignore the given features.
The `"ignores"` option accepts an array of the following strings.
+ - Vue.js 3.1.0+
+ - `"is-attribute-with-vue-prefix"` ... [`is` attribute with `vue:` prefix](https://v3.vuejs.org/api/special-attributes.html#is)
- Vue.js 3.0.0+
- `"v-model-argument"` ... [argument on `v-model`][Vue RFCs - 0005-replace-v-bind-sync-with-v-model-argument]
- `"v-model-custom-modifiers"` ... [custom modifiers on `v-model`][Vue RFCs - 0011-v-model-api-change]
diff --git a/lib/rules/no-deprecated-html-element-is.js b/lib/rules/no-deprecated-html-element-is.js
index e07f493a5..8382bb5c0 100644
--- a/lib/rules/no-deprecated-html-element-is.js
+++ b/lib/rules/no-deprecated-html-element-is.js
@@ -31,16 +31,34 @@ module.exports = {
},
/** @param {RuleContext} context */
create(context) {
+ /** @param {VElement} node */
+ function isValidElement(node) {
+ return (
+ !utils.isHtmlWellKnownElementName(node.rawName) &&
+ !utils.isSvgWellKnownElementName(node.rawName)
+ )
+ }
return utils.defineTemplateBodyVisitor(context, {
- /** @param {VDirective | VAttribute} node */
- "VAttribute[directive=true][key.name.name='bind'][key.argument.name='is'], VAttribute[directive=false][key.name='is']"(
+ /** @param {VDirective} node */
+ "VAttribute[directive=true][key.name.name='bind'][key.argument.name='is']"(
node
) {
- const element = node.parent.parent
- if (
- !utils.isHtmlWellKnownElementName(element.rawName) &&
- !utils.isSvgWellKnownElementName(element.rawName)
- ) {
+ if (isValidElement(node.parent.parent)) {
+ return
+ }
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'unexpected'
+ })
+ },
+ /** @param {VAttribute} node */
+ "VAttribute[directive=false][key.name='is']"(node) {
+ if (isValidElement(node.parent.parent)) {
+ return
+ }
+ if (node.value && node.value.value.startsWith('vue:')) {
+ // Usage on native elements 3.1+
return
}
context.report({
diff --git a/lib/rules/no-unregistered-components.js b/lib/rules/no-unregistered-components.js
index 6e6cb2ada..07d9e800a 100644
--- a/lib/rules/no-unregistered-components.js
+++ b/lib/rules/no-unregistered-components.js
@@ -119,11 +119,14 @@ module.exports = {
/** @param {VAttribute} node */
"VAttribute[directive=false][key.name='is']"(node) {
if (
- !node.value || // ``
- utils.isHtmlWellKnownElementName(node.value.value)
+ !node.value // ``
)
return
- usedComponentNodes.push({ node, name: node.value.value })
+ const value = node.value.value.startsWith('vue:') // Usage on native elements 3.1+
+ ? node.value.value.slice(4)
+ : node.value.value
+ if (utils.isHtmlWellKnownElementName(value)) return
+ usedComponentNodes.push({ node, name: value })
},
/** @param {VElement} node */
"VElement[name='template'][parent.type='VDocumentFragment']:exit"() {
diff --git a/lib/rules/no-unsupported-features.js b/lib/rules/no-unsupported-features.js
index 9eb00be0e..2d4caf3e2 100644
--- a/lib/rules/no-unsupported-features.js
+++ b/lib/rules/no-unsupported-features.js
@@ -25,7 +25,9 @@ const FEATURES = {
// Vue.js 3.0.0+
'v-model-argument': require('./syntaxes/v-model-argument'),
'v-model-custom-modifiers': require('./syntaxes/v-model-custom-modifiers'),
- 'v-is': require('./syntaxes/v-is')
+ 'v-is': require('./syntaxes/v-is'),
+ // Vue.js 3.1.0+
+ 'is-attribute-with-vue-prefix': require('./syntaxes/is-attribute-with-vue-prefix')
}
const SYNTAX_NAMES = /** @type {(keyof FEATURES)[]} */ (Object.keys(FEATURES))
@@ -97,7 +99,9 @@ module.exports = {
'Argument on `v-model` is not supported until Vue.js "3.0.0".',
forbiddenVModelCustomModifiers:
'Custom modifiers on `v-model` are not supported until Vue.js "3.0.0".',
- forbiddenVIs: '`v-is` are not supported until Vue.js "3.0.0".'
+ forbiddenVIs: '`v-is` are not supported until Vue.js "3.0.0".',
+ forbiddenIsAttributeWithVuePrefix:
+ '`is="vue:"` are not supported until Vue.js "3.1.0".'
}
},
/** @param {RuleContext} context */
diff --git a/lib/rules/no-unused-components.js b/lib/rules/no-unused-components.js
index e067e3eb0..8497dd961 100644
--- a/lib/rules/no-unused-components.js
+++ b/lib/rules/no-unused-components.js
@@ -88,7 +88,10 @@ module.exports = {
if (!node.value) {
return
}
- usedComponents.add(node.value.value)
+ const value = node.value.value.startsWith('vue:') // Usage on native elements 3.1+
+ ? node.value.value.slice(4)
+ : node.value.value
+ usedComponents.add(value)
},
/** @param {VElement} node */
"VElement[name='template']"(node) {
diff --git a/lib/rules/syntaxes/is-attribute-with-vue-prefix.js b/lib/rules/syntaxes/is-attribute-with-vue-prefix.js
new file mode 100644
index 000000000..9fd2afdd0
--- /dev/null
+++ b/lib/rules/syntaxes/is-attribute-with-vue-prefix.js
@@ -0,0 +1,25 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+module.exports = {
+ supported: '>=3.1.0',
+ /** @param {RuleContext} context @returns {TemplateListener} */
+ createTemplateBodyVisitor(context) {
+ return {
+ /** @param {VAttribute} node */
+ "VAttribute[directive=false][key.name='is']"(node) {
+ if (!node.value) {
+ return
+ }
+ if (node.value.value.startsWith('vue:')) {
+ context.report({
+ node: node.value,
+ messageId: 'forbiddenIsAttributeWithVuePrefix'
+ })
+ }
+ }
+ }
+ }
+}
diff --git a/lib/rules/syntaxes/v-is.js b/lib/rules/syntaxes/v-is.js
index 9aeeb8d7e..f5eed36d5 100644
--- a/lib/rules/syntaxes/v-is.js
+++ b/lib/rules/syntaxes/v-is.js
@@ -4,6 +4,7 @@
*/
'use strict'
module.exports = {
+ deprecated: '3.1.0',
supported: '>=3.0.0',
/** @param {RuleContext} context @returns {TemplateListener} */
createTemplateBodyVisitor(context) {
diff --git a/tests/lib/rules/no-deprecated-html-element-is.js b/tests/lib/rules/no-deprecated-html-element-is.js
index 961644946..9b7cfdb23 100644
--- a/tests/lib/rules/no-deprecated-html-element-is.js
+++ b/tests/lib/rules/no-deprecated-html-element-is.js
@@ -37,6 +37,12 @@ ruleTester.run('no-deprecated-html-element-is', rule, {
{
filename: 'test.vue',
code: ''
+ },
+
+ // is="vue:xxx"
+ {
+ filename: 'test.vue',
+ code: ''
}
],
diff --git a/tests/lib/rules/no-unregistered-components.js b/tests/lib/rules/no-unregistered-components.js
index 27739b78a..6a51bf494 100644
--- a/tests/lib/rules/no-unregistered-components.js
+++ b/tests/lib/rules/no-unregistered-components.js
@@ -446,6 +446,19 @@ tester.run('no-unregistered-components', rule, {
`
},
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
{
filename: 'test.vue',
code: `
@@ -706,6 +719,48 @@ tester.run('no-unregistered-components', rule, {
line: 3
}
]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ errors: [
+ {
+ message: 'The "foo" component has been used but not registered.',
+ line: 3
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ errors: [
+ {
+ message: 'The "foo" component has been used but not registered.',
+ line: 3
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/no-unsupported-features/is-attribute-with-vue-prefix.js b/tests/lib/rules/no-unsupported-features/is-attribute-with-vue-prefix.js
new file mode 100644
index 000000000..adcc64835
--- /dev/null
+++ b/tests/lib/rules/no-unsupported-features/is-attribute-with-vue-prefix.js
@@ -0,0 +1,64 @@
+/**
+ * @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-unsupported-features')
+const utils = require('./utils')
+
+const buildOptions = utils.optionsBuilder(
+ 'is-attribute-with-vue-prefix',
+ '^3.1.0'
+)
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2019
+ }
+})
+
+tester.run('no-unsupported-features/is-attribute-with-vue-prefix', rule, {
+ valid: [
+ {
+ code: `
+
+
+ `,
+ options: buildOptions()
+ },
+ {
+ code: `
+
+
+ `,
+ options: buildOptions({ version: '^2.5.0' })
+ },
+ {
+ code: `
+
+
+ `,
+ options: buildOptions({
+ version: '^2.5.0',
+ ignores: ['is-attribute-with-vue-prefix']
+ })
+ }
+ ],
+ invalid: [
+ {
+ code: `
+
+
+ `,
+ options: buildOptions({ version: '^3.0.0' }),
+ errors: [
+ {
+ message: '`is="vue:"` are not supported until Vue.js "3.1.0".',
+ line: 3
+ }
+ ]
+ }
+ ]
+})
diff --git a/tests/lib/rules/no-unused-components.js b/tests/lib/rules/no-unused-components.js
index 02cb1a3cf..f63b75f13 100644
--- a/tests/lib/rules/no-unused-components.js
+++ b/tests/lib/rules/no-unused-components.js
@@ -486,6 +486,21 @@ tester.run('no-unused-components', rule, {
}
`
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
}
],
invalid: [