fix(ngcc): do not attempt compilation when analysis fails (#34889)
In #34288, ngtsc was refactored to separate the result of the analysis and resolve phase for more granular incremental rebuilds. In this model, any errors in one phase transition the trait into an error state, which prevents it from being ran through subsequent phases. The ngcc compiler on the other hand did not adopt this strict error model, which would cause incomplete metadata—due to errors in earlier phases—to be offered for compilation that could result in a hard crash. This commit updates ngcc to take advantage of ngtsc's `TraitCompiler`, that internally manages all Ivy classes that are part of the compilation. This effectively replaces ngcc's own `AnalyzedFile` and `AnalyzedClass` types, together with all of the logic to drive the `DecoratorHandler`s. All of this is now handled in the `TraitCompiler`, benefiting from its explicit state transitions of `Trait`s so that the ngcc crash is a thing of the past. Fixes #34500 Resolves FW-1788 PR Close #34889
This commit is contained in:
@ -7,66 +7,40 @@
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
|
||||
import {MetadataReader} from '../../../src/ngtsc/metadata';
|
||||
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
||||
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
||||
import {DecoratorHandler, HandlerFlags} from '../../../src/ngtsc/transform';
|
||||
import {HandlerFlags, TraitState} from '../../../src/ngtsc/transform';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {MigrationHost} from '../migrations/migration';
|
||||
|
||||
import {AnalyzedClass, AnalyzedFile} from './types';
|
||||
import {analyzeDecorators, isWithinPackage} from './util';
|
||||
import {NgccTraitCompiler} from './ngcc_trait_compiler';
|
||||
import {isWithinPackage} from './util';
|
||||
|
||||
/**
|
||||
* The standard implementation of `MigrationHost`, which is created by the
|
||||
* `DecorationAnalyzer`.
|
||||
* The standard implementation of `MigrationHost`, which is created by the `DecorationAnalyzer`.
|
||||
*/
|
||||
export class DefaultMigrationHost implements MigrationHost {
|
||||
constructor(
|
||||
readonly reflectionHost: NgccReflectionHost, readonly metadata: MetadataReader,
|
||||
readonly evaluator: PartialEvaluator,
|
||||
private handlers: DecoratorHandler<unknown, unknown, unknown>[],
|
||||
private entryPointPath: AbsoluteFsPath, private analyzedFiles: AnalyzedFile[],
|
||||
private diagnosticHandler: (error: ts.Diagnostic) => void) {}
|
||||
readonly evaluator: PartialEvaluator, private compiler: NgccTraitCompiler,
|
||||
private entryPointPath: AbsoluteFsPath) {}
|
||||
|
||||
injectSyntheticDecorator(clazz: ClassDeclaration, decorator: Decorator, flags?: HandlerFlags):
|
||||
void {
|
||||
const classSymbol = this.reflectionHost.getClassSymbol(clazz) !;
|
||||
const newAnalyzedClass = analyzeDecorators(classSymbol, [decorator], this.handlers, flags);
|
||||
if (newAnalyzedClass === null) {
|
||||
return;
|
||||
}
|
||||
const migratedTraits = this.compiler.injectSyntheticDecorator(clazz, decorator, flags);
|
||||
|
||||
if (newAnalyzedClass.diagnostics !== undefined) {
|
||||
for (const diagnostic of newAnalyzedClass.diagnostics) {
|
||||
this.diagnosticHandler(createMigrationDiagnostic(diagnostic, clazz, decorator));
|
||||
for (const trait of migratedTraits) {
|
||||
if (trait.state === TraitState.ERRORED) {
|
||||
trait.diagnostics =
|
||||
trait.diagnostics.map(diag => createMigrationDiagnostic(diag, clazz, decorator));
|
||||
}
|
||||
}
|
||||
|
||||
const analyzedFile = getOrCreateAnalyzedFile(this.analyzedFiles, clazz.getSourceFile());
|
||||
const oldAnalyzedClass = analyzedFile.analyzedClasses.find(c => c.declaration === clazz);
|
||||
if (oldAnalyzedClass === undefined) {
|
||||
analyzedFile.analyzedClasses.push(newAnalyzedClass);
|
||||
} else {
|
||||
mergeAnalyzedClasses(oldAnalyzedClass, newAnalyzedClass);
|
||||
}
|
||||
}
|
||||
|
||||
getAllDecorators(clazz: ClassDeclaration): Decorator[]|null {
|
||||
const sourceFile = clazz.getSourceFile();
|
||||
const analyzedFile = this.analyzedFiles.find(file => file.sourceFile === sourceFile);
|
||||
if (analyzedFile === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.declaration === clazz);
|
||||
if (analyzedClass === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return analyzedClass.decorators;
|
||||
return this.compiler.getAllDecorators(clazz);
|
||||
}
|
||||
|
||||
isInScope(clazz: ClassDeclaration): boolean {
|
||||
@ -74,43 +48,6 @@ export class DefaultMigrationHost implements MigrationHost {
|
||||
}
|
||||
}
|
||||
|
||||
function getOrCreateAnalyzedFile(
|
||||
analyzedFiles: AnalyzedFile[], sourceFile: ts.SourceFile): AnalyzedFile {
|
||||
const analyzedFile = analyzedFiles.find(file => file.sourceFile === sourceFile);
|
||||
if (analyzedFile !== undefined) {
|
||||
return analyzedFile;
|
||||
} else {
|
||||
const newAnalyzedFile: AnalyzedFile = {sourceFile, analyzedClasses: []};
|
||||
analyzedFiles.push(newAnalyzedFile);
|
||||
return newAnalyzedFile;
|
||||
}
|
||||
}
|
||||
|
||||
function mergeAnalyzedClasses(oldClass: AnalyzedClass, newClass: AnalyzedClass) {
|
||||
if (newClass.decorators !== null) {
|
||||
if (oldClass.decorators === null) {
|
||||
oldClass.decorators = newClass.decorators;
|
||||
} else {
|
||||
for (const newDecorator of newClass.decorators) {
|
||||
if (oldClass.decorators.some(d => d.name === newDecorator.name)) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.NGCC_MIGRATION_DECORATOR_INJECTION_ERROR, newClass.declaration,
|
||||
`Attempted to inject "${newDecorator.name}" decorator over a pre-existing decorator with the same name on the "${newClass.name}" class.`);
|
||||
}
|
||||
}
|
||||
oldClass.decorators.push(...newClass.decorators);
|
||||
}
|
||||
}
|
||||
|
||||
if (newClass.diagnostics !== undefined) {
|
||||
if (oldClass.diagnostics === undefined) {
|
||||
oldClass.diagnostics = newClass.diagnostics;
|
||||
} else {
|
||||
oldClass.diagnostics.push(...newClass.diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a diagnostic from another one, containing additional information about the synthetic
|
||||
* decorator.
|
||||
|
Reference in New Issue
Block a user