Skip to content

Commit f626498

Browse files
authored
Add vue/valid-v-is rule (#1253)
1 parent 7ef7de8 commit f626498

File tree

7 files changed

+272
-0
lines changed

7 files changed

+272
-0
lines changed

Diff for: docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
9292
| [vue/valid-v-for](./valid-v-for.md) | enforce valid `v-for` directives | |
9393
| [vue/valid-v-html](./valid-v-html.md) | enforce valid `v-html` directives | |
9494
| [vue/valid-v-if](./valid-v-if.md) | enforce valid `v-if` directives | |
95+
| [vue/valid-v-is](./valid-v-is.md) | enforce valid `v-is` directives | |
9596
| [vue/valid-v-model](./valid-v-model.md) | enforce valid `v-model` directives | |
9697
| [vue/valid-v-on](./valid-v-on.md) | enforce valid `v-on` directives | |
9798
| [vue/valid-v-once](./valid-v-once.md) | enforce valid `v-once` directives | |

Diff for: docs/rules/valid-v-is.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/valid-v-is
5+
description: enforce valid `v-is` directives
6+
---
7+
# vue/valid-v-is
8+
> enforce valid `v-is` directives
9+
10+
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
11+
12+
This rule checks whether every `v-is` directive is valid.
13+
14+
## :book: Rule Details
15+
16+
This rule reports `v-is` directives in the following cases:
17+
18+
- The directive has that argument. E.g. `<div v-is:aaa="foo"></div>`
19+
- The directive has that modifier. E.g. `<div v-is.bbb="foo"></div>`
20+
- The directive does not have that attribute value. E.g. `<div v-is></div>`
21+
- The directive is on Vue-components. E.g. `<MyComponent v-is="foo"></MyComponent>`
22+
23+
<eslint-code-block :rules="{'vue/valid-v-is': ['error']}">
24+
25+
```vue
26+
<template>
27+
<!-- ✓ GOOD -->
28+
<tr v-is="'blog-post-row'"></tr>
29+
<tr v-is="foo"></tr>
30+
31+
<!-- ✗ BAD -->
32+
<tr v-is:a="foo"></tr>
33+
<tr v-is.m="foo"></tr>
34+
<tr v-is></tr>
35+
<tr v-is=""></tr>
36+
<MyComponent v-is="foo" />
37+
</template>
38+
```
39+
40+
</eslint-code-block>
41+
42+
::: warning Note
43+
This rule does not check syntax errors in directives because it's checked by [vue/no-parsing-error] rule.
44+
:::
45+
46+
## :wrench: Options
47+
48+
Nothing.
49+
50+
## :couple: Related Rules
51+
52+
- [vue/no-parsing-error]
53+
54+
[vue/no-parsing-error]: ./no-parsing-error.md
55+
56+
## :books: Further Reading
57+
58+
- [API - v-is](https://v3.vuejs.org/api/directives.html#v-is)
59+
60+
## :mag: Implementation
61+
62+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-is.js)
63+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-is.js)

