Skip to content

Commit 810123c

Browse files
authored
fix(component-meta): resolve defineModel options to collect default value (#5209)
1 parent 5c33b8f commit 810123c

File tree

5 files changed

+109
-31
lines changed

5 files changed

+109
-31
lines changed

Diff for: packages/component-meta/lib/base.ts

+42-8
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ ${commandLine.vueOptions.target < 3 ? vue2TypeHelpersCode : typeHelpersCode}
416416
const type = typeChecker.getTypeOfSymbolAtLocation($exposed, symbolNode);
417417
const properties = type.getProperties().filter(prop =>
418418
// only exposed props will not have a valueDeclaration
419-
!(prop as any).valueDeclaration
419+
!prop.valueDeclaration
420420
);
421421

422422
return properties.map(prop => {
@@ -728,10 +728,10 @@ function readVueComponentDefaultProps(
728728

729729
function scriptSetupWorker() {
730730

731-
const ast = sfc.scriptSetup?.ast;
732-
if (!ast) {
731+
if (!sfc.scriptSetup) {
733732
return;
734733
}
734+
const { ast } = sfc.scriptSetup;
735735

736736
const codegen = vue.tsCodegen.get(sfc);
737737
const scriptSetupRanges = codegen?.getScriptSetupRanges();
@@ -762,10 +762,22 @@ function readVueComponentDefaultProps(
762762
}
763763
}
764764
else if (scriptSetupRanges?.defineProps?.destructured) {
765-
for (const [prop, initializer] of scriptSetupRanges.defineProps.destructured) {
765+
for (const [name, initializer] of scriptSetupRanges.defineProps.destructured) {
766766
if (initializer) {
767767
const expText = printer?.printNode(ts.EmitHint.Expression, initializer, ast) ?? initializer.getText(ast);
768-
result[prop] = { default: expText };
768+
result[name] = {
769+
default: expText
770+
};
771+
}
772+
}
773+
}
774+
775+
if (scriptSetupRanges?.defineProp) {
776+
for (const defineProp of scriptSetupRanges.defineProp) {
777+
const obj = defineProp.argNode ? findObjectLiteralExpression(defineProp.argNode) : undefined;
778+
if (obj) {
779+
const name = defineProp.name ? sfc.scriptSetup.content.slice(defineProp.name.start, defineProp.name.end).slice(1, -1) : 'modelValue';
780+
result[name] = resolveModelOption(ast, obj, printer, ts);
769781
}
770782
}
771783
}
@@ -786,10 +798,10 @@ function readVueComponentDefaultProps(
786798

787799
function scriptWorker() {
788800

789-
const ast = sfc.script?.ast;
790-
if (!ast) {
801+
if (!sfc.script) {
791802
return;
792803
}
804+
const { ast } = sfc.script;
793805

794806
const scriptResult = readTsComponentDefaultProps(ast, 'default', printer, ts);
795807
for (const [key, value] of Object.entries(scriptResult)) {
@@ -899,7 +911,7 @@ function resolvePropsOption(
899911
result[name].required = exp === 'true';
900912
}
901913
if (defaultProp) {
902-
const expNode = resolveDefaultOptionExpression((defaultProp as any).initializer, ts);
914+
const expNode = resolveDefaultOptionExpression(defaultProp.initializer, ts);
903915
const expText = printer?.printNode(ts.EmitHint.Expression, expNode, ast) ?? expNode.getText(ast);
904916
result[name].default = expText;
905917
}
@@ -910,6 +922,28 @@ function resolvePropsOption(
910922
return result;
911923
}
912924

925+
function resolveModelOption(
926+
ast: ts.SourceFile,
927+
options: ts.ObjectLiteralExpression,
928+
printer: ts.Printer | undefined,
929+
ts: typeof import('typescript')
930+
) {
931+
const result: { default?: string } = {};
932+
933+
for (const prop of options.properties) {
934+
if (ts.isPropertyAssignment(prop)) {
935+
const name = prop.name.getText(ast);
936+
if (name === 'default') {
937+
const expNode = resolveDefaultOptionExpression(prop.initializer, ts);
938+
const expText = printer?.printNode(ts.EmitHint.Expression, expNode, ast) ?? expNode.getText(ast);
939+
result.default = expText;
940+
}
941+
}
942+
}
943+
944+
return result;
945+
}
946+
913947
function resolveDefaultOptionExpression(
914948
_default: ts.Expression,
915949
ts: typeof import('typescript')

Diff for: packages/component-meta/tests/index.spec.ts

+39-7
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,55 @@ const worker = (checker: ComponentMetaChecker, withTsconfig: boolean) => describ
2525

2626
// expect(meta.type).toEqual(TypeMeta.Class);
2727

28+
const modelValue = meta.props.find(prop => prop.name === 'modelValue');
29+
const onUpdateModelValue = meta.events.find(event => event.name === 'update:modelValue');
30+
2831
const foo = meta.props.find(prop => prop.name === 'foo');
2932
const onUpdateFoo = meta.events.find(event => event.name === 'update:foo');
3033

3134
const bar = meta.props.find(prop => prop.name === 'bar');
3235
const onUpdateBar = meta.events.find(event => event.name === 'update:bar');
3336

34-
const qux = meta.props.find(prop => prop.name === 'qux');
35-
const quxModifiers = meta.props.find(prop => prop.name === 'quxModifiers');
36-
const onUpdateQux = meta.events.find(event => event.name === 'update:qux');
37+
const baz = meta.props.find(prop => prop.name === 'baz');
38+
const bazModifiers = meta.props.find(prop => prop.name === 'bazModifiers');
39+
const onUpdateBaz = meta.events.find(event => event.name === 'update:baz');
40+
41+
expect(modelValue).toBeDefined();
42+
expect(modelValue?.default).toBeUndefined();
43+
expect(modelValue?.required).toBeFalsy();
44+
expect(modelValue?.type).toEqual('number | undefined');
45+
expect(modelValue?.schema).toEqual({
46+
kind: 'enum',
47+
type: 'number | undefined',
48+
schema: ['undefined', 'number'],
49+
});
50+
expect(onUpdateModelValue).toBeDefined();
3751

3852
expect(foo).toBeDefined();
39-
expect(bar).toBeDefined();
40-
expect(qux).toBeDefined();
41-
expect(quxModifiers).toBeDefined();
53+
expect(foo?.default).toBeUndefined();
54+
expect(foo?.required).toBeTruthy();
55+
expect(foo?.type).toEqual('string[]');
56+
expect(foo?.schema).toEqual({
57+
kind: 'array',
58+
type: 'string[]',
59+
schema: ['string'],
60+
});
4261
expect(onUpdateFoo).toBeDefined();
62+
63+
expect(bar).toBeDefined();
64+
expect(bar?.default).toBe('false');
65+
expect(bar?.required).toBeFalsy();
66+
expect(bar?.type).toEqual('boolean | undefined');
67+
expect(bar?.schema).toEqual({
68+
kind: 'enum',
69+
type: 'boolean | undefined',
70+
schema: ['undefined', 'false', 'true'],
71+
});
4372
expect(onUpdateBar).toBeDefined();
44-
expect(onUpdateQux).toBeDefined();
73+
74+
expect(baz).toBeDefined();
75+
expect(bazModifiers).toBeDefined();
76+
expect(onUpdateBaz).toBeDefined();
4577
});
4678

4779
test('reference-type-props', () => {

Diff for: packages/language-core/lib/parsers/scriptSetupRanges.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type DefineProp = {
2020
defaultValue?: TextRange;
2121
required?: boolean;
2222
isModel?: boolean;
23+
// used by component-meta
24+
argNode?: ts.Expression;
2325
};
2426

2527
type DefineProps = CallExpressionRange & {
@@ -153,7 +155,7 @@ export function parseScriptSetupRanges(
153155
if (vueCompilerOptions.macros.defineModel.includes(callText)) {
154156
let localName: TextRange | undefined;
155157
let propName: TextRange | undefined;
156-
let options: ts.Node | undefined;
158+
let options: ts.Expression | undefined;
157159

158160
if (
159161
ts.isVariableDeclaration(parent) &&
@@ -204,12 +206,13 @@ export function parseScriptSetupRanges(
204206
defaultValue,
205207
required,
206208
isModel: true,
209+
argNode: options,
207210
});
208211
}
209212
else if (callText === 'defineProp') {
210213
let localName: TextRange | undefined;
211214
let propName: TextRange | undefined;
212-
let options: ts.Node | undefined;
215+
let options: ts.Expression | undefined;
213216

214217
if (
215218
ts.isVariableDeclaration(parent) &&
@@ -280,6 +283,7 @@ export function parseScriptSetupRanges(
280283
runtimeType,
281284
defaultValue,
282285
required,
286+
argNode: options,
283287
});
284288
}
285289
else if (vueCompilerOptions.macros.defineProps.includes(callText)) {

Diff for: packages/tsc/tests/__snapshots__/dts.spec.ts.snap

+14-11
Original file line numberDiff line numberDiff line change
@@ -299,20 +299,23 @@ export default _default;
299299
300300
exports[`vue-tsc-dts > Input: reference-type-model/component.vue, Output: reference-type-model/component.vue.d.ts 1`] = `
301301
"type __VLS_PublicProps = {
302-
"foo"?: number;
303-
"bar"?: string[];
304-
"qux"?: string;
305-
'quxModifiers'?: Partial<Record<'lazy' | 'trim', true>>;
302+
modelValue?: number;
303+
'foo': string[];
304+
'bar'?: boolean;
305+
'baz'?: string;
306+
'bazModifiers'?: Partial<Record<'lazy' | 'trim', true>>;
306307
};
307308
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
308-
"update:foo": (value: number) => any;
309-
"update:bar": (value: string[]) => any;
310-
"update:qux": (value: string) => any;
309+
"update:modelValue": (value: number) => any;
310+
"update:foo": (value: string[]) => any;
311+
"update:bar": (value: boolean) => any;
312+
"update:baz": (value: string) => any;
311313
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
312-
"onUpdate:foo"?: (value: number) => any;
313-
"onUpdate:bar"?: (value: string[]) => any;
314-
"onUpdate:qux"?: (value: string) => any;
315-
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
314+
"onUpdate:modelValue"?: (value: number) => any;
315+
"onUpdate:foo"?: (value: string[]) => any;
316+
"onUpdate:bar"?: (value: boolean) => any;
317+
"onUpdate:baz"?: (value: string) => any;
318+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
316319
export default _default;
317320
"
318321
`;
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<script setup lang="ts">
2-
const bar = defineModel<number>("foo")
3-
const baz = defineModel<string[]>("bar")
4-
const [qux, modifiers] = defineModel<string, 'lazy' | 'trim'>("qux")
2+
defineModel<number>();
3+
defineModel<string[]>('foo', {
4+
required: true,
5+
});
6+
defineModel<boolean>('bar', {
7+
default: false,
8+
});
9+
defineModel<string, 'lazy' | 'trim'>('baz');
510
</script>

0 commit comments

Comments
 (0)