Skip to content

Commit 6c16829

Browse files
feat(typescript): add useTypeImports option (#314)
* feat: add option for useTypeImports * feat(typescript): improve type import handling and remove deprecated API * chore: run format * chore: modify template * chore: modify type import * chore: change default value of useTypeImports to true * fix: enabled useTypeImports by default. * Add @hayawata3626 to contributor * Fix linting --------- Co-authored-by: Fabien Bernard <[email protected]>
1 parent 75b71f7 commit 6c16829

14 files changed

+706
-51
lines changed

.all-contributorsrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@
119119
"avatar_url": "https://avatars.githubusercontent.com/u/172711?v=4",
120120
"profile": "https://tverdohleb.com/",
121121
"contributions": ["bug", "code", "ideas"]
122+
},
123+
{
124+
"login": "hayawata3626",
125+
"name": "Isco",
126+
"avatar_url": "https://avatars.githubusercontent.com/u/15213369?v=4",
127+
"profile": "https://zenn.dev/watahaya",
128+
"contributions": ["code"]
122129
}
123130
],
124131
"contributorsPerLine": 7

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
279279
</tr>
280280
<tr>
281281
<td align="center" valign="top" width="14.28%"><a href="https://tverdohleb.com/"><img src="https://avatars.githubusercontent.com/u/172711?v=4?s=100" width="100px;" alt="Valeriy"/><br /><sub><b>Valeriy</b></sub></a><br /><a href="https://github.com/fabien0102/openapi-codegen/issues?q=author%3Atverdohleb" title="Bug reports">🐛</a> <a href="https://github.com/fabien0102/openapi-codegen/commits?author=tverdohleb" title="Code">💻</a> <a href="#ideas-tverdohleb" title="Ideas, Planning, & Feedback">🤔</a></td>
282+
<td align="center" valign="top" width="14.28%"><a href="https://zenn.dev/watahaya"><img src="https://avatars.githubusercontent.com/u/15213369?v=4?s=100" width="100px;" alt="Isco"/><br /><sub><b>Isco</b></sub></a><br /><a href="https://github.com/fabien0102/openapi-codegen/commits?author=hayawata3626" title="Code">💻</a></td>
282283
</tr>
283284
</tbody>
284285
</table>
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
import { describe, it, expect } from "vitest";
2+
import ts, { factory as f } from "typescript";
3+
import { analyzeImportUsage } from "./analyzeImportUsage";
4+
5+
// Helper to set parent references for TypeScript AST nodes
6+
const setParentReferences = <T extends ts.Node>(node: T): T => {
7+
function setParent(child: ts.Node, parent: ts.Node) {
8+
Object.defineProperty(child, "parent", {
9+
value: parent,
10+
writable: true,
11+
configurable: true,
12+
});
13+
child.forEachChild((grandChild) => setParent(grandChild, child));
14+
}
15+
node.forEachChild((child) => setParent(child, node));
16+
return node;
17+
};
18+
19+
describe("analyzeImportUsage", () => {
20+
describe("type-only usage", () => {
21+
it("should detect type reference usage", () => {
22+
const nodes = [
23+
setParentReferences(
24+
f.createTypeAliasDeclaration(
25+
undefined,
26+
f.createIdentifier("MyAlias"),
27+
undefined,
28+
f.createTypeReferenceNode(
29+
f.createIdentifier("TestImport"),
30+
undefined
31+
)
32+
)
33+
),
34+
];
35+
36+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(true);
37+
});
38+
39+
it("should detect usage in type alias declaration", () => {
40+
const nodes = [
41+
setParentReferences(
42+
f.createTypeAliasDeclaration(
43+
undefined,
44+
f.createIdentifier("TestImport"),
45+
undefined,
46+
f.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
47+
)
48+
),
49+
];
50+
51+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(true);
52+
});
53+
54+
it("should detect usage in interface declaration", () => {
55+
const nodes = [
56+
setParentReferences(
57+
f.createInterfaceDeclaration(
58+
undefined,
59+
f.createIdentifier("TestImport"),
60+
undefined,
61+
undefined,
62+
[]
63+
)
64+
),
65+
];
66+
67+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(true);
68+
});
69+
70+
it("should detect indexed access type usage", () => {
71+
const typeRef = f.createTypeReferenceNode(
72+
f.createIdentifier("TestImport"),
73+
undefined
74+
);
75+
const nodes = [
76+
setParentReferences(
77+
f.createTypeAliasDeclaration(
78+
undefined,
79+
f.createIdentifier("MyType"),
80+
undefined,
81+
f.createIndexedAccessTypeNode(
82+
typeRef,
83+
f.createLiteralTypeNode(f.createStringLiteral("prop"))
84+
)
85+
)
86+
),
87+
];
88+
89+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(true);
90+
});
91+
});
92+
93+
describe("value usage", () => {
94+
it("should detect call expression usage", () => {
95+
const nodes = [
96+
setParentReferences(
97+
f.createExpressionStatement(
98+
f.createCallExpression(
99+
f.createIdentifier("TestImport"),
100+
undefined,
101+
[]
102+
)
103+
)
104+
),
105+
];
106+
107+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(false);
108+
});
109+
110+
it("should detect property access usage", () => {
111+
const nodes = [
112+
setParentReferences(
113+
f.createExpressionStatement(
114+
f.createPropertyAccessExpression(
115+
f.createIdentifier("TestImport"),
116+
f.createIdentifier("prop")
117+
)
118+
)
119+
),
120+
];
121+
122+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(false);
123+
});
124+
125+
it("should detect binary expression usage", () => {
126+
const nodes = [
127+
setParentReferences(
128+
f.createExpressionStatement(
129+
f.createBinaryExpression(
130+
f.createIdentifier("TestImport"),
131+
ts.SyntaxKind.EqualsEqualsToken,
132+
f.createStringLiteral("test")
133+
)
134+
)
135+
),
136+
];
137+
138+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(false);
139+
});
140+
141+
it("should detect new expression usage", () => {
142+
const nodes = [
143+
setParentReferences(
144+
f.createExpressionStatement(
145+
f.createNewExpression(
146+
f.createIdentifier("TestImport"),
147+
undefined,
148+
[]
149+
)
150+
)
151+
),
152+
];
153+
154+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(false);
155+
});
156+
157+
it("should detect array literal usage", () => {
158+
const nodes = [
159+
setParentReferences(
160+
f.createVariableStatement(
161+
undefined,
162+
f.createVariableDeclarationList([
163+
f.createVariableDeclaration(
164+
f.createIdentifier("arr"),
165+
undefined,
166+
undefined,
167+
f.createArrayLiteralExpression([
168+
f.createIdentifier("TestImport"),
169+
])
170+
),
171+
])
172+
)
173+
),
174+
];
175+
176+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(false);
177+
});
178+
179+
it("should detect object literal usage", () => {
180+
const objectLiteral = f.createObjectLiteralExpression([
181+
f.createPropertyAssignment("key", f.createIdentifier("TestImport")),
182+
]);
183+
const nodes = [
184+
setParentReferences(
185+
f.createVariableStatement(
186+
undefined,
187+
f.createVariableDeclarationList([
188+
f.createVariableDeclaration(
189+
f.createIdentifier("obj"),
190+
undefined,
191+
undefined,
192+
objectLiteral
193+
),
194+
])
195+
)
196+
),
197+
];
198+
199+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(false);
200+
});
201+
});
202+
203+
describe("mixed usage", () => {
204+
it("should detect mixed type and value usage", () => {
205+
const nodes = [
206+
// Type usage
207+
setParentReferences(
208+
f.createTypeAliasDeclaration(
209+
undefined,
210+
f.createIdentifier("MyType"),
211+
undefined,
212+
f.createTypeReferenceNode(
213+
f.createIdentifier("TestImport"),
214+
undefined
215+
)
216+
)
217+
),
218+
// Value usage
219+
setParentReferences(
220+
f.createExpressionStatement(
221+
f.createCallExpression(
222+
f.createIdentifier("TestImport"),
223+
undefined,
224+
[]
225+
)
226+
)
227+
),
228+
];
229+
230+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(false);
231+
});
232+
});
233+
234+
describe("unused imports", () => {
235+
it("should default to type-only for unused imports", () => {
236+
const nodes = [
237+
setParentReferences(
238+
f.createTypeAliasDeclaration(
239+
undefined,
240+
f.createIdentifier("MyType"),
241+
undefined,
242+
f.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
243+
)
244+
),
245+
];
246+
247+
expect(analyzeImportUsage(nodes, "UnusedImport")).toBe(true);
248+
});
249+
250+
it("should handle empty node array", () => {
251+
expect(analyzeImportUsage([], "TestImport")).toBe(true);
252+
});
253+
});
254+
255+
describe("edge cases", () => {
256+
it("should handle nodes without parents", () => {
257+
const identifier = f.createIdentifier("TestImport");
258+
// Explicitly don't set parent references
259+
expect(analyzeImportUsage([identifier], "TestImport")).toBe(true);
260+
});
261+
262+
it("should be case sensitive", () => {
263+
const nodes = [
264+
setParentReferences(
265+
f.createExpressionStatement(
266+
f.createCallExpression(
267+
f.createIdentifier("testImport"), // lowercase
268+
undefined,
269+
[]
270+
)
271+
)
272+
),
273+
];
274+
275+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(true); // Should default to type-only
276+
});
277+
278+
it("should handle deeply nested usage", () => {
279+
const nodes = [
280+
setParentReferences(
281+
f.createBlock([
282+
f.createIfStatement(
283+
f.createBinaryExpression(
284+
f.createTrue(),
285+
ts.SyntaxKind.EqualsEqualsToken,
286+
f.createTrue()
287+
),
288+
f.createBlock([
289+
f.createExpressionStatement(
290+
f.createCallExpression(
291+
f.createIdentifier("TestImport"),
292+
undefined,
293+
[]
294+
)
295+
),
296+
])
297+
),
298+
])
299+
),
300+
];
301+
302+
expect(analyzeImportUsage(nodes, "TestImport")).toBe(false);
303+
});
304+
});
305+
306+
describe("performance optimization", () => {
307+
it("should return false for mixed usage", () => {
308+
const nodes = [
309+
// Type usage
310+
setParentReferences(
311+
f.createTypeAliasDeclaration(
312+
undefined,
313+
f.createIdentifier("MyType"),
314+
undefined,
315+
f.createTypeReferenceNode(
316+
f.createIdentifier("TestImport"),
317+
undefined
318+
)
319+
)
320+
),
321+
// Value usage
322+
setParentReferences(
323+
f.createExpressionStatement(
324+
f.createCallExpression(
325+
f.createIdentifier("TestImport"),
326+
undefined,
327+
[]
328+
)
329+
)
330+
),
331+
// Additional nodes - should stop before processing these
332+
setParentReferences(
333+
f.createTypeAliasDeclaration(
334+
undefined,
335+
f.createIdentifier("AnotherType"),
336+
undefined,
337+
f.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
338+
)
339+
),
340+
];
341+
342+
const result = analyzeImportUsage(nodes, "TestImport");
343+
expect(result).toBe(false);
344+
});
345+
});
346+
});

0 commit comments

Comments
 (0)