Skip to content

Commit 5980cdc

Browse files
authored
Fixed an error when using spread elements in vue/require-default-prop. (#1046)
* Fixed an error when using spread elements in `vue/require-default-prop`. * Revert vscode/settings
1 parent a4e3f0f commit 5980cdc

File tree

3 files changed

+103
-20
lines changed

3 files changed

+103
-20
lines changed

lib/rules/require-default-prop.js

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
*/
55
'use strict'
66

7+
/**
8+
* @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression
9+
* @typedef {import('vue-eslint-parser').AST.ESLintObjectExpression} ObjectExpression
10+
* @typedef {import('vue-eslint-parser').AST.ESLintPattern} Pattern
11+
*/
12+
/**
13+
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
14+
* @typedef {ComponentObjectProp & { value: ObjectExpression} } ComponentObjectPropObject
15+
*/
16+
717
const utils = require('../utils')
818

919
const NATIVE_TYPES = new Set([
@@ -39,14 +49,14 @@ module.exports = {
3949

4050
/**
4151
* Checks if the passed prop is required
42-
* @param {Property} prop - Property AST node for a single prop
52+
* @param {ComponentObjectPropObject} prop - Property AST node for a single prop
4353
* @return {boolean}
4454
*/
4555
function propIsRequired (prop) {
4656
const propRequiredNode = prop.value.properties
4757
.find(p =>
4858
p.type === 'Property' &&
49-
p.key.name === 'required' &&
59+
utils.getStaticPropertyName(p) === 'required' &&
5060
p.value.type === 'Literal' &&
5161
p.value.value === true
5262
)
@@ -56,38 +66,38 @@ module.exports = {
5666

5767
/**
5868
* Checks if the passed prop has a default value
59-
* @param {Property} prop - Property AST node for a single prop
69+
* @param {ComponentObjectPropObject} prop - Property AST node for a single prop
6070
* @return {boolean}
6171
*/
6272
function propHasDefault (prop) {
6373
const propDefaultNode = prop.value.properties
6474
.find(p =>
65-
p.key &&
66-
(p.key.name === 'default' || p.key.value === 'default')
75+
p.type === 'Property' && utils.getStaticPropertyName(p) === 'default'
6776
)
6877

6978
return Boolean(propDefaultNode)
7079
}
7180

7281
/**
7382
* Finds all props that don't have a default value set
74-
* @param {Array} props - Vue component's "props" node
75-
* @return {Array} Array of props without "default" value
83+
* @param {ComponentObjectProp[]} props - Vue component's "props" node
84+
* @return {ComponentObjectProp[]} Array of props without "default" value
7685
*/
7786
function findPropsWithoutDefaultValue (props) {
7887
return props
7988
.filter(prop => {
8089
if (prop.value.type !== 'ObjectExpression') {
81-
return (prop.value.type !== 'CallExpression' && prop.value.type !== 'Identifier') || NATIVE_TYPES.has(prop.value.name)
90+
return (prop.value.type !== 'CallExpression' && prop.value.type !== 'Identifier') ||
91+
(prop.value.type === 'Identifier' && NATIVE_TYPES.has(prop.value.name))
8292
}
8393

84-
return !propIsRequired(prop) && !propHasDefault(prop)
94+
return !propIsRequired(/** @type {ComponentObjectPropObject} */(prop)) && !propHasDefault(/** @type {ComponentObjectPropObject} */(prop))
8595
})
8696
}
8797

8898
/**
8999
* Detects whether given value node is a Boolean type
90-
* @param {Node} value
100+
* @param {Expression | Pattern} value
91101
* @return {Boolean}
92102
*/
93103
function isValueNodeOfBooleanType (value) {
@@ -104,15 +114,16 @@ module.exports = {
104114

105115
/**
106116
* Detects whether given prop node is a Boolean
107-
* @param {Node} prop
117+
* @param {ComponentObjectProp} prop
108118
* @return {Boolean}
109119
*/
110120
function isBooleanProp (prop) {
111121
const value = utils.unwrapTypes(prop.value)
112122

113123
return isValueNodeOfBooleanType(value) || (
114124
value.type === 'ObjectExpression' &&
115-
value.properties.find(p =>
125+
value.properties.some(p =>
126+
p.type === 'Property' &&
116127
p.key.type === 'Identifier' &&
117128
p.key.name === 'type' &&
118129
isValueNodeOfBooleanType(p.value)
@@ -122,8 +133,8 @@ module.exports = {
122133

123134
/**
124135
* Excludes purely Boolean props from the Array
125-
* @param {Array} props - Array with props
126-
* @return {Array}
136+
* @param {ComponentObjectProp[]} props - Array with props
137+
* @return {ComponentObjectProp[]}
127138
*/
128139
function excludeBooleanProps (props) {
129140
return props.filter(prop => !isBooleanProp(prop))
@@ -135,9 +146,9 @@ module.exports = {
135146

136147
return utils.executeOnVue(context, (obj) => {
137148
const props = utils.getComponentProps(obj)
138-
.filter(prop => prop.key && prop.value && !prop.node.shorthand)
149+
.filter(prop => prop.key && prop.value && !(prop.node.type === 'Property' && prop.node.shorthand))
139150

140-
const propsWithoutDefault = findPropsWithoutDefaultValue(props)
151+
const propsWithoutDefault = findPropsWithoutDefaultValue(/** @type {ComponentObjectProp[]} */(props))
141152
const propsToReport = excludeBooleanProps(propsWithoutDefault)
142153

143154
for (const prop of propsToReport) {

lib/utils/index.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55
*/
66
'use strict'
77

8+
/**
9+
* @typedef {import('vue-eslint-parser').AST.ESLintArrayExpression} ArrayExpression
10+
* @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression
11+
* @typedef {import('vue-eslint-parser').AST.ESLintIdentifier} Identifier
12+
* @typedef {import('vue-eslint-parser').AST.ESLintLiteral} Literal
13+
* @typedef {import('vue-eslint-parser').AST.ESLintMemberExpression} MemberExpression
14+
* @typedef {import('vue-eslint-parser').AST.ESLintMethodDefinition} MethodDefinition
15+
* @typedef {import('vue-eslint-parser').AST.ESLintObjectExpression} ObjectExpression
16+
* @typedef {import('vue-eslint-parser').AST.ESLintProperty} Property
17+
* @typedef {import('vue-eslint-parser').AST.ESLintTemplateLiteral} TemplateLiteral
18+
*/
19+
20+
/**
21+
* @typedef { {key: Literal | null, value: null, node: ArrayExpression['elements'][0], propName: string} } ComponentArrayProp
22+
* @typedef { {key: Property['key'], value: Property['value'], node: Property, propName: string} } ComponentObjectProp
23+
*/
24+
825
// ------------------------------------------------------------------------------
926
// Helpers
1027
// ------------------------------------------------------------------------------
@@ -382,7 +399,7 @@ module.exports = {
382399

383400
/**
384401
* Gets the property name of a given node.
385-
* @param {ASTNode} node - The node to get.
402+
* @param {Property|MethodDefinition|MemberExpression|Literal|TemplateLiteral|Identifier} node - The node to get.
386403
* @return {string|null} The property name if static. Otherwise, null.
387404
*/
388405
getStaticPropertyName (node) {
@@ -425,7 +442,7 @@ module.exports = {
425442
/**
426443
* Get all props by looking at all component's properties
427444
* @param {ObjectExpression} componentObject Object with component definition
428-
* @return {Array} Array of component props in format: [{key?: String, value?: ASTNode, node: ASTNod}]
445+
* @return {(ComponentArrayProp | ComponentObjectProp)[]} Array of component props in format: [{key?: String, value?: ASTNode, node: ASTNod}]
429446
*/
430447
getComponentProps (componentObject) {
431448
const propsNode = componentObject.properties
@@ -844,8 +861,9 @@ module.exports = {
844861

845862
/**
846863
* Unwrap typescript types like "X as F"
847-
* @param {ASTNode} node
848-
* @return {ASTNode}
864+
* @template T
865+
* @param {T} node
866+
* @return {T}
849867
*/
850868
unwrapTypes (node) {
851869
return node.type === 'TSAsExpression' ? node.expression : node

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,33 @@ ruleTester.run('require-default-prop', rule, {
153153
}
154154
}
155155
`
156+
},
157+
{
158+
// https://github.com/vuejs/eslint-plugin-vue/issues/1040
159+
filename: 'destructuring-test.vue',
160+
code: `
161+
export default {
162+
props: {
163+
foo: {
164+
...foo,
165+
default: 0
166+
},
167+
}
168+
}
169+
`
170+
},
171+
{
172+
filename: 'unknown-prop-details-test.vue',
173+
code: `
174+
export default {
175+
props: {
176+
foo: {
177+
[bar]: true,
178+
default: 0
179+
},
180+
}
181+
}
182+
`
156183
}
157184
],
158185

@@ -282,6 +309,33 @@ ruleTester.run('require-default-prop', rule, {
282309
message: `Prop '[baz.baz]' requires default value to be set.`,
283310
line: 6
284311
}]
312+
},
313+
{
314+
// https://github.com/vuejs/eslint-plugin-vue/issues/1040
315+
filename: 'destructuring-test.vue',
316+
code: `
317+
export default {
318+
props: {
319+
foo: {
320+
...foo
321+
},
322+
}
323+
}
324+
`,
325+
errors: ['Prop \'foo\' requires default value to be set.']
326+
},
327+
{
328+
filename: 'unknown-prop-details-test.vue',
329+
code: `
330+
export default {
331+
props: {
332+
foo: {
333+
[bar]: true
334+
},
335+
}
336+
}
337+
`,
338+
errors: ['Prop \'foo\' requires default value to be set.']
285339
}
286340
]
287341
})

0 commit comments

Comments
 (0)