Skip to content

Commit 270754e

Browse files
committed
Add rules: no-dupe-keys and no-reserved-keys.
fixes #86
1 parent 3361366 commit 270754e

File tree

8 files changed

+646
-0
lines changed

8 files changed

+646
-0
lines changed

docs/rules/no-dupe-keys.md

Lines changed: 78 additions & 0 deletions
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

Lines changed: 54 additions & 0 deletions
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

Lines changed: 68 additions & 0 deletions
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

Lines changed: 74 additions & 0 deletions
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

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,48 @@ module.exports = {
272272
return members.reverse()
273273
},
274274

275+
/**
276+
* Gets the property name of a given node.
277+
* @param {ASTNode} node - The node to get.
278+
* @return {string|null} The property name if static. Otherwise, null.
279+
*/
280+
getStaticPropertyName (node) {
281+
let prop
282+
switch (node && node.type) {
283+
case 'Property':
284+
case 'MethodDefinition':
285+
prop = node.key
286+
break
287+
case 'MemberExpression':
288+
prop = node.property
289+
break
290+
case 'Literal':
291+
case 'TemplateLiteral':
292+
case 'Identifier':
293+
prop = node
294+
break
295+
// no default
296+
}
297+
298+
switch (prop && prop.type) {
299+
case 'Literal':
300+
return String(prop.value)
301+
case 'TemplateLiteral':
302+
if (prop.expressions.length === 0 && prop.quasis.length === 1) {
303+
return prop.quasis[0].value.cooked
304+
}
305+
break
306+
case 'Identifier':
307+
if (!node.computed) {
308+
return prop.name
309+
}
310+
break
311+
// no default
312+
}
313+
314+
return null
315+
},
316+
275317
/**
276318
* Get all computed properties by looking at all component's properties
277319
* @param {ObjectExpression} Object with component definition
@@ -398,5 +440,72 @@ module.exports = {
398440
cb(node.arguments.slice(-1)[0])
399441
}
400442
}
443+
},
444+
445+
/**
446+
* Return generator with all properties
447+
* @param {ASTNode} node Node to check
448+
* @param {string} groupName Name of parent group
449+
*/
450+
* iterateProperties (node, groups) {
451+
const nodes = node.properties.filter(p => p.type === 'Property' && groups.has(this.getStaticPropertyName(p.key)))
452+
for (const item of nodes) {
453+
const name = this.getStaticPropertyName(item.key)
454+
if (item.value.type === 'ArrayExpression') {
455+
yield * this.iterateArrayExpression(item.value, name)
456+
} else if (item.value.type === 'ObjectExpression') {
457+
yield * this.iterateObjectExpression(item.value, name)
458+
} else if (item.value.type === 'FunctionExpression') {
459+
yield * this.iterateFunctionExpression(item.value, name)
460+
}
461+
}
462+
},
463+
464+
/**
465+
* Return generator with all elements inside ArrayExpression
466+
* @param {ASTNode} node Node to check
467+
* @param {string} groupName Name of parent group
468+
*/
469+
* iterateArrayExpression (node, groupName) {
470+
assert(node.type === 'ArrayExpression')
471+
for (const item of node.elements) {
472+
const name = this.getStaticPropertyName(item)
473+
if (name) {
474+
const obj = { name, groupName, node: item }
475+
yield obj
476+
}
477+
}
478+
},
479+
480+
/**
481+
* Return generator with all elements inside ObjectExpression
482+
* @param {ASTNode} node Node to check
483+
* @param {string} groupName Name of parent group
484+
*/
485+
* iterateObjectExpression (node, groupName) {
486+
assert(node.type === 'ObjectExpression')
487+
for (const item of node.properties) {
488+
const name = this.getStaticPropertyName(item)
489+
if (name) {
490+
const obj = { name, groupName, node: item.key }
491+
yield obj
492+
}
493+
}
494+
},
495+
496+
/**
497+
* Return generator with all elements inside FunctionExpression
498+
* @param {ASTNode} node Node to check
499+
* @param {string} groupName Name of parent group
500+
*/
501+
* iterateFunctionExpression (node, groupName) {
502+
assert(node.type === 'FunctionExpression')
503+
if (node.body.type === 'BlockStatement') {
504+
for (const item of node.body.body) {
505+
if (item.type === 'ReturnStatement' && item.argument.type === 'ObjectExpression') {
506+
yield * this.iterateObjectExpression(item.argument, groupName)
507+
}
508+
}
509+
}
401510
}
402511
}

0 commit comments

Comments
 (0)