Skip to content

Commit a9202b1

Browse files
authored
fix: Preserve imports for types used in typeof generic (#348)
1 parent d03ed24 commit a9202b1

File tree

9 files changed

+73
-1
lines changed

9 files changed

+73
-1
lines changed

src/transform/DeclarationScope.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class DeclarationScope {
3939
declaration: ESTree.FunctionDeclaration;
4040
iife?: ESTree.ExpressionStatement;
4141
private returnExpr: ESTree.ArrayExpression;
42+
private valueReferences: Set<string> = new Set();
4243

4344
constructor({ id, range }: DeclarationScopeOptions) {
4445
if (id) {
@@ -72,6 +73,14 @@ export class DeclarationScope {
7273
this.scopes[this.scopes.length - 1]?.add(name);
7374
}
7475

76+
markAsValue(name: string) {
77+
this.valueReferences.add(name);
78+
}
79+
80+
getValueReferences(): Set<string> {
81+
return this.valueReferences;
82+
}
83+
7584
pushReference(id: ESTree.Expression) {
7685
let name: string | undefined;
7786
// We convert references from TS AST to ESTree
@@ -302,7 +311,17 @@ export class DeclarationScope {
302311
return;
303312
}
304313
if (ts.isTypeQueryNode(node)) {
305-
this.pushReference(this.convertEntityName(node.exprName));
314+
const reference = this.convertEntityName(node.exprName);
315+
this.pushReference(reference);
316+
317+
if (reference.type === "Identifier") {
318+
this.markAsValue(reference.name);
319+
} else if (reference.type === "MemberExpression" && reference.object.type === "Identifier") {
320+
this.markAsValue(reference.object.name);
321+
}
322+
323+
this.convertTypeArguments(node);
324+
306325
return;
307326
}
308327
if (ts.isRestTypeNode(node)) {

src/transform/Transformer.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface ConvertInput {
1212

1313
interface ConvertOutput {
1414
ast: ESTree.Program;
15+
valueReferences: Set<string>;
1516
}
1617

1718
export function convert({ sourceFile }: ConvertInput): ConvertOutput {
@@ -21,6 +22,7 @@ export function convert({ sourceFile }: ConvertInput): ConvertOutput {
2122

2223
class Transformer {
2324
ast: ESTree.Program;
25+
allTypeReferences: Set<string> = new Set();
2426

2527
declarations = new Map<string, DeclarationScope>();
2628

@@ -29,11 +31,18 @@ class Transformer {
2931
for (const stmt of sourceFile.statements) {
3032
this.convertStatement(stmt);
3133
}
34+
35+
for (const scope of this.declarations.values()) {
36+
for (const valueRef of scope.getValueReferences()) {
37+
this.allTypeReferences.add(valueRef);
38+
}
39+
}
3240
}
3341

3442
transform(): ConvertOutput {
3543
return {
3644
ast: this.ast,
45+
valueReferences: this.allTypeReferences,
3746
};
3847
}
3948

src/transform/TypeOnlyFixer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class TypeOnlyFixer {
1717
private values: Set<string> = new Set();
1818
private typeHints: Map<string, number> = new Map();
1919
private reExportTypeHints: Map<string, number> = new Map();
20+
private typeofValueReferences: Set<string> = new Set();
2021

2122
private importNodes: ImportDeclarationWithClause[] = [];
2223
private exportNodes: ExportDeclarationWithClause[] = [];
@@ -27,6 +28,10 @@ export class TypeOnlyFixer {
2728
this.code = new MagicString(rawCode);
2829
}
2930

31+
setValueReferences(valueReferences: Set<string>) {
32+
this.typeofValueReferences = valueReferences;
33+
}
34+
3035
fix() {
3136
this.analyze(this.source.statements);
3237

@@ -274,6 +279,9 @@ export class TypeOnlyFixer {
274279
}
275280

276281
private isTypeOnly(name: string) {
282+
if(this.typeofValueReferences.has(name)) {
283+
return false;
284+
}
277285
return this.typeHints.has(name)
278286
|| (this.types.has(name) && !this.values.has(name));
279287
}

src/transform/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { parse, trimExtension, JSON_EXTENSIONS } from "../helpers.js";
2727
export const transform = () => {
2828
const allTypeReferences = new Map<string, Set<string>>();
2929
const allFileReferences = new Map<string, Set<string>>();
30+
const allValueReferences = new Map<string, Set<string>>();
3031

3132
return {
3233
name: "dts-transform",
@@ -88,6 +89,8 @@ export const transform = () => {
8889
sourceFile = parse(fileName, code);
8990
const converted = convert({ sourceFile });
9091

92+
allValueReferences.set(sourceFile.fileName, converted.valueReferences);
93+
9194
if (process.env.DTS_DUMP_AST) {
9295
console.log(fileName);
9396
console.log(code);
@@ -103,10 +106,14 @@ export const transform = () => {
103106

104107
const typeReferences = new Set<string>();
105108
const fileReferences = new Set<string>();
109+
const valueReferences = new Set<string>();
106110
for (const fileName of Object.keys(chunk.modules)) {
107111
for (const ref of allTypeReferences.get(fileName.split("\\").join("/")) || []) {
108112
typeReferences.add(ref);
109113
}
114+
for (const ref of allValueReferences.get(fileName.split("\\").join("/")) || []) {
115+
valueReferences.add(ref);
116+
}
110117
for (const ref of allFileReferences.get(fileName.split("\\").join("/")) || []) {
111118
if (ref.startsWith(".")) {
112119
// Need absolute path of the target file here
@@ -135,6 +142,7 @@ export const transform = () => {
135142
}
136143

137144
const typeOnlyFixer = new TypeOnlyFixer(chunk.fileName, code, !!options.sourcemap);
145+
typeOnlyFixer.setValueReferences(typeReferences);
138146

139147
return typeOnlyFixer.fix();
140148
},
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { Test } from './models';
2+
import { test } from './test';
3+
declare const b: ReturnType<typeof test<Test>>;
4+
export { b };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { Test } from './models';
2+
import { test } from './test';
3+
4+
export const b: ReturnType<typeof test<Test>>;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @ts-check
2+
/** @type {import('../../testcases').Meta} */
3+
export default {
4+
tsVersion: "4.8",
5+
options: {
6+
respectExternal: true,
7+
},
8+
rollupOptions: {
9+
input: {
10+
index: "index.d.ts",
11+
},
12+
external: ["./models", "./test"],
13+
},
14+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type Test = {
2+
a: string;
3+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Test } from './models';
2+
3+
export function test<T extends Test>(input: T): () => T;

0 commit comments

Comments
 (0)