Skip to content

Commit 6032f21

Browse files
armano2michalsnik
authored andcommitted
Fix issues with props (#632)
* prop-name-casing: is working now with array props [literals] * prop-name-casing: reports all errors if there are non Literal keys in it * require-prop-types: reports names for types diffrent than literals * add new getPropsProperties helper to easly deal with props * Add unit test for getPropsProperties * require-default-prop: allow to use shorthand * fix false error in `require-prop-types` when is set to empty array * `require-prop-types` will return now errors about each prop from ArrayExpression
1 parent b363379 commit 6032f21

12 files changed

+292
-134
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.idea
2+
*.iml
23
/.nyc_output
34
/coverage
45
/tests/integrations/*/node_modules

Diff for: lib/rules/prop-name-casing.js

+11-28
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ const utils = require('../utils')
88
const casing = require('../utils/casing')
99
const allowedCaseOptions = ['camelCase', 'snake_case']
1010

11-
function canFixPropertyName (node, originalName) {
11+
function canFixPropertyName (node, key, originalName) {
1212
// Can not fix of computed property names & shorthand
1313
if (node.computed || node.shorthand) {
1414
return false
1515
}
16-
const key = node.key
16+
1717
// Can not fix of unknown types
1818
if (key.type !== 'Literal' && key.type !== 'Identifier') {
1919
return false
@@ -36,42 +36,25 @@ function create (context) {
3636
// ----------------------------------------------------------------------
3737

3838
return utils.executeOnVue(context, (obj) => {
39-
const node = obj.properties.find(p =>
40-
p.type === 'Property' &&
41-
p.key.type === 'Identifier' &&
42-
p.key.name === 'props' &&
43-
(p.value.type === 'ObjectExpression' || p.value.type === 'ArrayExpression')
44-
)
45-
46-
if (!node) return
47-
48-
const items = node.value.type === 'ObjectExpression' ? node.value.properties : node.value.elements
49-
for (const item of items) {
50-
if (item.type !== 'Property') {
51-
return
52-
}
53-
if (item.computed) {
54-
if (item.key.type !== 'Literal') {
55-
// TemplateLiteral | Identifier(variable) | Expression(s)
56-
return
57-
}
58-
if (typeof item.key.value !== 'string') {
59-
// (boolean | null | number | RegExp) Literal
60-
return
61-
}
62-
}
39+
const props = utils.getComponentProps(obj)
40+
.filter(prop => prop.key && prop.key.type === 'Literal' || (prop.key.type === 'Identifier' && !prop.node.computed))
6341

42+
for (const item of props) {
6443
const propName = item.key.type === 'Literal' ? item.key.value : item.key.name
44+
if (typeof propName !== 'string') {
45+
// (boolean | null | number | RegExp) Literal
46+
continue
47+
}
6548
const convertedName = converter(propName)
6649
if (convertedName !== propName) {
6750
context.report({
68-
node: item,
51+
node: item.node,
6952
message: 'Prop "{{name}}" is not in {{caseType}}.',
7053
data: {
7154
name: propName,
7255
caseType: caseType
7356
},
74-
fix: canFixPropertyName(item, propName) ? fixer => {
57+
fix: canFixPropertyName(item.node, item.key, propName) ? fixer => {
7558
return item.key.type === 'Literal'
7659
? fixer.replaceText(item.key, item.key.raw.replace(item.key.value, convertedName))
7760
: fixer.replaceText(item.key, convertedName)

Diff for: lib/rules/require-default-prop.js

+24-22
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66

77
const utils = require('../utils')
88

9+
const NATIVE_TYPES = new Set([
10+
'String',
11+
'Number',
12+
'Boolean',
13+
'Function',
14+
'Object',
15+
'Array',
16+
'Symbol'
17+
])
18+
919
// ------------------------------------------------------------------------------
1020
// Rule Definition
1121
// ------------------------------------------------------------------------------
@@ -21,7 +31,7 @@ module.exports = {
2131
schema: []
2232
},
2333

24-
create: function (context) {
34+
create (context) {
2535
// ----------------------------------------------------------------------
2636
// Helpers
2737
// ----------------------------------------------------------------------
@@ -32,7 +42,7 @@ module.exports = {
3242
* @return {boolean}
3343
*/
3444
function propIsRequired (prop) {
35-
const propRequiredNode = utils.unwrapTypes(prop.value).properties
45+
const propRequiredNode = prop.value.properties
3646
.find(p =>
3747
p.type === 'Property' &&
3848
p.key.name === 'required' &&
@@ -49,7 +59,7 @@ module.exports = {
4959
* @return {boolean}
5060
*/
5161
function propHasDefault (prop) {
52-
const propDefaultNode = utils.unwrapTypes(prop.value).properties
62+
const propDefaultNode = prop.value.properties
5363
.find(p =>
5464
p.key &&
5565
(p.key.name === 'default' || p.key.value === 'default')
@@ -60,15 +70,14 @@ module.exports = {
6070

6171
/**
6272
* Finds all props that don't have a default value set
63-
* @param {Property} propsNode - Vue component's "props" node
73+
* @param {Array} props - Vue component's "props" node
6474
* @return {Array} Array of props without "default" value
6575
*/
66-
function findPropsWithoutDefaultValue (propsNode) {
67-
return propsNode.value.properties
68-
.filter(prop => prop.type === 'Property')
76+
function findPropsWithoutDefaultValue (props) {
77+
return props
6978
.filter(prop => {
70-
if (utils.unwrapTypes(prop.value).type !== 'ObjectExpression') {
71-
return true
79+
if (prop.value.type !== 'ObjectExpression') {
80+
return (prop.value.type !== 'CallExpression' && prop.value.type !== 'Identifier') || NATIVE_TYPES.has(prop.value.name)
7281
}
7382

7483
return !propIsRequired(prop) && !propHasDefault(prop)
@@ -124,28 +133,21 @@ module.exports = {
124133
// ----------------------------------------------------------------------
125134

126135
return utils.executeOnVue(context, (obj) => {
127-
const propsNode = obj.properties
128-
.find(p =>
129-
p.type === 'Property' &&
130-
p.key.type === 'Identifier' &&
131-
p.key.name === 'props' &&
132-
p.value.type === 'ObjectExpression'
133-
)
134-
135-
if (!propsNode) return
136+
const props = utils.getComponentProps(obj)
137+
.filter(prop => prop.key && prop.value && !prop.node.shorthand)
136138

137-
const propsWithoutDefault = findPropsWithoutDefaultValue(propsNode)
139+
const propsWithoutDefault = findPropsWithoutDefaultValue(props)
138140
const propsToReport = excludeBooleanProps(propsWithoutDefault)
139141

140-
propsToReport.forEach(prop => {
142+
for (const prop of propsToReport) {
141143
context.report({
142-
node: prop,
144+
node: prop.node,
143145
message: `Prop '{{propName}}' requires default value to be set.`,
144146
data: {
145147
propName: prop.key.name
146148
}
147149
})
148-
})
150+
}
149151
})
150152
}
151153
}

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

+14-22
Original file line numberDiff line numberDiff line change
@@ -70,31 +70,23 @@ module.exports = {
7070
}
7171

7272
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-
)
73+
const props = utils.getComponentProps(obj)
74+
.filter(prop => prop.key && prop.value)
7975

80-
if (!node) return
76+
for (const prop of props) {
77+
if (isForbiddenType(prop.value) || prop.value.type === 'ArrayExpression') {
78+
checkPropertyNode(prop.key, prop.value)
79+
} else if (prop.value.type === 'ObjectExpression') {
80+
const typeProperty = prop.value.properties.find(property =>
81+
property.type === 'Property' &&
82+
property.key.name === 'type'
83+
)
8184

82-
node.value.properties
83-
.forEach(p => {
84-
const pValue = utils.unwrapTypes(p.value)
85-
if (isForbiddenType(pValue) || pValue.type === 'ArrayExpression') {
86-
checkPropertyNode(p.key, pValue)
87-
} else if (pValue.type === 'ObjectExpression') {
88-
const typeProperty = pValue.properties.find(prop =>
89-
prop.type === 'Property' &&
90-
prop.key.name === 'type'
91-
)
85+
if (!typeProperty) continue
9286

93-
if (!typeProperty) return
94-
95-
checkPropertyNode(p.key, utils.unwrapTypes(typeProperty.value))
96-
}
97-
})
87+
checkPropertyNode(prop.key, typeProperty.value)
88+
}
89+
}
9890
})
9991
}
10092
}

Diff for: lib/rules/require-prop-types.js

+22-40
Original file line numberDiff line numberDiff line change
@@ -42,30 +42,26 @@ module.exports = {
4242
return Boolean(typeProperty || validatorProperty)
4343
}
4444

45-
function checkProperties (items) {
46-
for (const cp of items) {
47-
if (cp.type !== 'Property') {
48-
return
49-
}
50-
let hasType = true
51-
const cpValue = utils.unwrapTypes(cp.value)
45+
function checkProperty (key, value, node) {
46+
let hasType = true
5247

53-
if (cpValue.type === 'ObjectExpression') { // foo: {
54-
hasType = objectHasType(cpValue)
55-
} else if (cpValue.type === 'ArrayExpression') { // foo: [
56-
hasType = cpValue.elements.length > 0
57-
} else if (cpValue.type === 'FunctionExpression' || cpValue.type === 'ArrowFunctionExpression') {
58-
hasType = false
59-
}
60-
if (!hasType) {
61-
context.report({
62-
node: cp,
63-
message: 'Prop "{{name}}" should define at least its type.',
64-
data: {
65-
name: cp.key.name
66-
}
67-
})
68-
}
48+
if (!value) {
49+
hasType = false
50+
} else if (value.type === 'ObjectExpression') { // foo: {
51+
hasType = objectHasType(value)
52+
} else if (value.type === 'ArrayExpression') { // foo: [
53+
hasType = value.elements.length > 0
54+
} else if (value.type === 'FunctionExpression' || value.type === 'ArrowFunctionExpression') {
55+
hasType = false
56+
}
57+
if (!hasType) {
58+
context.report({
59+
node,
60+
message: 'Prop "{{name}}" should define at least its type.',
61+
data: {
62+
name: utils.getStaticPropertyName(key || node) || 'Unknown prop'
63+
}
64+
})
6965
}
7066
}
7167

@@ -74,24 +70,10 @@ module.exports = {
7470
// ----------------------------------------------------------------------
7571

7672
return utils.executeOnVue(context, (obj) => {
77-
const node = obj.properties
78-
.find(p =>
79-
p.type === 'Property' &&
80-
p.key.type === 'Identifier' &&
81-
p.key.name === 'props'
82-
)
73+
const props = utils.getComponentProps(obj)
8374

84-
if (!node) return
85-
86-
if (node.value.type === 'ObjectExpression') {
87-
checkProperties(node.value.properties)
88-
}
89-
90-
if (node.value.type === 'ArrayExpression') {
91-
context.report({
92-
node,
93-
message: 'Props should at least define their types.'
94-
})
75+
for (const prop of props) {
76+
checkProperty(prop.key, prop.value, prop.node)
9577
}
9678
})
9779
}

Diff for: lib/rules/require-valid-default-prop.js

+5-14
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,11 @@ module.exports = {
8888
// ----------------------------------------------------------------------
8989

9090
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-
utils.unwrapTypes(p.value).type === 'ObjectExpression'
101-
)
91+
const props = utils.getComponentProps(obj)
92+
.filter(prop => prop.key && prop.value && prop.value.type === 'ObjectExpression')
10293

103-
for (const prop of properties) {
104-
const type = getPropertyNode(utils.unwrapTypes(prop.value), 'type')
94+
for (const prop of props) {
95+
const type = getPropertyNode(prop.value, 'type')
10596
if (!type) continue
10697

10798
const typeNames = new Set(getTypes(type.value)
@@ -111,7 +102,7 @@ module.exports = {
111102
// There is no native types detected
112103
if (typeNames.size === 0) continue
113104

114-
const def = getPropertyNode(utils.unwrapTypes(prop.value), 'default')
105+
const def = getPropertyNode(prop.value, 'default')
115106
if (!def) continue
116107

117108
const defType = getValueType(def.value)

Diff for: lib/utils/index.js

+38-1
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,46 @@ module.exports = {
365365
return null
366366
},
367367

368+
/**
369+
* Get all props by looking at all component's properties
370+
* @param {ObjectExpression} componentObject Object with component definition
371+
* @return {Array} Array of component props in format: [{key?: String, value?: ASTNode, node: ASTNod}]
372+
*/
373+
getComponentProps (componentObject) {
374+
const propsNode = componentObject.properties
375+
.find(p =>
376+
p.type === 'Property' &&
377+
p.key.type === 'Identifier' &&
378+
p.key.name === 'props' &&
379+
(p.value.type === 'ObjectExpression' || p.value.type === 'ArrayExpression')
380+
)
381+
382+
if (!propsNode) {
383+
return []
384+
}
385+
386+
let props
387+
388+
if (propsNode.value.type === 'ObjectExpression') {
389+
props = propsNode.value.properties
390+
.filter(prop => prop.type === 'Property')
391+
.map(prop => {
392+
return { key: prop.key, value: this.unwrapTypes(prop.value), node: prop }
393+
})
394+
} else {
395+
props = propsNode.value.elements
396+
.map(prop => {
397+
const key = prop.type === 'Literal' && typeof prop.value === 'string' ? prop : null
398+
return { key, value: null, node: prop }
399+
})
400+
}
401+
402+
return props
403+
},
404+
368405
/**
369406
* Get all computed properties by looking at all component's properties
370-
* @param {ObjectExpression} Object with component definition
407+
* @param {ObjectExpression} componentObject Object with component definition
371408
* @return {Array} Array of computed properties in format: [{key: String, value: ASTNode}]
372409
*/
373410
getComputedProperties (componentObject) {

0 commit comments

Comments
 (0)