diff --git a/packages/compiler-cli/ngcc/src/analysis/util.ts b/packages/compiler-cli/ngcc/src/analysis/util.ts index dfe2b6188bad3..82612413055da 100644 --- a/packages/compiler-cli/ngcc/src/analysis/util.ts +++ b/packages/compiler-cli/ngcc/src/analysis/util.ts @@ -18,6 +18,7 @@ class NoopDependencyTracker implements DependencyTracker { addResourceDependency(): void {} addTransitiveDependency(): void {} addTransitiveResources(): void {} + recordDependencyAnalysisFailure(): void {} } export const NOOP_DEPENDENCY_TRACKER: DependencyTracker = new NoopDependencyTracker(); diff --git a/packages/compiler-cli/src/ngtsc/incremental/api.ts b/packages/compiler-cli/src/ngtsc/incremental/api.ts index 10a222e2bd2d6..d2f6a635c251b 100644 --- a/packages/compiler-cli/src/ngtsc/incremental/api.ts +++ b/packages/compiler-cli/src/ngtsc/incremental/api.ts @@ -64,4 +64,12 @@ export interface DependencyTracker * `resourcesOf` they will not automatically be added to `from`. */ addTransitiveResources(from: T, resourcesOf: T): void; + + /** + * Record that the given file contains unresolvable dependencies. + * + * In practice, this means that the dependency graph cannot provide insight into the effects of + * future changes on that file. + */ + recordDependencyAnalysisFailure(file: T): void; } diff --git a/packages/compiler-cli/src/ngtsc/incremental/src/dependency_tracking.ts b/packages/compiler-cli/src/ngtsc/incremental/src/dependency_tracking.ts index 42fc122f00e60..97cf6cca5a2da 100644 --- a/packages/compiler-cli/src/ngtsc/incremental/src/dependency_tracking.ts +++ b/packages/compiler-cli/src/ngtsc/incremental/src/dependency_tracking.ts @@ -53,6 +53,10 @@ export class FileDependencyGraph i } } + recordDependencyAnalysisFailure(file: T): void { + this.nodeFor(file).failedAnalysis = true; + } + getResourceDependencies(from: T): AbsoluteFsPath[] { const node = this.nodes.get(from); @@ -97,6 +101,7 @@ export class FileDependencyGraph i this.nodes.set(sf, { dependsOn: new Set(node.dependsOn), usesResources: new Set(node.usesResources), + failedAnalysis: false, }); } } @@ -109,6 +114,7 @@ export class FileDependencyGraph i this.nodes.set(sf, { dependsOn: new Set(), usesResources: new Set(), + failedAnalysis: false, }); } return this.nodes.get(sf)!; @@ -122,6 +128,12 @@ export class FileDependencyGraph i function isLogicallyChanged( sf: T, node: FileNode, changedTsPaths: ReadonlySet, deletedTsPaths: ReadonlySet, changedResources: ReadonlySet): boolean { + // A file is assumed to have logically changed if its dependencies could not be determined + // accurately. + if (node.failedAnalysis) { + return true; + } + // A file is logically changed if it has physically changed itself (including being deleted). if (changedTsPaths.has(sf.fileName) || deletedTsPaths.has(sf.fileName)) { return true; @@ -146,6 +158,7 @@ function isLogicallyChanged( interface FileNode { dependsOn: Set; usesResources: Set; + failedAnalysis: boolean; } const EMPTY_SET: ReadonlySet = new Set(); diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts index 20f19320550a3..5579e6ccf3946 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts @@ -220,6 +220,15 @@ export class StaticInterpreter { if (node.originalKeywordKind === ts.SyntaxKind.UndefinedKeyword) { return undefined; } else { + // Check if the symbol here is imported. + if (this.dependencyTracker !== null && this.host.getImportOfIdentifier(node) !== null) { + // It was, but no declaration for the node could be found. This means that the dependency + // graph for the current file cannot be properly updated to account for this (broken) + // import. Instead, the originating file is reported as failing dependency analysis, + // ensuring that future compilations will always attempt to re-resolve the previously + // broken identifier. + this.dependencyTracker.recordDependencyAnalysisFailure(context.originatingFile); + } return DynamicValue.fromUnknownIdentifier(node); } } diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts index 0f728bae4413a..4b6ad7443c84e 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts @@ -991,4 +991,5 @@ const fakeDepTracker: DependencyTracker = { addResourceDependency: () => undefined, addTransitiveDependency: () => undefined, addTransitiveResources: () => undefined, + recordDependencyAnalysisFailure: () => undefined, };