fix(language-service): improve performance of updateModuleAnalysis() (#15543)
				
					
				
			This commit is contained in:
		
							parent
							
								
									d438b88f19
								
							
						
					
					
						commit
						6269d28bb0
					
				| @ -33,6 +33,7 @@ export class CompilerHost implements AotCompilerHost { | ||||
|   private resolverCache = new Map<string, ModuleMetadata[]>(); | ||||
|   private bundleIndexCache = new Map<string, boolean>(); | ||||
|   private bundleIndexNames = new Set<string>(); | ||||
|   private moduleFileNames = new Map<string, string>(); | ||||
|   protected resolveModuleNameHost: CompilerHostContext; | ||||
| 
 | ||||
|   constructor( | ||||
| @ -69,19 +70,25 @@ export class CompilerHost implements AotCompilerHost { | ||||
|   getCanonicalFileName(fileName: string): string { return fileName; } | ||||
| 
 | ||||
|   moduleNameToFileName(m: string, containingFile: string): string|null { | ||||
|     if (!containingFile || !containingFile.length) { | ||||
|       if (m.indexOf('.') === 0) { | ||||
|         throw new Error('Resolution of relative paths requires a containing file.'); | ||||
|     const key = m + ':' + (containingFile || ''); | ||||
|     let result = this.moduleFileNames.get(key); | ||||
|     if (!result) { | ||||
|       if (!containingFile || !containingFile.length) { | ||||
|         if (m.indexOf('.') === 0) { | ||||
|           throw new Error('Resolution of relative paths requires a containing file.'); | ||||
|         } | ||||
|         // Any containing file gives the same result for absolute imports
 | ||||
|         containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts')); | ||||
|       } | ||||
|       // Any containing file gives the same result for absolute imports
 | ||||
|       containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts')); | ||||
|       m = m.replace(EXT, ''); | ||||
|       const resolved = | ||||
|           ts.resolveModuleName( | ||||
|                 m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost) | ||||
|               .resolvedModule; | ||||
|       result = resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null; | ||||
|       this.moduleFileNames.set(key, result); | ||||
|     } | ||||
|     m = m.replace(EXT, ''); | ||||
|     const resolved = | ||||
|         ts.resolveModuleName( | ||||
|               m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost) | ||||
|             .resolvedModule; | ||||
|     return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null; | ||||
|     return result; | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|  | ||||
| @ -59,6 +59,7 @@ export class StaticSymbolResolver { | ||||
|   // Note: this will only contain StaticSymbols without members!
 | ||||
|   private importAs = new Map<StaticSymbol, StaticSymbol>(); | ||||
|   private symbolResourcePaths = new Map<StaticSymbol, string>(); | ||||
|   private symbolFromFile = new Map<string, StaticSymbol[]>(); | ||||
| 
 | ||||
|   constructor( | ||||
|       private host: StaticSymbolResolverHost, private staticSymbolCache: StaticSymbolCache, | ||||
| @ -143,6 +144,25 @@ export class StaticSymbolResolver { | ||||
|     this.importAs.set(sourceSymbol, targetSymbol); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Invalidate all information derived from the given file. | ||||
|    * | ||||
|    * @param fileName the file to invalidate | ||||
|    */ | ||||
|   invalidateFile(fileName: string) { | ||||
|     this.metadataCache.delete(fileName); | ||||
|     this.resolvedFilePaths.delete(fileName); | ||||
|     const symbols = this.symbolFromFile.get(fileName); | ||||
|     if (symbols) { | ||||
|       this.symbolFromFile.delete(fileName); | ||||
|       for (const symbol of symbols) { | ||||
|         this.resolvedSymbols.delete(symbol); | ||||
|         this.importAs.delete(symbol); | ||||
|         this.symbolResourcePaths.delete(symbol); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private _resolveSymbolMembers(staticSymbol: StaticSymbol): ResolvedStaticSymbol { | ||||
|     const members = staticSymbol.members; | ||||
|     const baseResolvedSymbol = | ||||
| @ -281,6 +301,7 @@ export class StaticSymbolResolver { | ||||
|     } | ||||
|     resolvedSymbols.forEach( | ||||
|         (resolvedSymbol) => this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol)); | ||||
|     this.symbolFromFile.set(filePath, resolvedSymbols.map(resolvedSymbol => resolvedSymbol.symbol)); | ||||
|   } | ||||
| 
 | ||||
|   private createResolvedSymbol( | ||||
|  | ||||
| @ -87,6 +87,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { | ||||
|   private fileToComponent: Map<string, StaticSymbol>; | ||||
|   private templateReferences: string[]; | ||||
|   private collectedErrors: Map<string, any[]>; | ||||
|   private fileVersions = new Map<string, string>(); | ||||
| 
 | ||||
|   constructor(private host: ts.LanguageServiceHost, private tsService: ts.LanguageService) {} | ||||
| 
 | ||||
| @ -222,7 +223,6 @@ export class TypeScriptServiceHost implements LanguageServiceHost { | ||||
|     if (this.modulesOutOfDate) { | ||||
|       this.analyzedModules = null; | ||||
|       this._reflector = null; | ||||
|       this._staticSymbolResolver = null; | ||||
|       this.templateReferences = null; | ||||
|       this.fileToComponent = null; | ||||
|       this.ensureAnalyzedModules(); | ||||
| @ -242,8 +242,28 @@ export class TypeScriptServiceHost implements LanguageServiceHost { | ||||
| 
 | ||||
|   private validate() { | ||||
|     const program = this.program; | ||||
|     if (this.lastProgram != program) { | ||||
|     if (this._staticSymbolResolver && this.lastProgram != program) { | ||||
|       // Invalidate file that have changed in the static symbol resolver
 | ||||
|       const invalidateFile = (fileName: string) => | ||||
|           this._staticSymbolResolver.invalidateFile(fileName); | ||||
|       this.clearCaches(); | ||||
|       const seen = new Set<string>(); | ||||
|       for (let sourceFile of this.program.getSourceFiles()) { | ||||
|         const fileName = sourceFile.fileName; | ||||
|         seen.add(fileName); | ||||
|         const version = this.host.getScriptVersion(fileName); | ||||
|         const lastVersion = this.fileVersions.get(fileName); | ||||
|         if (version != lastVersion) { | ||||
|           this.fileVersions.set(fileName, version); | ||||
|           invalidateFile(fileName); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       // Remove file versions that are no longer in the file and invalidate them.
 | ||||
|       const missing = Array.from(this.fileVersions.keys()).filter(f => !seen.has(f)); | ||||
|       missing.forEach(f => this.fileVersions.delete(f)); | ||||
|       missing.forEach(invalidateFile); | ||||
| 
 | ||||
|       this.lastProgram = program; | ||||
|     } | ||||
|   } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user