55 * Use of this source code is governed by an MIT-style license that can be 
66 * found in the LICENSE file at https://angular.io/license 
77 */ 
8+ import  {  dirname ,  relative  }  from  'path' ; 
89import  *  as  ts  from  'typescript' ; 
10+ import  {  forwardSlashPath  }  from  '../utils' ; 
911
1012
1113/** 
@@ -51,6 +53,7 @@ import * as ts from 'typescript';
5153
5254export  function  importFactory ( 
5355  warningCb : ( warning : string )  =>  void , 
56+   getTypeChecker : ( )  =>  ts . TypeChecker , 
5457) : ts . TransformerFactory < ts . SourceFile >  { 
5558  return  ( context : ts . TransformationContext )  =>  { 
5659    // TODO(filipesilva): change the link to https://angular.io/guide/ivy once it is out. 
@@ -69,7 +72,7 @@ Visit https://next.angular.io/guide/ivy for more information on using Ivy.
6972      const  emitWarning  =  ( )  =>  warningCb ( warning ) ; 
7073      const  visitVariableStatement : ts . Visitor  =  ( node : ts . Node )  =>  { 
7174        if  ( ts . isVariableDeclaration ( node ) )  { 
72-           return  replaceImport ( node ,  context ,  emitWarning ) ; 
75+           return  replaceImport ( node ,  context ,  emitWarning ,   sourceFile . fileName ,   getTypeChecker ( ) ) ; 
7376        } 
7477
7578        return  ts . visitEachChild ( node ,  visitVariableStatement ,  context ) ; 
@@ -95,6 +98,8 @@ function replaceImport(
9598  node : ts . VariableDeclaration , 
9699  context : ts . TransformationContext , 
97100  emitWarning : ( )  =>  void , 
101+   fileName : string , 
102+   typeChecker : ts . TypeChecker , 
98103) : ts . Node  { 
99104  // This ONLY matches the original source code format below: 
100105  // loadChildren: () => import('IMPORT_STRING').then(M => M.EXPORT_NAME) 
@@ -157,16 +162,20 @@ function replaceImport(
157162    return  node ; 
158163  } 
159164
165+   // Now that we know it's both `ɵ0` (generated by NGC) and a `import()`, start emitting a warning 
166+   // if the structure isn't as expected to help users identify unusable syntax. 
167+   const  warnAndBail  =  ( )  =>  { 
168+     emitWarning ( ) ; 
169+ 
170+     return  node ; 
171+   } ; 
172+ 
160173  // ɵ0 = () => import('IMPORT_STRING').then(m => m.EXPORT_NAME) 
161174  if  ( ! ( 
162175    topArrowFnBody . arguments . length  ===  1 
163176    &&  ts . isArrowFunction ( topArrowFnBody . arguments [ 0 ] ) 
164177  ) )  { 
165-     // Now that we know it's both `ɵ0` (generated by NGC) and a `import()`, start emitting a warning 
166-     // if the structure isn't as expected to help users identify unusable syntax. 
167-     emitWarning ( ) ; 
168- 
169-     return  node ; 
178+     return  warnAndBail ( ) ; 
170179  } 
171180
172181  const  thenArrowFn  =  topArrowFnBody . arguments [ 0 ]  as  ts . ArrowFunction ; 
@@ -175,20 +184,51 @@ function replaceImport(
175184    &&  ts . isPropertyAccessExpression ( thenArrowFn . body ) 
176185    &&  ts . isIdentifier ( thenArrowFn . body . name ) 
177186  ) )  { 
178-     emitWarning ( ) ; 
179- 
180-     return  node ; 
187+     return  warnAndBail ( ) ; 
181188  } 
182189
183190  // At this point we know what are the nodes we need to replace. 
184-   const  importStringLit  =  importCall . arguments [ 0 ]  as  ts . StringLiteral ; 
185191  const  exportNameId  =  thenArrowFn . body . name ; 
192+   const  importStringLit  =  importCall . arguments [ 0 ]  as  ts . StringLiteral ; 
193+ 
194+   // Try to resolve the import. It might be a reexport from somewhere and the ngfactory will only 
195+   // be present next to the original module. 
196+   const  exportedSymbol  =  typeChecker . getSymbolAtLocation ( exportNameId ) ; 
197+   if  ( ! exportedSymbol )  { 
198+     return  warnAndBail ( ) ; 
199+   } 
200+ 
201+   // Symbols have parents. But it's an internal thing. We shouldn't use it. 
202+   // TODO: find a better way of discovering the file where this symbol is declared. 
203+   type  SymbolWithParent  =  ts . Symbol  &  {  parent ?: SymbolWithParent  } ; 
204+ 
205+   let  rootSymbol  =  exportedSymbol  as  { }  as  SymbolWithParent ; 
206+   while  ( rootSymbol . parent )  { 
207+     rootSymbol  =  rootSymbol . parent ; 
208+   } 
209+ 
210+   if  ( ! ts . isSourceFile ( rootSymbol . valueDeclaration ) )  { 
211+     return  warnAndBail ( ) ; 
212+   } 
213+ 
214+   // SourceFile have paths. But it's an internal thing. We shouldn't use it. 
215+   // TODO: find a better way of discovering the source file path. 
216+   type  SourceFileWithPath  =  ts . SourceFile  &  {  path : string  } ; 
217+ 
218+   const  importedModulePath  =  ( rootSymbol . valueDeclaration  as  SourceFileWithPath ) . path ; 
219+ 
220+   // Get the relative path from the containing module to the imported module. 
221+   const  relativePath  =  relative ( dirname ( fileName ) ,  importedModulePath ) ; 
222+ 
223+   // node's `relative` call doesn't actually add `./` so we add it here. 
224+   // Also replace the 'ts' extension with just 'ngfactory'. 
225+   const  newImportString  =  `./${ forwardSlashPath ( relativePath ) }  . replace ( / t s $ / ,  'ngfactory' ) ; 
186226
187227  // The easiest way to alter them is with a simple visitor. 
188228  const  replacementVisitor : ts . Visitor  =  ( node : ts . Node )  =>  { 
189229    if  ( node  ===  importStringLit )  { 
190230      // Transform the import string. 
191-       return  ts . createStringLiteral ( importStringLit . text   +   '.ngfactory' ) ; 
231+       return  ts . createStringLiteral ( newImportString ) ; 
192232    }  else  if  ( node  ===  exportNameId )  { 
193233      // Transform the export name. 
194234      return  ts . createIdentifier ( exportNameId . text  +  'NgFactory' ) ; 
0 commit comments