Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b7b2393

Browse files
Add vue/valid-v-memo rule (#1596)
* Add `vue/valid-v-memo` rule * update
1 parent 3faf520 commit b7b2393

File tree

6 files changed

+343
-0
lines changed

6 files changed

+343
-0
lines changed

‎docs/rules/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ For example:
338338
| [vue/valid-define-emits](./valid-define-emits.md) | enforce valid `defineEmits` compiler macro | |
339339
| [vue/valid-define-props](./valid-define-props.md) | enforce valid `defineProps` compiler macro | |
340340
| [vue/valid-next-tick](./valid-next-tick.md) | enforce valid `nextTick` function calls | :wrench: |
341+
| [vue/valid-v-memo](./valid-v-memo.md) | enforce valid `v-memo` directives | |
341342

342343
### Extension Rules
343344

‎docs/rules/valid-v-memo.md‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/valid-v-memo
5+
description: enforce valid `v-memo` directives
6+
---
7+
# vue/valid-v-memo
8+
9+
> enforce valid `v-memo` directives
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
13+
This rule checks whether every `v-memo` directive is valid.
14+
15+
## :book: Rule Details
16+
17+
This rule reports `v-memo` directives in the following cases:
18+
19+
- The directive has that argument. E.g. `<div v-memo:aaa></div>`
20+
- The directive has that modifier. E.g. `<div v-memo.bbb></div>`
21+
- The directive does not have that attribute value. E.g. `<div v-memo></div>`
22+
- The attribute value of the directive is definitely not array. E.g. `<div v-memo="{x}"></div>`
23+
- The directive was used inside v-for. E.g. `<div v-for="i in items"><div v-memo="[i]" /></div>`
24+
25+
<eslint-code-block :rules="{'vue/valid-v-memo': ['error']}">
26+
27+
```vue
28+
<template>
29+
<!-- ✓ GOOD -->
30+
<div v-memo="[x]"/>
31+
32+
<!-- ✗ BAD -->
33+
<div v-memo/>
34+
<div v-memo:aaa="[x]"/>
35+
<div v-memo.bbb="[x]"/>
36+
<div v-memo="{x}"/>
37+
<div v-for="i in items">
38+
<div v-memo="[i]" />
39+
</div>
40+
</template>
41+
```
42+
43+
</eslint-code-block>
44+
45+
::: warning Note
46+
This rule does not check syntax errors in directives because it's checked by [vue/no-parsing-error] rule.
47+
:::
48+
49+
## :wrench: Options
50+
51+
Nothing.
52+
53+
## :couple: Related Rules
54+
55+
- [vue/no-parsing-error]
56+
57+
[vue/no-parsing-error]: ./no-parsing-error.md
58+
59+
## :books: Further Reading
60+
61+
- [API - v-memo](https://v3.vuejs.org/api/directives.html#v-memo)
62+
63+
## :mag: Implementation
64+
65+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-memo.js)
66+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-memo.js)

