Skip to content

Commit edd2248

Browse files
authored
Update vue/require-valid-default-prop rule to support <script setup> (#1538)
1 parent ee5ea4b commit edd2248

File tree

3 files changed

+292
-102
lines changed

3 files changed

+292
-102
lines changed

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

+199-102
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const { capitalize } = require('../utils/casing')
99
/**
1010
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
1111
* @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
12+
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
1213
* @typedef {import('../utils').VueObjectData} VueObjectData
1314
*/
1415

@@ -88,18 +89,45 @@ module.exports = {
8889
/** @param {RuleContext} context */
8990
create(context) {
9091
/**
91-
* @typedef { { type: string, function: false } } StandardValueType
92-
* @typedef { { type: 'Function', function: true, expression: true, functionBody: Expression, returnType: string | null } } FunctionExprValueType
93-
* @typedef { { type: 'Function', function: true, expression: false, functionBody: BlockStatement, returnTypes: ReturnType[] } } FunctionValueType
92+
* @typedef {object} StandardValueType
93+
* @property {string} type
94+
* @property {false} function
95+
*/
96+
/**
97+
* @typedef {object} FunctionExprValueType
98+
* @property {'Function'} type
99+
* @property {true} function
100+
* @property {true} expression
101+
* @property {Expression} functionBody
102+
* @property {string | null} returnType
103+
*/
104+
/**
105+
* @typedef {object} FunctionValueType
106+
* @property {'Function'} type
107+
* @property {true} function
108+
* @property {false} expression
109+
* @property {BlockStatement} functionBody
110+
* @property {ReturnType[]} returnTypes
111+
*/
112+
/**
94113
* @typedef { ComponentObjectProp & { value: ObjectExpression } } ComponentObjectDefineProp
95-
* @typedef { { prop: ComponentObjectDefineProp, type: Set<string>, default: FunctionValueType } } PropDefaultFunctionContext
96114
* @typedef { { type: string, node: Expression } } ReturnType
97115
*/
116+
/**
117+
* @typedef {object} PropDefaultFunctionContext
118+
* @property {ComponentObjectProp | ComponentTypeProp} prop
119+
* @property {Set<string>} types
120+
* @property {FunctionValueType} default
121+
*/
98122

99123
/**
100124
* @type {Map<ObjectExpression, PropDefaultFunctionContext[]>}
101125
*/
102126
const vueObjectPropsContexts = new Map()
127+
/**
128+
* @type { {node: CallExpression, props:PropDefaultFunctionContext[]}[] }
129+
*/
130+
const scriptSetupPropsContexts = []
103131

104132
/**
105133
* @typedef {object} ScopeStack
@@ -194,7 +222,7 @@ module.exports = {
194222

195223
/**
196224
* @param {*} node
197-
* @param {ComponentObjectProp} prop
225+
* @param {ComponentObjectProp | ComponentTypeProp} prop
198226
* @param {Iterable<string>} expectedTypeNames
199227
*/
200228
function report(node, prop, expectedTypeNames) {
@@ -213,127 +241,196 @@ module.exports = {
213241
})
214242
}
215243

216-
// ----------------------------------------------------------------------
217-
// Public
218-
// ----------------------------------------------------------------------
219-
220-
return utils.defineVueVisitor(context, {
221-
onVueObjectEnter(obj) {
222-
/** @type {ComponentObjectDefineProp[]} */
223-
const props = utils.getComponentProps(obj).filter(
224-
/**
225-
* @param {ComponentObjectProp | ComponentArrayProp} prop
226-
* @returns {prop is ComponentObjectDefineProp}
227-
*/
228-
(prop) =>
229-
Boolean(prop.value && prop.value.type === 'ObjectExpression')
230-
)
231-
/** @type {PropDefaultFunctionContext[]} */
232-
const propContexts = []
233-
for (const prop of props) {
244+
/**
245+
* @param {(ComponentObjectDefineProp | ComponentTypeProp)[]} props
246+
* @param { { [key: string]: Expression | undefined } } withDefaults
247+
*/
248+
function processPropDefs(props, withDefaults) {
249+
/** @type {PropDefaultFunctionContext[]} */
250+
const propContexts = []
251+
for (const prop of props) {
252+
let typeList
253+
let defExpr
254+
if (prop.type === 'object') {
234255
const type = getPropertyNode(prop.value, 'type')
235256
if (!type) continue
236257

237-
const typeNames = new Set(
238-
getTypes(type.value).filter((item) => NATIVE_TYPES.has(item))
239-
)
240-
241-
// There is no native types detected
242-
if (typeNames.size === 0) continue
258+
typeList = getTypes(type.value)
243259

244260
const def = getPropertyNode(prop.value, 'default')
245261
if (!def) continue
246262

247-
const defType = getValueType(def.value)
263+
defExpr = def.value
264+
} else {
265+
typeList = prop.types
266+
defExpr = withDefaults[prop.propName]
267+
}
268+
if (!defExpr) continue
269+
270+
const typeNames = new Set(
271+
typeList.filter((item) => NATIVE_TYPES.has(item))
272+
)
273+
// There is no native types detected
274+
if (typeNames.size === 0) continue
275+
276+
const defType = getValueType(defExpr)
248277

249-
if (!defType) continue
278+
if (!defType) continue
250279

251-
if (!defType.function) {
252-
if (typeNames.has(defType.type)) {
253-
if (!FUNCTION_VALUE_TYPES.has(defType.type)) {
254-
continue
255-
}
280+
if (!defType.function) {
281+
if (typeNames.has(defType.type)) {
282+
if (!FUNCTION_VALUE_TYPES.has(defType.type)) {
283+
continue
256284
}
257-
report(
258-
def.value,
259-
prop,
260-
Array.from(typeNames).map((type) =>
261-
FUNCTION_VALUE_TYPES.has(type) ? 'Function' : type
262-
)
285+
}
286+
report(
287+
defExpr,
288+
prop,
289+
Array.from(typeNames).map((type) =>
290+
FUNCTION_VALUE_TYPES.has(type) ? 'Function' : type
263291
)
264-
} else {
265-
if (typeNames.has('Function')) {
292+
)
293+
} else {
294+
if (typeNames.has('Function')) {
295+
continue
296+
}
297+
if (defType.expression) {
298+
if (!defType.returnType || typeNames.has(defType.returnType)) {
266299
continue
267300
}
268-
if (defType.expression) {
269-
if (!defType.returnType || typeNames.has(defType.returnType)) {
270-
continue
271-
}
272-
report(defType.functionBody, prop, typeNames)
273-
} else {
274-
propContexts.push({
275-
prop,
276-
type: typeNames,
277-
default: defType
301+
report(defType.functionBody, prop, typeNames)
302+
} else {
303+
propContexts.push({
304+
prop,
305+
types: typeNames,
306+
default: defType
307+
})
308+
}
309+
}
310+
}
311+
return propContexts
312+
}
313+
314+
// ----------------------------------------------------------------------
315+
// Public
316+
// ----------------------------------------------------------------------
317+
318+
return utils.compositingVisitors(
319+
{
320+
/**
321+
* @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
322+
*/
323+
':function'(node) {
324+
scopeStack = {
325+
upper: scopeStack,
326+
body: node.body,
327+
returnTypes: null
328+
}
329+
},
330+
/**
331+
* @param {ReturnStatement} node
332+
*/
333+
ReturnStatement(node) {
334+
if (!scopeStack) {
335+
return
336+
}
337+
if (scopeStack.returnTypes && node.argument) {
338+
const type = getValueType(node.argument)
339+
if (type) {
340+
scopeStack.returnTypes.push({
341+
type: type.type,
342+
node: node.argument
278343
})
279344
}
280345
}
281-
}
282-
vueObjectPropsContexts.set(obj, propContexts)
346+
},
347+
':function:exit': onFunctionExit
283348
},
284-
/**
285-
* @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
286-
* @param {VueObjectData} data
287-
*/
288-
':function'(node, { node: vueNode }) {
289-
scopeStack = {
290-
upper: scopeStack,
291-
body: node.body,
292-
returnTypes: null
293-
}
349+
utils.defineVueVisitor(context, {
350+
onVueObjectEnter(obj) {
351+
/** @type {ComponentObjectDefineProp[]} */
352+
const props = utils.getComponentProps(obj).filter(
353+
/**
354+
* @param {ComponentObjectProp | ComponentArrayProp} prop
355+
* @returns {prop is ComponentObjectDefineProp}
356+
*/
357+
(prop) =>
358+
Boolean(
359+
prop.type === 'object' && prop.value.type === 'ObjectExpression'
360+
)
361+
)
362+
const propContexts = processPropDefs(props, {})
363+
vueObjectPropsContexts.set(obj, propContexts)
364+
},
365+
/**
366+
* @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
367+
* @param {VueObjectData} data
368+
*/
369+
':function'(node, { node: vueNode }) {
370+
const data = vueObjectPropsContexts.get(vueNode)
371+
if (!data || !scopeStack) {
372+
return
373+
}
294374

295-
const data = vueObjectPropsContexts.get(vueNode)
296-
if (!data) {
297-
return
298-
}
375+
for (const { default: defType } of data) {
376+
if (node.body === defType.functionBody) {
377+
scopeStack.returnTypes = defType.returnTypes
378+
}
379+
}
380+
},
381+
onVueObjectExit(obj) {
382+
const data = vueObjectPropsContexts.get(obj)
383+
if (!data) {
384+
return
385+
}
386+
for (const { prop, types: typeNames, default: defType } of data) {
387+
for (const returnType of defType.returnTypes) {
388+
if (typeNames.has(returnType.type)) continue
299389

300-
for (const { default: defType } of data) {
301-
if (node.body === defType.functionBody) {
302-
scopeStack.returnTypes = defType.returnTypes
390+
report(returnType.node, prop, typeNames)
391+
}
303392
}
304393
}
305-
},
306-
/**
307-
* @param {ReturnStatement} node
308-
*/
309-
ReturnStatement(node) {
310-
if (!scopeStack) {
311-
return
312-
}
313-
if (scopeStack.returnTypes && node.argument) {
314-
const type = getValueType(node.argument)
315-
if (type) {
316-
scopeStack.returnTypes.push({
317-
type: type.type,
318-
node: node.argument
319-
})
394+
}),
395+
utils.defineScriptSetupVisitor(context, {
396+
onDefinePropsEnter(node, baseProps) {
397+
/** @type {(ComponentObjectDefineProp | ComponentTypeProp)[]} */
398+
const props = baseProps.filter(
399+
/**
400+
* @param {ComponentObjectProp | ComponentArrayProp | ComponentTypeProp} prop
401+
* @returns {prop is ComponentObjectDefineProp | ComponentTypeProp}
402+
*/
403+
(prop) =>
404+
Boolean(
405+
prop.type === 'type' ||
406+
(prop.type === 'object' &&
407+
prop.value.type === 'ObjectExpression')
408+
)
409+
)
410+
const defaults = utils.getWithDefaultsPropExpressions(node)
411+
const propContexts = processPropDefs(props, defaults)
412+
scriptSetupPropsContexts.push({ node, props: propContexts })
413+
},
414+
/**
415+
* @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
416+
*/
417+
':function'(node) {
418+
const data =
419+
scriptSetupPropsContexts[scriptSetupPropsContexts.length - 1]
420+
if (!data || !scopeStack) {
421+
return
320422
}
321-
}
322-
},
323-
':function:exit': onFunctionExit,
324-
onVueObjectExit(obj) {
325-
const data = vueObjectPropsContexts.get(obj)
326-
if (!data) {
327-
return
328-
}
329-
for (const { prop, type: typeNames, default: defType } of data) {
330-
for (const returnType of defType.returnTypes) {
331-
if (typeNames.has(returnType.type)) continue
332423

333-
report(returnType.node, prop, typeNames)
424+
for (const { default: defType } of data.props) {
425+
if (node.body === defType.functionBody) {
426+
scopeStack.returnTypes = defType.returnTypes
427+
}
334428
}
429+
},
430+
onDefinePropsExit(node) {
431+
scriptSetupPropsContexts.pop()
335432
}
336-
}
337-
})
433+
})
434+
)
338435
}
339436
}

0 commit comments

Comments
 (0)