Skip to content

Commit 39c9df5

Browse files
armano2michalsnik
authored andcommitted
Add rules: no-dupe-keys and no-reserved-keys. (#88)
fixes #86
1 parent e42491f commit 39c9df5

9 files changed

+607
-3
lines changed

docs/rules/no-dupe-keys.md

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Prevents duplication of field names (no-dupe-keys)
2+
3+
This rule prevents to use duplicated names.
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+
`"groups"`: [] (default) array of additional groups to search for duplicates.
58+
59+
### Example:
60+
61+
```
62+
vue/no-dupe-keys: [2, {
63+
groups: ['asyncComputed']
64+
}]
65+
```
66+
67+
:-1: Examples of **incorrect** code for this configuration
68+
69+
```js
70+
export default {
71+
computed: {
72+
foo () {}
73+
},
74+
asyncComputed: {
75+
foo () {}
76+
}
77+
}
78+
```

docs/rules/no-reservered-keys.md

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Prevent overwrite reserved keys (no-reservered-keys)
2+
3+
This rule prevents to use reserved names from to avoid conflicts and unexpected behavior.
4+
5+
## Rule Details
6+
7+
:-1: Examples of **incorrect** code for this rule:
8+
9+
```js
10+
export default {
11+
props: {
12+
$el: String
13+
},
14+
computed: {
15+
$on: {
16+
get () {
17+
}
18+
}
19+
},
20+
data: {
21+
_foo: null
22+
},
23+
methods: {
24+
$nextTick () {
25+
}
26+
}
27+
}
28+
```
29+
30+
## :wrench: Options
31+
32+
This rule has an object option:
33+
34+
`"reserved"`: [] (default) array of dissalowed names inside `groups`.
35+
36+
`"groups"`: [] (default) array of additional groups to search for duplicates.
37+
38+
### Example:
39+
40+
```
41+
vue/no-dupe-keys: [2, {
42+
reserved: ['foo']
43+
}]
44+
```
45+
46+
:-1: Examples of **incorrect** code for this configuration
47+
48+
```js
49+
export default {
50+
computed: {
51+
foo () {}
52+
}
53+
}
54+
```

lib/rules/no-dupe-keys.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @fileoverview Prevents duplication of field names.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
const GROUP_NAMES = ['props', 'computed', 'data', 'methods']
14+
15+
function create (context) {
16+
const usedNames = []
17+
18+
const options = context.options[0] || {}
19+
const groups = new Set(GROUP_NAMES.concat(options.groups || []))
20+
21+
// ----------------------------------------------------------------------
22+
// Public
23+
// ----------------------------------------------------------------------
24+
25+
return utils.executeOnVue(context, (obj) => {
26+
const properties = utils.iterateProperties(obj, groups)
27+
for (const o of properties) {
28+
if (usedNames.indexOf(o.name) !== -1) {
29+
context.report({
30+
node: o.node,
31+
message: "Duplicated key '{{name}}'.",
32+
data: {
33+
name: o.name
34+
}
35+
})
36+
}
37+
usedNames.push(o.name)
38+
}
39+
})
40+
}
41+
42+
// ------------------------------------------------------------------------------
43+
// Rule Definition
44+
// ------------------------------------------------------------------------------
45+
46+
module.exports = {
47+
meta: {
48+
docs: {
49+
description: 'Prevents duplication of field names.',
50+
category: 'Possible Errors',
51+
recommended: false
52+
},
53+
fixable: null, // or "code" or "whitespace"
54+
schema: [
55+
{
56+
type: 'object',
57+
properties: {
58+
groups: {
59+
type: 'array'
60+
}
61+
},
62+
additionalProperties: false
63+
}
64+
]
65+
},
66+
67+
create
68+
}

lib/rules/no-reservered-keys.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @fileoverview Prevent overwrite reserved keys
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
const RESERVED_KEYS = require('../utils/vue-reserved.json')
14+
const GROUP_NAMES = ['props', 'computed', 'data', 'methods']
15+
16+
function create (context) {
17+
const options = context.options[0] || {}
18+
const reservedKeys = new Set(RESERVED_KEYS.concat(options.reserved || []))
19+
const groups = new Set(GROUP_NAMES.concat(options.groups || []))
20+
21+
// ----------------------------------------------------------------------
22+
// Public
23+
// ----------------------------------------------------------------------
24+
25+
return utils.executeOnVue(context, (obj) => {
26+
const properties = utils.iterateProperties(obj, groups)
27+
for (const o of properties) {
28+
if (o.groupName === 'data' && o.name[0] === '_') {
29+
context.report({
30+
node: o.node,
31+
message: "Keys starting with with '_' are reserved in '{{name}}' group.",
32+
data: {
33+
name: o.name
34+
}
35+
})
36+
} else if (reservedKeys.has(o.name)) {
37+
context.report({
38+
node: o.node,
39+
message: "Key '{{name}}' is reserved.",
40+
data: {
41+
name: o.name
42+
}
43+
})
44+
}
45+
}
46+
})
47+
}
48+
49+
module.exports = {
50+
meta: {
51+
docs: {
52+
description: 'Prevent overwrite reserved keys.',
53+
category: 'Possible Errors',
54+
recommended: false
55+
},
56+
fixable: null, // or "code" or "whitespace"
57+
schema: [
58+
{
59+
type: 'object',
60+
properties: {
61+
reserved: {
62+
type: 'array'
63+
},
64+
groups: {
65+
type: 'array'
66+
}
67+
},
68+
additionalProperties: false
69+
}
70+
]
71+
},
72+
73+
create
74+
}

lib/utils/index.js

+67
Original file line numberDiff line numberDiff line change
@@ -453,5 +453,72 @@ module.exports = {
453453
cb(node.arguments.slice(-1)[0])
454454
}
455455
}
456+
},
457+
458+
/**
459+
* Return generator with all properties
460+
* @param {ASTNode} node Node to check
461+
* @param {string} groupName Name of parent group
462+
*/
463+
* iterateProperties (node, groups) {
464+
const nodes = node.properties.filter(p => p.type === 'Property' && groups.has(this.getStaticPropertyName(p.key)))
465+
for (const item of nodes) {
466+
const name = this.getStaticPropertyName(item.key)
467+
if (item.value.type === 'ArrayExpression') {
468+
yield * this.iterateArrayExpression(item.value, name)
469+
} else if (item.value.type === 'ObjectExpression') {
470+
yield * this.iterateObjectExpression(item.value, name)
471+
} else if (item.value.type === 'FunctionExpression') {
472+
yield * this.iterateFunctionExpression(item.value, name)
473+
}
474+
}
475+
},
476+
477+
/**
478+
* Return generator with all elements inside ArrayExpression
479+
* @param {ASTNode} node Node to check
480+
* @param {string} groupName Name of parent group
481+
*/
482+
* iterateArrayExpression (node, groupName) {
483+
assert(node.type === 'ArrayExpression')
484+
for (const item of node.elements) {
485+
const name = this.getStaticPropertyName(item)
486+
if (name) {
487+
const obj = { name, groupName, node: item }
488+
yield obj
489+
}
490+
}
491+
},
492+
493+
/**
494+
* Return generator with all elements inside ObjectExpression
495+
* @param {ASTNode} node Node to check
496+
* @param {string} groupName Name of parent group
497+
*/
498+
* iterateObjectExpression (node, groupName) {
499+
assert(node.type === 'ObjectExpression')
500+
for (const item of node.properties) {
501+
const name = this.getStaticPropertyName(item)
502+
if (name) {
503+
const obj = { name, groupName, node: item.key }
504+
yield obj
505+
}
506+
}
507+
},
508+
509+
/**
510+
* Return generator with all elements inside FunctionExpression
511+
* @param {ASTNode} node Node to check
512+
* @param {string} groupName Name of parent group
513+
*/
514+
* iterateFunctionExpression (node, groupName) {
515+
assert(node.type === 'FunctionExpression')
516+
if (node.body.type === 'BlockStatement') {
517+
for (const item of node.body.body) {
518+
if (item.type === 'ReturnStatement' && item.argument.type === 'ObjectExpression') {
519+
yield * this.iterateObjectExpression(item.argument, groupName)
520+
}
521+
}
522+
}
456523
}
457524
}

lib/utils/vue-reserved.json

+4
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+
]

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)