Skip to content

Commit af90fed

Browse files
authored
Improved to not report that a value is required when parsing error, for vue/valid-v-bind-sync, vue/valid-v-bind, vue/valid-v-else-if, vue/valid-v-for, vue/valid-v-html, vue/valid-v-if, vue/valid-v-model, vue/valid-v-on, vue/valid-v-show, vue/valid-v-slot and vue/valid-v-text rules. (#1184)
1 parent 50bf17a commit af90fed

28 files changed

+426
-84
lines changed

lib/rules/valid-v-bind-sync.js

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ function isValidElement(node) {
3737
* @returns {boolean} `true` if the node can be LHS.
3838
*/
3939
function isLhs(node) {
40-
return (
41-
Boolean(node) &&
42-
(node.type === 'Identifier' || node.type === 'MemberExpression')
43-
)
40+
return node.type === 'Identifier' || node.type === 'MemberExpression'
4441
}
4542

4643
// ------------------------------------------------------------------------------
@@ -85,30 +82,32 @@ module.exports = {
8582
})
8683
}
8784

88-
if (node.value) {
89-
if (!isLhs(node.value.expression)) {
85+
if (!node.value || !node.value.expression) {
86+
return
87+
}
88+
89+
if (!isLhs(node.value.expression)) {
90+
context.report({
91+
node,
92+
loc: node.loc,
93+
messageId: 'unexpectedNonLhsExpression'
94+
})
95+
}
96+
97+
for (const reference of node.value.references) {
98+
const id = reference.id
99+
if (id.parent.type !== 'VExpressionContainer') {
100+
continue
101+
}
102+
const variable = reference.variable
103+
if (variable) {
90104
context.report({
91105
node,
92106
loc: node.loc,
93-
messageId: 'unexpectedNonLhsExpression'
107+
messageId: 'unexpectedUpdateIterationVariable',
108+
data: { varName: id.name }
94109
})
95110
}
96-
97-
for (const reference of node.value.references) {
98-
const id = reference.id
99-
if (id.parent.type !== 'VExpressionContainer') {
100-
continue
101-
}
102-
const variable = reference.variable
103-
if (variable) {
104-
context.report({
105-
node,
106-
loc: node.loc,
107-
messageId: 'unexpectedUpdateIterationVariable',
108-
data: { varName: id.name }
109-
})
110-
}
111-
}
112111
}
113112
}
114113
})

lib/rules/valid-v-bind.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ module.exports = {
4848
}
4949
}
5050

