Skip to content

Commit

Permalink
Add support for is="vue:" (Vue 3.1) (#1509)
Browse files Browse the repository at this point in the history
* Add support `is="vue:"` (Vue 3.1)

* update

* update
  • Loading branch information
ota-meshi authored Jun 9, 2021
1 parent 021fe2b commit e9d20fd
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 13 deletions.
2 changes: 2 additions & 0 deletions docs/rules/no-unsupported-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
32 changes: 25 additions & 7 deletions lib/rules/no-deprecated-html-element-is.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
9 changes: 6 additions & 3 deletions lib/rules/no-unregistered-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,14 @@ module.exports = {
/** @param {VAttribute} node */
"VAttribute[directive=false][key.name='is']"(node) {
if (
!node.value || // `<component is />`
utils.isHtmlWellKnownElementName(node.value.value)
!node.value // `<component is />`
)
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"() {
Expand Down
8 changes: 6 additions & 2 deletions lib/rules/no-unsupported-features.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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 */
Expand Down
5 changes: 4 additions & 1 deletion lib/rules/no-unused-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
25 changes: 25 additions & 0 deletions lib/rules/syntaxes/is-attribute-with-vue-prefix.js
Original file line number Diff line number Diff line change
@@ -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'
})
}
}
}
}
}
1 change: 1 addition & 0 deletions lib/rules/syntaxes/v-is.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
'use strict'
module.exports = {
deprecated: '3.1.0',
supported: '>=3.0.0',
/** @param {RuleContext} context @returns {TemplateListener} */
createTemplateBodyVisitor(context) {
Expand Down
6 changes: 6 additions & 0 deletions tests/lib/rules/no-deprecated-html-element-is.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ ruleTester.run('no-deprecated-html-element-is', rule, {
{
filename: 'test.vue',
code: '<template><component :is="\'foo\'" /></template>'
},

// is="vue:xxx"
{
filename: 'test.vue',
code: '<template><div is="vue:foo" /></template>'
}
],

Expand Down
55 changes: 55 additions & 0 deletions tests/lib/rules/no-unregistered-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,19 @@ tester.run('no-unregistered-components', rule, {
</script>
`
},
{
filename: 'test.vue',
code: `
<template>
<component is="vue:CustomComponent" />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`
},
{
filename: 'test.vue',
code: `
Expand Down Expand Up @@ -706,6 +719,48 @@ tester.run('no-unregistered-components', rule, {
line: 3
}
]
},
{
filename: 'test.vue',
code: `
<template>
<div is="vue:foo" />
</template>
<script>
export default {
components: {
bar
}
}
</script>
`,
errors: [
{
message: 'The "foo" component has been used but not registered.',
line: 3
}
]
},
{
filename: 'test.vue',
code: `
<template>
<div v-is="'foo'" />
</template>
<script>
export default {
components: {
bar
}
}
</script>
`,
errors: [
{
message: 'The "foo" component has been used but not registered.',
line: 3
}
]
}
]
})
Original file line number Diff line number Diff line change
@@ -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: `
<template>
<div is="vue:foo" />
</template>`,
options: buildOptions()
},
{
code: `
<template>
<div is="foo" />
</template>`,
options: buildOptions({ version: '^2.5.0' })
},
{
code: `
<template>
<div is="vue:foo" />
</template>`,
options: buildOptions({
version: '^2.5.0',
ignores: ['is-attribute-with-vue-prefix']
})
}
],
invalid: [
{
code: `
<template>
<div is="vue:foo" />
</template>`,
options: buildOptions({ version: '^3.0.0' }),
errors: [
{
message: '`is="vue:"` are not supported until Vue.js "3.1.0".',
line: 3
}
]
}
]
})
15 changes: 15 additions & 0 deletions tests/lib/rules/no-unused-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,21 @@ tester.run('no-unused-components', rule, {
}
</script>
`
},
{
filename: 'test.vue',
code: `
<template>
<div is="vue:CustomComponent" />
</template>
<script>
export default {
components: {
CustomComponent
}
}
</script>
`
}
],
invalid: [
Expand Down

0 comments on commit e9d20fd

Please sign in to comment.