Skip to content

Commit 9c8f293

Browse files
authored
Update vue/require-default-prop rule to support <script setup> (#1545)
* Update `vue/require-default-prop` rule to support `<script setup>` * fix test on node v8
1 parent 86aff15 commit 9c8f293

File tree

3 files changed

+241
-72
lines changed

3 files changed

+241
-72
lines changed

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

+84-64
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
'use strict'
66

77
/**
8+
* @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
89
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
10+
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
911
* @typedef {ComponentObjectProp & { value: ObjectExpression} } ComponentObjectPropObject
1012
*/
1113

@@ -35,7 +37,10 @@ module.exports = {
3537
url: 'https://eslint.vuejs.org/rules/require-default-prop.html'
3638
},
3739
fixable: null, // or "code" or "whitespace"
38-
schema: []
40+
schema: [],
41+
messages: {
42+
missingDefault: `Prop '{{propName}}' requires default value to be set.`
43+
}
3944
},
4045
/** @param {RuleContext} context */
4146
create(context) {
@@ -45,11 +50,11 @@ module.exports = {
4550

4651
/**
4752
* Checks if the passed prop is required
48-
* @param {ComponentObjectPropObject} prop - Property AST node for a single prop
53+
* @param {ObjectExpression} propValue - ObjectExpression AST node for a single prop
4954
* @return {boolean}
5055
*/
51-
function propIsRequired(prop) {
52-
const propRequiredNode = prop.value.properties.find(
56+
function propIsRequired(propValue) {
57+
const propRequiredNode = propValue.properties.find(
5358
(p) =>
5459
p.type === 'Property' &&
5560
utils.getStaticPropertyName(p) === 'required' &&
@@ -62,11 +67,11 @@ module.exports = {
6267

6368
/**
6469
* Checks if the passed prop has a default value
65-
* @param {ComponentObjectPropObject} prop - Property AST node for a single prop
70+
* @param {ObjectExpression} propValue - ObjectExpression AST node for a single prop
6671
* @return {boolean}
6772
*/
68-
function propHasDefault(prop) {
69-
const propDefaultNode = prop.value.properties.find(
73+
function propHasDefault(propValue) {
74+
const propDefaultNode = propValue.properties.find(
7075
(p) =>
7176
p.type === 'Property' && utils.getStaticPropertyName(p) === 'default'
7277
)
@@ -75,32 +80,27 @@ module.exports = {
7580
}
7681

7782
/**
78-
* Finds all props that don't have a default value set
79-
* @param {ComponentObjectProp[]} props - Vue component's "props" node
80-
* @return {ComponentObjectProp[]} Array of props without "default" value
83+
* Checks whether the given props that don't have a default value
84+
* @param {ComponentObjectProp} prop Vue component's "props" node
85+
* @return {boolean}
8186
*/
82-
function findPropsWithoutDefaultValue(props) {
83-
return props.filter((prop) => {
84-
if (prop.value.type !== 'ObjectExpression') {
85-
if (prop.value.type === 'Identifier') {
86-
return NATIVE_TYPES.has(prop.value.name)
87-
}
88-
if (
89-
prop.value.type === 'CallExpression' ||
90-
prop.value.type === 'MemberExpression'
91-
) {
92-
// OK
93-
return false
94-
}
95-
// NG
96-
return true
87+
function isWithoutDefaultValue(prop) {
88+
if (prop.value.type !== 'ObjectExpression') {
89+
if (prop.value.type === 'Identifier') {
90+
return NATIVE_TYPES.has(prop.value.name)
91+
}
92+
if (
93+
prop.value.type === 'CallExpression' ||
94+
prop.value.type === 'MemberExpression'
95+
) {
96+
// OK
97+
return false
9798
}
99+
// NG
100+
return true
101+
}
98102

99-
return (
100-
!propIsRequired(/** @type {ComponentObjectPropObject} */ (prop)) &&
101-
!propHasDefault(/** @type {ComponentObjectPropObject} */ (prop))
102-
)
103-
})
103+
return !propIsRequired(prop.value) && !propHasDefault(prop.value)
104104
}
105105

106106
/**
@@ -145,46 +145,66 @@ module.exports = {
145145
}
146146

147147
/**
148-
* Excludes purely Boolean props from the Array
149-
* @param {ComponentObjectProp[]} props - Array with props
150-
* @return {ComponentObjectProp[]}
148+
* @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props
149+
* @param {boolean} [withDefaults]
150+
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions]
151151
*/
152-
function excludeBooleanProps(props) {
153-
return props.filter((prop) => !isBooleanProp(prop))
152+
function processProps(props, withDefaults, withDefaultsExpressions) {
153+
for (const prop of props) {
154+
if (prop.type === 'object' && !prop.node.shorthand) {
155+
if (!isWithoutDefaultValue(prop)) {
156+
continue
157+
}
158+
if (isBooleanProp(prop)) {
159+
continue
160+
}
161+
const propName =
162+
prop.propName != null
163+
? prop.propName
164+
: `[${context.getSourceCode().getText(prop.node.key)}]`
165+
166+
context.report({
167+
node: prop.node,
168+
messageId: `missingDefault`,
169+
data: {
170+
propName
171+
}
172+
})
173+
} else if (
174+
prop.type === 'type' &&
175+
withDefaults &&
176+
withDefaultsExpressions
177+
) {
178+
if (!withDefaultsExpressions[prop.propName]) {
179+
context.report({
180+
node: prop.node,
181+
messageId: `missingDefault`,
182+
data: {
183+
propName: prop.propName
184+
}
185+
})
186+
}
187+
}
188+
}
154189
}
155190

156191
// ----------------------------------------------------------------------
157192
// Public
158193
// ----------------------------------------------------------------------
159194

160-
return utils.executeOnVue(context, (obj) => {
161-
const props = utils
162-
.getComponentProps(obj)
163-
.filter(
164-
(prop) =>
165-
prop.value &&
166-
!(prop.node.type === 'Property' && prop.node.shorthand)
167-
)
168-
169-
const propsWithoutDefault = findPropsWithoutDefaultValue(
170-
/** @type {ComponentObjectProp[]} */ (props)
171-
)
172-
const propsToReport = excludeBooleanProps(propsWithoutDefault)
173-
174-
for (const prop of propsToReport) {
175-
const propName =
176-
prop.propName != null
177-
? prop.propName
178-
: `[${context.getSourceCode().getText(prop.node.key)}]`
179-
180-
context.report({
181-
node: prop.node,
182-
message: `Prop '{{propName}}' requires default value to be set.`,
183-
data: {
184-
propName
185-
}
186-
})
187-
}
188-
})
195+
return utils.compositingVisitors(
196+
utils.defineScriptSetupVisitor(context, {
197+
onDefinePropsEnter(node, props) {
198+
processProps(
199+
props,
200+
utils.hasWithDefaults(node),
201+
utils.getWithDefaultsPropExpressions(node)
202+
)
203+
}
204+
}),
205+
utils.executeOnVue(context, (obj) => {
206+
processProps(utils.getComponentProps(obj))
207+
})
208+
)
189209
}
190210
}

Diff for: lib/utils/index.js

+23-7
Original file line numberDiff line numberDiff line change
@@ -1139,19 +1139,20 @@ module.exports = {
11391139
return scriptSetupVisitor
11401140
},
11411141

1142+
/**
1143+
* Checks whether given defineProps call node has withDefaults.
1144+
* @param {CallExpression} node The node of defineProps
1145+
* @returns {node is CallExpression & { parent: CallExpression }}
1146+
*/
1147+
hasWithDefaults,
1148+
11421149
/**
11431150
* Gets a map of the expressions defined in withDefaults.
11441151
* @param {CallExpression} node The node of defineProps
11451152
* @returns { { [key: string]: Expression | undefined } }
11461153
*/
11471154
getWithDefaultsPropExpressions(node) {
1148-
if (
1149-
!node.parent ||
1150-
node.parent.type !== 'CallExpression' ||
1151-
node.parent.arguments[0] !== node ||
1152-
node.parent.callee.type !== 'Identifier' ||
1153-
node.parent.callee.name !== 'withDefaults'
1154-
) {
1155+
if (!hasWithDefaults(node)) {
11551156
return {}
11561157
}
11571158
const param = node.parent.arguments[1]
@@ -2384,6 +2385,21 @@ function hasDirective(node, name, argument) {
23842385
return Boolean(getDirective(node, name, argument))
23852386
}
23862387

2388+
/**
2389+
* Checks whether given defineProps call node has withDefaults.
2390+
* @param {CallExpression} node The node of defineProps
2391+
* @returns {node is CallExpression & { parent: CallExpression }}
2392+
*/
2393+
function hasWithDefaults(node) {
2394+
return (
2395+
node.parent &&
2396+
node.parent.type === 'CallExpression' &&
2397+
node.parent.arguments[0] === node &&
2398+
node.parent.callee.type === 'Identifier' &&
2399+
node.parent.callee.name === 'withDefaults'
2400+
)
2401+
}
2402+
23872403
/**
23882404
* Get all props by looking at all component's properties
23892405
* @param {ObjectExpression|ArrayExpression} propsNode Object with props definition

0 commit comments

Comments
 (0)