feat(ivy): track compilation scope dependencies for components (#30238)

To support skipping analysis of a file containing a component
we need to know that none of the declarations that might affect
its ngtsc compilation have not changed. The files that we need to
check are those that contain classes from the `CompilationScope`
of the component. These classes are already tracked in the
`LocalModuleScopeRegistry`.

This commit modifies the `IvyCompilation` class to record the
files that are in each declared class's `CompilationScope` via
a new method, `recordNgModuleScopeDependencies()`, that is called
after all the handlers have been "resolved".

Further, if analysis is skipped for a declared class, then we need
to recover the analysis from the previous compilation run. To
support this, the `IncrementalState` class has been updated to
expose the `MetadataReader` and `MetadataRegistry` interfaces.
This is included in the `metaRegistry` object to capture these analyses,
and also in the `localMetaReader` as a fallback to use if the
current compilation analysis was skipped.

PR Close #30238
This commit is contained in:
Pete Bacon Darwin 2019-05-08 15:10:50 +01:00 committed by Alex Rickabaugh
parent 0a0b4c1d8f
commit 411524d341
6 changed files with 111 additions and 22 deletions

View File

@ -8,7 +8,10 @@ ts_library(
"src/**/*.ts", "src/**/*.ts",
]), ]),
deps = [ deps = [
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/reflection",
"@npm//typescript", "@npm//typescript",
], ],
) )

View File