‎lib/index.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ module.exports = {
188188
'valid-v-html': require('./rules/valid-v-html'),
189189
'valid-v-if': require('./rules/valid-v-if'),
190190
'valid-v-is': require('./rules/valid-v-is'),
191+
'valid-v-memo': require('./rules/valid-v-memo'),
191192
'valid-v-model': require('./rules/valid-v-model'),
192193
'valid-v-on': require('./rules/valid-v-on'),
193194
'valid-v-once': require('./rules/valid-v-once'),

‎lib/rules/valid-v-memo.js‎

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* @author Yosuke Ota <https://github.com/ota-meshi>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Rule Definition
15+
// ------------------------------------------------------------------------------
16+
17+
module.exports = {
18+
meta: {
19+
type: 'problem',
20+
docs: {
21+
description: 'enforce valid `v-memo` directives',
22+
// TODO Switch to `vue3-essential` in the major version.
23+
// categories: ['vue3-essential'],
24+
categories: undefined,
25+
url: 'https://eslint.vuejs.org/rules/valid-v-memo.html'
26+
},
27+
fixable: null,
28+
schema: [],
29+
messages: {
30+
unexpectedArgument: "'v-memo' directives require no argument.",
31+
unexpectedModifier: "'v-memo' directives require no modifier.",
32+
expectedValue: "'v-memo' directives require that attribute value.",
33+
expectedArray:
34+
"'v-memo' directives require the attribute value to be an array.",
35+
insideVFor: "'v-memo' directive does not work inside 'v-for'."
36+
}
37+
},
38+
/** @param {RuleContext} context */
39+
create(context) {
40+
/** @type {VElement | null} */
41+
let vForElement = null
42+
return utils.defineTemplateBodyVisitor(context, {
43+
VElement(node) {
44+
if (!vForElement && utils.hasDirective(node, 'for')) {
45+
vForElement = node
46+
}
47+
},
48+
'VElement:exit'(node) {
49+
if (vForElement === node) {
50+
vForElement = null
51+
}
52+
},
53+
/** @param {VDirective} node */
54+
"VAttribute[directive=true][key.name.name='memo']"(node) {
55+
if (vForElement && vForElement !== node.parent.parent) {
56+
context.report({
57+
node: node.key,
58+
messageId: 'insideVFor'
59+
})
60+
}
61+
if (node.key.argument) {
62+
context.report({
63+
node: node.key.argument,
64+
messageId: 'unexpectedArgument'
65+
})
66+
}
67+
if (node.key.modifiers.length > 0) {
68+
context.report({
69+
node,
70+
loc: {
71+
start: node.key.modifiers[0].loc.start,
72+
end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
73+
},
74+
messageId: 'unexpectedModifier'
75+
})
76+
}
77+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
78+
context.report({
79+
node,
80+
messageId: 'expectedValue'
81+
})
82+
return
83+
}
84+
if (!node.value.expression) {
85+
return
86+
}
87+
const expressions = [node.value.expression]
88+
let expression
89+
while ((expression = expressions.pop())) {
90+
if (
91+
expression.type === 'ObjectExpression' ||
92+
expression.type === 'ClassExpression' ||
93+
expression.type === 'ArrowFunctionExpression' ||
94+
expression.type === 'FunctionExpression' ||
95+
expression.type === 'Literal' ||
96+
expression.type === 'TemplateLiteral' ||
97+
expression.type === 'UnaryExpression' ||
98+
expression.type === 'BinaryExpression' ||
99+
expression.type === 'UpdateExpression'
100+
) {
101+
context.report({
102+
node: expression,
103+
messageId: 'expectedArray'
104+
})
105+
} else if (expression.type === 'AssignmentExpression') {
106+
expressions.push(expression.right)
107+
} else if (expression.type === 'TSAsExpression') {
108+
expressions.push(expression.expression)
109+
} else if (expression.type === 'SequenceExpression') {
110+
expressions.push(
111+
expression.expressions[expression.expressions.length - 1]
112+
)
113+
} else if (expression.type === 'ConditionalExpression') {
114+
expressions.push(expression.consequent, expression.alternate)
115+
}
116+
}
117+
}
118+
})
119+
}
120+
}

