Skip to content

Commit 47cc8d3

Browse files
authored
(implements #323) Add require-prop-type-constructor rule (#546)
* Add no-string-prop-type rule - basic implementation * Rename rule * Add more tests, update docs
1 parent 8673fc3 commit 47cc8d3

File tree

5 files changed

+294
-0
lines changed

5 files changed

+294
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
231231
| | Rule ID | Description |
232232
|:---|:--------|:------------|
233233
| :wrench: | [vue/component-name-in-template-casing](./docs/rules/component-name-in-template-casing.md) | enforce specific casing for the component naming style in template |
234+
| :wrench: | [vue/require-prop-type-constructor](./docs/rules/require-prop-type-constructor.md) | require prop type to be a constructor |
234235
| :wrench: | [vue/script-indent](./docs/rules/script-indent.md) | enforce consistent indentation in `<script>` |
235236

236237
### Deprecated

Diff for: docs/rules/require-prop-type-constructor.md

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# require prop type to be a constructor (vue/require-prop-type-constructor)
2+
3+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
4+
5+
This rule reports prop types that can't be presumed as constructors.
6+
7+
It's impossible to catch every possible case and know whether the prop type is a constructor or not, hence this rule black list few types of nodes, instead of white-listing correct ones.
8+
9+
The following types are forbidden and will be reported:
10+
11+
- Literal
12+
- TemplateLiteral
13+
- BinaryExpression
14+
- UpdateExpression
15+
16+
It will catch most commonly made mistakes which are using strings instead of constructors.
17+
18+
## Rule Details
19+
20+
Examples of **incorrect** code for this rule:
21+
22+
```js
23+
export default {
24+
props: {
25+
myProp: "Number",
26+
anotherProp: ["Number", "String"],
27+
myFieldWithBadType: {
28+
type: "Object",
29+
default: function() {
30+
return {}
31+
},
32+
},
33+
myOtherFieldWithBadType: {
34+
type: "Number",
35+
default: 1,
36+
},
37+
}
38+
}
39+
```
40+
41+
Examples of **correct** code for this rule:
42+
43+
```js
44+
export default {
45+
props: {
46+
myProp: Number,
47+
anotherProp: [Number, String],
48+
myFieldWithBadType: {
49+
type: Object,
50+
default: function() {
51+
return {}
52+
},
53+
},
54+
myOtherFieldWithBadType: {
55+
type: Number,
56+
default: 1,
57+
},
58+
}
59+
}
60+
```

Diff for: lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module.exports = {
4141
'prop-name-casing': require('./rules/prop-name-casing'),
4242
'require-component-is': require('./rules/require-component-is'),
4343
'require-default-prop': require('./rules/require-default-prop'),
44+
'require-prop-type-constructor': require('./rules/require-prop-type-constructor'),
4445
'require-prop-types': require('./rules/require-prop-types'),
4546
'require-render-return': require('./rules/require-render-return'),
4647
'require-v-for-key': require('./rules/require-v-for-key'),

Diff for: lib/rules/require-prop-type-constructor.js

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* @fileoverview require prop type to be a constructor
3+
* @author Michał Sajnóg
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
const message = 'The "{{name}}" property should be a constructor.'
14+
15+
const forbiddenTypes = [
16+
'Literal',
17+
'TemplateLiteral',
18+
'BinaryExpression',
19+
'UpdateExpression'
20+
]
21+
22+
const isForbiddenType = nodeType => forbiddenTypes.indexOf(nodeType) > -1
23+
24+
module.exports = {
25+
meta: {
26+
docs: {
27+
description: 'require prop type to be a constructor',
28+
category: undefined, // essential
29+
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.1/docs/rules/require-prop-type-constructor.md'
30+
},
31+
fixable: 'code', // or "code" or "whitespace"
32+
schema: []
33+
},
34+
35+
create (context) {
36+
const fix = node => fixer => {
37+
if (node.type === 'Literal') {
38+
return fixer.replaceText(node, node.value)
39+
} else if (
40+
node.type === 'TemplateLiteral' &&
41+
node.expressions.length === 0 &&
42+
node.quasis.length === 1
43+
) {
44+
return fixer.replaceText(node, node.quasis[0].value.cooked)
45+
}
46+
}
47+
48+
const checkPropertyNode = (p) => {
49+
if (isForbiddenType(p.value.type)) {
50+
context.report({
51+
node: p.value,
52+
message,
53+
data: {
54+
name: utils.getStaticPropertyName(p.key)
55+
},
56+
fix: fix(p.value)
57+
})
58+
} else if (p.value.type === 'ArrayExpression') {
59+
p.value.elements
60+
.filter(prop => isForbiddenType(prop.type))
61+
.forEach(prop => context.report({
62+
node: prop,
63+
message,
64+
data: {
65+
name: utils.getStaticPropertyName(p.key)
66+
},
67+
fix: fix(prop)
68+
}))
69+
}
70+
}
71+
72+
return utils.executeOnVueComponent(context, (obj) => {
73+
const node = obj.properties.find(p =>
74+
p.type === 'Property' &&
75+
p.key.type === 'Identifier' &&
76+
p.key.name === 'props' &&
77+
p.value.type === 'ObjectExpression'
78+
)
79+
80+
if (!node) return
81+
82+
node.value.properties.forEach(p => {
83+
if (isForbiddenType(p.value.type) || p.value.type === 'ArrayExpression') {
84+
checkPropertyNode(p)
85+
} else if (p.value.type === 'ObjectExpression') {
86+
const typeProperty = p.value.properties.find(prop =>
87+
prop.type === 'Property' &&
88+
prop.key.name === 'type'
89+
)
90+
91+
if (!typeProperty) return
92+
93+
checkPropertyNode(typeProperty)
94+
}
95+
})
96+
})
97+
}
98+
}

Diff for: tests/lib/rules/require-prop-type-constructor.js

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* @fileoverview require prop type to be a constructor
3+
* @author Michał Sajnóg
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const rule = require('../../../lib/rules/require-prop-type-constructor')
12+
const RuleTester = require('eslint').RuleTester
13+
14+
// ------------------------------------------------------------------------------
15+
// Tests
16+
// ------------------------------------------------------------------------------
17+
18+
var ruleTester = new RuleTester({
19+
parserOptions: {
20+
ecmaVersion: 7,
21+
sourceType: 'module'
22+
}
23+
})
24+
ruleTester.run('require-prop-type-constructor', rule, {
25+
26+
valid: [
27+
{
28+
filename: 'SomeComponent.vue',
29+
code: `
30+
export default {
31+
props: {
32+
myProp: Number,
33+
anotherType: [Number, String],
34+
extraProp: {
35+
type: Number,
36+
default: 10
37+
},
38+
lastProp: {
39+
type: [Number, Boolean]
40+
}
41+
}
42+
}
43+
`
44+
}
45+
],
46+
47+
invalid: [
48+
{
49+
filename: 'SomeComponent.vue',
50+
code: `
51+
export default {
52+
props: {
53+
myProp: 'Number',
54+
anotherType: ['Number', 'String'],
55+
extraProp: {
56+
type: 'Number',
57+
default: 10
58+
},
59+
lastProp: {
60+
type: ['Boolean']
61+
}
62+
}
63+
}
64+
`,
65+
output: `
66+
export default {
67+
props: {
68+
myProp: Number,
69+
anotherType: [Number, String],
70+
extraProp: {
71+
type: Number,
72+
default: 10
73+
},
74+
lastProp: {
75+
type: [Boolean]
76+
}
77+
}
78+
}
79+
`,
80+
errors: [{
81+
message: 'The "myProp" property should be a constructor.',
82+
line: 4
83+
}, {
84+
message: 'The "anotherType" property should be a constructor.',
85+
line: 5
86+
}, {
87+
message: 'The "anotherType" property should be a constructor.',
88+
line: 5
89+
}, {
90+
message: 'The "type" property should be a constructor.',
91+
line: 7
92+
}, {
93+
message: 'The "type" property should be a constructor.',
94+
line: 11
95+
}]
96+
},
97+
{
98+
filename: 'SomeComponent.vue',
99+
code: `
100+
export default {
101+
props: {
102+
a: \`String\`,
103+
b: Foo + '',
104+
c: 1,
105+
d: true,
106+
}
107+
}
108+
`,
109+
output: `
110+
export default {
111+
props: {
112+
a: String,
113+
b: Foo + '',
114+
c: 1,
115+
d: true,
116+
}
117+
}
118+
`,
119+
errors: [{
120+
message: 'The "a" property should be a constructor.',
121+
line: 4
122+
}, {
123+
message: 'The "b" property should be a constructor.',
124+
line: 5
125+
}, {
126+
message: 'The "c" property should be a constructor.',
127+
line: 6
128+
}, {
129+
message: 'The "d" property should be a constructor.',
130+
line: 7
131+
}]
132+
}
133+
]
134+
})

0 commit comments

Comments
 (0)