@ -7,12 +7,15 @@
*/ */
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Reference} from '../../imports';
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {DependencyTracker} from '../../partial_evaluator'; import {DependencyTracker} from '../../partial_evaluator';
import {ClassDeclaration} from '../../reflection';
/** /**
* Accumulates state between compilations. * Accumulates state between compilations.
*/ */
export class IncrementalState implements DependencyTracker { export class IncrementalState implements DependencyTracker, MetadataReader, MetadataRegistry {
private constructor( private constructor(
private unchangedFiles: Set<ts.SourceFile>, private unchangedFiles: Set<ts.SourceFile>,
private metadata: Map<ts.SourceFile, FileMetadata>) {} private metadata: Map<ts.SourceFile, FileMetadata>) {}
@ -85,6 +88,33 @@ export class IncrementalState implements DependencyTracker {
metadata.fileDependencies.add(dep); metadata.fileDependencies.add(dep);
} }
getNgModuleMetadata(ref: Reference<ClassDeclaration>): NgModuleMeta|null {
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
return metadata && metadata.ngModuleMeta.get(ref.node) || null;
}
registerNgModuleMetadata(meta: NgModuleMeta): void {
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
metadata.ngModuleMeta.set(meta.ref.node, meta);
}
getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null {
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
return metadata && metadata.directiveMeta.get(ref.node) || null;
}
registerDirectiveMetadata(meta: DirectiveMeta): void {
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
metadata.directiveMeta.set(meta.ref.node, meta);
}
getPipeMetadata(ref: Reference<ClassDeclaration>): PipeMeta|null {
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
return metadata && metadata.pipeMeta.get(ref.node) || null;
}
registerPipeMetadata(meta: PipeMeta): void {
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
metadata.pipeMeta.set(meta.ref.node, meta);
}
private ensureMetadata(sf: ts.SourceFile): FileMetadata { private ensureMetadata(sf: ts.SourceFile): FileMetadata {
const metadata = this.metadata.get(sf) || new FileMetadata(); const metadata = this.metadata.get(sf) || new FileMetadata();
this.metadata.set(sf, metadata); this.metadata.set(sf, metadata);
@ -100,4 +130,7 @@ class FileMetadata {
safeToSkipEmitIfUnchanged = false; safeToSkipEmitIfUnchanged = false;
/** A set of source files that this file depends upon. */ /** A set of source files that this file depends upon. */
fileDependencies = new Set<ts.SourceFile>(); fileDependencies = new Set<ts.SourceFile>();
directiveMeta = new Map<ClassDeclaration, DirectiveMeta>();
ngModuleMeta = new Map<ClassDeclaration, NgModuleMeta>();
pipeMeta = new Map<ClassDeclaration, PipeMeta>();
} }

View File

@ -460,12 +460,14 @@ export class NgtscProgram implements api.Program {
const evaluator = new PartialEvaluator(this.reflector, checker, this.incrementalState); const evaluator = new PartialEvaluator(this.reflector, checker, this.incrementalState);
const dtsReader = new DtsMetadataReader(checker, this.reflector); const dtsReader = new DtsMetadataReader(checker, this.reflector);
const localMetaRegistry = new LocalMetadataRegistry(); const localMetaRegistry = new LocalMetadataRegistry();
const localMetaReader = new CompoundMetadataReader([localMetaRegistry, this.incrementalState]);
const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasGenerator); const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasGenerator);
const scopeRegistry = new LocalModuleScopeRegistry( const scopeRegistry = new LocalModuleScopeRegistry(
localMetaRegistry, depScopeReader, this.refEmitter, aliasGenerator); localMetaReader, depScopeReader, this.refEmitter, aliasGenerator);
const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry]); const metaRegistry =
new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry, this.incrementalState]);
this.metaReader = new CompoundMetadataReader([localMetaRegistry, dtsReader]); this.metaReader = new CompoundMetadataReader([localMetaReader, dtsReader]);
// If a flat module entrypoint was specified, then track references via a `ReferenceGraph` // If a flat module entrypoint was specified, then track references via a `ReferenceGraph`
@ -504,8 +506,8 @@ export class NgtscProgram implements api.Program {
]; ];
return new IvyCompilation( return new IvyCompilation(
handlers, checker, this.reflector, this.importRewriter, this.incrementalState, handlers, this.reflector, this.importRewriter, this.incrementalState, this.perfRecorder,
this.perfRecorder, this.sourceToFactorySymbols); this.sourceToFactorySymbols, scopeRegistry);
} }
private get reflector(): TypeScriptReflectionHost { private get reflector(): TypeScriptReflectionHost {

View File

@ -29,6 +29,16 @@ export interface LocalModuleScope extends ExportScope {
reexports: Reexport[]|null; reexports: Reexport[]|null;
} }
/**
* Information about the compilation scope of a registered declaration.
*/
export interface CompilationScope extends ScopeData {
/** The declaration whose compilation scope is described here. */
declaration: ClassDeclaration;
/** The declaration of the NgModule that declares this `declaration`. */
ngModule: ClassDeclaration;
}
/** /**
* A registry which collects information about NgModules, Directives, Components, and Pipes which * A registry which collects information about NgModules, Directives, Components, and Pipes which
* are local (declared in the ts.Program being compiled), and can produce `LocalModuleScope`s * are local (declared in the ts.Program being compiled), and can produce `LocalModuleScope`s
@ -73,7 +83,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
* A value of `undefined` indicates the scope was invalid and produced errors (therefore, * A value of `undefined` indicates the scope was invalid and produced errors (therefore,
* diagnostics should exist in the `scopeErrors` map). * diagnostics should exist in the `scopeErrors` map).
*/ */
private cache = new Map<ClassDeclaration, LocalModuleScope|undefined>(); private cache = new Map<ClassDeclaration, LocalModuleScope|undefined|null>();
/** /**
* Tracks whether a given component requires "remote scoping". * Tracks whether a given component requires "remote scoping".
@ -125,11 +135,9 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
* available or the scope contains errors. * available or the scope contains errors.
*/ */
getScopeOfModule(clazz: ClassDeclaration): LocalModuleScope|null { getScopeOfModule(clazz: ClassDeclaration): LocalModuleScope|null {
if (!this.moduleToRef.has(clazz)) { const scope = this.moduleToRef.has(clazz) ?
return null; this.getScopeOfModuleReference(this.moduleToRef.get(clazz) !) :
} null;
const scope = this.getScopeOfModuleInternal(this.moduleToRef.get(clazz) !);
// Translate undefined -> null. // Translate undefined -> null.
return scope !== undefined ? scope : null; return scope !== undefined ? scope : null;
} }
@ -151,11 +159,32 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
} }
/** /**
* Implementation of `getScopeOfModule` which differentiates between no scope being available * Returns a collection of the compilation scope for each registered declaration.
* (returns `null`) and a scope being produced with errors (returns `undefined`).
*/ */
private getScopeOfModuleInternal(ref: Reference<ClassDeclaration>): LocalModuleScope|null getCompilationScopes(): CompilationScope[] {
const scopes: CompilationScope[] = [];
this.declarationToModule.forEach((ngModule, declaration) => {
const scope = this.getScopeOfModule(ngModule);
if (scope !== null) {
scopes.push({declaration, ngModule, ...scope.compilation});
}
});
return scopes;
}
/**
* Implementation of `getScopeOfModule` which accepts a reference to a class and differentiates
* between:
*
* * no scope being available (returns `null`)
* * a scope being produced with errors (returns `undefined`).
*/
private getScopeOfModuleReference(ref: Reference<ClassDeclaration>): LocalModuleScope|null
|undefined { |undefined {
if (this.cache.has(ref.node)) {
return this.cache.get(ref.node);
}
// Seal the registry to protect the integrity of the `LocalModuleScope` cache. // Seal the registry to protect the integrity of the `LocalModuleScope` cache.
this.sealed = true; this.sealed = true;
@ -163,6 +192,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
// cannot be produced. // cannot be produced.
const ngModule = this.localReader.getNgModuleMetadata(ref); const ngModule = this.localReader.getNgModuleMetadata(ref);
if (ngModule === null) { if (ngModule === null) {
this.cache.set(ref.node, null);
return null; return null;
} }
@ -326,6 +356,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
this.scopeErrors.set(ref.node, diagnostics); this.scopeErrors.set(ref.node, diagnostics);
// Return undefined to indicate the scope is invalid. // Return undefined to indicate the scope is invalid.
this.cache.set(ref.node, undefined);
return undefined; return undefined;
} }
@ -360,8 +391,10 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
* The NgModule in question may be declared locally in the current ts.Program, or it may be * The NgModule in question may be declared locally in the current ts.Program, or it may be
* declared in a .d.ts file. * declared in a .d.ts file.
* *
* This function will return `null` if no scope could be found, or `undefined` if an invalid scope * @returns `null` if no scope could be found, or `undefined` if an invalid scope
* was found. It can also contribute diagnostics of its own by adding to the given `diagnostics` * was found.
*
* May also contribute diagnostics of its own by adding to the given `diagnostics`
* array parameter. * array parameter.
*/ */
private getExportedScope( private getExportedScope(
@ -382,7 +415,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
return this.dependencyScopeReader.resolve(ref); return this.dependencyScopeReader.resolve(ref);
} else { } else {
// The NgModule is declared locally in the current program. Resolve it from the registry. // The NgModule is declared locally in the current program. Resolve it from the registry.
return this.getScopeOfModuleInternal(ref); return this.getScopeOfModuleReference(ref);
} }
} }

