Skip to content

Commit 5bef08a

Browse files
committed
Fix issues with props
* 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
1 parent 1b5a799 commit 5bef08a

File tree

8 files changed

+110
-107
lines changed

8 files changed

+110
-107
lines changed

lib/rules/prop-name-casing.js

Lines changed: 10 additions & 27 deletions
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
39+
const items = utils.getPropsProperties(obj)
40+
.filter(cp => cp.key.type === 'Literal' || (cp.key.type === 'Identifier' && !cp.node.computed))
4741

48-
const items = node.value.type === 'ObjectExpression' ? node.value.properties : node.value.elements
4942
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-
}
63-
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)

lib/rules/require-default-prop.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,15 @@ module.exports = {
137137
const propsWithoutDefault = findPropsWithoutDefaultValue(propsNode)
138138
const propsToReport = excludeBooleanProps(propsWithoutDefault)
139139

140-
propsToReport.forEach(prop => {
140+
for (const prop of propsToReport) {
141141
context.report({
142142
node: prop,
143143
message: `Prop '{{propName}}' requires default value to be set.`,
144144
data: {
145145
propName: prop.key.name
146146
}
147147
})
148-
})
148+
}
149149
})
150150
}
151151
}

lib/rules/require-prop-type-constructor.js

Lines changed: 14 additions & 22 deletions
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 properties = utils.getPropsProperties(obj)
74+
.filter(cp => cp.value)
7975

80-
if (!node) return
76+
for (const p of properties) {
77+
if (isForbiddenType(p.value) || p.value.type === 'ArrayExpression') {
78+
checkPropertyNode(p.key, p.value)
79+
} else if (p.value.type === 'ObjectExpression') {
80+
const typeProperty = p.value.properties.find(prop =>
81+
prop.type === 'Property' &&
82+
prop.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(p.key, utils.unwrapTypes(typeProperty.value))
88+
}
89+
}
9890
})
9991
}
10092
}

lib/rules/require-prop-types.js

Lines changed: 22 additions & 40 deletions
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)
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 properties = utils.getPropsProperties(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 cp of properties) {
76+
checkProperty(cp.key, cp.value, cp.node)
9577
}
9678
})
9779
}

lib/rules/require-valid-default-prop.js

Lines changed: 4 additions & 13 deletions
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 properties = utils.getPropsProperties(obj)
92+
.filter(cp => cp.value && cp.value.type === 'ObjectExpression')
10293

10394
for (const prop of properties) {
104-
const type = getPropertyNode(utils.unwrapTypes(prop.value), 'type')
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)

lib/utils/index.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,41 @@ module.exports = {
365365
return null
366366
},
367367

368+
/**
369+
* Get all props by looking at all component's properties
370+
* @param {ObjectExpression} Object with component definition
371+
* @return {Array<{key: ASTNode, value: ASTNode | null, node: ASTNode}>} Array of props in format:
372+
*/
373+
getPropsProperties (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) { return [] }
383+
384+
let props
385+
386+
if (propsNode.value.type === 'ObjectExpression') {
387+
props = propsNode.value.properties
388+
.filter(cp => cp.type === 'Property')
389+
.map(cp => {
390+
return { key: cp.key, value: this.unwrapTypes(cp.value), node: cp }
391+
})
392+
} else {
393+
props = propsNode.value.elements
394+
.filter(cp => cp.type === 'Literal' && typeof cp.value === 'string')
395+
.map(cp => {
396+
return { key: cp, value: null, node: cp }
397+
})
398+
}
399+
400+
return props
401+
},
402+
368403
/**
369404
* Get all computed properties by looking at all component's properties
370405
* @param {ObjectExpression} Object with component definition

tests/lib/rules/prop-name-casing.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ ruleTester.run('prop-name-casing', rule, {
6767
filename: 'test.vue',
6868
code: `
6969
export default {
70-
props: ['greetingText']
70+
props: ['greeting_text']
7171
}
7272
`,
7373
options: ['snake_case'],
@@ -338,6 +338,26 @@ ruleTester.run('prop-name-casing', rule, {
338338
line: 4
339339
}]
340340
},
341+
{
342+
filename: 'test.vue',
343+
code: `
344+
export default {
345+
props: ['greeting_text']
346+
}
347+
`,
348+
options: ['camelCase'],
349+
output: `
350+
export default {
351+
props: ['greetingText']
352+
}
353+
`,
354+
parserOptions,
355+
errors: [{
356+
message: 'Prop "greeting_text" is not in camelCase.',
357+
type: 'Literal',
358+
line: 3
359+
}]
360+
},
341361
{
342362
filename: 'test.vue',
343363
code: `

tests/lib/rules/require-prop-types.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ ruleTester.run('require-prop-types', rule, {
154154
`,
155155
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
156156
errors: [{
157-
message: 'Props should at least define their types.',
157+
message: 'Prop "foo" should define at least its type.',
158158
line: 3
159159
}]
160160
},
@@ -167,7 +167,7 @@ ruleTester.run('require-prop-types', rule, {
167167
`,
168168
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
169169
errors: [{
170-
message: 'Props should at least define their types.',
170+
message: 'Prop "foo" should define at least its type.',
171171
line: 3
172172
}]
173173
},

0 commit comments

Comments
 (0)