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:
@ -15,20 +15,19 @@ import {FileSystem, LogicalFileSystem, absoluteFrom, dirname, resolve} from '../
|
||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports';
|
||||
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../../src/ngtsc/metadata';
|
||||
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
||||
import {ClassDeclaration} from '../../../src/ngtsc/reflection';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
|
||||
import {CompileResult, DecoratorHandler} from '../../../src/ngtsc/transform';
|
||||
import {NgccClassSymbol, NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {DecoratorHandler} from '../../../src/ngtsc/transform';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {Migration} from '../migrations/migration';
|
||||
import {MissingInjectableMigration} from '../migrations/missing_injectable_migration';
|
||||
import {UndecoratedChildMigration} from '../migrations/undecorated_child_migration';
|
||||
import {UndecoratedParentMigration} from '../migrations/undecorated_parent_migration';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {isDefined} from '../utils';
|
||||
|
||||
import {DefaultMigrationHost} from './migration_host';
|
||||
import {AnalyzedClass, AnalyzedFile, CompiledClass, CompiledFile, DecorationAnalyses} from './types';
|
||||
import {NOOP_DEPENDENCY_TRACKER, analyzeDecorators, isWithinPackage} from './util';
|
||||
import {NgccTraitCompiler} from './ngcc_trait_compiler';
|
||||
import {CompiledClass, CompiledFile, DecorationAnalyses} from './types';
|
||||
import {NOOP_DEPENDENCY_TRACKER, isWithinPackage} from './util';
|
||||
|
||||
|
||||
|
||||
@ -57,10 +56,6 @@ export class DecorationAnalyzer {
|
||||
private packagePath = this.bundle.entryPoint.package;
|
||||
private isCore = this.bundle.isCore;
|
||||
|
||||
/**
|
||||
* Map of NgModule declarations to the re-exports for that NgModule.
|
||||
*/
|
||||
private reexportMap = new Map<ts.Declaration, Map<string, [string, string]>>();
|
||||
moduleResolver =
|
||||
new ModuleResolver(this.program, this.options, this.host, /* moduleResolutionCache */ null);
|
||||
resourceManager = new NgccResourceLoader(this.fs);
|
||||
@ -118,6 +113,7 @@ export class DecorationAnalyzer {
|
||||
/* factoryTracker */ null, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||
/* annotateForClosureCompiler */ false, this.injectableRegistry),
|
||||
];
|
||||
compiler = new NgccTraitCompiler(this.handlers, this.reflectionHost);
|
||||
migrations: Migration[] = [
|
||||
new UndecoratedParentMigration(),
|
||||
new UndecoratedChildMigration(),
|
||||
@ -135,56 +131,54 @@ export class DecorationAnalyzer {
|
||||
* @returns a map of the source files to the analysis for those files.
|
||||
*/
|
||||
analyzeProgram(): DecorationAnalyses {
|
||||
for (const sourceFile of this.program.getSourceFiles()) {
|
||||
if (!sourceFile.isDeclarationFile && isWithinPackage(this.packagePath, sourceFile)) {
|
||||
this.compiler.analyzeFile(sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
this.applyMigrations();
|
||||
|
||||
this.compiler.resolve();
|
||||
|
||||
this.reportDiagnostics();
|
||||
|
||||
const decorationAnalyses = new DecorationAnalyses();
|
||||
const analyzedFiles = this.program.getSourceFiles()
|
||||
.filter(sourceFile => !sourceFile.isDeclarationFile)
|
||||
.filter(sourceFile => isWithinPackage(this.packagePath, sourceFile))
|
||||
.map(sourceFile => this.analyzeFile(sourceFile))
|
||||
.filter(isDefined);
|
||||
|
||||
this.applyMigrations(analyzedFiles);
|
||||
|
||||
analyzedFiles.forEach(analyzedFile => this.resolveFile(analyzedFile));
|
||||
const compiledFiles = analyzedFiles.map(analyzedFile => this.compileFile(analyzedFile));
|
||||
compiledFiles.forEach(
|
||||
compiledFile => decorationAnalyses.set(compiledFile.sourceFile, compiledFile));
|
||||
for (const analyzedFile of this.compiler.analyzedFiles) {
|
||||
const compiledFile = this.compileFile(analyzedFile);
|
||||
decorationAnalyses.set(compiledFile.sourceFile, compiledFile);
|
||||
}
|
||||
return decorationAnalyses;
|
||||
}
|
||||
|
||||
protected analyzeFile(sourceFile: ts.SourceFile): AnalyzedFile|undefined {
|
||||
const analyzedClasses = this.reflectionHost.findClassSymbols(sourceFile)
|
||||
.map(symbol => this.analyzeClass(symbol))
|
||||
.filter(isDefined);
|
||||
return analyzedClasses.length ? {sourceFile, analyzedClasses} : undefined;
|
||||
}
|
||||
|
||||
protected analyzeClass(symbol: NgccClassSymbol): AnalyzedClass|null {
|
||||
const decorators = this.reflectionHost.getDecoratorsOfSymbol(symbol);
|
||||
const analyzedClass = analyzeDecorators(symbol, decorators, this.handlers);
|
||||
if (analyzedClass !== null && analyzedClass.diagnostics !== undefined) {
|
||||
for (const diagnostic of analyzedClass.diagnostics) {
|
||||
this.diagnosticHandler(diagnostic);
|
||||
}
|
||||
}
|
||||
return analyzedClass;
|
||||
}
|
||||
|
||||
protected applyMigrations(analyzedFiles: AnalyzedFile[]): void {
|
||||
protected applyMigrations(): void {
|
||||
const migrationHost = new DefaultMigrationHost(
|
||||
this.reflectionHost, this.fullMetaReader, this.evaluator, this.handlers,
|
||||
this.bundle.entryPoint.path, analyzedFiles, this.diagnosticHandler);
|
||||
this.reflectionHost, this.fullMetaReader, this.evaluator, this.compiler,
|
||||
this.bundle.entryPoint.path);
|
||||
|
||||
this.migrations.forEach(migration => {
|
||||
analyzedFiles.forEach(analyzedFile => {
|
||||
analyzedFile.analyzedClasses.forEach(({declaration}) => {
|
||||
this.compiler.analyzedFiles.forEach(analyzedFile => {
|
||||
const records = this.compiler.recordsFor(analyzedFile);
|
||||
if (records === null) {
|
||||
throw new Error('Assertion error: file to migrate must have records.');
|
||||
}
|
||||
|
||||
records.forEach(record => {
|
||||
const addDiagnostic = (diagnostic: ts.Diagnostic) => {
|
||||
if (record.metaDiagnostics === null) {
|
||||
record.metaDiagnostics = [];
|
||||
}
|
||||
record.metaDiagnostics.push(diagnostic);
|
||||
};
|
||||
|
||||
try {
|
||||
const result = migration.apply(declaration, migrationHost);
|
||||
const result = migration.apply(record.node, migrationHost);
|
||||
if (result !== null) {
|
||||
this.diagnosticHandler(result);
|
||||
addDiagnostic(result);
|
||||
}
|
||||
} catch (e) {
|
||||
if (isFatalDiagnosticError(e)) {
|
||||
this.diagnosticHandler(e.toDiagnostic());
|
||||
addDiagnostic(e.toDiagnostic());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
@ -194,67 +188,45 @@ export class DecorationAnalyzer {
|
||||
});
|
||||
}
|
||||
|
||||
protected compileFile(analyzedFile: AnalyzedFile): CompiledFile {
|
||||
protected reportDiagnostics() { this.compiler.diagnostics.forEach(this.diagnosticHandler); }
|
||||
|
||||
protected compileFile(sourceFile: ts.SourceFile): CompiledFile {
|
||||
const constantPool = new ConstantPool();
|
||||
const compiledClasses: CompiledClass[] = analyzedFile.analyzedClasses.map(analyzedClass => {
|
||||
const compilation = this.compileClass(analyzedClass, constantPool);
|
||||
const declaration = analyzedClass.declaration;
|
||||
const reexports: Reexport[] = this.getReexportsForClass(declaration);
|
||||
return {...analyzedClass, compilation, reexports};
|
||||
});
|
||||
return {constantPool, sourceFile: analyzedFile.sourceFile, compiledClasses};
|
||||
}
|
||||
|
||||
protected compileClass(clazz: AnalyzedClass, constantPool: ConstantPool): CompileResult[] {
|
||||
const compilations: CompileResult[] = [];
|
||||
for (const {handler, analysis, resolution} of clazz.matches) {
|
||||
const result = handler.compile(clazz.declaration, analysis, resolution, constantPool);
|
||||
if (Array.isArray(result)) {
|
||||
result.forEach(current => {
|
||||
if (!compilations.some(compilation => compilation.name === current.name)) {
|
||||
compilations.push(current);
|
||||
}
|
||||
});
|
||||
} else if (!compilations.some(compilation => compilation.name === result.name)) {
|
||||
compilations.push(result);
|
||||
}
|
||||
const records = this.compiler.recordsFor(sourceFile);
|
||||
if (records === null) {
|
||||
throw new Error('Assertion error: file to compile must have records.');
|
||||
}
|
||||
return compilations;
|
||||
}
|
||||
|
||||
protected resolveFile(analyzedFile: AnalyzedFile): void {
|
||||
for (const {declaration, matches} of analyzedFile.analyzedClasses) {
|
||||
for (const match of matches) {
|
||||
const {handler, analysis} = match;
|
||||
if ((handler.resolve !== undefined) && analysis) {
|
||||
const {reexports, diagnostics, data} = handler.resolve(declaration, analysis);
|
||||
if (reexports !== undefined) {
|
||||
this.addReexports(reexports, declaration);
|
||||
}
|
||||
if (diagnostics !== undefined) {
|
||||
diagnostics.forEach(error => this.diagnosticHandler(error));
|
||||
}
|
||||
match.resolution = data as Readonly<unknown>;
|
||||
}
|
||||
const compiledClasses: CompiledClass[] = [];
|
||||
|
||||
for (const record of records) {
|
||||
const compilation = this.compiler.compile(record.node, constantPool);
|
||||
if (compilation === null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getReexportsForClass(declaration: ClassDeclaration<ts.Declaration>) {
|
||||
const reexports: Reexport[] = [];
|
||||
if (this.reexportMap.has(declaration)) {
|
||||
this.reexportMap.get(declaration) !.forEach(([fromModule, symbolName], asAlias) => {
|
||||
reexports.push({asAlias, fromModule, symbolName});
|
||||
compiledClasses.push({
|
||||
name: record.node.name.text,
|
||||
decorators: this.compiler.getAllDecorators(record.node),
|
||||
declaration: record.node, compilation
|
||||
});
|
||||
}
|
||||
return reexports;
|
||||
|
||||
const reexports = this.getReexportsForSourceFile(sourceFile);
|
||||
return {constantPool, sourceFile: sourceFile, compiledClasses, reexports};
|
||||
}
|
||||
|
||||
private addReexports(reexports: Reexport[], declaration: ClassDeclaration<ts.Declaration>) {
|
||||
const map = new Map<string, [string, string]>();
|
||||
for (const reexport of reexports) {
|
||||
map.set(reexport.asAlias, [reexport.fromModule, reexport.symbolName]);
|
||||
private getReexportsForSourceFile(sf: ts.SourceFile): Reexport[] {
|
||||
const exportStatements = this.compiler.exportStatements;
|
||||
if (!exportStatements.has(sf.fileName)) {
|
||||
return [];
|
||||
}
|
||||
this.reexportMap.set(declaration, map);
|
||||
const exports = exportStatements.get(sf.fileName) !;
|
||||
|
||||
const reexports: Reexport[] = [];
|
||||
exports.forEach(([fromModule, symbolName], asAlias) => {
|
||||
reexports.push({asAlias, fromModule, symbolName});
|
||||
});
|
||||
return reexports;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {IncrementalBuild} from '../../../src/ngtsc/incremental/api';
|
||||
import {NOOP_PERF_RECORDER} from '../../../src/ngtsc/perf';
|
||||
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
||||
import {DecoratorHandler, DtsTransformRegistry, HandlerFlags, Trait, TraitCompiler} from '../../../src/ngtsc/transform';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {isDefined} from '../utils';
|
||||
|
||||
/**
|
||||
* Specializes the `TraitCompiler` for ngcc purposes. Mainly, this includes an alternative way of
|
||||
* scanning for classes to compile using the reflection host's `findClassSymbols`, together with
|
||||
* support to inject synthetic decorators into the compilation for ad-hoc migrations that ngcc
|
||||
* performs.
|
||||
*/
|
||||
export class NgccTraitCompiler extends TraitCompiler {
|
||||
constructor(
|
||||
handlers: DecoratorHandler<unknown, unknown, unknown>[],
|
||||
private ngccReflector: NgccReflectionHost) {
|
||||
super(
|
||||
handlers, ngccReflector, NOOP_PERF_RECORDER, new NoIncrementalBuild(),
|
||||
/* compileNonExportedClasses */ true, new DtsTransformRegistry());
|
||||
}
|
||||
|
||||
get analyzedFiles(): ts.SourceFile[] { return Array.from(this.fileToClasses.keys()); }
|
||||
|
||||
/**
|
||||
* Analyzes the source file in search for classes to process. For any class that is found in the
|
||||
* file, a `ClassRecord` is created and the source file is included in the `analyzedFiles` array.
|
||||
*/
|
||||
analyzeFile(sf: ts.SourceFile): void {
|
||||
const ngccClassSymbols = this.ngccReflector.findClassSymbols(sf);
|
||||
for (const classSymbol of ngccClassSymbols) {
|
||||
this.analyzeClass(classSymbol.declaration.valueDeclaration, null);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a new synthesized decorator, which did not appear in the original source, with a
|
||||
* given class.
|
||||
* @param clazz the class to receive the new decorator.
|
||||
* @param decorator the decorator to inject.
|
||||
* @param flags optional bitwise flag to influence the compilation of the decorator.
|
||||
*/
|
||||
injectSyntheticDecorator(clazz: ClassDeclaration, decorator: Decorator, flags?: HandlerFlags):
|
||||
Trait<unknown, unknown, unknown>[] {
|
||||
const migratedTraits = this.detectTraits(clazz, [decorator]);
|
||||
if (migratedTraits === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const trait of migratedTraits) {
|
||||
this.analyzeTrait(clazz, trait, flags);
|
||||
}
|
||||
|
||||
return migratedTraits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all decorators that have been recognized for the provided class, including any
|
||||
* synthetically injected decorators.
|
||||
* @param clazz the declaration for which the decorators are returned.
|
||||
*/
|
||||
getAllDecorators(clazz: ClassDeclaration): Decorator[]|null {
|
||||
const record = this.recordFor(clazz);
|
||||
if (record === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return record.traits.map(trait => trait.detected.decorator).filter(isDefined);
|
||||
}
|
||||
}
|
||||
|
||||
class NoIncrementalBuild implements IncrementalBuild<any> {
|
||||
priorWorkFor(sf: ts.SourceFile): any[]|null { return null; }
|
||||
}
|
@ -9,23 +9,19 @@ import {ConstantPool} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
import {Reexport} from '../../../src/ngtsc/imports';
|
||||
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
||||
import {CompileResult, DecoratorHandler, DetectResult} from '../../../src/ngtsc/transform';
|
||||
import {CompileResult} from '../../../src/ngtsc/transform';
|
||||
|
||||
export interface AnalyzedFile {
|
||||
sourceFile: ts.SourceFile;
|
||||
analyzedClasses: AnalyzedClass[];
|
||||
}
|
||||
|
||||
export interface AnalyzedClass {
|
||||
export interface CompiledClass {
|
||||
name: string;
|
||||
decorators: Decorator[]|null;
|
||||
declaration: ClassDeclaration;
|
||||
diagnostics?: ts.Diagnostic[];
|
||||
matches: MatchingHandler<unknown, unknown, unknown>[];
|
||||
compilation: CompileResult[];
|
||||
}
|
||||
|
||||
export interface CompiledClass extends AnalyzedClass {
|
||||
compilation: CompileResult[];
|
||||
export interface CompiledFile {
|
||||
compiledClasses: CompiledClass[];
|
||||
sourceFile: ts.SourceFile;
|
||||
constantPool: ConstantPool;
|
||||
|
||||
/**
|
||||
* Any re-exports which should be added next to this class, both in .js and (if possible) .d.ts.
|
||||
@ -33,18 +29,5 @@ export interface CompiledClass extends AnalyzedClass {
|
||||
reexports: Reexport[];
|
||||
}
|
||||
|
||||
export interface CompiledFile {
|
||||
compiledClasses: CompiledClass[];
|
||||
sourceFile: ts.SourceFile;
|
||||
constantPool: ConstantPool;
|
||||
}
|
||||
|
||||
export type DecorationAnalyses = Map<ts.SourceFile, CompiledFile>;
|
||||
export const DecorationAnalyses = Map;
|
||||
|
||||
export interface MatchingHandler<D, A, R> {
|
||||
handler: DecoratorHandler<D, A, R>;
|
||||
detected: DetectResult<D>;
|
||||
analysis: Readonly<A>;
|
||||
resolution: Readonly<R>;
|
||||
}
|
||||
|
@ -7,104 +7,13 @@
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
|
||||
import {AbsoluteFsPath, absoluteFromSourceFile, relative} from '../../../src/ngtsc/file_system';
|
||||
import {DependencyTracker} from '../../../src/ngtsc/incremental/api';
|
||||
import {Decorator} from '../../../src/ngtsc/reflection';
|
||||
import {DecoratorHandler, HandlerFlags, HandlerPrecedence} from '../../../src/ngtsc/transform';
|
||||
import {NgccClassSymbol} from '../host/ngcc_host';
|
||||
|
||||
import {AnalyzedClass, MatchingHandler} from './types';
|
||||
|
||||
export function isWithinPackage(packagePath: AbsoluteFsPath, sourceFile: ts.SourceFile): boolean {
|
||||
return !relative(packagePath, absoluteFromSourceFile(sourceFile)).startsWith('..');
|
||||
}
|
||||
|
||||
const NOT_YET_KNOWN: Readonly<unknown> = null as unknown as Readonly<unknown>;
|
||||
|
||||
export function analyzeDecorators(
|
||||
classSymbol: NgccClassSymbol, decorators: Decorator[] | null,
|
||||
handlers: DecoratorHandler<unknown, unknown, unknown>[], flags?: HandlerFlags): AnalyzedClass|
|
||||
null {
|
||||
const declaration = classSymbol.declaration.valueDeclaration;
|
||||
const matchingHandlers: MatchingHandler<unknown, unknown, unknown>[] = [];
|
||||
for (const handler of handlers) {
|
||||
const detected = handler.detect(declaration, decorators);
|
||||
if (detected !== undefined) {
|
||||
matchingHandlers.push({
|
||||
handler,
|
||||
detected,
|
||||
analysis: NOT_YET_KNOWN,
|
||||
resolution: NOT_YET_KNOWN,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingHandlers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const detections: MatchingHandler<unknown, unknown, unknown>[] = [];
|
||||
let hasWeakHandler: boolean = false;
|
||||
let hasNonWeakHandler: boolean = false;
|
||||
let hasPrimaryHandler: boolean = false;
|
||||
|
||||
for (const match of matchingHandlers) {
|
||||
const {handler} = match;
|
||||
if (hasNonWeakHandler && handler.precedence === HandlerPrecedence.WEAK) {
|
||||
continue;
|
||||
} else if (hasWeakHandler && handler.precedence !== HandlerPrecedence.WEAK) {
|
||||
// Clear all the WEAK handlers from the list of matches.
|
||||
detections.length = 0;
|
||||
}
|
||||
if (hasPrimaryHandler && handler.precedence === HandlerPrecedence.PRIMARY) {
|
||||
throw new Error(`TODO.Diagnostic: Class has multiple incompatible Angular decorators.`);
|
||||
}
|
||||
|
||||
detections.push(match);
|
||||
if (handler.precedence === HandlerPrecedence.WEAK) {
|
||||
hasWeakHandler = true;
|
||||
} else if (handler.precedence === HandlerPrecedence.SHARED) {
|
||||
hasNonWeakHandler = true;
|
||||
} else if (handler.precedence === HandlerPrecedence.PRIMARY) {
|
||||
hasNonWeakHandler = true;
|
||||
hasPrimaryHandler = true;
|
||||
}
|
||||
}
|
||||
|
||||
const matches: MatchingHandler<unknown, unknown, unknown>[] = [];
|
||||
const allDiagnostics: ts.Diagnostic[] = [];
|
||||
for (const match of detections) {
|
||||
try {
|
||||
const {analysis, diagnostics} =
|
||||
match.handler.analyze(declaration, match.detected.metadata, flags);
|
||||
if (diagnostics !== undefined) {
|
||||
allDiagnostics.push(...diagnostics);
|
||||
}
|
||||
if (analysis !== undefined) {
|
||||
match.analysis = analysis;
|
||||
if (match.handler.register !== undefined) {
|
||||
match.handler.register(declaration, analysis);
|
||||
}
|
||||
}
|
||||
matches.push(match);
|
||||
} catch (e) {
|
||||
if (isFatalDiagnosticError(e)) {
|
||||
allDiagnostics.push(e.toDiagnostic());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: classSymbol.name,
|
||||
declaration,
|
||||
decorators,
|
||||
matches,
|
||||
diagnostics: allDiagnostics.length > 0 ? allDiagnostics : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
class NoopDependencyTracker implements DependencyTracker {
|
||||
addDependency(): void {}
|
||||
addResourceDependency(): void {}
|
||||
|
Reference in New Issue
Block a user