51-
if (!utils.hasAttributeValue(node)) {
51+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
5252
context.report({
5353
node,
5454
loc: node.loc,

lib/rules/valid-v-else-if.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ module.exports = {
7070
message: "'v-else-if' directives require no modifier."
7171
})
7272
}
73-
if (!utils.hasAttributeValue(node)) {
73+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
7474
context.report({
7575
node,
7676
loc: node.loc,

lib/rules/valid-v-else.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ module.exports = {
7070
message: "'v-else' directives require no modifier."
7171
})
7272
}
73-
if (utils.hasAttributeValue(node)) {
73+
if (node.value) {
7474
context.report({
7575
node,
7676
loc: node.loc,

lib/rules/valid-v-for.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ module.exports = {
137137
message: "'v-for' directives require no modifier."
138138
})
139139
}
140-
if (!utils.hasAttributeValue(node)) {
140+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
141141
context.report({
142142
node,
143143
loc: node.loc,

lib/rules/valid-v-html.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module.exports = {
4444
message: "'v-html' directives require no modifier."
4545
})
4646
}
47-
if (!utils.hasAttributeValue(node)) {
47+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
4848
context.report({
4949
node,
5050
loc: node.loc,

lib/rules/valid-v-if.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ module.exports = {
6262
message: "'v-if' directives require no modifier."
6363
})
6464
}
65-
if (!utils.hasAttributeValue(node)) {
65+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
6666
context.report({
6767
node,
6868
loc: node.loc,

lib/rules/valid-v-model.js

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,7 @@ function isValidElement(node) {
4242
* @returns {boolean} `true` if the node can be LHS.
4343
*/
4444
function isLhs(node) {
45-
return (
46-
node != null &&
47-
(node.type === 'Identifier' || node.type === 'MemberExpression')
48-
)
45+
return node.type === 'Identifier' || node.type === 'MemberExpression'
4946
}
5047

5148
/**
@@ -133,40 +130,43 @@ module.exports = {
133130
}
134131
}
135132

136-
if (!utils.hasAttributeValue(node)) {
133+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
137134
context.report({
138135
node,
139136
loc: node.loc,
140137
message: "'v-model' directives require that attribute value."
141138
})
139+
return
140+
}
141+
if (!node.value.expression) {
142+
// Parsing error
143+
return
142144
}
143-
if (node.value) {
144-
if (!isLhs(node.value.expression)) {
145+
if (!isLhs(node.value.expression)) {
146+
context.report({
147+
node,
148+
loc: node.loc,
149+
message:
150+
"'v-model' directives require the attribute value which is valid as LHS."
151+
})
152+
}
153+
154+
for (const reference of node.value.references) {
155+
const id = reference.id
156+
if (id.parent.type !== 'VExpressionContainer') {
157+
continue
158+
}
159+
160+
const variable = getVariable(id.name, element)
161+
if (variable != null) {
145162
context.report({
146163
node,
147164
loc: node.loc,
148165
message:
149-
"'v-model' directives require the attribute value which is valid as LHS."
166+
"'v-model' directives cannot update the iteration variable '{{varName}}' itself.",
167+
data: { varName: id.name }
150168
})
151169
}
152-
153-
for (const reference of node.value.references) {
154-
const id = reference.id
155-
if (id.parent.type !== 'VExpressionContainer') {
156-
continue
157-
}
158-
159-
const variable = getVariable(id.name, element)
160-
if (variable != null) {
161-
context.report({
162-
node,
163-
loc: node.loc,
164-
message:
165-
"'v-model' directives cannot update the iteration variable '{{varName}}' itself.",
166-
data: { varName: id.name }
167-
})
168-
}
169-
}
170170
}
171171
}
172172
})

lib/rules/valid-v-on.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,30 @@ module.exports = {
108108
}
109109

110110
if (
111-
!utils.hasAttributeValue(node) &&
111+
(!node.value || !node.value.expression) &&
112112
!node.key.modifiers.some((modifier) =>
113113
VERB_MODIFIERS.has(modifier.name)
114114
)
115115
) {
116-
if (node.value && sourceCode.getText(node.value.expression)) {
117-
const value = sourceCode.getText(node.value)
118-
context.report({
119-
node,
120-
loc: node.loc,
121-
message:
122-
'Avoid using JavaScript keyword as "v-on" value: {{value}}.',
123-
data: { value }
124-
})
116+
if (node.value && !utils.isEmptyValueDirective(node, context)) {
117+
const valueText = sourceCode.getText(node.value)
118+
let innerText = valueText
119+
if (
120+
(valueText[0] === '"' || valueText[0] === "'") &&
121+
valueText[0] === valueText[valueText.length - 1]
122+
) {
123+
// quoted
124+
innerText = valueText.slice(1, -1)
125+
}
126+
if (/^\w+$/.test(innerText)) {
127+
context.report({
128+
node,
129+
loc: node.loc,
130+
message:
131+
'Avoid using JavaScript keyword as "v-on" value: {{value}}.',
132+
data: { value: valueText }
133+
})
134+
}
125135
} else {
126136
context.report({
127137
node,

lib/rules/valid-v-show.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module.exports = {
4444
message: "'v-show' directives require no modifier."
4545
})
4646
}
47-
if (!utils.hasAttributeValue(node)) {
47+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
4848
context.report({
4949
node,
5050
loc: node.loc,

lib/rules/valid-v-slot.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,9 @@ module.exports = {
264264
if (
265265
ownerElement === element &&
266266
isDefaultSlot &&
267-
!utils.hasAttributeValue(node)
267+
(!node.value ||
268+
utils.isEmptyValueDirective(node, context) ||
269+
utils.isEmptyExpressionValueDirective(node, context))
268270
) {
269271
context.report({
270272
node,

lib/rules/valid-v-text.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module.exports = {
4444
message: "'v-text' directives require no modifier."
4545
})
4646
}
47-
if (!utils.hasAttributeValue(node)) {
47+
if (!node.value || utils.isEmptyValueDirective(node, context)) {
4848
context.report({
4949
node,
5050
loc: node.loc,

lib/utils/index.js

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -290,16 +290,73 @@ module.exports = {
290290
},
291291

292292
/**
293-
* Check whether the given attribute has their attribute value.
294-
* @param {ASTNode} node The attribute node to check.
295-
* @returns {boolean} `true` if the attribute has their value.
293+
* Check whether the given directive attribute has their empty value (`=""`).
294+
* @param {ASTNode} node The directive attribute node to check.
295+
* @param {RuleContext} context The rule context to use parser services.
296+
* @returns {boolean} `true` if the directive attribute has their empty value (`=""`).
296297
*/
297-
hasAttributeValue (node) {
298+
isEmptyValueDirective (node, context) {
298299
assert(node && node.type === 'VAttribute')
299-
return (
300-
node.value != null &&
301-
(node.value.expression != null || node.value.syntaxError != null)
302-
)
300+
if (node.value == null) {
301+
return false
302+
}
303+
if (node.value.expression != null) {
304+
return false
305+
}
306+
307+
let valueText = context.getSourceCode().getText(node.value)
308+
if ((valueText[0] === '"' || valueText[0] === "'") && valueText[0] === valueText[valueText.length - 1]) {
309+
// quoted
310+
valueText = valueText.slice(1, -1)
311+
}
312+
if (!valueText) {
313+
// empty
314+
return true
315+
}
316+
return false
317+
},
318+
319+
/**
320+
* Check whether the given directive attribute has their empty expression value (e.g. `=" "`, `="/* */"`).
321+
* @param {ASTNode} node The directive attribute node to check.
322+
* @param {RuleContext} context The rule context to use parser services.
323+
* @returns {boolean} `true` if the directive attribute has their empty expression value.
324+
*/
325+
isEmptyExpressionValueDirective (node, context) {
326+
assert(node && node.type === 'VAttribute')
327+
if (node.value == null) {
328+
return false
329+
}
330+
if (node.value.expression != null) {
331+
return false
332+
}
333+
334+
const valueNode = node.value
335+
const tokenStore = context.parserServices.getTemplateBodyTokenStore()
336+
let quote1 = null
337+
let quote2 = null
338+
// `node.value` may be only comments, so cannot get the correct tokens with `tokenStore.getTokens(node.value)`.
339+
for (const token of tokenStore.getTokens(node)) {
340+
if (token.range[1] <= valueNode.range[0]) {
341+
continue
342+
}
343+
if (valueNode.range[1] <= token.range[0]) {
344+
// empty
345+
return true
346+
}
347+
if (!quote1 && token.type === 'Punctuator' && (token.value === '"' || token.value === "'")) {
348+
quote1 = token
349+
continue
350+
}
351+
if (!quote2 && quote1 && token.type === 'Punctuator' && (token.value === quote1.value)) {
352+
quote2 = token
353+
continue
354+
}
355+
// not empty
356+
return false
357+
}
358+
// empty
359+
return true
303360
},
304361

305362
/**

tests/lib/rules/valid-v-bind-sync.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,21 @@ tester.run('valid-v-bind-sync', rule, {
130130
{
131131
filename: 'test.vue',
132132
code: '<template><MyComponent :foo.sync.unknown="foo" /></template>'
133+
},
134+
// parsing error
135+
{
136+
filename: 'parsing-error.vue',
137+
code: '<template><MyComponent :foo.sync="." /></template>'
138+
},
139+
// comment value (parsing error)
140+
{
141+
filename: 'comment-value.vue',
142+
code: '<template><MyComponent :foo.sync="/**/" /></template>'
143+
},
144+
// empty value (valid-v-bind)
145+
{
146+
filename: 'empty-value.vue',
147+
code: '<template><MyComponent :foo.sync="" /></template>'
133148
}
134149
],
135150
invalid: [

tests/lib/rules/valid-v-bind.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ tester.run('valid-v-bind', rule, {
7474
{
7575
filename: 'test.vue',
7676
code: "<template><input v-bind='$attrs' /></template>"
77+
},
78+
// parsing error
79+
{
80+
filename: 'parsing-error.vue',
81+
code: '<template><MyComponent :foo="." /></template>'
82+
},
83+
// comment value (parsing error)
84+
{
85+
filename: 'comment-value.vue',
86+
code: '<template><MyComponent :foo="/**/" /></template>'
7787
}
7888
],
7989
invalid: [
@@ -91,6 +101,12 @@ tester.run('valid-v-bind', rule, {
91101
filename: 'test.vue',
92102
code: "<template><div :aaa.unknown='bbb'></div></template>",
93103
errors: ["'v-bind' directives don't support the modifier 'unknown'."]
104+
},
105+
// empty value
106+
{
107+
filename: 'empty-value.vue',
108+
code: '<template><MyComponent :foo="" /></template>',
109+
errors: ["'v-bind' directives require an attribute value."]
94110
}
95111
]
96112
})

0 commit comments

Comments
 (0)