Skip to content

Commit 2a1fef6

Browse files
authored
fix: preserve external import types (#243)
1 parent ead6774 commit 2a1fef6

11 files changed

Lines changed: 115 additions & 107 deletions

File tree

src/fake-js.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
isTypeOf,
88
resolveString,
99
walkAST,
10+
walkASTAsync,
1011
} from 'ast-kit'
1112
import {
1213
filename_dts_to,
@@ -295,8 +296,10 @@ export function createFakeJsPlugin({
295296
const params: TypeParams = collectParams(decl)
296297

297298
const childrenSet = new Set<t.Node>()
298-
const deps = collectDependencies(
299+
const deps = await collectDependencies(
300+
this,
299301
decl,
302+
id,
300303
namespaceStmts,
301304
childrenSet,
302305
identifierMap,
@@ -903,29 +906,33 @@ function collectParams(node: t.Node): TypeParams {
903906
}))
904907
}
905908

906-
function collectDependencies(
909+
async function collectDependencies(
910+
context: TransformPluginContext,
907911
node: t.Node,
912+
importer: string,
908913
namespaceStmts: NamespaceMap,
909914
children: Set<t.Node>,
910915
identifierMap: Record<string, number>,
911-
): Dep[] {
916+
): Promise<Dep[]> {
912917
const deps = new Set<Dep>()
913918
const seen = new Set<t.Node>()
919+
const preserveImportTypeCache = new Map<string, boolean>()
914920

915921
const inferredStack: string[][] = []
916922
let currentInferred = new Set<string>()
917923
function isInferred(node: t.Node): boolean {
918924
return node.type === 'Identifier' && currentInferred.has(node.name)
919925
}
920926

921-
walkAST(node, {
927+
await walkASTAsync(node, {
922928
enter(node) {
923929
if (node.type === 'TSConditionalType') {
924930
const inferred = collectInferredNames(node.extendsType)
925931
inferredStack.push(inferred)
926932
}
933+
return Promise.resolve()
927934
},
928-
leave(node, parent) {
935+
async leave(node, parent) {
929936
// handle infer scope
930937
if (node.type === 'TSConditionalType') {
931938
inferredStack.pop()
@@ -990,14 +997,18 @@ function collectDependencies(
990997
case 'TSImportType': {
991998
seen.add(node)
992999
const { source, qualifier } = node
993-
const dep = importNamespace(
1000+
1001+
const dep = await importNamespace(
1002+
context,
1003+
importer,
9941004
node,
9951005
qualifier,
9961006
source,
9971007
namespaceStmts,
9981008
identifierMap,
1009+
preserveImportTypeCache,
9991010
)
1000-
addDependency(dep)
1011+
if (dep) addDependency(dep)
10011012
break
10021013
}
10031014
}
@@ -1008,6 +1019,7 @@ function collectDependencies(
10081019
}
10091020
},
10101021
})
1022+
10111023
return Array.from(deps)
10121024

10131025
function addDependency(node: Dep) {
@@ -1016,13 +1028,25 @@ function collectDependencies(
10161028
}
10171029
}
10181030

1019-
function importNamespace(
1031+
async function importNamespace(
1032+
context: TransformPluginContext,
1033+
importer: string,
10201034
node: t.TSImportType,
10211035
imported: t.TSEntityName | null | undefined,
10221036
source: t.StringLiteral,
10231037
namespaceStmts: NamespaceMap,
10241038
identifierMap: Record<string, number>,
1025-
): Dep {
1039+
preserveCache: Map<string, boolean>,
1040+
): Promise<Dep | undefined> {
1041+
let preserve = preserveCache.get(source.value)
1042+
if (preserve === undefined) {
1043+
const resolved = await context.resolve(source.value, importer)
1044+
preserve = !resolved || !!resolved.external
1045+
preserveCache.set(source.value, preserve)
1046+
}
1047+
1048+
if (preserve) return
1049+
10261050
const sourceText = source.value.replaceAll(/\W/g, '_')
10271051
// Use original source if it's already a valid identifier,
10281052
// otherwise use formatted text with index.

tests/__snapshots__/index.test.ts.snap

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -123,72 +123,54 @@ export { Product };"
123123
124124
exports[`deterministic namespace import index 1`] = `
125125
"// a.d.ts
126-
import * as _$stub_lib from "stub_lib";
127-
128126
//#region a.d.ts
129-
type MyTypeA = _$stub_lib.LibType;
127+
type MyTypeA = import('stub_lib').LibType;
130128
//#endregion
131129
export { MyTypeA };
132130
// b.d.ts
133-
import * as _$stub_lib from "stub_lib";
134-
135131
//#region b.d.ts
136-
type MyTypeB = _$stub_lib.LibType;
132+
type MyTypeB = import('stub_lib').LibType;
137133
//#endregion
138134
export { MyTypeB };
139135
// c.d.ts
140-
import * as _$stub_lib from "stub_lib";
141-
142136
//#region c.d.ts
143-
type MyTypeC = _$stub_lib.LibType;
137+
type MyTypeC = import('stub_lib').LibType;
144138
//#endregion
145139
export { MyTypeC };"
146140
`;
147141
148142
exports[`deterministic namespace import index 2`] = `
149143
"// a.d.ts
150-
import * as _$stub_lib from "stub_lib";
151-
152144
//#region a.d.ts
153-
type MyTypeA = _$stub_lib.LibType;
145+
type MyTypeA = import('stub_lib').LibType;
154146
//#endregion
155147
export { MyTypeA };
156148
// b.d.ts
157-
import * as _$stub_lib from "stub_lib";
158-
159149
//#region b.d.ts
160-
type MyTypeB = _$stub_lib.LibType;
150+
type MyTypeB = import('stub_lib').LibType;
161151
//#endregion
162152
export { MyTypeB };
163153
// c.d.ts
164-
import * as _$stub_lib from "stub_lib";
165-
166154
//#region c.d.ts
167-
type MyTypeC = _$stub_lib.LibType;
155+
type MyTypeC = import('stub_lib').LibType;
168156
//#endregion
169157
export { MyTypeC };"
170158
`;
171159
172160
exports[`deterministic namespace import index 3`] = `
173161
"// a.d.ts
174-
import * as _$stub_lib from "stub_lib";
175-
176162
//#region a.d.ts
177-
type MyTypeA = _$stub_lib.LibType;
163+
type MyTypeA = import('stub_lib').LibType;
178164
//#endregion
179165
export { MyTypeA };
180166
// b.d.ts
181-
import * as _$stub_lib from "stub_lib";
182-
183167
//#region b.d.ts
184-
type MyTypeB = _$stub_lib.LibType;
168+
type MyTypeB = import('stub_lib').LibType;
185169
//#endregion
186170
export { MyTypeB };
187171
// c.d.ts
188-
import * as _$stub_lib from "stub_lib";
189-
190172
//#region c.d.ts
191-
type MyTypeC = _$stub_lib.LibType;
173+
type MyTypeC = import('stub_lib').LibType;
192174
//#endregion
193175
export { MyTypeC };"
194176
`;

tests/__snapshots__/tsc.test.ts.snap

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22

33
exports[`tsc > arktype 1`] = `
44
"// arktype.d.ts
5-
import * as _$arktype_internal_variants_object_ts0 from "arktype/internal/variants/object.ts";
6-
75
//#region tests/fixtures/arktype.d.ts
8-
declare const test: _$arktype_internal_variants_object_ts0.ObjectType<{
6+
declare const test: import("arktype/internal/variants/object.ts").ObjectType<{
97
test: string;
108
}, {}>;
119
//#endregion
@@ -479,8 +477,6 @@ export { Unused };"
479477
480478
exports[`tsc > vue-sfc w/ ts-compiler 1`] = `
481479
"// main.d.ts
482-
import * as _$_vue_runtime_core0 from "@vue/runtime-core";
483-
484480
//#region tests/fixtures/vue-sfc/App.vue.d.ts
485481
type __VLS_Props = {
486482
foo: string;
@@ -490,30 +486,28 @@ declare global {
490486
foo: string;
491487
}
492488
}
493-
declare const __VLS_export: _$_vue_runtime_core0.DefineComponent<__VLS_Props, {}, {}, {}, {}, _$_vue_runtime_core0.ComponentOptionsMixin, _$_vue_runtime_core0.ComponentOptionsMixin, {}, string, _$_vue_runtime_core0.PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, _$_vue_runtime_core0.ComponentProvideOptions, false, {}, any>;
489+
declare const __VLS_export: import("@vue/runtime-core").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("@vue/runtime-core").ComponentOptionsMixin, import("@vue/runtime-core").ComponentOptionsMixin, {}, string, import("@vue/runtime-core").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("@vue/runtime-core").ComponentProvideOptions, false, {}, any>;
494490
declare const _default: typeof __VLS_export;
495491
//#endregion
496492
export { _default as App };"
497493
`;
498494
499495
exports[`tsc > vue-sfc w/ ts-compiler w/ vueCompilerOptions in tsconfig 1`] = `
500496
"// main.d.ts
501-
import * as _$vue from "vue";
502-
503497
//#region tests/fixtures/vue-sfc-fallthrough/App.vue.d.ts
504-
declare const __VLS_export: _$vue.DefineComponent<{
498+
declare const __VLS_export: import("vue").DefineComponent<{
505499
foo: string;
506500
bar?: string | undefined;
507501
innerHTML?: string | undefined;
508-
class?: _$vue.ClassValue;
509-
style?: _$vue.StyleValue;
502+
class?: import("vue").ClassValue;
503+
style?: import("vue").StyleValue;
510504
accesskey?: string | undefined;
511505
contenteditable?: (boolean | "true" | "false") | "inherit" | "plaintext-only" | undefined;
512506
contextmenu?: string | undefined;
513507
dir?: string | undefined;
514508
draggable?: (boolean | "true" | "false") | undefined;
515509
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined;
516-
enterKeyHint?: _$vue.HTMLAttributes["enterkeyhint"];
510+
enterKeyHint?: import("vue").HTMLAttributes["enterkeyhint"];
517511
hidden?: (boolean | "true" | "false") | "" | "hidden" | "until-found" | undefined;
518512
id?: string | undefined;
519513
inert?: (boolean | "true" | "false") | undefined;
@@ -692,28 +686,28 @@ declare const __VLS_export: _$vue.DefineComponent<{
692686
onTransitionrun?: ((payload: TransitionEvent) => void) | undefined;
693687
onTransitionstart?: ((payload: TransitionEvent) => void) | undefined;
694688
key?: PropertyKey | undefined;
695-
ref?: _$vue.VNodeRef | undefined;
689+
ref?: import("vue").VNodeRef | undefined;
696690
ref_for?: boolean | undefined | undefined;
697691
ref_key?: string | undefined | undefined;
698-
onVnodeBeforeMount?: (((vnode: _$vue.VNode) => void) | ((vnode: _$vue.VNode) => void)[]) | undefined;
699-
onVnodeMounted?: (((vnode: _$vue.VNode) => void) | ((vnode: _$vue.VNode) => void)[]) | undefined;
700-
onVnodeBeforeUpdate?: (((vnode: _$vue.VNode, oldVNode: _$vue.VNode) => void) | ((vnode: _$vue.VNode, oldVNode: _$vue.VNode) => void)[]) | undefined;
701-
onVnodeUpdated?: (((vnode: _$vue.VNode, oldVNode: _$vue.VNode) => void) | ((vnode: _$vue.VNode, oldVNode: _$vue.VNode) => void)[]) | undefined;
702-
onVnodeBeforeUnmount?: (((vnode: _$vue.VNode) => void) | ((vnode: _$vue.VNode) => void)[]) | undefined;
703-
onVnodeUnmounted?: (((vnode: _$vue.VNode) => void) | ((vnode: _$vue.VNode) => void)[]) | undefined;
704-
}, {}, {}, {}, {}, _$vue.ComponentOptionsMixin, _$vue.ComponentOptionsMixin, {}, string, _$vue.PublicProps, Readonly<{
692+
onVnodeBeforeMount?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
693+
onVnodeMounted?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
694+
onVnodeBeforeUpdate?: (((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void) | ((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void)[]) | undefined;
695+
onVnodeUpdated?: (((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void) | ((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void)[]) | undefined;
696+
onVnodeBeforeUnmount?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
697+
onVnodeUnmounted?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
698+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{
705699
foo: string;
706700
bar?: string | undefined;
707701
innerHTML?: string | undefined;
708-
class?: _$vue.ClassValue;
709-
style?: _$vue.StyleValue;
702+
class?: import("vue").ClassValue;
703+
style?: import("vue").StyleValue;
710704
accesskey?: string | undefined;
711705
contenteditable?: (boolean | "true" | "false") | "inherit" | "plaintext-only" | undefined;
712706
contextmenu?: string | undefined;
713707
dir?: string | undefined;
714708
draggable?: (boolean | "true" | "false") | undefined;
715709
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined;
716-
enterKeyHint?: _$vue.HTMLAttributes["enterkeyhint"];
710+
enterKeyHint?: import("vue").HTMLAttributes["enterkeyhint"];
717711
hidden?: (boolean | "true" | "false") | "" | "hidden" | "until-found" | undefined;
718712
id?: string | undefined;
719713
inert?: (boolean | "true" | "false") | undefined;
@@ -892,25 +886,23 @@ declare const __VLS_export: _$vue.DefineComponent<{
892886
onTransitionrun?: ((payload: TransitionEvent) => void) | undefined;
893887
onTransitionstart?: ((payload: TransitionEvent) => void) | undefined;
894888
key?: PropertyKey | undefined;
895-
ref?: _$vue.VNodeRef | undefined;
889+
ref?: import("vue").VNodeRef | undefined;
896890
ref_for?: boolean | undefined | undefined;
897891
ref_key?: string | undefined | undefined;
898-
onVnodeBeforeMount?: (((vnode: _$vue.VNode) => void) | ((vnode: _$vue.VNode) => void)[]) | undefined;
899-
onVnodeMounted?: (((vnode: _$vue.VNode) => void) | ((vnode: _$vue.VNode) => void)[]) | undefined;
900-
onVnodeBeforeUpdate?: (((vnode: _$vue.VNode, oldVNode: _$vue.VNode) => void) | ((vnode: _$vue.VNode, oldVNode: _$vue.VNode) => void)[]) | undefined;
901-
onVnodeUpdated?: (((vnode: _$vue.VNode, oldVNode: _$vue.VNode) => void) | ((vnode: _$vue.VNode, oldVNode: _$vue.VNode) => void)[]) | undefined;
902-
onVnodeBeforeUnmount?: (((vnode: _$vue.VNode) => void) | ((vnode: _$vue.VNode) => void)[]) | undefined;
903-
onVnodeUnmounted?: (((vnode: _$vue.VNode) => void) | ((vnode: _$vue.VNode) => void)[]) | undefined;
904-
}> & Readonly<{}>, {}, {}, {}, {}, string, _$vue.ComponentProvideOptions, false, {}, any>;
892+
onVnodeBeforeMount?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
893+
onVnodeMounted?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
894+
onVnodeBeforeUpdate?: (((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void) | ((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void)[]) | undefined;
895+
onVnodeUpdated?: (((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void) | ((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void)[]) | undefined;
896+
onVnodeBeforeUnmount?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
897+
onVnodeUnmounted?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
898+
}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
905899
declare const _default: typeof __VLS_export;
906900
//#endregion
907901
export { _default as App };"
908902
`;
909903
910904
exports[`tsc > vue-sfc w/ ts-macro w/ ts-compiler 1`] = `
911905
"// main.d.ts
912-
import * as _$vue from "vue";
913-
914906
//#region tests/fixtures/vue-sfc-with-ts-macro/App.vue.d.ts
915907
type __VLS_Props = {
916908
foo: string;
@@ -920,7 +912,7 @@ declare global {
920912
foo: string;
921913
}
922914
}
923-
declare const __VLS_export: _$vue.DefineComponent<__VLS_Props, {}, {}, {}, {}, _$vue.ComponentOptionsMixin, _$vue.ComponentOptionsMixin, {}, string, _$vue.PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, _$vue.ComponentProvideOptions, false, {}, any>;
915+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
924916
declare const _default: typeof __VLS_export;
925917
//#endregion
926918
//#region tests/fixtures/vue-sfc-with-ts-macro/main.d.ts
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const useDep: (dep: import('cjs-dts-dep')) => void

tests/fixtures/cjs-dts-dep-external/node_modules/cjs-dts-dep/index.d.ts

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/fixtures/cjs-dts-dep-external/node_modules/cjs-dts-dep/package.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/index.test.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,25 @@ describe('dts input', () => {
211211
expect(cjsWarning).toContain('Please mark this module as external')
212212
})
213213

214+
test('preserves external CommonJS dts import types', async () => {
215+
const root = path.resolve(dirname, 'fixtures/cjs-dts-dep-external')
216+
217+
const { chunks } = await rolldownBuild(
218+
[path.join(root, 'index.d.ts')],
219+
[dts({ dtsInput: true })],
220+
{
221+
cwd: root,
222+
external: 'cjs-dts-dep',
223+
onwarn(warning) {
224+
expect.unreachable(warning.message)
225+
},
226+
},
227+
)
228+
expect(chunks[0].code).toContain(
229+
"declare const useDep: (dep: import('cjs-dts-dep')) => void",
230+
)
231+
})
232+
214233
test('input object', async () => {
215234
const { snapshot, chunks } = await rolldownBuild(
216235
{ index: path.resolve(dirname, 'fixtures/dts-input.d.ts') },
@@ -682,9 +701,8 @@ test('deterministic namespace import index', async () => {
682701
// All results should be identical
683702
expect(results[0]).toBe(results[1])
684703
expect(results[1]).toBe(results[2])
685-
// Valid identifiers (stub_lib) don't need index suffix
686-
expect(results[0]).toContain('import * as _$stub_lib from "stub_lib"')
687-
// Should not have stub_lib0 since each file is independent
704+
expect(results[0]).toContain("import('stub_lib').LibType")
705+
expect(results[0]).not.toContain('import * as _$stub_lib from "stub_lib"')
688706
expect(results[0]).not.toContain('stub_lib0')
689707
})
690708

0 commit comments

Comments
 (0)