1
1
import type { Declaration , ProcessingContext } from './types'
2
2
3
+ /**
4
+ * Extract all imported items from an import statement
5
+ */
6
+ function extractAllImportedItems ( importText : string ) : string [ ] {
7
+ const items : string [ ] = [ ]
8
+
9
+ // Handle mixed imports: import defaultName, { a, b } from 'module'
10
+ const mixedMatch = importText . match ( / i m p o r t \s + ( [ ^ { , \s ] + ) , \s * \{ ? \s * ( [ ^ } ] + ) \s * \} ? \s + f r o m / )
11
+ if ( mixedMatch ) {
12
+ // Add default import
13
+ items . push ( mixedMatch [ 1 ] . trim ( ) )
14
+ // Add named imports
15
+ const namedItems = mixedMatch [ 2 ] . split ( ',' ) . map ( item => item . replace ( / ^ t y p e \s + / , '' ) . trim ( ) )
16
+ items . push ( ...namedItems )
17
+ return items
18
+ }
19
+
20
+ // Handle named imports: import { a, b } from 'module'
21
+ const namedMatch = importText . match ( / i m p o r t \s + (?: t y p e \s + ) ? \{ ? \s * ( [ ^ } ] + ) \s * \} ? \s + f r o m / )
22
+ if ( namedMatch ) {
23
+ const namedItems = namedMatch [ 1 ] . split ( ',' ) . map ( item => item . replace ( / ^ t y p e \s + / , '' ) . trim ( ) )
24
+ items . push ( ...namedItems )
25
+ return items
26
+ }
27
+
28
+ // Handle default imports: import defaultName from 'module'
29
+ const defaultMatch = importText . match ( / i m p o r t \s + (?: t y p e \s + ) ? ( [ ^ { , \s ] + ) \s + f r o m / )
30
+ if ( defaultMatch ) {
31
+ items . push ( defaultMatch [ 1 ] . trim ( ) )
32
+ return items
33
+ }
34
+
35
+ return items
36
+ }
37
+
3
38
/**
4
39
* Process declarations and convert them to narrow DTS format
5
40
*/
@@ -60,16 +95,16 @@ export function processDeclarations(
60
95
// Check which imports are needed based on exported functions and types
61
96
for ( const func of functions ) {
62
97
if ( func . isExported ) {
63
- // Check function signature for imported types (only in the function signature, not the body)
64
- const funcDeclaration = func . text . split ( '{' ) [ 0 ] // Only check the signature part
98
+ // Check the entire function signature for imported types
99
+ const funcDeclaration = func . text
65
100
for ( const imp of imports ) {
66
- const importMatch = imp . text . match ( / i m p o r t \s + (?: t y p e \s + ) ? \{ ? \s * ( [ ^ } ] + ) \s * \} ? \s + f r o m / )
67
- if ( importMatch ) {
68
- const importedItems = importMatch [ 1 ] . split ( ',' ) . map ( item => item . trim ( ) )
69
- for ( const item of importedItems ) {
70
- if ( funcDeclaration . includes ( item ) ) {
71
- usedImportItems . add ( item )
72
- }
101
+ // Handle all import patterns: named, default, and mixed
102
+ const allImportedItems = extractAllImportedItems ( imp . text )
103
+ for ( const item of allImportedItems ) {
104
+ // Use word boundary regex to match exact identifiers, not substrings
105
+ const regex = new RegExp ( `\\b ${ item . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g , '\\$&' ) } \\b` )
106
+ if ( regex . test ( funcDeclaration ) ) {
107
+ usedImportItems . add ( item )
73
108
}
74
109
}
75
110
}
@@ -85,11 +120,12 @@ export function processDeclarations(
85
120
const typeMatches = importText . match ( / t y p e \s + ( [ A - Z a - z _ $ ] [ A - Z a - z 0 - 9 _ $ ] * ) / g)
86
121
const valueMatches = importText . match ( / i m p o r t \s + \{ ( [ ^ } ] + ) \} / )
87
122
88
- // Check type imports
123
+ // Check type imports
89
124
if ( typeMatches ) {
90
125
for ( const typeMatch of typeMatches ) {
91
126
const typeName = typeMatch . replace ( 'type ' , '' ) . trim ( )
92
- if ( variable . text . includes ( typeName ) ) {
127
+ const regex = new RegExp ( `\\b${ typeName . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) } \\b` )
128
+ if ( regex . test ( variable . text ) ) {
93
129
usedImportItems . add ( typeName )
94
130
}
95
131
}
@@ -101,7 +137,8 @@ export function processDeclarations(
101
137
item . replace ( / t y p e \s + / , '' ) . trim ( )
102
138
)
103
139
for ( const importName of imports ) {
104
- if ( variable . text . includes ( importName ) ) {
140
+ const regex = new RegExp ( `\\b${ importName . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) } \\b` )
141
+ if ( regex . test ( variable . text ) ) {
105
142
usedImportItems . add ( importName )
106
143
}
107
144
}
@@ -119,13 +156,11 @@ export function processDeclarations(
119
156
120
157
if ( iface . isExported || isReferencedByExports ) {
121
158
for ( const imp of imports ) {
122
- const importMatch = imp . text . match ( / i m p o r t \s + (?: t y p e \s + ) ? \{ ? \s * ( [ ^ } ] + ) \s * \} ? \s + f r o m / )
123
- if ( importMatch ) {
124
- const importedItems = importMatch [ 1 ] . split ( ',' ) . map ( item => item . trim ( ) )
125
- for ( const item of importedItems ) {
126
- if ( iface . text . includes ( item ) ) {
127
- usedImportItems . add ( item )
128
- }
159
+ const allImportedItems = extractAllImportedItems ( imp . text )
160
+ for ( const item of allImportedItems ) {
161
+ const regex = new RegExp ( `\\b${ item . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) } \\b` )
162
+ if ( regex . test ( iface . text ) ) {
163
+ usedImportItems . add ( item )
129
164
}
130
165
}
131
166
}
@@ -135,13 +170,11 @@ export function processDeclarations(
135
170
for ( const type of types ) {
136
171
if ( type . isExported ) {
137
172
for ( const imp of imports ) {
138
- const importMatch = imp . text . match ( / i m p o r t \s + (?: t y p e \s + ) ? \{ ? \s * ( [ ^ } ] + ) \s * \} ? \s + f r o m / )
139
- if ( importMatch ) {
140
- const importedItems = importMatch [ 1 ] . split ( ',' ) . map ( item => item . trim ( ) )
141
- for ( const item of importedItems ) {
142
- if ( type . text . includes ( item ) ) {
143
- usedImportItems . add ( item )
144
- }
173
+ const allImportedItems = extractAllImportedItems ( imp . text )
174
+ for ( const item of allImportedItems ) {
175
+ const regex = new RegExp ( `\\b${ item . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) } \\b` )
176
+ if ( regex . test ( type . text ) ) {
177
+ usedImportItems . add ( item )
145
178
}
146
179
}
147
180
}
@@ -151,16 +184,10 @@ export function processDeclarations(
151
184
// Check which imports are needed for re-exports
152
185
for ( const item of exportedItems ) {
153
186
for ( const imp of imports ) {
154
- if ( imp . text . includes ( item ) ) {
155
- // Extract the specific items from this import
156
- const importMatch = imp . text . match ( / i m p o r t \s + (?: t y p e \s + ) ? \{ ? \s * ( [ ^ } ] + ) \s * \} ? \s + f r o m / )
157
- if ( importMatch ) {
158
- const importedItems = importMatch [ 1 ] . split ( ',' ) . map ( item => item . trim ( ) )
159
- for ( const importedItem of importedItems ) {
160
- if ( item === importedItem ) {
161
- usedImportItems . add ( importedItem )
162
- }
163
- }
187
+ const allImportedItems = extractAllImportedItems ( imp . text )
188
+ for ( const importedItem of allImportedItems ) {
189
+ if ( item === importedItem ) {
190
+ usedImportItems . add ( importedItem )
164
191
}
165
192
}
166
193
}
@@ -169,33 +196,63 @@ export function processDeclarations(
169
196
// Also check for value imports that are re-exported
170
197
for ( const exp of exports ) {
171
198
for ( const imp of imports ) {
172
- const importMatch = imp . text . match ( / i m p o r t \s + (?: t y p e \s + ) ? \{ ? \s * ( [ ^ } ] + ) \s * \} ? \s + f r o m / )
173
- if ( importMatch ) {
174
- const importedItems = importMatch [ 1 ] . split ( ',' ) . map ( item => item . trim ( ) )
175
- for ( const importedItem of importedItems ) {
176
- if ( exp . text . includes ( importedItem ) ) {
177
- usedImportItems . add ( importedItem )
178
- }
199
+ const allImportedItems = extractAllImportedItems ( imp . text )
200
+ for ( const importedItem of allImportedItems ) {
201
+ // Use word boundary regex to match exact identifiers in export statements
202
+ const regex = new RegExp ( `\\b${ importedItem . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) } \\b` )
203
+ if ( regex . test ( exp . text ) ) {
204
+ usedImportItems . add ( importedItem )
179
205
}
180
206
}
181
207
}
182
208
}
183
209
184
- // Create filtered imports based on actually used items
210
+ // Create filtered imports based on actually used items
185
211
const processedImports : string [ ] = [ ]
186
212
for ( const imp of imports ) {
187
- const importMatch = imp . text . match ( / i m p o r t \s + (?: t y p e \s + ) ? \{ ? \s * ( [ ^ } ] + ) \s * \} ? \s + f r o m \s + [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / )
188
- if ( importMatch ) {
189
- const importedItems = importMatch [ 1 ] . split ( ',' ) . map ( item => item . trim ( ) )
213
+ // Handle different import patterns - check mixed imports first
214
+ const mixedImportMatch = imp . text . match ( / i m p o r t \s + ( [ ^ { , \s ] + ) , \s * \{ ? \s * ( [ ^ } ] + ) \s * \} ? \s + f r o m \s + [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / )
215
+ const namedImportMatch = imp . text . match ( / i m p o r t \s + (?: t y p e \s + ) ? \{ \s * ( [ ^ } ] + ) \s * \} \s + f r o m \s + [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / )
216
+ const defaultImportMatch = imp . text . match ( / i m p o r t \s + (?: t y p e \s + ) ? ( [ ^ { , \s ] + ) \s + f r o m \s + [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / )
217
+
218
+ if ( mixedImportMatch ) {
219
+ // Mixed import: import defaultName, { a, b } from 'module'
220
+ const defaultName = mixedImportMatch [ 1 ] . trim ( )
221
+ const namedItems = mixedImportMatch [ 2 ] . split ( ',' ) . map ( item => item . trim ( ) )
222
+ const source = mixedImportMatch [ 3 ]
223
+
224
+ const usedDefault = usedImportItems . has ( defaultName )
225
+ const usedNamed = namedItems . filter ( item => {
226
+ const cleanItem = item . replace ( / ^ t y p e \s + / , '' ) . trim ( )
227
+ return usedImportItems . has ( cleanItem )
228
+ } )
229
+
230
+ if ( usedDefault || usedNamed . length > 0 ) {
231
+ const isOriginalTypeOnly = imp . text . includes ( 'import type' )
232
+
233
+ let importStatement = 'import '
234
+ if ( isOriginalTypeOnly ) {
235
+ importStatement += 'type '
236
+ }
237
+
238
+ const parts = [ ]
239
+ if ( usedDefault ) parts . push ( defaultName )
240
+ if ( usedNamed . length > 0 ) parts . push ( `{ ${ usedNamed . join ( ', ' ) } }` )
241
+
242
+ importStatement += `${ parts . join ( ', ' ) } from '${ source } ';`
243
+
244
+ processedImports . push ( importStatement )
245
+ }
246
+ } else if ( namedImportMatch && ! defaultImportMatch ) {
247
+ // Named imports only: import { a, b } from 'module'
248
+ const importedItems = namedImportMatch [ 1 ] . split ( ',' ) . map ( item => item . trim ( ) )
190
249
const usedItems = importedItems . filter ( item => {
191
250
const cleanItem = item . replace ( / ^ t y p e \s + / , '' ) . trim ( )
192
251
return usedImportItems . has ( cleanItem )
193
252
} )
194
253
195
254
if ( usedItems . length > 0 ) {
196
- const source = importMatch [ 2 ]
197
-
198
- // Check if original import was type-only
255
+ const source = namedImportMatch [ 2 ]
199
256
const isOriginalTypeOnly = imp . text . includes ( 'import type' )
200
257
201
258
let importStatement = 'import '
@@ -204,6 +261,22 @@ export function processDeclarations(
204
261
}
205
262
importStatement += `{ ${ usedItems . join ( ', ' ) } } from '${ source } ';`
206
263
264
+ processedImports . push ( importStatement )
265
+ }
266
+ } else if ( defaultImportMatch && ! namedImportMatch ) {
267
+ // Default import only: import defaultName from 'module'
268
+ const defaultName = defaultImportMatch [ 1 ] . trim ( )
269
+ const source = defaultImportMatch [ 2 ]
270
+
271
+ if ( usedImportItems . has ( defaultName ) ) {
272
+ const isOriginalTypeOnly = imp . text . includes ( 'import type' )
273
+
274
+ let importStatement = 'import '
275
+ if ( isOriginalTypeOnly ) {
276
+ importStatement += 'type '
277
+ }
278
+ importStatement += `${ defaultName } from '${ source } ';`
279
+
207
280
processedImports . push ( importStatement )
208
281
}
209
282
}
0 commit comments