Skip to content

Commit c4e7cc3

Browse files
committed
Add rule vue/require-valid-default-prop.
fixes #117
1 parent 39c9df5 commit c4e7cc3

File tree

3 files changed

+576
-0
lines changed

3 files changed

+576
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Enforces prop default values to be valid (require-valid-default-prop)
2+
3+
This rule is doing basic type checking between type and default value and inform about missuesed or invalid default values.
4+
Type checking is working for all `Native types`, with requirement that `Array` and `Object` has to be a function.
5+
6+
## :book: Rule Details
7+
8+
:-1: Examples of **incorrect** code for this rule:
9+
10+
```js
11+
Vue.component('example', {
12+
props: {
13+
propA: {
14+
type: String,
15+
default: {}
16+
},
17+
propB: {
18+
type: String,
19+
default: []
20+
},
21+
propC: {
22+
type: Object,
23+
default: []
24+
},
25+
propD: {
26+
type: Array,
27+
default: []
28+
},
29+
propE: {
30+
type: Object,
31+
default: { message: 'hello' }
32+
}
33+
}
34+
})
35+
```
36+
37+
:+1: Examples of **correct** code for this rule:
38+
39+
```js
40+
Vue.component('example', {
41+
props: {
42+
// basic type check (`null` means accept any type)
43+
propA: Number,
44+
// multiple possible types
45+
propB: [String, Number],
46+
// a number with default value
47+
propD: {
48+
type: Number,
49+
default: 100
50+
},
51+
// object/array defaults should be returned from a factory function
52+
propE: {
53+
type: Object,
54+
default: function () {
55+
return { message: 'hello' }
56+
}
57+
}
58+
}
59+
})
60+
```
61+
62+
## :wrench: Options
63+
64+
Nothing.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* @fileoverview Enforces prop default values to be valid.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
const utils = require('../utils')
7+
8+
const NATIVE_TYPES = new Set([
9+
'String',
10+
'Number',
11+
'Boolean',
12+
'Function',
13+
'Object',
14+
'Array',
15+
'Symbol'
16+
])
17+
18+
// ------------------------------------------------------------------------------
19+
// Rule Definition
20+
// ------------------------------------------------------------------------------
21+
22+
module.exports = {
23+
meta: {
24+
docs: {
25+
description: 'Enforces prop default values to be valid.',
26+
category: 'Possible Errors',
27+
recommended: false
28+
},
29+
fixable: null,
30+
schema: []
31+
},
32+
33+
create (context) {
34+
// ----------------------------------------------------------------------
35+
// Helpers
36+
// ----------------------------------------------------------------------
37+
38+
function isPropertyIdentifier (node) {
39+
return node.type === 'Property' && node.key.type === 'Identifier'
40+
}
41+
42+
function getPropertyNode (obj, name) {
43+
return obj.properties.find(p =>
44+
isPropertyIdentifier(p) &&
45+
p.key.name === name
46+
)
47+
}
48+
49+
function getTypes (node) {
50+
if (node.type === 'Identifier') {
51+
return [node.name]
52+
} else if (node.type === 'ArrayExpression') {
53+
return node.elements
54+
.filter(item => item.type === 'Identifier')
55+
.map(item => item.name)
56+
}
57+
return []
58+
}
59+
60+
function ucFirst (text) {
61+
return text[0].toUpperCase() + text.slice(1)
62+
}
63+
64+
function getValueType (node) {
65+
if (node.type === 'CallExpression') { // Symbol(), Number() ...
66+
if (node.callee.type === 'Identifier' && NATIVE_TYPES.has(node.callee.name)) {
67+
return node.callee.name
68+
}
69+
} else if (node.type === 'TemplateLiteral') { // String
70+
return 'String'
71+
} else if (node.type === 'Literal') { // String, Boolean, Number
72+
if (node.value === null) return null
73+
const type = ucFirst(typeof node.value)
74+
if (NATIVE_TYPES.has(type)) {
75+
return type
76+
}
77+
} else if (node.type === 'ArrayExpression') { // Array
78+
return 'Array'
79+
} else if (node.type === 'ObjectExpression') { // Array
80+
return 'Object'
81+
}
82+
// FunctionExpression, ArrowFunctionExpression
83+
return null
84+
}
85+
86+
// ----------------------------------------------------------------------
87+
// Public
88+
// ----------------------------------------------------------------------
89+
90+
return utils.executeOnVue(context, obj => {
91+
const props = obj.properties.find(p =>
92+
isPropertyIdentifier(p) &&
93+
p.key.name === 'props' &&
94+
p.value.type === 'ObjectExpression'
95+
)
96+
if (!props) return
97+
98+
const properties = props.value.properties.filter(p =>
99+
isPropertyIdentifier(p) &&
100+
p.value.type === 'ObjectExpression'
101+
)
102+
103+
for (const prop of properties) {
104+
const type = getPropertyNode(prop.value, 'type')
105+
if (!type) {
106+
return
107+
}
108+
109+
const typeNames = new Set(getTypes(type.value)
110+
.map(item => item === 'Object' || item === 'Array' ? 'Function' : item) // Object and Array require function
111+
.filter(item => NATIVE_TYPES.has(item)))
112+
113+
if (typeNames.size === 0) { // There is no native types detected
114+
return
115+
}
116+
117+
const def = getPropertyNode(prop.value, 'default')
118+
if (!def) return
119+
120+
const defType = getValueType(def.value)
121+
if (typeNames.has(defType)) return
122+
123+
context.report({
124+
node: def,
125+
message: "Type of default value prop '{{name}}' must be a {{types}}.",
126+
data: {
127+
name: prop.key.name,
128+
types: Array.from(typeNames).join(' or ').toLowerCase()
129+
}
130+
})
131+
}
132+
})
133+
}
134+
}

0 commit comments

Comments
 (0)