Skip to content

Commit cecd4bf

Browse files
committed
Add no-duplicate-field-names rule.
1 parent 1d00dd6 commit cecd4bf

File tree

4 files changed

+409
-0
lines changed

4 files changed

+409
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Prevent duplicate field names (no-duplicate-field-names)
2+
3+
This rule prevents to use duplicated names and reserved/internal names from vue.
4+
5+
## :book: Rule Details
6+
7+
This rule is aimed at preventing duplicated property names.
8+
9+
:-1: Examples of **incorrect** code for this rule:
10+
11+
```js
12+
export default {
13+
props: {
14+
foo: String
15+
},
16+
computed: {
17+
foo: {
18+
get () {
19+
}
20+
}
21+
},
22+
data: {
23+
foo: null
24+
},
25+
methods: {
26+
foo () {
27+
}
28+
}
29+
}
30+
```
31+
32+
:+1: Examples of **correct** code for this rule:
33+
34+
```js
35+
export default {
36+
props: ['foo'],
37+
computed: {
38+
bar () {
39+
}
40+
},
41+
data () {
42+
return {
43+
dat: null
44+
}
45+
},
46+
methods: {
47+
test () {
48+
}
49+
}
50+
}
51+
```
52+
53+
## :wrench: Options
54+
55+
This rule has an object option:
56+
57+
`"reserved"`: [] (default) array of dissalowed names inside `scopes`.
58+
59+
`"scope"`: [] (default) array of additional scopes to search for duplicates.
60+
61+
### Example 1:
62+
63+
```
64+
vue/no-duplicate-field-names: [2, {
65+
reserved: ['foo']
66+
}]
67+
```
68+
69+
:-1: Examples of **incorrect** code for this configuration
70+
71+
```js
72+
export default {
73+
computed: {
74+
foo () {}
75+
}
76+
}
77+
```
78+
79+
### Example 2:
80+
81+
```
82+
vue/no-duplicate-field-names: [2, {
83+
scope: ['asyncComputed']
84+
}]
85+
```
86+
87+
:-1: Examples of **incorrect** code for this configuration
88+
89+
```js
90+
export default {
91+
computed: {
92+
foo () {}
93+
},
94+
asyncComputed: {
95+
foo () {}
96+
}
97+
}
98+
```

lib/rules/no-duplicate-field-names.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* @fileoverview Prevent duplicate field names
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
const assert = require('assert')
9+
10+
const RESERVER_NAMES = new Set(require('../utils/vue-reserved.json'))
11+
const SCOPE_NAMES = new Set(['props', 'computed', 'data', 'methods'])
12+
13+
function create (context) {
14+
const usedNames = []
15+
const reservedNames = RESERVER_NAMES
16+
const scopeNames = SCOPE_NAMES
17+
18+
const options = context.options[0] || {}
19+
if (options.reserved) {
20+
options.reserved.forEach(name => {
21+
reservedNames.add(name)
22+
})
23+
}
24+
if (options.scope) {
25+
options.scope.forEach(name => {
26+
scopeNames.add(name)
27+
})
28+
}
29+
30+
// ----------------------------------------------------------------------
31+
// Helpers
32+
// ----------------------------------------------------------------------
33+
34+
function checkUsedNames (name, node) {
35+
assert(typeof name === 'string')
36+
37+
if (reservedNames.has(name)) {
38+
context.report({
39+
node: node,
40+
message: 'Reserved key \'{{name}}\'.',
41+
data: {
42+
name: name
43+
}
44+
})
45+
} else if (usedNames.indexOf(name) !== -1) {
46+
context.report({
47+
node: node,
48+
message: 'Duplicate key \'{{name}}\'.',
49+
data: {
50+
name: name
51+
}
52+
})
53+
}
54+
usedNames.push(name)
55+
}
56+
57+
function checkArrayExpression (node) {
58+
node.elements.forEach(item => {
59+
if (item.type === 'Literal') {
60+
checkUsedNames(item.value, item)
61+
}
62+
})
63+
}
64+
65+
function checkObjectExpression (node) {
66+
node.properties.forEach(item => {
67+
if (item.type === 'Property' && item.key.type === 'Identifier') {
68+
checkUsedNames(item.key.name, item.key)
69+
}
70+
})
71+
}
72+
73+
function checkFunctionExpression (node) {
74+
if (node.body.type === 'BlockStatement') {
75+
node.body.body.forEach(item => {
76+
if (item.type === 'ReturnStatement' && item.argument.type === 'ObjectExpression') {
77+
checkObjectExpression(item.argument)
78+
}
79+
})
80+
}
81+
}
82+
83+
// ----------------------------------------------------------------------
84+
// Public
85+
// ----------------------------------------------------------------------
86+
87+
return utils.executeOnVueComponent(context, (obj) => {
88+
obj.properties
89+
.filter(p => p.type === 'Property' && p.key.type === 'Identifier' && scopeNames.has(p.key.name))
90+
.forEach(node => {
91+
if (node.value.type === 'ArrayExpression') {
92+
checkArrayExpression(node.value)
93+
} else if (node.value.type === 'ObjectExpression') {
94+
checkObjectExpression(node.value)
95+
} else if (node.value.type === 'FunctionExpression') {
96+
checkFunctionExpression(node.value)
97+
}
98+
})
99+
})
100+
}
101+
102+
// ------------------------------------------------------------------------------
103+
// Rule Definition
104+
// ------------------------------------------------------------------------------
105+
106+
module.exports = {
107+
meta: {
108+
docs: {
109+
description: 'Prevent duplicate field names',
110+
category: 'Possible Errors',
111+
recommended: false
112+
},
113+
fixable: null, // or "code" or "whitespace"
114+
schema: [
115+
{
116+
type: 'object',
117+
properties: {
118+
reserved: {
119+
type: 'array'
120+
},
121+
scope: {
122+
type: 'array'
123+
}
124+
},
125+
additionalProperties: false
126+
}
127+
]
128+
},
129+
130+
create
131+
}

lib/utils/vue-reserved.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
"$data", "$props", "$el", "$options", "$parent", "$root", "$children", "$slots", "$scopedSlots", "$refs", "$isServer", "$attrs", "$listeners",
3+
"$watch", "$set", "$delete", "$on", "$once", "$off", "$emit", "$mount", "$forceUpdate", "$nextTick", "$destroy"
4+
]

0 commit comments

Comments
 (0)