diff --git a/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-default-props.vue b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-default-props.vue new file mode 100644 index 0000000000..5f7ea7aa4c --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-default-props.vue @@ -0,0 +1,11 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-expose.vue b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-expose.vue new file mode 100644 index 0000000000..ec3afee6ee --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-expose.vue @@ -0,0 +1,11 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-generic.vue b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-generic.vue new file mode 100644 index 0000000000..6d4fe6483d --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-generic.vue @@ -0,0 +1,10 @@ + + + diff --git a/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-type-only.vue b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-type-only.vue new file mode 100644 index 0000000000..e1e2b57197 --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup-type-only.vue @@ -0,0 +1,11 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/components/script-setup.vue b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup.vue new file mode 100644 index 0000000000..fdb37a19ec --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/components/script-setup.vue @@ -0,0 +1,7 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/defineEmits-new-syntax/main.vue b/packages/vue-test-workspace/vue-tsc-dts/defineEmits-new-syntax/main.vue new file mode 100644 index 0000000000..92dbfb1e78 --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/defineEmits-new-syntax/main.vue @@ -0,0 +1,3 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/defineExpose/main.vue b/packages/vue-test-workspace/vue-tsc-dts/defineExpose/main.vue new file mode 100644 index 0000000000..30c7f2e8bc --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/defineExpose/main.vue @@ -0,0 +1,5 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/defineModel/main.vue b/packages/vue-test-workspace/vue-tsc-dts/defineModel/main.vue new file mode 100644 index 0000000000..e3acc6e5ee --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/defineModel/main.vue @@ -0,0 +1,11 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/defineProp_A/main.vue b/packages/vue-test-workspace/vue-tsc-dts/defineProp_A/main.vue new file mode 100644 index 0000000000..e31d92f72d --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/defineProp_A/main.vue @@ -0,0 +1,20 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/defineProp_B/script-setup-generic.vue b/packages/vue-test-workspace/vue-tsc-dts/defineProp_B/script-setup-generic.vue new file mode 100644 index 0000000000..003f247864 --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/defineProp_B/script-setup-generic.vue @@ -0,0 +1,8 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/defineProp_B/script-setup.vue b/packages/vue-test-workspace/vue-tsc-dts/defineProp_B/script-setup.vue new file mode 100644 index 0000000000..1bbbdec008 --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/defineProp_B/script-setup.vue @@ -0,0 +1,11 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/events/union_type.vue b/packages/vue-test-workspace/vue-tsc-dts/events/union_type.vue new file mode 100644 index 0000000000..3d4c2cdceb --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/events/union_type.vue @@ -0,0 +1,5 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/generic-interface/main.vue b/packages/vue-test-workspace/vue-tsc-dts/generic-interface/main.vue new file mode 100644 index 0000000000..9576d7d219 --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/generic-interface/main.vue @@ -0,0 +1,6 @@ + diff --git a/packages/vue-test-workspace/vue-tsc-dts/slots/main.vue b/packages/vue-test-workspace/vue-tsc-dts/slots/main.vue new file mode 100644 index 0000000000..ccc6942d8b --- /dev/null +++ b/packages/vue-test-workspace/vue-tsc-dts/slots/main.vue @@ -0,0 +1,13 @@ + + + diff --git a/packages/vue-test-workspace/vue-tsc/non-strict-template/directives/main.vue b/packages/vue-test-workspace/vue-tsc/non-strict-template/directives/main.vue index c9d75040a2..006440002f 100644 --- a/packages/vue-test-workspace/vue-tsc/non-strict-template/directives/main.vue +++ b/packages/vue-test-workspace/vue-tsc/non-strict-template/directives/main.vue @@ -2,7 +2,7 @@ import { FunctionDirective } from 'vue'; import { exactType } from '../../shared'; -let Foo: (_: { foo?: string }) => void; +let Foo: (_: { foo?: string; }) => void; let vFoo: FunctionDirective void>; diff --git a/packages/vue-tsc/tests/__snapshots__/dts.spec.ts.snap b/packages/vue-tsc/tests/__snapshots__/dts.spec.ts.snap new file mode 100644 index 0000000000..6f08659375 --- /dev/null +++ b/packages/vue-tsc/tests/__snapshots__/dts.spec.ts.snap @@ -0,0 +1,290 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`vue-tsc-dts > Input: components/script-setup.vue, Output: components/script-setup.vue.d.ts 1`] = ` +"declare const _default: import(\\"vue\\").DefineComponent<{ + foo: StringConstructor; +}, {}, unknown, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, (\\"change\\" | \\"delete\\")[], \\"change\\" | \\"delete\\", import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly> & { + onChange?: (...args: any[]) => any; + onDelete?: (...args: any[]) => any; +}, {}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: components/script-setup-default-props.vue, Output: components/script-setup-default-props.vue.d.ts 1`] = ` +"export interface Props { + msg?: string; + labels?: string[]; +} +declare const _default: import(\\"vue\\").DefineComponent<{ + msg: { + type: import(\\"vue\\").PropType; + default: string; + }; + labels: { + type: import(\\"vue\\").PropType; + default: () => string[]; + }; +}, {}, unknown, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, {}, string, import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly; + default: string; + }; + labels: { + type: import(\\"vue\\").PropType; + default: () => string[]; + }; +}>>, { + msg: string; + labels: string[]; +}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: components/script-setup-expose.vue, Output: components/script-setup-expose.vue.d.ts 1`] = ` +"declare const _default: import(\\"vue\\").DefineComponent<{}, { + a: number; + b: import(\\"vue\\").Ref; +}, {}, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, {}, string, import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly>, {}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: components/script-setup-generic.vue, Output: components/script-setup-generic.vue.d.ts 1`] = ` +"declare const _default: (__VLS_props: { + foo: T; +} & import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, __VLS_ctx?: Pick<{ + props: { + foo: T; + }; + expose(exposed: { + baz: T; + }): void; + attrs: any; + slots: { + default?(data: T): any; + }; + emit: (e: 'bar', data: T) => void; +}, \\"slots\\" | \\"attrs\\" | \\"emit\\">, __VLS_expose?: (exposed: { + baz: T; +}) => void, __VLS_setup?: Promise<{ + props: { + foo: T; + }; + expose(exposed: { + baz: T; + }): void; + attrs: any; + slots: { + default?(data: T): any; + }; + emit: (e: 'bar', data: T) => void; +}>) => import(\\"vue\\").VNode & { + __ctx?: { + props: { + foo: T; + }; + expose(exposed: { + baz: T; + }): void; + attrs: any; + slots: { + default?(data: T): any; + }; + emit: (e: 'bar', data: T) => void; + }; +}; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: components/script-setup-type-only.vue, Output: components/script-setup-type-only.vue.d.ts 1`] = ` +"declare const _default: import(\\"vue\\").DefineComponent<{ + foo: { + type: import(\\"vue\\").PropType; + required: true; + }; + bar: { + type: import(\\"vue\\").PropType; + }; +}, {}, unknown, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, { + change: (id: number) => void; + update: (value: string) => void; +}, string, import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly; + required: true; + }; + bar: { + type: import(\\"vue\\").PropType; + }; +}>> & { + onChange?: (id: number) => any; + onUpdate?: (value: string) => any; +}, {}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: defineEmits-new-syntax/main.vue, Output: defineEmits-new-syntax/main.vue.d.ts 1`] = ` +"declare const _default: import(\\"vue\\").DefineComponent<{}, {}, {}, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, { + foo: (args_0: string) => void; +}, string, import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly> & { + onFoo?: (args_0: string) => any; +}, {}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: defineExpose/main.vue, Output: defineExpose/main.vue.d.ts 1`] = ` +"declare const _default: import(\\"vue\\").DefineComponent<{}, { + foo: number; +}, {}, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, {}, string, import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly>, {}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: defineModel/main.vue, Output: defineModel/main.vue.d.ts 1`] = ` +"declare const _default: import(\\"vue\\").DefineComponent<{ + modelValue: import(\\"vue\\").PropType; + c: { + required: true; + type: import(\\"vue\\").PropType; + }; + d: import(\\"vue\\").PropType; + e: import(\\"vue\\").PropType; + f: { + required: true; + type: import(\\"vue\\").PropType; + }; + g: import(\\"vue\\").PropType; +}, {}, unknown, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, {}, string, import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly; + c: { + required: true; + type: import(\\"vue\\").PropType; + }; + d: import(\\"vue\\").PropType; + e: import(\\"vue\\").PropType; + f: { + required: true; + type: import(\\"vue\\").PropType; + }; + g: import(\\"vue\\").PropType; +}>>, {}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: defineProp_A/main.vue, Output: defineProp_A/main.vue.d.ts 1`] = ` +"interface Qux { + qux: true; +} +declare const _default: import(\\"vue\\").DefineComponent<{ + foo: import(\\"vue\\").PropType; + bar: { + required: true; + type: import(\\"vue\\").PropType; + }; + baz: { + required: true; + type: import(\\"vue\\").PropType; + }; + qux: import(\\"vue\\").PropType; + quux: import(\\"vue\\").PropType; + quuz: import(\\"vue\\").PropType<{}>; +}, {}, unknown, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, {}, string, import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly; + bar: { + required: true; + type: import(\\"vue\\").PropType; + }; + baz: { + required: true; + type: import(\\"vue\\").PropType; + }; + qux: import(\\"vue\\").PropType; + quux: import(\\"vue\\").PropType; + quuz: import(\\"vue\\").PropType<{}>; +}>>, {}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: defineProp_B/script-setup.vue, Output: defineProp_B/script-setup.vue.d.ts 1`] = ` +"declare const _default: import(\\"vue\\").DefineComponent<{ + a: import(\\"vue\\").PropType; + b: { + required: true; + type: import(\\"vue\\").PropType; + }; + c: { + required: true; + type: import(\\"vue\\").PropType; + }; + d: import(\\"vue\\").PropType; + e: import(\\"vue\\").PropType; + f: { + required: true; + type: import(\\"vue\\").PropType; + }; + g: import(\\"vue\\").PropType; +}, {}, unknown, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, {}, string, import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly; + b: { + required: true; + type: import(\\"vue\\").PropType; + }; + c: { + required: true; + type: import(\\"vue\\").PropType; + }; + d: import(\\"vue\\").PropType; + e: import(\\"vue\\").PropType; + f: { + required: true; + type: import(\\"vue\\").PropType; + }; + g: import(\\"vue\\").PropType; +}>>, {}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: defineProp_B/script-setup-generic.vue, Output: defineProp_B/script-setup-generic.vue.d.ts 1`] = `""`; + +exports[`vue-tsc-dts > Input: events/union_type.vue, Output: events/union_type.vue.d.ts 1`] = ` +"declare const _default: import(\\"vue\\").DefineComponent<{}, {}, {}, {}, {}, import(\\"vue\\").ComponentOptionsMixin, import(\\"vue\\").ComponentOptionsMixin, { + a: (a: string) => void; + b: (a: string) => void; +}, string, import(\\"vue\\").VNodeProps & import(\\"vue\\").AllowedComponentProps & import(\\"vue\\").ComponentCustomProps, Readonly> & { + onB?: (a: string) => any; + onA?: (a: string) => any; +}, {}, {}>; +export default _default; +" +`; + +exports[`vue-tsc-dts > Input: slots/main.vue, Output: slots/main.vue.d.ts 1`] = ` +"declare const _default: __VLS_WithTemplateSlots>, {}, {}>, Partial any>> & { + bar?(_: { + str: string; + num: number; + }): any; +}>; +export default _default; +type __VLS_WithTemplateSlots = T & { + new (): { + $slots: S; + }; +}; +" +`; diff --git a/packages/vue-tsc/tests/dts.spec.ts b/packages/vue-tsc/tests/dts.spec.ts new file mode 100644 index 0000000000..98e1ec3981 --- /dev/null +++ b/packages/vue-tsc/tests/dts.spec.ts @@ -0,0 +1,56 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import * as ts from 'typescript'; +import { describe, expect, it } from 'vitest'; +import { createProgram } from '../src'; + +const workspace = path.resolve(__dirname, '../../vue-test-workspace/vue-tsc-dts'); +const testFiles = readFilesRecursive(workspace); +const ensureTs = (filename: string) => filename.endsWith('.ts') ? filename : filename + '.ts'; +const normalizePath = (filename: string) => filename.replace(/\\/g, '/'); + +describe('vue-tsc-dts', () => { + const compilerOptions: ts.CompilerOptions = { + rootDir: workspace, + declaration: true, + emitDeclarationOnly: true, + }; + const host = ts.createCompilerHost(compilerOptions); + const program = createProgram({ + host, + rootNames: testFiles, + options: compilerOptions + }); + const service = program.__vue.languageService; + + for (const file of testFiles) { + const output = service.getEmitOutput(ensureTs(file), true); + for (const outputFile of output.outputFiles) { + it(`Input: ${shortenPath(file)}, Output: ${shortenPath(outputFile.name)}`, () => { + expect(outputFile.text).toMatchSnapshot(); + }); + } + } +}); + +function readFilesRecursive(dir: string) { + const result: string[] = []; + + for (const file of fs.readdirSync(dir)) { + const filepath = path.join(dir, file); + const stat = fs.statSync(filepath); + if (stat.isDirectory()) { + result.push(...readFilesRecursive(filepath)); + } else { + result.push(filepath); + } + } + + return result; +} + +function shortenPath(path: string): string { + path = normalizePath(path); + const segments = path.split('/'); + return segments.slice(-2).join('/'); +}