@@ -229,15 +229,12 @@ async function parseSnippetDefinitions(
229
229
sourceFile ,
230
230
) ;
231
231
232
- const imports : [ string , string ] [ ] = [ ] ;
232
+ const imports : { name : string ; moduleSpecifier : string ; isDefault : boolean } [ ] = [ ] ;
233
233
234
234
// This nested visitor is just for extracting the imports of a symbol.
235
235
const symbolImportVisitor : ts . Visitor = ( node : ts . Node ) => {
236
- let importLocations : string [ ] | undefined ;
237
- if (
238
- ts . isIdentifier ( node ) &&
239
- ( importLocations = extractImportLocations ( node ) ) !== undefined
240
- ) {
236
+ if ( ts . isIdentifier ( node ) ) {
237
+ const importLocations = extractImportLocations ( node ) ;
241
238
if ( importLocations . length > 1 ) {
242
239
// We can probably handle this, but it's an obscure case and it's probably better to let it error out and
243
240
// then observe whether or not we actually need (or even _want_) snippets with merged imports.
@@ -247,7 +244,10 @@ async function parseSnippetDefinitions(
247
244
} else if ( importLocations . length === 1 ) {
248
245
// The symbol was imported, so we need to track the imports to add them to the snippet later.
249
246
log . debug ( `symbol ${ node . text } was imported from ${ importLocations [ 0 ] } ` ) ;
250
- imports . push ( [ node . text , importLocations [ 0 ] ] ) ;
247
+ imports . push ( {
248
+ name : node . text ,
249
+ ...importLocations [ 0 ] ,
250
+ } ) ;
251
251
}
252
252
// else the symbol was not imported within this file, so it must be defined in the ambient context of the
253
253
// module, so we don't need to generate any code for it.
@@ -264,23 +264,56 @@ async function parseSnippetDefinitions(
264
264
// file using `convert`.
265
265
log . debug ( `found a snippet named ${ name . text } : \n${ contents } ` ) ;
266
266
267
+ interface ImportedSymbols {
268
+ default ?: string ;
269
+ named ?: Set < string > ;
270
+ }
271
+
267
272
// We have a loose map of imports in the form { [k:symbol]: module } and we need to anneal it into a map
268
273
// { [k: module]: symbol[] } (one import statement per module with the whole list of symbols imported from it)
269
- const importMap = new Map < string , Set < string > > (
270
- imports . map ( ( [ , module ] ) => [ module , new Set ( ) ] ) ,
271
- ) ;
274
+ const importMap = new Map < string , ImportedSymbols > ( ) ;
272
275
273
- for ( const [ symbol , name ] of imports ) {
274
- importMap . get ( name ) ! . add ( symbol ) ;
276
+ for ( const { name, moduleSpecifier, isDefault } of imports ) {
277
+ let moduleImports = importMap . get ( moduleSpecifier ) ;
278
+ if ( ! moduleImports ) {
279
+ moduleImports = { } ;
280
+ importMap . set ( moduleSpecifier , moduleImports ) ;
281
+ }
282
+ if ( isDefault ) {
283
+ if ( moduleImports . default ) {
284
+ throw new Error (
285
+ `unrecoverable error: multiple default imports from the same module '${ moduleSpecifier } '` ,
286
+ ) ;
287
+ }
288
+ moduleImports . default = name ;
289
+ } else {
290
+ if ( ! moduleImports . named ) {
291
+ moduleImports . named = new Set ( ) ;
292
+ }
293
+ moduleImports . named . add ( name ) ;
294
+ }
275
295
}
276
296
277
297
// Form import declarations and prepend them to the rest of the contents.
278
298
const fullSnippetTypeScriptText = (
279
299
[ ...importMap . entries ( ) ]
280
- . map (
281
- ( [ module , symbols ] ) =>
282
- `import { ${ [ ...symbols . values ( ) ] . join ( ", " ) } } from "${ module } ";` ,
283
- )
300
+ . map ( ( [ module , imports ] ) => {
301
+ const importParts = [ ] ;
302
+ if ( imports . default ) {
303
+ importParts . push ( imports . default ) ;
304
+ }
305
+ if ( imports . named ) {
306
+ importParts . push ( `{ ${ [ ...imports . named ] . join ( ", " ) } }` ) ;
307
+ }
308
+
309
+ if ( importParts . length === 0 ) {
310
+ throw new Error (
311
+ `unrecoverable error: no imports were generated for the snippet '${ name . text } '` ,
312
+ ) ;
313
+ }
314
+
315
+ return `import ${ importParts . join ( ", " ) } from "${ module } ";` ;
316
+ } )
284
317
. join ( EOL ) +
285
318
EOL +
286
319
EOL +
@@ -387,11 +420,14 @@ async function parseSnippetDefinitions(
387
420
* @param node - the node to check for imports
388
421
* @returns a list of module specifiers that form the definition of the node's symbol, or undefined
389
422
*/
390
- function extractImportLocations ( node : ts . Node ) : string [ ] | undefined {
423
+ function extractImportLocations ( node : ts . Node ) : {
424
+ isDefault : boolean ;
425
+ moduleSpecifier : string ;
426
+ } [ ] {
391
427
const sym = checker . getSymbolAtLocation ( node ) ;
392
428
393
429
// Get all the decls that are in source files and where the decl comes from an import clause.
394
- return sym ?. declarations
430
+ const nonDefaultExports = sym ?. declarations
395
431
?. filter (
396
432
( decl ) =>
397
433
decl . getSourceFile ( ) === sourceFile &&
@@ -411,12 +447,38 @@ async function parseSnippetDefinitions(
411
447
moduleSpecifierText === path . join ( relativeIndexPath , "index.js" ) ||
412
448
moduleSpecifierText === path . join ( relativeIndexPath , "index" )
413
449
) {
414
- return project . name ;
450
+ return { moduleSpecifier : project . name , isDefault : false } ;
415
451
} else {
416
- return moduleSpecifierText ;
452
+ return { moduleSpecifier : moduleSpecifierText , isDefault : false } ;
417
453
}
418
454
} ,
419
455
) ;
456
+
457
+ const defaultExports = sym ?. declarations
458
+ ?. filter (
459
+ ( decl ) =>
460
+ decl . getSourceFile ( ) === sourceFile &&
461
+ ts . isImportClause ( decl ) &&
462
+ ts . isImportDeclaration ( decl . parent ) &&
463
+ decl . name ,
464
+ )
465
+ . map ( ( decl ) => {
466
+ const moduleSpecifierText = (
467
+ ( decl . parent as ts . ImportDeclaration ) . moduleSpecifier as ts . StringLiteral
468
+ ) . text ;
469
+
470
+ if (
471
+ moduleSpecifierText === relativeIndexPath ||
472
+ moduleSpecifierText === path . join ( relativeIndexPath , "index.js" ) ||
473
+ moduleSpecifierText === path . join ( relativeIndexPath , "index" )
474
+ ) {
475
+ return { moduleSpecifier : project . name , isDefault : true } ;
476
+ } else {
477
+ return { moduleSpecifier : moduleSpecifierText , isDefault : true } ;
478
+ }
479
+ } ) ;
480
+
481
+ return [ ...( nonDefaultExports ?? [ ] ) , ...( defaultExports ?? [ ] ) ] ;
420
482
}
421
483
}
422
484
0 commit comments