Skip to content

Commit bb235b6

Browse files
refactor(component-meta): search prop defaults with symbol declarations (#5879)
1 parent 19a81d4 commit bb235b6

File tree

2 files changed

+88
-125
lines changed

2 files changed

+88
-125
lines changed

packages/component-meta/lib/base.ts

Lines changed: 52 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -291,56 +291,31 @@ function baseCreate(
291291
.map(prop => {
292292
const {
293293
resolveNestedProperties,
294-
} = createSchemaResolvers(ts, typeChecker, language, symbolNode, checkerOptions);
294+
} = createSchemaResolvers(ts, typeChecker, printer, language, symbolNode, checkerOptions);
295295

296296
return resolveNestedProperties(prop);
297297
})
298298
.filter(prop => !vnodeEventRegex.test(prop.name) && !eventProps.has(prop.name));
299299
}
300300

301-
// fill defaults
301+
// fill <script setup> defaults
302302
const sourceScript = language.scripts.get(componentPath);
303-
const sourceFile = program.getSourceFile(componentPath);
304-
const scriptRanges = sourceFile ? getScriptRanges(sourceFile) : undefined;
305303
const vueFile = sourceScript?.generated?.root;
306-
const defaults = sourceFile && scriptRanges
307-
? readDefaultsFromScript(
304+
const virtualCode = vueFile ? getVirtualCode(componentPath) : undefined;
305+
const scriptSetupRanges = virtualCode ? getScriptSetupRanges(virtualCode) : undefined;
306+
const defaults: Map<string, string> = virtualCode?.sfc.scriptSetup && scriptSetupRanges
307+
? collectPropDefaultsFromScriptSetup(
308308
ts,
309309
printer,
310-
sourceFile,
311-
scriptRanges,
312-
exportName,
310+
virtualCode.sfc.scriptSetup.ast,
311+
scriptSetupRanges,
313312
)
314-
: {};
315-
const virtualCode = vueFile ? getVirtualCode(componentPath) : undefined;
316-
const scriptSetupRanges = virtualCode ? getScriptSetupRanges(virtualCode) : undefined;
317-
318-
if (virtualCode?.sfc.scriptSetup && scriptSetupRanges) {
319-
Object.assign(
320-
defaults,
321-
readDefaultsFromScriptSetup(
322-
ts,
323-
printer,
324-
virtualCode.sfc.scriptSetup.ast,
325-
scriptSetupRanges,
326-
),
327-
);
328-
}
313+
: new Map();
329314

330-
for (
331-
const [propName, defaultExp] of Object.entries(defaults)
332-
) {
315+
for (const [propName, defaultExp] of defaults) {
333316
const prop = result.find(p => p.name === propName);
334317
if (prop) {
335-
prop.default = defaultExp.default;
336-
337-
if (defaultExp.required !== undefined) {
338-
prop.required = defaultExp.required;
339-
}
340-
341-
if (prop.default !== undefined) {
342-
prop.required = false; // props with default are always optional
343-
}
318+
prop.default ??= defaultExp;
344319
}
345320
}
346321

@@ -356,7 +331,7 @@ function baseCreate(
356331
return calls.map(call => {
357332
const {
358333
resolveEventSignature,
359-
} = createSchemaResolvers(ts, typeChecker, language, symbolNode, checkerOptions);
334+
} = createSchemaResolvers(ts, typeChecker, printer, language, symbolNode, checkerOptions);
360335

361336
return resolveEventSignature(call);
362337
}).filter(event => event.name);
@@ -374,7 +349,7 @@ function baseCreate(
374349
return properties.map(prop => {
375350
const {
376351
resolveSlotProperties,
377-
} = createSchemaResolvers(ts, typeChecker, language, symbolNode, checkerOptions);
352+
} = createSchemaResolvers(ts, typeChecker, printer, language, symbolNode, checkerOptions);
378353

379354
return resolveSlotProperties(prop);
380355
});
@@ -402,7 +377,7 @@ function baseCreate(
402377
return properties.map(prop => {
403378
const {
404379
resolveExposedProperties,
405-
} = createSchemaResolvers(ts, typeChecker, language, symbolNode, checkerOptions);
380+
} = createSchemaResolvers(ts, typeChecker, printer, language, symbolNode, checkerOptions);
406381

407382
return resolveExposedProperties(prop);
408383
});
@@ -458,6 +433,7 @@ function baseCreate(
458433
function createSchemaResolvers(
459434
ts: typeof import('typescript'),
460435
typeChecker: ts.TypeChecker,
436+
printer: ts.Printer,
461437
language: core.Language<string>,
462438
symbolNode: ts.Expression,
463439
{ rawType, schema: options, noDeclarations }: MetaCheckerOptions,
@@ -503,30 +479,49 @@ function createSchemaResolvers(
503479
}));
504480
}
505481

506-
function resolveNestedProperties(prop: ts.Symbol): PropertyMeta {
507-
const subtype = typeChecker.getTypeOfSymbolAtLocation(prop, symbolNode);
482+
function resolveNestedProperties(propSymbol: ts.Symbol): PropertyMeta {
483+
const subtype = typeChecker.getTypeOfSymbolAtLocation(propSymbol, symbolNode);
508484
let schema: PropertyMetaSchema | undefined;
509485
let declarations: Declaration[] | undefined;
510486
let global = false;
487+
let _default: string | undefined;
488+
let required = !(propSymbol.flags & ts.SymbolFlags.Optional);
511489

512-
for (const decl of prop.declarations ?? []) {
490+
for (const decl of propSymbol.declarations ?? []) {
513491
if (
514492
decl.getSourceFile() !== symbolNode.getSourceFile()
515493
&& isPublicProp(decl)
516494
) {
517495
global = true;
518496
}
497+
if (ts.isPropertyAssignment(decl) && ts.isObjectLiteralExpression(decl.initializer)) {
498+
for (const option of decl.initializer.properties) {
499+
if (ts.isPropertyAssignment(option)) {
500+
const key = option.name.getText();
501+
if (key === 'default') {
502+
const defaultExp = resolveDefaultOptionExpression(ts, option.initializer);
503+
_default = printer.printNode(ts.EmitHint.Expression, defaultExp, decl.getSourceFile());
504+
}
505+
else if (key === 'required') {
506+
if (option.initializer.getText() === 'true') {
507+
required = true;
508+
}
509+
}
510+
}
511+
}
512+
}
519513
}
520514

521515
return {
522-
name: prop.getEscapedName().toString(),
516+
name: propSymbol.getEscapedName().toString(),
523517
global,
524-
description: ts.displayPartsToString(prop.getDocumentationComment(typeChecker)),
525-
tags: getJsDocTags(prop),
526-
required: !(prop.flags & ts.SymbolFlags.Optional),
518+
default: _default,
519+
description: ts.displayPartsToString(propSymbol.getDocumentationComment(typeChecker)),
520+
tags: getJsDocTags(propSymbol),
521+
required,
527522
type: getFullyQualifiedName(subtype),
528523
get declarations() {
529-
return declarations ??= getDeclarations(prop.declarations ?? []);
524+
return declarations ??= getDeclarations(propSymbol.declarations ?? []);
530525
},
531526
get schema() {
532527
return schema ??= resolveSchema(subtype);
@@ -768,13 +763,13 @@ function createSchemaResolvers(
768763
};
769764
}
770765

771-
function readDefaultsFromScriptSetup(
766+
function collectPropDefaultsFromScriptSetup(
772767
ts: typeof import('typescript'),
773768
printer: ts.Printer,
774769
sourceFile: ts.SourceFile,
775770
scriptSetupRanges: core.ScriptSetupRanges,
776771
) {
777-
const result: Record<string, { default?: string }> = {};
772+
const result = new Map<string, string>();
778773

779774
if (scriptSetupRanges.withDefaults?.arg) {
780775
const obj = findObjectLiteralExpression(ts, scriptSetupRanges.withDefaults.arg.node);
@@ -783,28 +778,17 @@ function readDefaultsFromScriptSetup(
783778
if (ts.isPropertyAssignment(prop)) {
784779
const name = prop.name.getText(sourceFile);
785780
const expNode = resolveDefaultOptionExpression(ts, prop.initializer);
786-
const expText = printer.printNode(ts.EmitHint.Expression, expNode, sourceFile)
787-
?? expNode.getText(sourceFile);
788-
result[name] = { default: expText };
781+
const expText = printer.printNode(ts.EmitHint.Expression, expNode, sourceFile);
782+
result.set(name, expText);
789783
}
790784
}
791785
}
792786
}
793-
else if (scriptSetupRanges.defineProps?.arg) {
794-
const obj = findObjectLiteralExpression(ts, scriptSetupRanges.defineProps.arg.node);
795-
if (obj) {
796-
Object.assign(
797-
result,
798-
resolvePropsOption(ts, printer, sourceFile, obj),
799-
);
800-
}
801-
}
802787
else if (scriptSetupRanges.defineProps?.destructured) {
803788
for (const [name, initializer] of scriptSetupRanges.defineProps.destructured) {
804789
if (initializer) {
805-
const expText = printer.printNode(ts.EmitHint.Expression, initializer, sourceFile)
806-
?? initializer.getText(sourceFile);
807-
result[name] = { default: expText };
790+
const expText = printer.printNode(ts.EmitHint.Expression, initializer, sourceFile);
791+
result.set(name, expText);
808792
}
809793
}
810794
}
@@ -816,7 +800,10 @@ function readDefaultsFromScriptSetup(
816800
const name = defineModel.name
817801
? sourceFile.text.slice(defineModel.name.start, defineModel.name.end).slice(1, -1)
818802
: 'modelValue';
819-
result[name] = { default: resolveModelOption(ts, printer, sourceFile, obj) };
803+
const _default = resolveModelOption(ts, printer, sourceFile, obj);
804+
if (_default) {
805+
result.set(name, _default);
806+
}
820807
}
821808
}
822809
}
@@ -840,64 +827,6 @@ function findObjectLiteralExpression(
840827
return result;
841828
}
842829

843-
function readDefaultsFromScript(
844-
ts: typeof import('typescript'),
845-
printer: ts.Printer,
846-
sourceFile: ts.SourceFile,
847-
scriptRanges: core.ScriptRanges,
848-
exportName: string,
849-
) {
850-
const component = scriptRanges.exports[exportName];
851-
if (!component) {
852-
return {};
853-
}
854-
const props = component?.options?.args.node.properties.find(prop => prop.name?.getText(sourceFile) === 'props');
855-
if (props && ts.isPropertyAssignment(props)) {
856-
if (ts.isObjectLiteralExpression(props.initializer)) {
857-
return resolvePropsOption(ts, printer, sourceFile, props.initializer);
858-
}
859-
}
860-
return {};
861-
}
862-
863-
function resolvePropsOption(
864-
ts: typeof import('typescript'),
865-
printer: ts.Printer,
866-
sourceFile: ts.SourceFile,
867-
props: ts.ObjectLiteralExpression,
868-
) {
869-
const result: Record<string, { default?: string; required?: boolean }> = {};
870-
871-
for (const prop of props.properties) {
872-
if (ts.isPropertyAssignment(prop)) {
873-
const name = prop.name.getText(sourceFile);
874-
if (ts.isObjectLiteralExpression(prop.initializer)) {
875-
const defaultProp = prop.initializer.properties.find(p =>
876-
ts.isPropertyAssignment(p) && p.name.getText(sourceFile) === 'default'
877-
) as ts.PropertyAssignment | undefined;
878-
const requiredProp = prop.initializer.properties.find(p =>
879-
ts.isPropertyAssignment(p) && p.name.getText(sourceFile) === 'required'
880-
) as ts.PropertyAssignment | undefined;
881-
882-
result[name] = {};
883-
884-
if (requiredProp) {
885-
const exp = requiredProp.initializer.getText(sourceFile);
886-
result[name].required = exp === 'true';
887-
}
888-
if (defaultProp) {
889-
const expNode = resolveDefaultOptionExpression(ts, defaultProp.initializer);
890-
const expText = printer.printNode(ts.EmitHint.Expression, expNode, sourceFile)
891-
?? expNode.getText(sourceFile);
892-
result[name].default = expText;
893-
}
894-
}
895-
}
896-
}
897-
898-
return result;
899-
}
900-
901830
function resolveModelOption(
902831
ts: typeof import('typescript'),
903832
printer: ts.Printer,

0 commit comments

Comments
 (0)