Skip to content

Commit da4ea71

Browse files
armano2michalsnik
authored andcommitted
⭐️New: Add rule no-template-shadow. (#158)
* Add rule `no-template-shadow`. fixes #101 * fix/merge changes with master * Update tests suite, review suggestions * Add more test cases * Change no-template-shadow category to strongly-recommended
1 parent 341bccf commit da4ea71

File tree

4 files changed

+346
-1
lines changed

4 files changed

+346
-1
lines changed

docs/rules/no-template-shadow.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Disallow variable declarations from shadowing variables declared in the outer scope. (no-template-shadow)
2+
3+
`no-template-shadow` should report variable definitions of v-for directives or scope attributes if those shadows the variables in parent scopes.
4+
5+
## :book: Rule Details
6+
7+
This rule aims to eliminate shadowed variable declarations of v-for directives or scope attributes.
8+
9+
:-1: Examples of **incorrect** code for this rule:
10+
11+
```html
12+
<template>
13+
<div>
14+
<div v-for="i in 5">
15+
<div v-for="i in 10"></div>
16+
</div>
17+
</div>
18+
</template>
19+
```
20+
21+
```html
22+
<template>
23+
<div>
24+
<div v-for="i in 5"></div>
25+
</div>
26+
</template>
27+
<script>
28+
export default {
29+
data () {
30+
return {
31+
i: 10
32+
}
33+
}
34+
}
35+
</script>
36+
```
37+
38+
:+1: Examples of **correct** code for this rule:
39+
40+
```html
41+
<template>
42+
<div v-for="i in 5"></div>
43+
<div v-for="i in 5"></div>
44+
</template>
45+
<script>
46+
export default {
47+
computed: {
48+
f () { }
49+
}
50+
}
51+
</script>
52+
```
53+
54+
## :wrench: Options
55+
56+
Nothing.

lib/rules/no-template-shadow.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @fileoverview Disallow variable declarations from shadowing variables declared in the outer scope.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Rule Definition
15+
// ------------------------------------------------------------------------------
16+
17+
const GROUP_NAMES = ['props', 'computed', 'data', 'methods']
18+
19+
module.exports = {
20+
meta: {
21+
docs: {
22+
description: 'disallow variable declarations from shadowing variables declared in the outer scope',
23+
category: 'strongly-recommended',
24+
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.1/docs/rules/no-template-shadow.md'
25+
},
26+
fixable: null,
27+
schema: []
28+
},
29+
30+
create (context) {
31+
const jsVars = new Set()
32+
let scope = {
33+
parent: null,
34+
nodes: []
35+
}
36+
37+
// ----------------------------------------------------------------------
38+
// Public
39+
// ----------------------------------------------------------------------
40+
41+
return utils.defineTemplateBodyVisitor(context, {
42+
VElement (node) {
43+
scope = {
44+
parent: scope,
45+
nodes: scope.nodes.slice() // make copy
46+
}
47+
if (node.variables) {
48+
for (const variable of node.variables) {
49+
const varNode = variable.id
50+
const name = varNode.name
51+
if (scope.nodes.some(node => node.name === name) || jsVars.has(name)) {
52+
context.report({
53+
node: varNode,
54+
loc: varNode.loc,
55+
message: "Variable '{{name}}' is already declared in the upper scope.",
56+
data: {
57+
name
58+
}
59+
})
60+
} else {
61+
scope.nodes.push(varNode)
62+
}
63+
}
64+
}
65+
},
66+
'VElement:exit' (node) {
67+
scope = scope.parent
68+
}
69+
}, utils.executeOnVue(context, (obj) => {
70+
const properties = Array.from(utils.iterateProperties(obj, new Set(GROUP_NAMES)))
71+
for (const node of properties) {
72+
jsVars.add(node.name)
73+
}
74+
}))
75+
}
76+
}

lib/utils/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ module.exports = {
2626
*
2727
* @param {RuleContext} context The rule context to use parser services.
2828
* @param {Object} templateBodyVisitor The visitor to traverse the template body.
29-
* @param {Object} scriptVisitor The visitor to traverse the script.
29+
* @param {Object} [scriptVisitor] The visitor to traverse the script.
3030
* @returns {Object} The merged visitor.
3131
*/
3232
defineTemplateBodyVisitor (context, templateBodyVisitor, scriptVisitor) {

tests/lib/rules/no-template-shadow.js

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/**
2+
* @fileoverview Disallow variable declarations from shadowing variables declared in the outer scope.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const rule = require('../../../lib/rules/no-template-shadow')
12+
const RuleTester = require('eslint').RuleTester
13+
14+
// ------------------------------------------------------------------------------
15+
// Tests
16+
// ------------------------------------------------------------------------------
17+
18+
const ruleTester = new RuleTester({
19+
parser: 'vue-eslint-parser',
20+
parserOptions: {
21+
ecmaVersion: 2018,
22+
sourceType: 'module'
23+
}
24+
})
25+
26+
ruleTester.run('no-template-shadow', rule, {
27+
28+
valid: [
29+
'',
30+
'<template><div></div></template>',
31+
'<template><div v-for="i in 5"></div></template>',
32+
'<template><div v-for="i in 5"><div v-for="b in 5"></div></div></template>',
33+
'<template><div v-for="i in 5"></div><div v-for="i in 5"></div></template>',
34+
'<template> <ul v-for="i in 5"> <li> <span v-for="j in 10">{{i}},{{j}}</span> </li> </ul> </template>',
35+
{
36+
filename: 'test.vue',
37+
code: `<template>
38+
<div v-for="i in 5"></div>
39+
<div v-for="i in f"></div>
40+
<div v-for="i in 5"></div>
41+
</template>
42+
<script>
43+
export default {
44+
computed: {
45+
f () { }
46+
}
47+
}
48+
</script>`
49+
},
50+
{
51+
filename: 'test.vue',
52+
code: `<template>
53+
<div v-for="i in b" />
54+
<div v-for="b in c" />
55+
<div v-for="d in f" />
56+
</template>
57+
<script>
58+
export default {
59+
...a,
60+
data() {
61+
return {
62+
...b,
63+
c: [1, 2, 3]
64+
}
65+
},
66+
computed: {
67+
...d,
68+
e,
69+
['f']: [1, 2],
70+
}
71+
}
72+
</script>`
73+
}
74+
],
75+
76+
invalid: [
77+
{
78+
filename: 'test.vue',
79+
code: '<template><div v-for="i in 5"><div v-for="i in 5"></div></div></template>',
80+
errors: [{
81+
message: "Variable 'i' is already declared in the upper scope.",
82+
type: 'Identifier'
83+
}]
84+
},
85+
{
86+
filename: 'test.vue',
87+
code: `<template>
88+
<div v-for="i in 5"></div>
89+
</template>
90+
<script>
91+
export default {
92+
data: {
93+
i: 7
94+
}
95+
}
96+
</script>`,
97+
errors: [{
98+
message: "Variable 'i' is already declared in the upper scope.",
99+
type: 'Identifier',
100+
line: 2
101+
}]
102+
},
103+
{
104+
filename: 'test.vue',
105+
code: `<template>
106+
<div v-for="i in 5"></div>
107+
<div v-for="i in 5"></div>
108+
</template>
109+
<script>
110+
export default {
111+
data: {
112+
i: 7
113+
}
114+
}
115+
</script>`,
116+
errors: [{
117+
message: "Variable 'i' is already declared in the upper scope.",
118+
type: 'Identifier',
119+
line: 2
120+
}, {
121+
message: "Variable 'i' is already declared in the upper scope.",
122+
type: 'Identifier',
123+
line: 3
124+
}]
125+
},
126+
{
127+
filename: 'test.vue',
128+
code: `<template>
129+
<div v-for="i in 5">
130+
<div v-for="i in 5"></div>
131+
</div>
132+
</template>
133+
<script>
134+
export default {
135+
data: {
136+
i: 7
137+
}
138+
}
139+
</script>`,
140+
errors: [{
141+
message: "Variable 'i' is already declared in the upper scope.",
142+
type: 'Identifier',
143+
line: 2
144+
}, {
145+
message: "Variable 'i' is already declared in the upper scope.",
146+
type: 'Identifier',
147+
line: 3
148+
}]
149+
},
150+
{
151+
filename: 'test.vue',
152+
code: `<template>
153+
<div v-for="i in 5"></div>
154+
<div v-for="f in 5"></div>
155+
</template>
156+
<script>
157+
export default {
158+
computed: {
159+
i () { }
160+
},
161+
methods: {
162+
f () { }
163+
}
164+
}
165+
</script>`,
166+
errors: [{
167+
message: "Variable 'i' is already declared in the upper scope.",
168+
type: 'Identifier',
169+
line: 2
170+
}, {
171+
message: "Variable 'f' is already declared in the upper scope.",
172+
type: 'Identifier',
173+
line: 3
174+
}]
175+
},
176+
{
177+
filename: 'test.vue',
178+
code: `<template>
179+
<div v-for="i in c" />
180+
<div v-for="a in c" />
181+
<div v-for="b in c" />
182+
<div v-for="d in c" />
183+
<div v-for="e in f" />
184+
<div v-for="f in c" />
185+
</template>
186+
<script>
187+
export default {
188+
...a,
189+
data() {
190+
return {
191+
...b,
192+
c: [1, 2, 3]
193+
}
194+
},
195+
computed: {
196+
...d,
197+
e,
198+
['f']: [1, 2],
199+
}
200+
}
201+
</script>`,
202+
errors: [{
203+
message: "Variable 'e' is already declared in the upper scope.",
204+
type: 'Identifier',
205+
line: 6
206+
}, {
207+
message: "Variable 'f' is already declared in the upper scope.",
208+
type: 'Identifier',
209+
line: 7
210+
}]
211+
}
212+
]
213+
})

0 commit comments

Comments
 (0)