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('/');
+}