From a9015fdec4e11aef6e6d7d57cc0045adffb5268d Mon Sep 17 00:00:00 2001 From: Shean de Montigny-Desautels Date: Thu, 29 Feb 2024 08:18:44 -0500 Subject: [PATCH] feat(language-core): add emit codegen for defineModel (#3895) * feat() add emit codegen for defineModel * chore() add generated snapshot by test --- packages/component-meta/tests/index.spec.ts | 18 ++++++++ .../language-core/src/generators/script.ts | 41 +++++++++++++++++-- .../src/parsers/scriptSetupRanges.ts | 2 + .../tsc/tests/__snapshots__/dts.spec.ts.snap | 18 ++++++++ .../reference-type-model/component.vue | 4 ++ test-workspace/tsc/vue2/tsconfig.json | 1 + test-workspace/tsc/vue3/defineModel/main.vue | 23 ++++++++++- 7 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 test-workspace/component-meta/reference-type-model/component.vue diff --git a/packages/component-meta/tests/index.spec.ts b/packages/component-meta/tests/index.spec.ts index 7091baa0f8..ba956cebdb 100644 --- a/packages/component-meta/tests/index.spec.ts +++ b/packages/component-meta/tests/index.spec.ts @@ -19,6 +19,24 @@ const worker = (checker: ComponentMetaChecker, withTsconfig: boolean) => describ expect(meta.props.filter(prop => !prop.global)).toEqual([]); }); + test('reference-type-model', () => { + const componentPath = path.resolve(__dirname, '../../../test-workspace/component-meta/reference-type-model/component.vue'); + const meta = checker.getComponentMeta(componentPath); + + // expect(meta.type).toEqual(TypeMeta.Class); + + const foo = meta.props.find(prop => prop.name === 'foo'); + const onUpdateFoo = meta.events.find(event => event.name === 'update:foo') + + const bar = meta.props.find(prop => prop.name === 'bar'); + const onUpdateBar = meta.events.find(event => event.name === 'update:bar') + + expect(foo).toBeDefined(); + expect(bar).toBeDefined(); + expect(onUpdateFoo).toBeDefined(); + expect(onUpdateBar).toBeDefined(); + }) + test('reference-type-props', () => { const componentPath = path.resolve(__dirname, '../../../test-workspace/component-meta/reference-type-props/component.vue'); const meta = checker.getComponentMeta(componentPath); diff --git a/packages/language-core/src/generators/script.ts b/packages/language-core/src/generators/script.ts index b28e6ab8c3..aa95a72a15 100644 --- a/packages/language-core/src/generators/script.ts +++ b/packages/language-core/src/generators/script.ts @@ -422,6 +422,37 @@ type __VLS_PrettifyGlobal = { [K in keyof T]: T[K]; } & {}; enableAllFeatures({}), ]); } + function* generateModelEmits(): Generator { + if (!scriptSetup || !scriptSetupRanges) { + return; + } + + yield _(`let __VLS_modelEmitsType!: {}`); + + if (scriptSetupRanges.defineProp.length) { + yield _(` & ReturnType>`); + } + yield _(`;\n`); + } function* generateScriptSetupAndTemplate(): Generator { if (!scriptSetup || !scriptSetupRanges) return; @@ -515,6 +546,8 @@ type __VLS_PrettifyGlobal = { [K in keyof T]: T[K]; } & {}; } yield _(`;\n`); + yield* generateModelEmits() + yield _(`let __VLS_fnPropsDefineComponent!: InstanceType['$props'];\n`); yield _(`let __VLS_fnPropsSlots!: `); if (scriptSetupRanges.slots.define && vueCompilerOptions.jsxSlots) { @@ -536,7 +569,7 @@ type __VLS_PrettifyGlobal = { [K in keyof T]: T[K]; } & {}; + ` expose(exposed: import('${vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.expose.define ? 'typeof __VLS_exposed' : '{}'}>): void,\n` + ` attrs: any,\n` + ` slots: ReturnType,\n` - + ` emit: typeof ${scriptSetupRanges.emits.name ?? '__VLS_emit'},\n` + + ` emit: typeof ${scriptSetupRanges.emits.name ?? '__VLS_emit'} & typeof __VLS_modelEmitsType,\n` + ` };\n`); yield _(` })(),\n`); // __VLS_setup = (async () => { yield _(`) => ({} as import('${vueCompilerOptions.lib}').VNode & { __ctx?: Awaited }))`); @@ -723,6 +756,7 @@ type __VLS_PrettifyGlobal = { [K in keyof T]: T[K]; } & {}; yield _(`};\n`); } + yield* generateModelEmits() yield* generateTemplate(functional); if (mode === 'return' || mode === 'export') { @@ -823,11 +857,12 @@ type __VLS_PrettifyGlobal = { [K in keyof T]: T[K]; } & {}; } yield _(`},\n`); } + yield _(`emits: ({} as __VLS_NormalizeEmits),\n`); } + yield _(`>),\n`); } if (script && scriptRanges?.exportDefault?.args) { yield _(generateSourceCode(script, scriptRanges.exportDefault.args.start + 1, scriptRanges.exportDefault.args.end - 1)); diff --git a/packages/language-core/src/parsers/scriptSetupRanges.ts b/packages/language-core/src/parsers/scriptSetupRanges.ts index 281dee9f99..a03e8e477e 100644 --- a/packages/language-core/src/parsers/scriptSetupRanges.ts +++ b/packages/language-core/src/parsers/scriptSetupRanges.ts @@ -42,6 +42,7 @@ export function parseScriptSetupRanges( type: TextRange | undefined; defaultValue: TextRange | undefined; required: boolean; + isModel?: boolean; }[] = []; const bindings = parseBindingRanges(ts, ast); const text = ast.text; @@ -133,6 +134,7 @@ export function parseScriptSetupRanges( type: node.typeArguments?.length ? _getStartEnd(node.typeArguments[0]) : undefined, defaultValue: undefined, required, + isModel: true, }); } else if (callText === 'defineProp') { diff --git a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap index eeacec7efe..3c3d92da7e 100644 --- a/packages/tsc/tests/__snapshots__/dts.spec.ts.snap +++ b/packages/tsc/tests/__snapshots__/dts.spec.ts.snap @@ -238,6 +238,24 @@ export default _default; " `; +exports[`vue-tsc-dts > Input: reference-type-model/component.vue, Output: reference-type-model/component.vue.d.ts 1`] = ` +"declare const _default: import("vue").DefineComponent<{ + foo: import("vue").PropType; + bar: import("vue").PropType; +}, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, { + "update:foo": (foo: number) => void; + "update:bar": (bar: string[]) => void; +}, string, import("vue").PublicProps, Readonly; + bar: import("vue").PropType; +}>> & { + "onUpdate:foo"?: (foo: number) => any; + "onUpdate:bar"?: (bar: string[]) => any; +}, {}, {}>; +export default _default; +" +`; + exports[`vue-tsc-dts > Input: reference-type-props/component.vue, Output: reference-type-props/component.vue.d.ts 1`] = ` "import { MyProps } from './my-props'; declare const _default: import("vue").DefineComponent<__VLS_WithDefaults<__VLS_TypePropsToOption, { diff --git a/test-workspace/component-meta/reference-type-model/component.vue b/test-workspace/component-meta/reference-type-model/component.vue new file mode 100644 index 0000000000..a1d95a2b50 --- /dev/null +++ b/test-workspace/component-meta/reference-type-model/component.vue @@ -0,0 +1,4 @@ + diff --git a/test-workspace/tsc/vue2/tsconfig.json b/test-workspace/tsc/vue2/tsconfig.json index 6e91250ec8..b5c07b027b 100644 --- a/test-workspace/tsc/vue2/tsconfig.json +++ b/test-workspace/tsc/vue2/tsconfig.json @@ -19,6 +19,7 @@ "../vue3/#3672", "../vue3/components", "../vue3/defineEmits", + "../vue3/defineModel", "../vue3/defineProp_B", "../vue3/events", "../vue3/no-script-block", diff --git a/test-workspace/tsc/vue3/defineModel/main.vue b/test-workspace/tsc/vue3/defineModel/main.vue index 87fa75468a..ff0c0be2f1 100644 --- a/test-workspace/tsc/vue3/defineModel/main.vue +++ b/test-workspace/tsc/vue3/defineModel/main.vue @@ -1,4 +1,4 @@ - + +