@@ -214,28 +214,35 @@ function nodeToLocation(node) {
214214
215215/**
216216 * @param {ts.Node } node
217+ * @param {boolean } needExportModifier
217218 * @returns {ts.Node | undefined }
218219 */
219- function removeDeclareConstExport ( node ) {
220+ function removeDeclareConstExport ( node , needExportModifier ) {
220221 switch ( node . kind ) {
221222 case ts . SyntaxKind . DeclareKeyword : // No need to emit this in d.ts files.
222223 case ts . SyntaxKind . ConstKeyword : // Remove const from const enums.
223- case ts . SyntaxKind . ExportKeyword : // No export modifier; we are already in the namespace.
224224 return undefined ;
225+ case ts . SyntaxKind . ExportKeyword : // No export modifier; we are already in the namespace.
226+ if ( ! needExportModifier ) {
227+ return undefined ;
228+ }
225229 }
226230 return node ;
227231}
228232
229- /** @type {Map<string, ts.Symbol> [] } */
233+ /** @type {{ locals: Map<string, { symbol: ts.Symbol, writeTarget: WriteTarget }>, exports: Map<string, ts.Symbol>} [] } */
230234const scopeStack = [ ] ;
231235
236+ /** @type {Map<ts.Symbol, string> } */
237+ const symbolToNamespace = new Map ( ) ;
238+
232239/**
233240 * @param {string } name
234241 */
235242function findInScope ( name ) {
236243 for ( let i = scopeStack . length - 1 ; i >= 0 ; i -- ) {
237244 const scope = scopeStack [ i ] ;
238- const symbol = scope . get ( name ) ;
245+ const symbol = scope . exports . get ( name ) ;
239246 if ( symbol ) {
240247 return symbol ;
241248 }
@@ -290,8 +297,9 @@ function symbolsConflict(s1, s2) {
290297
291298/**
292299 * @param {ts.Statement } decl
300+ * @param {boolean } isInternal
293301 */
294- function verifyMatchingSymbols ( decl ) {
302+ function verifyMatchingSymbols ( decl , isInternal ) {
295303 ts . visitEachChild ( decl , /** @type {(node: ts.Node) => ts.Node } */ function visit ( node ) {
296304 if ( ts . isIdentifier ( node ) && ts . isPartOfTypeNode ( node ) ) {
297305 if ( ts . isQualifiedName ( node . parent ) && node !== node . parent . left ) {
@@ -310,6 +318,10 @@ function verifyMatchingSymbols(decl) {
310318 }
311319 const symbolInScope = findInScope ( symbolOfNode . name ) ;
312320 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+ }
313325 // We didn't find the symbol in scope at all. Just allow it and we'll fail at test time.
314326 return node ;
315327 }
@@ -323,39 +335,72 @@ function verifyMatchingSymbols(decl) {
323335 } , /*context*/ undefined ) ;
324336}
325337
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+
326355/**
327356 * @param {string } name
357+ * @param {string } parent
358+ * @param {boolean } needExportModifier
328359 * @param {ts.Symbol } moduleSymbol
329360 */
330- function emitAsNamespace ( name , moduleSymbol ) {
361+ function emitAsNamespace ( name , parent , moduleSymbol , needExportModifier ) {
331362 assert ( moduleSymbol . flags & ts . SymbolFlags . ValueModule , "moduleSymbol is not a module" ) ;
332363
333- scopeStack . push ( new Map ( ) ) ;
364+ const fullName = parent ? `${ parent } .${ name } ` : name ;
365+
366+ scopeStack . push ( { locals : new Map ( ) , exports : new Map ( ) } ) ;
334367 const currentScope = scopeStack [ scopeStack . length - 1 ] ;
335368
336369 const target = containsPublicAPI ( moduleSymbol ) ? WriteTarget . Both : WriteTarget . Internal ;
337370
338371 if ( name === "ts" ) {
339372 // We will write `export = ts` at the end.
373+ assert ( ! needExportModifier , "ts namespace should not have an export modifier" ) ;
340374 write ( `declare namespace ${ name } {` , target ) ;
341375 }
342376 else {
343- // No export modifier; we are already in the namespace.
344- write ( `namespace ${ name } {` , target ) ;
377+ write ( `${ needExportModifier ? "export " : "" } namespace ${ name } {` , target ) ;
345378 }
346379 increaseIndent ( ) ;
347380
348381 const moduleExports = typeChecker . getExportsOfModule ( moduleSymbol ) ;
349382 for ( const me of moduleExports ) {
350- currentScope . set ( me . name , me ) ;
383+ currentScope . exports . set ( me . name , me ) ;
384+ symbolToNamespace . set ( me , fullName ) ;
351385 }
352386
387+ /** @type {[ts.Statement, ts.SourceFile, WriteTarget][] } */
388+ const exportedStatements = [ ] ;
389+ /** @type {[name: string, fullName: string, moduleSymbol: ts.Symbol][] } */
390+ const nestedNamespaces = [ ] ;
353391 for ( const me of moduleExports ) {
354392 assert ( me . declarations ?. length ) ;
355393
356394 if ( me . flags & ts . SymbolFlags . Alias ) {
357395 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+ }
359404 continue ;
360405 }
361406
@@ -367,34 +412,60 @@ function emitAsNamespace(name, moduleSymbol) {
367412 fail ( `Unhandled declaration for ${ me . name } at ${ nodeToLocation ( decl ) } ` ) ;
368413 }
369414
370- verifyMatchingSymbols ( statement ) ;
371-
372415 const isInternal = ts . isInternalDeclaration ( statement ) ;
416+ if ( ! ts . isModuleDeclaration ( decl ) ) {
417+ verifyMatchingSymbols ( statement , isInternal ) ;
418+ }
419+
373420 if ( ! isInternal ) {
374421 const publicStatement = ts . visitEachChild ( statement , node => {
375422 // No @internal comments in the public API.
376423 if ( ts . isInternalDeclaration ( node ) ) {
377424 return undefined ;
378425 }
379- return removeDeclareConstExport ( node ) ;
426+ return node ;
380427 } , /*context*/ undefined ) ;
381428
382- writeNode ( publicStatement , sourceFile , WriteTarget . Public ) ;
429+ exportedStatements . push ( [ publicStatement , sourceFile , WriteTarget . Public ] ) ;
383430 }
384431
385- const internalStatement = ts . visitEachChild ( statement , removeDeclareConstExport , /*context*/ undefined ) ;
386-
387- writeNode ( internalStatement , sourceFile , WriteTarget . Internal ) ;
432+ exportedStatements . push ( [ statement , sourceFile , WriteTarget . Internal ] ) ;
388433 }
389434 }
390435
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+
391462 scopeStack . pop ( ) ;
392463
393464 decreaseIndent ( ) ;
394465 write ( `}` , target ) ;
395466}
396467
397- emitAsNamespace ( "ts" , moduleSymbol ) ;
468+ emitAsNamespace ( "ts" , "" , moduleSymbol , /*needExportModifier*/ false ) ;
398469
399470write ( "export = ts;" , WriteTarget . Both ) ;
400471
0 commit comments