@@ -214,28 +214,35 @@ function nodeToLocation(node) {
214
214
215
215
/**
216
216
* @param {ts.Node } node
217
+ * @param {boolean } needExportModifier
217
218
* @returns {ts.Node | undefined }
218
219
*/
219
- function removeDeclareConstExport ( node ) {
220
+ function removeDeclareConstExport ( node , needExportModifier ) {
220
221
switch ( node . kind ) {
221
222
case ts . SyntaxKind . DeclareKeyword : // No need to emit this in d.ts files.
222
223
case ts . SyntaxKind . ConstKeyword : // Remove const from const enums.
223
- case ts . SyntaxKind . ExportKeyword : // No export modifier; we are already in the namespace.
224
224
return undefined ;
225
+ case ts . SyntaxKind . ExportKeyword : // No export modifier; we are already in the namespace.
226
+ if ( ! needExportModifier ) {
227
+ return undefined ;
228
+ }
225
229
}
226
230
return node ;
227
231
}
228
232
229
- /** @type {Map<string, ts.Symbol> [] } */
233
+ /** @type {{ locals: Map<string, { symbol: ts.Symbol, writeTarget: WriteTarget }>, exports: Map<string, ts.Symbol>} [] } */
230
234
const scopeStack = [ ] ;
231
235
236
+ /** @type {Map<ts.Symbol, string> } */
237
+ const symbolToNamespace = new Map ( ) ;
238
+
232
239
/**
233
240
* @param {string } name
234
241
*/
235
242
function findInScope ( name ) {
236
243
for ( let i = scopeStack . length - 1 ; i >= 0 ; i -- ) {
237
244
const scope = scopeStack [ i ] ;
238
- const symbol = scope . get ( name ) ;
245
+ const symbol = scope . exports . get ( name ) ;
239
246
if ( symbol ) {
240
247
return symbol ;
241
248
}
@@ -290,8 +297,9 @@ function symbolsConflict(s1, s2) {
290
297
291
298
/**
292
299
* @param {ts.Statement } decl
300
+ * @param {boolean } isInternal
293
301
*/
294
- function verifyMatchingSymbols ( decl ) {
302
+ function verifyMatchingSymbols ( decl , isInternal ) {
295
303
ts . visitEachChild ( decl , /** @type {(node: ts.Node) => ts.Node } */ function visit ( node ) {
296
304
if ( ts . isIdentifier ( node ) && ts . isPartOfTypeNode ( node ) ) {
297
305
if ( ts . isQualifiedName ( node . parent ) && node !== node . parent . left ) {
@@ -310,6 +318,10 @@ function verifyMatchingSymbols(decl) {
310
318
}
311
319
const symbolInScope = findInScope ( symbolOfNode . name ) ;
312
320
if ( ! symbolInScope ) {
321
+ if ( symbolOfNode . declarations ?. every ( d => isLocalDeclaration ( d ) && d . getSourceFile ( ) === decl . getSourceFile ( ) ) && ! isSelfReference ( node , symbolOfNode ) ) {
322
+ // The symbol is a local that needs to be copied into the scope.
323
+ scopeStack [ scopeStack . length - 1 ] . locals . set ( symbolOfNode . name , { symbol : symbolOfNode , writeTarget : isInternal ? WriteTarget . Internal : WriteTarget . Both } ) ;
324
+ }
313
325
// We didn't find the symbol in scope at all. Just allow it and we'll fail at test time.
314
326
return node ;
315
327
}
@@ -323,39 +335,72 @@ function verifyMatchingSymbols(decl) {
323
335
} , /*context*/ undefined ) ;
324
336
}
325
337
338
+ /**
339
+ * @param {ts.Declaration } decl
340
+ */
341
+ function isLocalDeclaration ( decl ) {
342
+ return ts . canHaveModifiers ( decl )
343
+ && ! ts . getModifiers ( decl ) ?. some ( m => m . kind === ts . SyntaxKind . ExportKeyword )
344
+ && ! ! getDeclarationStatement ( decl ) ;
345
+ }
346
+
347
+ /**
348
+ * @param {ts.Node } reference
349
+ * @param {ts.Symbol } symbol
350
+ */
351
+ function isSelfReference ( reference , symbol ) {
352
+ return symbol . declarations ?. every ( parent => ts . findAncestor ( reference , p => p === parent ) ) ;
353
+ }
354
+
326
355
/**
327
356
* @param {string } name
357
+ * @param {string } parent
358
+ * @param {boolean } needExportModifier
328
359
* @param {ts.Symbol } moduleSymbol
329
360
*/
330
- function emitAsNamespace ( name , moduleSymbol ) {
361
+ function emitAsNamespace ( name , parent , moduleSymbol , needExportModifier ) {
331
362
assert ( moduleSymbol . flags & ts . SymbolFlags . ValueModule , "moduleSymbol is not a module" ) ;
332
363
333
- scopeStack . push ( new Map ( ) ) ;
364
+ const fullName = parent ? `${ parent } .${ name } ` : name ;
365
+
366
+ scopeStack . push ( { locals : new Map ( ) , exports : new Map ( ) } ) ;
334
367
const currentScope = scopeStack [ scopeStack . length - 1 ] ;
335
368
336
369
const target = containsPublicAPI ( moduleSymbol ) ? WriteTarget . Both : WriteTarget . Internal ;
337
370
338
371
if ( name === "ts" ) {
339
372
// We will write `export = ts` at the end.
373
+ assert ( ! needExportModifier , "ts namespace should not have an export modifier" ) ;
340
374
write ( `declare namespace ${ name } {` , target ) ;
341
375
}
342
376
else {
343
- // No export modifier; we are already in the namespace.
344
- write ( `namespace ${ name } {` , target ) ;
377
+ write ( `${ needExportModifier ? "export " : "" } namespace ${ name } {` , target ) ;
345
378
}
346
379
increaseIndent ( ) ;
347
380
348
381
const moduleExports = typeChecker . getExportsOfModule ( moduleSymbol ) ;
349
382
for ( const me of moduleExports ) {
350
- currentScope . set ( me . name , me ) ;
383
+ currentScope . exports . set ( me . name , me ) ;
384
+ symbolToNamespace . set ( me , fullName ) ;
351
385
}
352
386
387
+ /** @type {[ts.Statement, ts.SourceFile, WriteTarget][] } */
388
+ const exportedStatements = [ ] ;
389
+ /** @type {[name: string, fullName: string, moduleSymbol: ts.Symbol][] } */
390
+ const nestedNamespaces = [ ] ;
353
391
for ( const me of moduleExports ) {
354
392
assert ( me . declarations ?. length ) ;
355
393
356
394
if ( me . flags & ts . SymbolFlags . Alias ) {
357
395
const resolved = typeChecker . getAliasedSymbol ( me ) ;
358
- emitAsNamespace ( me . name , resolved ) ;
396
+ if ( resolved . flags & ts . SymbolFlags . ValueModule ) {
397
+ nestedNamespaces . push ( [ me . name , fullName , resolved ] ) ;
398
+ }
399
+ else {
400
+ const namespaceName = symbolToNamespace . get ( resolved ) ;
401
+ assert ( namespaceName , `Failed to find namespace for ${ me . name } at ${ nodeToLocation ( me . declarations [ 0 ] ) } ` ) ;
402
+ write ( `export import ${ me . name } = ${ namespaceName } .${ me . name } ` , target ) ;
403
+ }
359
404
continue ;
360
405
}
361
406
@@ -367,34 +412,60 @@ function emitAsNamespace(name, moduleSymbol) {
367
412
fail ( `Unhandled declaration for ${ me . name } at ${ nodeToLocation ( decl ) } ` ) ;
368
413
}
369
414
370
- verifyMatchingSymbols ( statement ) ;
371
-
372
415
const isInternal = ts . isInternalDeclaration ( statement ) ;
416
+ if ( ! ts . isModuleDeclaration ( decl ) ) {
417
+ verifyMatchingSymbols ( statement , isInternal ) ;
418
+ }
419
+
373
420
if ( ! isInternal ) {
374
421
const publicStatement = ts . visitEachChild ( statement , node => {
375
422
// No @internal comments in the public API.
376
423
if ( ts . isInternalDeclaration ( node ) ) {
377
424
return undefined ;
378
425
}
379
- return removeDeclareConstExport ( node ) ;
426
+ return node ;
380
427
} , /*context*/ undefined ) ;
381
428
382
- writeNode ( publicStatement , sourceFile , WriteTarget . Public ) ;
429
+ exportedStatements . push ( [ publicStatement , sourceFile , WriteTarget . Public ] ) ;
383
430
}
384
431
385
- const internalStatement = ts . visitEachChild ( statement , removeDeclareConstExport , /*context*/ undefined ) ;
386
-
387
- writeNode ( internalStatement , sourceFile , WriteTarget . Internal ) ;
432
+ exportedStatements . push ( [ statement , sourceFile , WriteTarget . Internal ] ) ;
388
433
}
389
434
}
390
435
436
+ const childrenNeedExportModifier = ! ! currentScope . locals . size ;
437
+
438
+ nestedNamespaces . forEach ( namespace => emitAsNamespace ( ...namespace , childrenNeedExportModifier ) ) ;
439
+
440
+ currentScope . locals . forEach ( ( { symbol, writeTarget } ) => {
441
+ symbol . declarations ?. forEach ( decl => {
442
+ // We already checked that getDeclarationStatement(decl) works for each declaration.
443
+ const statement = getDeclarationStatement ( decl ) ;
444
+ writeNode ( /** @type {ts.Statement } */ ( statement ) , decl . getSourceFile ( ) , writeTarget ) ;
445
+ } ) ;
446
+ } ) ;
447
+
448
+ exportedStatements . forEach ( ( [ statement , ...rest ] ) => {
449
+ let updated = ts . visitEachChild ( statement , node => removeDeclareConstExport ( node , childrenNeedExportModifier ) , /*context*/ undefined ) ;
450
+ if ( childrenNeedExportModifier && ts . canHaveModifiers ( updated ) && ! updated . modifiers ?. some ( m => m . kind === ts . SyntaxKind . ExportKeyword ) ) {
451
+ updated = ts . factory . replaceModifiers (
452
+ updated ,
453
+ [
454
+ ts . factory . createModifier ( ts . SyntaxKind . ExportKeyword ) ,
455
+ .../**@type {ts.NodeArray<ts.Modifier> | undefined }*/ ( updated . modifiers ) ?? [ ] ,
456
+ ] ,
457
+ ) ;
458
+ }
459
+ writeNode ( updated , ...rest ) ;
460
+ } ) ;
461
+
391
462
scopeStack . pop ( ) ;
392
463
393
464
decreaseIndent ( ) ;
394
465
write ( `}` , target ) ;
395
466
}
396
467
397
- emitAsNamespace ( "ts" , moduleSymbol ) ;
468
+ emitAsNamespace ( "ts" , "" , moduleSymbol , /*needExportModifier*/ false ) ;
398
469
399
470
write ( "export = ts;" , WriteTarget . Both ) ;
400
471
0 commit comments