View File

@ -14,6 +14,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/incremental", "//packages/compiler-cli/src/ngtsc/incremental",
"//packages/compiler-cli/src/ngtsc/perf", "//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/translator", "//packages/compiler-cli/src/ngtsc/translator",
"//packages/compiler-cli/src/ngtsc/typecheck", "//packages/compiler-cli/src/ngtsc/typecheck",
"//packages/compiler-cli/src/ngtsc/util", "//packages/compiler-cli/src/ngtsc/util",

View File

@ -14,6 +14,7 @@ import {ImportRewriter} from '../../imports';
import {IncrementalState} from '../../incremental'; import {IncrementalState} from '../../incremental';
import {PerfRecorder} from '../../perf'; import {PerfRecorder} from '../../perf';
import {ClassDeclaration, ReflectionHost, isNamedClassDeclaration, reflectNameOfDeclaration} from '../../reflection'; import {ClassDeclaration, ReflectionHost, isNamedClassDeclaration, reflectNameOfDeclaration} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope';
import {TypeCheckContext} from '../../typecheck'; import {TypeCheckContext} from '../../typecheck';
import {getSourceFile} from '../../util/src/typescript'; import {getSourceFile} from '../../util/src/typescript';
@ -75,10 +76,10 @@ export class IvyCompilation {
* `null` in most cases. * `null` in most cases.
*/ */
constructor( constructor(
private handlers: DecoratorHandler<any, any>[], private checker: ts.TypeChecker, private handlers: DecoratorHandler<any, any>[], private reflector: ReflectionHost,
private reflector: ReflectionHost, private importRewriter: ImportRewriter, private importRewriter: ImportRewriter, private incrementalState: IncrementalState,
private incrementalState: IncrementalState, private perf: PerfRecorder, private perf: PerfRecorder, private sourceToFactorySymbols: Map<string, Set<string>>|null,
private sourceToFactorySymbols: Map<string, Set<string>>|null) {} private scopeRegistry: LocalModuleScopeRegistry) {}
get exportStatements(): Map<string, Map<string, [string, string]>> { return this.reexportMap; } get exportStatements(): Map<string, Map<string, [string, string]>> { return this.reexportMap; }
@ -302,6 +303,22 @@ export class IvyCompilation {
} }
}); });
this.perf.stop(resolveSpan); this.perf.stop(resolveSpan);
this.recordNgModuleScopeDependencies();
}
private recordNgModuleScopeDependencies() {
const recordSpan = this.perf.start('recordDependencies');
this.scopeRegistry !.getCompilationScopes().forEach(scope => {
const file = scope.declaration.getSourceFile();
// Register the file containing the NgModule where the declaration is declared.
this.incrementalState.trackFileDependency(scope.ngModule.getSourceFile(), file);
scope.directives.forEach(
directive =>
this.incrementalState.trackFileDependency(directive.ref.node.getSourceFile(), file));
scope.pipes.forEach(
pipe => this.incrementalState.trackFileDependency(pipe.ref.node.getSourceFile(), file));
});
this.perf.stop(recordSpan);
} }
typeCheck(context: TypeCheckContext): void { typeCheck(context: TypeCheckContext): void {