Diff for: lib/configs/vue3-essential.js

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ module.exports = {
6060
'vue/valid-v-for': 'error',
6161
'vue/valid-v-html': 'error',
6262
'vue/valid-v-if': 'error',
63+
'vue/valid-v-is': 'error',
6364
'vue/valid-v-model': 'error',
6465
'vue/valid-v-on': 'error',
6566
'vue/valid-v-once': 'error',

Diff for: lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ module.exports = {
158158
'valid-v-for': require('./rules/valid-v-for'),
159159
'valid-v-html': require('./rules/valid-v-html'),
160160
'valid-v-if': require('./rules/valid-v-if'),
161+
'valid-v-is': require('./rules/valid-v-is'),
161162
'valid-v-model': require('./rules/valid-v-model'),
162163
'valid-v-on': require('./rules/valid-v-on'),
163164
'valid-v-once': require('./rules/valid-v-once'),

Diff for: lib/rules/valid-v-is.js

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* @fileoverview enforce valid `v-is` directives
3+
* @author Yosuke Ota
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Helpers
15+
// ------------------------------------------------------------------------------
16+
17+
/**
18+
* Check whether the given node is valid or not.
19+
* @param {VElement} node The element node to check.
20+
* @returns {boolean} `true` if the node is valid.
21+
*/
22+
function isValidElement(node) {
23+
if (
24+
utils.isHtmlElementNode(node) &&
25+
!utils.isHtmlWellKnownElementName(node.rawName)
26+
) {
27+
// Vue-component
28+
return false
29+
}
30+
return true
31+
}
32+
33+
// ------------------------------------------------------------------------------
34+
// Rule Definition
35+
// ------------------------------------------------------------------------------
36+
37+
module.exports = {
38+
meta: {
39+
type: 'problem',
40+
docs: {
41+
description: 'enforce valid `v-is` directives',
42+
categories: ['vue3-essential'],
43+
url: 'https://eslint.vuejs.org/rules/valid-v-is.html'
44+
},
45+
fixable: null,
46+
schema: [],
47+
messages: {
48+
unexpectedArgument: "'v-is' directives require no argument.",
49+
unexpectedModifier: "'v-is' directives require no modifier.",
50+
expectedValue: "'v-is' directives require that attribute value.",
51+
ownerMustBeHTMLElement:
52+
"'v-is' directive must be owned by a native HTML element, but '{{name}}' is not."
53+
}
54+
},
55+
/** @param {RuleContext} context */
56+
create(context) {
57+
return utils.defineTemplateBodyVisitor(context, {
58+
"VAttribute[directive=true][key.name.name='is']"(node) {
59+
if (node.key.argument) {
60+
context.report({
61+
node,
62+
loc: node.loc,
63+
messageId: 'unexpectedArgument'
64+
})
65+
}
66+
if (node.key.modifiers.length > 0) {
67+
context.report({
68+
node,
69+
loc: node.loc,
70+
messageId: 'unexpectedModifier'
71+
})
72+
}
73+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
74+
context.report({
75+
node,
76+
loc: node.loc,
77+
messageId: 'expectedValue'
78+
})
79+
}
80+
81+
const element = node.parent.parent
82+
83+
if (!isValidElement(element)) {
84+
const name = element.name
85+
context.report({
86+
node,
87+
loc: node.loc,
88+
messageId: 'ownerMustBeHTMLElement',
89+
data: { name }
90+
})
91+
}
92+
}
93+
})
94+
}
95+
}

Diff for: tests/lib/rules/valid-v-is.js

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* @author Yosuke Ota
3+
*/
4+
'use strict'
5+
6+
// ------------------------------------------------------------------------------
7+
// Requirements
8+
// ------------------------------------------------------------------------------
9+
10+
const RuleTester = require('eslint').RuleTester
11+
const rule = require('../../../lib/rules/valid-v-is')
12+
13+
// ------------------------------------------------------------------------------
14+
// Tests
15+
// ------------------------------------------------------------------------------
16+
17+
const tester = new RuleTester({
18+
parser: require.resolve('vue-eslint-parser'),
19+
parserOptions: { ecmaVersion: 2020 }
20+
})
21+
22+
tester.run('valid-v-is', rule, {
23+
valid: [
24+
{
25+
filename: 'test.vue',
26+
code: ''
27+
},
28+
{
29+
filename: 'test.vue',
30+
code: '<template><div v-is="foo" /></template>'
31+
},
32+
{
33+
filename: 'test.vue',
34+
code: '<template><div v-bind:foo="foo" /></template>'
35+
},
36+
{
37+
filename: 'test.vue',
38+
code: `<template><div v-is="'foo'" /></template>`
39+
},
40+
// parsing error
41+
{
42+
filename: 'parsing-error.vue',
43+
code: '<template><div v-is="." /></template>'
44+
},
45+
// comment value (parsing error)
46+
{
47+
filename: 'comment-value.vue',
48+
code: '<template><div v-is="/**/" /></template>'
49+
}
50+
],
51+
invalid: [
52+
{
53+
filename: 'test.vue',
54+
code: '<template><div v-is:a="foo" /></template>',
55+
errors: [
56+
{
57+
message: "'v-is' directives require no argument.",
58+
column: 16,
59+
endColumn: 28
60+
}
61+
]
62+
},
63+
{
64+
filename: 'test.vue',
65+
code: '<template><div v-is.a="foo" /></template>',
66+
errors: [
67+
{
68+
message: "'v-is' directives require no modifier.",
69+
column: 16,
70+
endColumn: 28
71+
}
72+
]
73+
},
74+
{
75+
filename: 'test.vue',
76+
code: '<template><div v-is /></template>',
77+
errors: [
78+
{
79+
message: "'v-is' directives require that attribute value.",
80+
column: 16,
81+
endColumn: 20
82+
}
83+
]
84+
},
85+
{
86+
filename: 'test.vue',
87+
code: '<template><div v-is="" /></template>',
88+
errors: [
89+
{
90+
message: "'v-is' directives require that attribute value.",
91+
column: 16,
92+
endColumn: 23
93+
}
94+
]
95+
},
96+
{
97+
filename: 'test.vue',
98+
code: '<template><MyComponent v-is="foo" /></template>',
99+
errors: [
100+
{
101+
message:
102+
"'v-is' directive must be owned by a native HTML element, but 'mycomponent' is not.",
103+
column: 24,
104+
endColumn: 34
105+
}
106+
]
107+
}
108+
]
109+
})

Diff for: typings/eslint-plugin-vue/util-types/ast/ast.ts

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export type VNodeListenerMap = {
6262
| (V.VExpressionContainer & { expression: ES.Expression | null })
6363
| null
6464
}
65+
"VAttribute[directive=true][key.name.name='is']": V.VDirective
66+
"VAttribute[directive=true][key.name.name='is']:exit": V.VDirective
6567
"VAttribute[directive=true][key.name.name='model']": V.VDirective & {
6668
value:
6769
| (V.VExpressionContainer & { expression: ES.Expression | null })

0 commit comments

Comments
 (0)