Skip to content

Commit 71929fa

Browse files
authored
Add "require-default-prop" rule (fixes vuejs#122) (vuejs#135)
* Add "require-default-prop" rule * Handle spread operator cases
1 parent b25be73 commit 71929fa

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

docs/rules/require-default-prop.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Require default value for props (require-default-prop)
2+
3+
This rule requires default value to be set for each props that are not marked as `required`.
4+
5+
## Rule Details
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
export default {
11+
props: {
12+
a: Number,
13+
b: [Number, String],
14+
c: {
15+
type: Number
16+
},
17+
d: {
18+
type: Number,
19+
required: false
20+
}
21+
}
22+
}
23+
```
24+
25+
Examples of **correct** code for this rule:
26+
27+
```js
28+
export default {
29+
props: {
30+
a: {
31+
type: Number,
32+
required: true
33+
},
34+
b: {
35+
type: Number,
36+
default: 0
37+
},
38+
c: {
39+
type: Number,
40+
default: 0,
41+
required: false
42+
}
43+
}
44+
}
45+
```

lib/rules/require-default-prop.js

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* @fileoverview Require default value for props
3+
* @author Michał Sajnóg <[email protected]> (http://github.com/michalsnik)
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
module.exports = {
14+
meta: {
15+
docs: {
16+
description: 'Require default value for props',
17+
category: 'Best Practices',
18+
recommended: false
19+
},
20+
fixable: null, // or "code" or "whitespace"
21+
schema: []
22+
},
23+
24+
create: function (context) {
25+
// ----------------------------------------------------------------------
26+
// Helpers
27+
// ----------------------------------------------------------------------
28+
29+
/**
30+
* Checks if the passed prop is required
31+
* @param {Property} prop - Property AST node for a single prop
32+
* @return {boolean}
33+
*/
34+
function propIsRequired (prop) {
35+
const propRequiredNode = prop.value.properties
36+
.find(p =>
37+
p.type === 'Property' &&
38+
p.key.name === 'required' &&
39+
p.value.type === 'Literal' &&
40+
p.value.value === true
41+
)
42+
43+
return Boolean(propRequiredNode)
44+
}
45+
46+
/**
47+
* Checks if the passed prop has a defualt value
48+
* @param {Property} prop - Property AST node for a single prop
49+
* @return {boolean}
50+
*/
51+
function propHasDefault (prop) {
52+
const propDefaultNode = prop.value.properties
53+
.find(p => p.key.name === 'default')
54+
55+
return Boolean(propDefaultNode)
56+
}
57+
58+
/**
59+
* Finds all props that don't have a default value set
60+
* @param {Property} propsNode - Vue component's "props" node
61+
* @return {boolean}
62+
*/
63+
function findPropsWithoutDefaultValue (propsNode) {
64+
return propsNode.value.properties
65+
.filter(prop => prop.type === 'Property')
66+
.filter(prop => {
67+
if (prop.value.type !== 'ObjectExpression') {
68+
return true
69+
}
70+
71+
return !propIsRequired(prop) && !propHasDefault(prop)
72+
})
73+
}
74+
75+
// ----------------------------------------------------------------------
76+
// Public
77+
// ----------------------------------------------------------------------
78+
79+
return utils.executeOnVue(context, (obj) => {
80+
const propsNode = obj.properties
81+
.find(p =>
82+
p.type === 'Property' &&
83+
p.key.type === 'Identifier' &&
84+
p.key.name === 'props' &&
85+
p.value.type === 'ObjectExpression'
86+
)
87+
88+
if (!propsNode) return
89+
90+
const propsWithoutDefault = findPropsWithoutDefaultValue(propsNode)
91+
92+
propsWithoutDefault.forEach(prop => {
93+
context.report({
94+
node: prop,
95+
message: `Prop '{{propName}}' requires default value to be set.`,
96+
data: {
97+
propName: prop.key.name
98+
}
99+
})
100+
})
101+
})
102+
}
103+
}
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* @fileoverview Require default value for props
3+
* @author Michał Sajnóg <[email protected]> (http://github.com/michalsnik)
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const rule = require('../../../lib/rules/require-default-prop')
12+
const RuleTester = require('eslint').RuleTester
13+
const parserOptions = {
14+
ecmaVersion: 6,
15+
ecmaFeatures: { experimentalObjectRestSpread: true },
16+
sourceType: 'module'
17+
}
18+
19+
// ------------------------------------------------------------------------------
20+
// Tests
21+
// ------------------------------------------------------------------------------
22+
23+
const ruleTester = new RuleTester()
24+
ruleTester.run('require-default-prop', rule, {
25+
26+
valid: [
27+
{
28+
filename: 'test.vue',
29+
code: `
30+
export default {
31+
props: {
32+
a: {
33+
type: Number,
34+
required: true
35+
},
36+
b: {
37+
type: Number,
38+
default: 0
39+
},
40+
c: {
41+
type: Number,
42+
default: 0,
43+
required: false
44+
},
45+
// eslint-disable-next-line require-default-prop
46+
d: Number
47+
}
48+
}
49+
`,
50+
parserOptions
51+
},
52+
{
53+
filename: 'test.vue',
54+
code: `
55+
export default {
56+
props: {
57+
...x,
58+
a: {
59+
...y,
60+
type: Number,
61+
required: true
62+
},
63+
b: {
64+
type: Number,
65+
default: 0
66+
}
67+
}
68+
}
69+
`,
70+
parserOptions
71+
}
72+
],
73+
74+
invalid: [
75+
{
76+
filename: 'test.vue',
77+
code: `
78+
export default {
79+
props: {
80+
a: Number,
81+
b: [Number, String],
82+
c: {
83+
type: Number
84+
},
85+
d: {
86+
type: Number,
87+
required: false
88+
}
89+
}
90+
}
91+
`,
92+
parserOptions,
93+
errors: [{
94+
message: `Prop 'a' requires default value to be set.`,
95+
line: 4
96+
}, {
97+
message: `Prop 'b' requires default value to be set.`,
98+
line: 5
99+
}, {
100+
message: `Prop 'c' requires default value to be set.`,
101+
line: 6
102+
}, {
103+
message: `Prop 'd' requires default value to be set.`,
104+
line: 9
105+
}]
106+
}
107+
]
108+
})

0 commit comments

Comments
 (0)