‎tests/lib/rules/valid-v-memo.js‎

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* @author Yosuke Ota <https://github.com/ota-meshi>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const RuleTester = require('eslint').RuleTester
12+
const rule = require('../../../lib/rules/valid-v-memo')
13+
14+
// ------------------------------------------------------------------------------
15+
// Tests
16+
// ------------------------------------------------------------------------------
17+
18+
const tester = new RuleTester({
19+
parser: require.resolve('vue-eslint-parser'),
20+
parserOptions: { ecmaVersion: 2021 }
21+
})
22+
23+
tester.run('valid-v-memo', rule, {
24+
valid: [
25+
{
26+
filename: 'test.js',
27+
code: 'test'
28+
},
29+
{
30+
filename: 'test.vue',
31+
code: ''
32+
},
33+
{
34+
filename: 'test.vue',
35+
code: '<template><div v-memo="[x]"></div></template>'
36+
},
37+
{
38+
filename: 'test.vue',
39+
code: '<template><div v-memo="x"></div></template>'
40+
},
41+
{
42+
filename: 'test.vue',
43+
code: '<template><div v-memo="x?y:z"></div></template>'
44+
},
45+
// parsing error
46+
{
47+
filename: 'parsing-error.vue',
48+
code: '<template><div v-memo="." /></template>'
49+
},
50+
// comment value (parsing error)
51+
{
52+
filename: 'parsing-error.vue',
53+
code: '<template><div v-memo="/**/" /></template>'
54+
},
55+
// v-for
56+
{
57+
filename: 'test.vue',
58+
code: '<template><div v-for="i in items" v-memo="[x]"></div></template>'
59+
}
60+
],
61+
invalid: [
62+
{
63+
filename: 'test.vue',
64+
code: '<template><div v-memo:aaa="x"></div></template>',
65+
errors: ["'v-memo' directives require no argument."]
66+
},
67+
{
68+
filename: 'test.vue',
69+
code: '<template><div v-memo.aaa="x"></div></template>',
70+
errors: ["'v-memo' directives require no modifier."]
71+
},
72+
{
73+
filename: 'test.vue',
74+
code: '<template><div v-memo></div></template>',
75+
errors: ["'v-memo' directives require that attribute value."]
76+
},
77+
// empty value
78+
{
79+
filename: 'empty-value.vue',
80+
code: '<template><div v-memo="" /></template>',
81+
errors: ["'v-memo' directives require that attribute value."]
82+
},
83+
{
84+
filename: 'test.vue',
85+
code: `
86+
<template>
87+
<div v-memo="{x}" />
88+
<div v-memo="a ? {b}: c+d" />
89+
<div v-memo="(a,{b},c(),d+1)" />
90+
<div v-memo="()=>42" />
91+
<div v-memo="a=42" />
92+
</template>`,
93+
errors: [
94+
{
95+
message:
96+
"'v-memo' directives require the attribute value to be an array.",
97+
line: 3,
98+
column: 22
99+
},
100+
{
101+
message:
102+
"'v-memo' directives require the attribute value to be an array.",
103+
line: 4,
104+
column: 26
105+
},
106+
{
107+
message:
108+
"'v-memo' directives require the attribute value to be an array.",
109+
line: 4,
110+
column: 31
111+
},
112+
{
113+
message:
114+
"'v-memo' directives require the attribute value to be an array.",
115+
line: 5,
116+
column: 33
117+
},
118+
{
119+
message:
120+
"'v-memo' directives require the attribute value to be an array.",
121+
line: 6,
122+
column: 22
123+
},
124+
{
125+
message:
126+
"'v-memo' directives require the attribute value to be an array.",
127+
line: 7,
128+
column: 24
129+
}
130+
]
131+
},
132+
// v-for
133+
{
134+
filename: 'test.vue',
135+
code: `<template><div v-for="i in items"><div v-memo="[x]" /></div></template>`,
136+
errors: [
137+
{
138+
message: "'v-memo' directive does not work inside 'v-for'.",
139+
line: 1,
140+
column: 40
141+
}
142+
]
143+
}
144+
]
145+
})

‎typings/eslint-plugin-vue/util-types/ast/ast.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ export type VNodeListenerMap = {
7474
| (V.VExpressionContainer & { expression: ES.Expression | null })
7575
| null
7676
}
77+
"VAttribute[directive=true][key.name.name='memo']": V.VDirective & {
78+
value:
79+
| (V.VExpressionContainer & { expression: ES.Expression | null })
80+
| null
81+
}
82+
"VAttribute[directive=true][key.name.name='memo']:exit": V.VDirective & {
83+
value:
84+
| (V.VExpressionContainer & { expression: ES.Expression | null })
85+
| null
86+
}
7787
"VAttribute[directive=true][key.name.name='on']": V.VDirective & {
7888
value:
7989
| (V.VExpressionContainer & {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /