diff --git a/packages/compiler/src/aot/static_reflector.ts b/packages/compiler/src/aot/static_reflector.ts index d699cd6210..1bb58fb4e4 100644 --- a/packages/compiler/src/aot/static_reflector.ts +++ b/packages/compiler/src/aot/static_reflector.ts @@ -86,6 +86,22 @@ export class StaticReflector implements CompileReflector { return this.symbolResolver.getResourcePath(staticSymbol); } + /** + * Invalidate the specified `symbols` on program change. + * @param symbols + */ + invalidateSymbols(symbols: StaticSymbol[]) { + for (const symbol of symbols) { + this.annotationCache.delete(symbol); + this.shallowAnnotationCache.delete(symbol); + this.propertyCache.delete(symbol); + this.parameterCache.delete(symbol); + this.methodCache.delete(symbol); + this.staticCache.delete(symbol); + this.conversionMap.delete(symbol); + } + } + resolveExternalReference(ref: o.ExternalReference, containingFile?: string): StaticSymbol { let key: string|undefined = undefined; if (!containingFile) { diff --git a/packages/compiler/src/aot/static_symbol_resolver.ts b/packages/compiler/src/aot/static_symbol_resolver.ts index 4d444723e4..74ff525e7d 100644 --- a/packages/compiler/src/aot/static_symbol_resolver.ts +++ b/packages/compiler/src/aot/static_symbol_resolver.ts @@ -63,7 +63,6 @@ export class StaticSymbolResolver { private metadataCache = new Map(); // Note: this will only contain StaticSymbols without members! private resolvedSymbols = new Map(); - private resolvedFilePaths = new Set(); // Note: this will only contain StaticSymbols without members! private importAs = new Map(); private symbolResourcePaths = new Map(); @@ -176,22 +175,24 @@ export class StaticSymbolResolver { } /** - * Invalidate all information derived from the given file. + * Invalidate all information derived from the given file and return the + * static symbols contained in the file. * * @param fileName the file to invalidate */ - invalidateFile(fileName: string) { + invalidateFile(fileName: string): StaticSymbol[] { 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); - } + if (!symbols) { + return []; } + this.symbolFromFile.delete(fileName); + for (const symbol of symbols) { + this.resolvedSymbols.delete(symbol); + this.importAs.delete(symbol); + this.symbolResourcePaths.delete(symbol); + } + return symbols; } /** @internal */ @@ -277,10 +278,9 @@ export class StaticSymbolResolver { } private _createSymbolsOf(filePath: string) { - if (this.resolvedFilePaths.has(filePath)) { + if (this.symbolFromFile.has(filePath)) { return; } - this.resolvedFilePaths.add(filePath); const resolvedSymbols: ResolvedStaticSymbol[] = []; const metadata = this.getModuleMetadata(filePath); if (metadata['importAs']) { diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts index c6d5f1e0f6..32baa7d1b9 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts @@ -280,7 +280,7 @@ export class UndecoratedClassesTransform { return [{ node, message: `Class cannot be migrated as the inherited metadata from ` + - `${identifier.getText()} cannot be converted into a decorator. Please manually + `${identifier.getText()} cannot be converted into a decorator. Please manually decorate the class.`, }]; } @@ -431,7 +431,7 @@ export class UndecoratedClassesTransform { // future calls to "StaticReflector#annotations" are based on metadata files. this.symbolResolver['_resolveSymbolFromSummary'] = () => null; this.symbolResolver['resolvedSymbols'].clear(); - this.symbolResolver['resolvedFilePaths'].clear(); + this.symbolResolver['symbolFromFile'].clear(); this.compiler.reflector['annotationCache'].clear(); // Original summary resolver used by the AOT compiler. diff --git a/packages/language-service/src/typescript_host.ts b/packages/language-service/src/typescript_host.ts index c25eb6f7ff..9614112e99 100644 --- a/packages/language-service/src/typescript_host.ts +++ b/packages/language-service/src/typescript_host.ts @@ -166,9 +166,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { this.templateReferences = []; this.fileToComponent.clear(); this.collectedErrors.clear(); - // TODO: This is only temporary. When https://github.com/angular/angular/pull/32543 - // is merged this is no longer necessary. - this._resolver = undefined; // Invalidate the resolver + this.resolver.clearCache(); const analyzeHost = {isSourceFile(filePath: string) { return true; }}; const programFiles = this.program.getSourceFiles().map(sf => sf.fileName); @@ -289,37 +287,37 @@ export class TypeScriptServiceHost implements LanguageServiceHost { * Returns true if modules are up-to-date, false otherwise. * This should only be called by getAnalyzedModules(). */ - private upToDate() { - const program = this.program; - if (this.lastProgram === program) { + private upToDate(): boolean { + const {lastProgram, program} = this; + if (lastProgram === program) { return true; } + this.lastProgram = program; // Invalidate file that have changed in the static symbol resolver const seen = new Set(); - let hasChanges = false; for (const sourceFile of program.getSourceFiles()) { const fileName = sourceFile.fileName; seen.add(fileName); const version = this.tsLsHost.getScriptVersion(fileName); const lastVersion = this.fileVersions.get(fileName); - if (version !== lastVersion) { - hasChanges = true; - this.fileVersions.set(fileName, version); - this.staticSymbolResolver.invalidateFile(fileName); + this.fileVersions.set(fileName, version); + // Should not invalidate file on the first encounter or if file hasn't changed + if (lastVersion !== undefined && version !== lastVersion) { + const symbols = this.staticSymbolResolver.invalidateFile(fileName); + this.reflector.invalidateSymbols(symbols); } } - // Remove file versions that are no longer in the file and invalidate them. + // Remove file versions that are no longer in the program and invalidate them. const missing = Array.from(this.fileVersions.keys()).filter(f => !seen.has(f)); missing.forEach(f => { this.fileVersions.delete(f); - this.staticSymbolResolver.invalidateFile(f); + const symbols = this.staticSymbolResolver.invalidateFile(f); + this.reflector.invalidateSymbols(symbols); }); - this.lastProgram = program; - - return missing.length === 0 && !hasChanges; + return false; } /** diff --git a/packages/language-service/test/typescript_host_spec.ts b/packages/language-service/test/typescript_host_spec.ts index de7ff73f3d..df9fabbe9a 100644 --- a/packages/language-service/test/typescript_host_spec.ts +++ b/packages/language-service/test/typescript_host_spec.ts @@ -51,22 +51,24 @@ describe('TypeScriptServiceHost', () => { expect(analyzedModules.files.length).toBe(0); expect(analyzedModules.ngModules.length).toBe(0); expect(analyzedModules.ngModuleByPipeOrDirective.size).toBe(0); - expect(analyzedModules.symbolsMissingModule).toBeUndefined(); + expect(analyzedModules.symbolsMissingModule).toEqual([]); }); - it('should clear the caches if program changes', () => { + it('should clear the caches if new script is added', () => { // First create a TypescriptHost with empty script names const tsLSHost = new MockTypescriptHost([]); const tsLS = ts.createLanguageService(tsLSHost); const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); - expect(ngLSHost.getAnalyzedModules().ngModules).toEqual([]); + const oldModules = ngLSHost.getAnalyzedModules(); + expect(oldModules.ngModules).toEqual([]); // Now add a script, this would change the program const fileName = '/app/main.ts'; const content = tsLSHost.readFile(fileName) !; tsLSHost.addScript(fileName, content); // If the caches are not cleared, we would get back an empty array. // But if the caches are cleared then the analyzed modules will be non-empty. - expect(ngLSHost.getAnalyzedModules().ngModules.length).not.toEqual(0); + const newModules = ngLSHost.getAnalyzedModules(); + expect(newModules.ngModules.length).toBeGreaterThan(0); }); it('should throw if getSourceFile is called on non-TS file', () => {