perf(compiler-cli): refactor the performance tracing infrastructure (#41125)
ngtsc has an internal performance tracing package, which previously has not really seen much use. It used to track performance statistics on a very granular basis (microseconds per actual class analysis, for example). This had two problems: * it produced voluminous amounts of data, complicating the analysis of such results and providing dubious value. * it added nontrivial overhead to compilation when used (which also affected the very performance of the operations being measured). This commit replaces the old system with a streamlined performance tracing setup which is lightweight and designed to be always-on. The new system tracks 3 metrics: * time taken by various phases and operations within the compiler * events (counters) which measure the shape and size of the compilation * memory usage measured at various points of the compilation process If the compiler option `tracePerformance` is set, the compiler will serialize these metrics to a JSON file at that location after compilation is complete. PR Close #41125
This commit is contained in:
parent
dd82c8e9f4
commit
48fec08c95
@ -16,6 +16,7 @@ ts_library(
|
|||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//packages/compiler-cli",
|
"//packages/compiler-cli",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/perf",
|
||||||
"@npm//@bazel/typescript",
|
"@npm//@bazel/typescript",
|
||||||
"@npm//@types/node",
|
"@npm//@types/node",
|
||||||
"@npm//tsickle",
|
"@npm//tsickle",
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ng from '@angular/compiler-cli';
|
import * as ng from '@angular/compiler-cli';
|
||||||
|
import {PerfPhase} from '@angular/compiler-cli/src/ngtsc/perf';
|
||||||
import {BazelOptions, CachedFileLoader, CompilerHost, constructManifest, debug, FileCache, FileLoader, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop, UncachedFileLoader} from '@bazel/typescript';
|
import {BazelOptions, CachedFileLoader, CompilerHost, constructManifest, debug, FileCache, FileLoader, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop, UncachedFileLoader} from '@bazel/typescript';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
@ -515,6 +516,12 @@ function gatherDiagnosticsForInputsOnly(
|
|||||||
options: ng.CompilerOptions, bazelOpts: BazelOptions,
|
options: ng.CompilerOptions, bazelOpts: BazelOptions,
|
||||||
ngProgram: ng.Program): (ng.Diagnostic|ts.Diagnostic)[] {
|
ngProgram: ng.Program): (ng.Diagnostic|ts.Diagnostic)[] {
|
||||||
const tsProgram = ngProgram.getTsProgram();
|
const tsProgram = ngProgram.getTsProgram();
|
||||||
|
|
||||||
|
// For the Ivy compiler, track the amount of time spent fetching TypeScript diagnostics.
|
||||||
|
let previousPhase = PerfPhase.Unaccounted;
|
||||||
|
if (ngProgram instanceof ng.NgtscProgram) {
|
||||||
|
previousPhase = ngProgram.compiler.perfRecorder.phase(PerfPhase.TypeScriptDiagnostics);
|
||||||
|
}
|
||||||
const diagnostics: (ng.Diagnostic|ts.Diagnostic)[] = [];
|
const diagnostics: (ng.Diagnostic|ts.Diagnostic)[] = [];
|
||||||
// These checks mirror ts.getPreEmitDiagnostics, with the important
|
// These checks mirror ts.getPreEmitDiagnostics, with the important
|
||||||
// exception of avoiding b/30708240, which is that if you call
|
// exception of avoiding b/30708240, which is that if you call
|
||||||
@ -529,6 +536,11 @@ function gatherDiagnosticsForInputsOnly(
|
|||||||
diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf));
|
diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf));
|
||||||
diagnostics.push(...tsProgram.getSemanticDiagnostics(sf));
|
diagnostics.push(...tsProgram.getSemanticDiagnostics(sf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ngProgram instanceof ng.NgtscProgram) {
|
||||||
|
ngProgram.compiler.perfRecorder.phase(previousPhase);
|
||||||
|
}
|
||||||
|
|
||||||
if (!diagnostics.length) {
|
if (!diagnostics.length) {
|
||||||
// only gather the angular diagnostics if we have no diagnostics
|
// only gather the angular diagnostics if we have no diagnostics
|
||||||
// in any other files.
|
// in any other files.
|
||||||
|
@ -22,5 +22,6 @@ export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api'
|
|||||||
|
|
||||||
export {ngToTsDiagnostic} from './src/transformers/util';
|
export {ngToTsDiagnostic} from './src/transformers/util';
|
||||||
export {NgTscPlugin} from './src/ngtsc/tsc_plugin';
|
export {NgTscPlugin} from './src/ngtsc/tsc_plugin';
|
||||||
|
export {NgtscProgram} from './src/ngtsc/program';
|
||||||
|
|
||||||
setFileSystem(new NodeJSFileSystem());
|
setFileSystem(new NodeJSFileSystem());
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {ConstantPool} from '@angular/compiler';
|
import {ConstantPool} from '@angular/compiler';
|
||||||
|
import {NOOP_PERF_RECORDER} from '@angular/compiler-cli/src/ngtsc/perf';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ParsedConfiguration} from '../../..';
|
import {ParsedConfiguration} from '../../..';
|
||||||
@ -89,7 +90,7 @@ export class DecorationAnalyzer {
|
|||||||
fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]);
|
fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]);
|
||||||
evaluator =
|
evaluator =
|
||||||
new PartialEvaluator(this.reflectionHost, this.typeChecker, /* dependencyTracker */ null);
|
new PartialEvaluator(this.reflectionHost, this.typeChecker, /* dependencyTracker */ null);
|
||||||
importGraph = new ImportGraph(this.typeChecker);
|
importGraph = new ImportGraph(this.typeChecker, NOOP_PERF_RECORDER);
|
||||||
cycleAnalyzer = new CycleAnalyzer(this.importGraph);
|
cycleAnalyzer = new CycleAnalyzer(this.importGraph);
|
||||||
injectableRegistry = new InjectableClassRegistry(this.reflectionHost);
|
injectableRegistry = new InjectableClassRegistry(this.reflectionHost);
|
||||||
typeCheckScopeRegistry = new TypeCheckScopeRegistry(this.scopeRegistry, this.fullMetaReader);
|
typeCheckScopeRegistry = new TypeCheckScopeRegistry(this.scopeRegistry, this.fullMetaReader);
|
||||||
@ -104,7 +105,8 @@ export class DecorationAnalyzer {
|
|||||||
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
|
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
|
||||||
CycleHandlingStrategy.UseRemoteScoping, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER,
|
CycleHandlingStrategy.UseRemoteScoping, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||||
NOOP_DEPENDENCY_TRACKER, this.injectableRegistry,
|
NOOP_DEPENDENCY_TRACKER, this.injectableRegistry,
|
||||||
/* semanticDepGraphUpdater */ null, !!this.compilerOptions.annotateForClosureCompiler),
|
/* semanticDepGraphUpdater */ null, !!this.compilerOptions.annotateForClosureCompiler,
|
||||||
|
NOOP_PERF_RECORDER),
|
||||||
|
|
||||||
// See the note in ngtsc about why this cast is needed.
|
// See the note in ngtsc about why this cast is needed.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@ -117,23 +119,26 @@ export class DecorationAnalyzer {
|
|||||||
// version 10, undecorated classes that use Angular features are no longer handled
|
// version 10, undecorated classes that use Angular features are no longer handled
|
||||||
// in ngtsc, but we want to ensure compatibility in ngcc for outdated libraries that
|
// in ngtsc, but we want to ensure compatibility in ngcc for outdated libraries that
|
||||||
// have not migrated to explicit decorators. See: https://hackmd.io/@alx/ryfYYuvzH.
|
// have not migrated to explicit decorators. See: https://hackmd.io/@alx/ryfYYuvzH.
|
||||||
/* compileUndecoratedClassesWithAngularFeatures */ true
|
/* compileUndecoratedClassesWithAngularFeatures */ true,
|
||||||
|
NOOP_PERF_RECORDER
|
||||||
) as DecoratorHandler<unknown, unknown, SemanticSymbol|null,unknown>,
|
) as DecoratorHandler<unknown, unknown, SemanticSymbol|null,unknown>,
|
||||||
// clang-format on
|
// clang-format on
|
||||||
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
||||||
// before injectable factories (so injectable factories can delegate to them)
|
// before injectable factories (so injectable factories can delegate to them)
|
||||||
new PipeDecoratorHandler(
|
new PipeDecoratorHandler(
|
||||||
this.reflectionHost, this.evaluator, this.metaRegistry, this.scopeRegistry,
|
this.reflectionHost, this.evaluator, this.metaRegistry, this.scopeRegistry,
|
||||||
NOOP_DEFAULT_IMPORT_RECORDER, this.injectableRegistry, this.isCore),
|
NOOP_DEFAULT_IMPORT_RECORDER, this.injectableRegistry, this.isCore, NOOP_PERF_RECORDER),
|
||||||
new InjectableDecoratorHandler(
|
new InjectableDecoratorHandler(
|
||||||
this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
|
this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
|
||||||
/* strictCtorDeps */ false, this.injectableRegistry, /* errorOnDuplicateProv */ false),
|
/* strictCtorDeps */ false, this.injectableRegistry, NOOP_PERF_RECORDER,
|
||||||
|
/* errorOnDuplicateProv */ false),
|
||||||
new NgModuleDecoratorHandler(
|
new NgModuleDecoratorHandler(
|
||||||
this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
|
this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
|
||||||
this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
|
this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
|
||||||
this.refEmitter,
|
this.refEmitter,
|
||||||
/* factoryTracker */ null, NOOP_DEFAULT_IMPORT_RECORDER,
|
/* factoryTracker */ null, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||||
!!this.compilerOptions.annotateForClosureCompiler, this.injectableRegistry),
|
!!this.compilerOptions.annotateForClosureCompiler, this.injectableRegistry,
|
||||||
|
NOOP_PERF_RECORDER),
|
||||||
];
|
];
|
||||||
compiler = new NgccTraitCompiler(this.handlers, this.reflectionHost);
|
compiler = new NgccTraitCompiler(this.handlers, this.reflectionHost);
|
||||||
migrations: Migration[] = [
|
migrations: Migration[] = [
|
||||||
|
@ -18,6 +18,7 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/indexer",
|
"//packages/compiler-cli/src/ngtsc/indexer",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//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/perf",
|
||||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||||
"//packages/compiler-cli/src/ngtsc/routing",
|
"//packages/compiler-cli/src/ngtsc/routing",
|
||||||
"//packages/compiler-cli/src/ngtsc/scope",
|
"//packages/compiler-cli/src/ngtsc/scope",
|
||||||
|
@ -18,6 +18,7 @@ import {extractSemanticTypeParameters, isArrayEqual, isReferenceEqual, SemanticD
|
|||||||
import {IndexingContext} from '../../indexer';
|
import {IndexingContext} from '../../indexer';
|
||||||
import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, Resource, ResourceRegistry} from '../../metadata';
|
import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, Resource, ResourceRegistry} from '../../metadata';
|
||||||
import {EnumValue, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
import {EnumValue, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
||||||
|
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||||
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {ComponentScopeReader, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../scope';
|
import {ComponentScopeReader, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../scope';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||||
@ -208,7 +209,7 @@ export class ComponentDecoratorHandler implements
|
|||||||
private depTracker: DependencyTracker|null,
|
private depTracker: DependencyTracker|null,
|
||||||
private injectableRegistry: InjectableClassRegistry,
|
private injectableRegistry: InjectableClassRegistry,
|
||||||
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
|
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
|
||||||
private annotateForClosureCompiler: boolean) {}
|
private annotateForClosureCompiler: boolean, private perf: PerfRecorder) {}
|
||||||
|
|
||||||
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
||||||
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
@ -309,6 +310,7 @@ export class ComponentDecoratorHandler implements
|
|||||||
analyze(
|
analyze(
|
||||||
node: ClassDeclaration, decorator: Readonly<Decorator>,
|
node: ClassDeclaration, decorator: Readonly<Decorator>,
|
||||||
flags: HandlerFlags = HandlerFlags.NONE): AnalysisOutput<ComponentAnalysisData> {
|
flags: HandlerFlags = HandlerFlags.NONE): AnalysisOutput<ComponentAnalysisData> {
|
||||||
|
this.perf.eventCount(PerfEvent.AnalyzeComponent);
|
||||||
const containingFile = node.getSourceFile().fileName;
|
const containingFile = node.getSourceFile().fileName;
|
||||||
this.literalCache.delete(decorator);
|
this.literalCache.delete(decorator);
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import {areTypeParametersEqual, extractSemanticTypeParameters, isArrayEqual, isS
|
|||||||
import {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, TemplateGuardMeta} from '../../metadata';
|
import {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, TemplateGuardMeta} from '../../metadata';
|
||||||
import {extractDirectiveTypeCheckMeta} from '../../metadata/src/util';
|
import {extractDirectiveTypeCheckMeta} from '../../metadata/src/util';
|
||||||
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||||
|
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||||
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry} from '../../scope';
|
import {LocalModuleScopeRegistry} from '../../scope';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||||
@ -180,7 +181,7 @@ export class DirectiveDecoratorHandler implements
|
|||||||
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
|
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
|
||||||
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
|
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
|
||||||
private annotateForClosureCompiler: boolean,
|
private annotateForClosureCompiler: boolean,
|
||||||
private compileUndecoratedClassesWithAngularFeatures: boolean) {}
|
private compileUndecoratedClassesWithAngularFeatures: boolean, private perf: PerfRecorder) {}
|
||||||
|
|
||||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||||
readonly name = DirectiveDecoratorHandler.name;
|
readonly name = DirectiveDecoratorHandler.name;
|
||||||
@ -211,6 +212,8 @@ export class DirectiveDecoratorHandler implements
|
|||||||
return {diagnostics: [getUndecoratedClassWithAngularFeaturesDiagnostic(node)]};
|
return {diagnostics: [getUndecoratedClassWithAngularFeaturesDiagnostic(node)]};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.perf.eventCount(PerfEvent.AnalyzeDirective);
|
||||||
|
|
||||||
const directiveResult = extractDirectiveMetadata(
|
const directiveResult = extractDirectiveMetadata(
|
||||||
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
|
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
|
||||||
flags, this.annotateForClosureCompiler);
|
flags, this.annotateForClosureCompiler);
|
||||||
|
@ -12,6 +12,7 @@ import * as ts from 'typescript';
|
|||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {DefaultImportRecorder} from '../../imports';
|
import {DefaultImportRecorder} from '../../imports';
|
||||||
import {InjectableClassRegistry} from '../../metadata';
|
import {InjectableClassRegistry} from '../../metadata';
|
||||||
|
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ export class InjectableDecoratorHandler implements
|
|||||||
constructor(
|
constructor(
|
||||||
private reflector: ReflectionHost, private defaultImportRecorder: DefaultImportRecorder,
|
private reflector: ReflectionHost, private defaultImportRecorder: DefaultImportRecorder,
|
||||||
private isCore: boolean, private strictCtorDeps: boolean,
|
private isCore: boolean, private strictCtorDeps: boolean,
|
||||||
private injectableRegistry: InjectableClassRegistry,
|
private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
|
||||||
/**
|
/**
|
||||||
* What to do if the injectable already contains a ɵprov property.
|
* What to do if the injectable already contains a ɵprov property.
|
||||||
*
|
*
|
||||||
@ -64,6 +65,8 @@ export class InjectableDecoratorHandler implements
|
|||||||
|
|
||||||
analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
|
analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
|
||||||
AnalysisOutput<InjectableHandlerData> {
|
AnalysisOutput<InjectableHandlerData> {
|
||||||
|
this.perf.eventCount(PerfEvent.AnalyzeInjectable);
|
||||||
|
|
||||||
const meta = extractInjectableMetadata(node, decorator, this.reflector);
|
const meta = extractInjectableMetadata(node, decorator, this.reflector);
|
||||||
const decorators = this.reflector.getDecoratorsOfDeclaration(node);
|
const decorators = this.reflector.getDecoratorsOfDeclaration(node);
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports'
|
|||||||
import {isArrayEqual, isReferenceEqual, isSymbolEqual, SemanticReference, SemanticSymbol} from '../../incremental/semantic_graph';
|
import {isArrayEqual, isReferenceEqual, isSymbolEqual, SemanticReference, SemanticSymbol} from '../../incremental/semantic_graph';
|
||||||
import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
||||||
|
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||||
import {ClassDeclaration, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
|
import {ClassDeclaration, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
|
||||||
import {NgModuleRouteAnalyzer} from '../../routing';
|
import {NgModuleRouteAnalyzer} from '../../routing';
|
||||||
import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
|
import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
|
||||||
@ -131,7 +132,8 @@ export class NgModuleDecoratorHandler implements
|
|||||||
private factoryTracker: FactoryTracker|null,
|
private factoryTracker: FactoryTracker|null,
|
||||||
private defaultImportRecorder: DefaultImportRecorder,
|
private defaultImportRecorder: DefaultImportRecorder,
|
||||||
private annotateForClosureCompiler: boolean,
|
private annotateForClosureCompiler: boolean,
|
||||||
private injectableRegistry: InjectableClassRegistry, private localeId?: string) {}
|
private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
|
||||||
|
private localeId?: string) {}
|
||||||
|
|
||||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||||
readonly name = NgModuleDecoratorHandler.name;
|
readonly name = NgModuleDecoratorHandler.name;
|
||||||
@ -154,6 +156,8 @@ export class NgModuleDecoratorHandler implements
|
|||||||
|
|
||||||
analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
|
analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
|
||||||
AnalysisOutput<NgModuleAnalysis> {
|
AnalysisOutput<NgModuleAnalysis> {
|
||||||
|
this.perf.eventCount(PerfEvent.AnalyzeNgModule);
|
||||||
|
|
||||||
const name = node.name.text;
|
const name = node.name.text;
|
||||||
if (decorator.args === null || decorator.args.length > 1) {
|
if (decorator.args === null || decorator.args.length > 1) {
|
||||||
throw new FatalDiagnosticError(
|
throw new FatalDiagnosticError(
|
||||||
|
@ -14,6 +14,7 @@ import {DefaultImportRecorder, Reference} from '../../imports';
|
|||||||
import {SemanticSymbol} from '../../incremental/semantic_graph';
|
import {SemanticSymbol} from '../../incremental/semantic_graph';
|
||||||
import {InjectableClassRegistry, MetadataRegistry} from '../../metadata';
|
import {InjectableClassRegistry, MetadataRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
|
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry} from '../../scope';
|
import {LocalModuleScopeRegistry} from '../../scope';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||||
@ -55,7 +56,8 @@ export class PipeDecoratorHandler implements
|
|||||||
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
||||||
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
||||||
private defaultImportRecorder: DefaultImportRecorder,
|
private defaultImportRecorder: DefaultImportRecorder,
|
||||||
private injectableRegistry: InjectableClassRegistry, private isCore: boolean) {}
|
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
|
||||||
|
private perf: PerfRecorder) {}
|
||||||
|
|
||||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||||
readonly name = PipeDecoratorHandler.name;
|
readonly name = PipeDecoratorHandler.name;
|
||||||
@ -78,6 +80,8 @@ export class PipeDecoratorHandler implements
|
|||||||
|
|
||||||
analyze(clazz: ClassDeclaration, decorator: Readonly<Decorator>):
|
analyze(clazz: ClassDeclaration, decorator: Readonly<Decorator>):
|
||||||
AnalysisOutput<PipeHandlerData> {
|
AnalysisOutput<PipeHandlerData> {
|
||||||
|
this.perf.eventCount(PerfEvent.AnalyzePipe);
|
||||||
|
|
||||||
const name = clazz.name.text;
|
const name = clazz.name.text;
|
||||||
const type = wrapTypeReference(this.reflector, clazz);
|
const type = wrapTypeReference(this.reflector, clazz);
|
||||||
const internalType = new WrappedNodeExpr(this.reflector.getInternalNameOfClass(clazz));
|
const internalType = new WrappedNodeExpr(this.reflector.getInternalNameOfClass(clazz));
|
||||||
|
@ -20,6 +20,7 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/incremental:api",
|
"//packages/compiler-cli/src/ngtsc/incremental:api",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//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/perf",
|
||||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||||
"//packages/compiler-cli/src/ngtsc/scope",
|
"//packages/compiler-cli/src/ngtsc/scope",
|
||||||
"//packages/compiler-cli/src/ngtsc/testing",
|
"//packages/compiler-cli/src/ngtsc/testing",
|
||||||
|
@ -16,6 +16,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
|
|||||||
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||||
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../metadata';
|
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
|
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../scope';
|
||||||
import {getDeclaration, makeProgram} from '../../testing';
|
import {getDeclaration, makeProgram} from '../../testing';
|
||||||
@ -41,7 +42,7 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
|
|||||||
const evaluator = new PartialEvaluator(reflectionHost, checker, /* dependencyTracker */ null);
|
const evaluator = new PartialEvaluator(reflectionHost, checker, /* dependencyTracker */ null);
|
||||||
const moduleResolver =
|
const moduleResolver =
|
||||||
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
|
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
|
||||||
const importGraph = new ImportGraph(checker);
|
const importGraph = new ImportGraph(checker, NOOP_PERF_RECORDER);
|
||||||
const cycleAnalyzer = new CycleAnalyzer(importGraph);
|
const cycleAnalyzer = new CycleAnalyzer(importGraph);
|
||||||
const metaRegistry = new LocalMetadataRegistry();
|
const metaRegistry = new LocalMetadataRegistry();
|
||||||
const dtsReader = new DtsMetadataReader(checker, reflectionHost);
|
const dtsReader = new DtsMetadataReader(checker, reflectionHost);
|
||||||
@ -80,6 +81,7 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
|
|||||||
injectableRegistry,
|
injectableRegistry,
|
||||||
/* semanticDepGraphUpdater */ null,
|
/* semanticDepGraphUpdater */ null,
|
||||||
/* annotateForClosureCompiler */ false,
|
/* annotateForClosureCompiler */ false,
|
||||||
|
NOOP_PERF_RECORDER,
|
||||||
);
|
);
|
||||||
return {reflectionHost, handler};
|
return {reflectionHost, handler};
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
|
|||||||
import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||||
import {DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
import {DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
|
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||||
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||||
import {getDeclaration, makeProgram} from '../../testing';
|
import {getDeclaration, makeProgram} from '../../testing';
|
||||||
@ -171,7 +172,7 @@ runInEachFileSystem(() => {
|
|||||||
NOOP_DEFAULT_IMPORT_RECORDER, injectableRegistry, /*isCore*/ false,
|
NOOP_DEFAULT_IMPORT_RECORDER, injectableRegistry, /*isCore*/ false,
|
||||||
/*semanticDepGraphUpdater*/ null,
|
/*semanticDepGraphUpdater*/ null,
|
||||||
/*annotateForClosureCompiler*/ false,
|
/*annotateForClosureCompiler*/ false,
|
||||||
/*detectUndecoratedClassesWithAngularFeatures*/ false);
|
/*detectUndecoratedClassesWithAngularFeatures*/ false, NOOP_PERF_RECORDER);
|
||||||
|
|
||||||
const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);
|
const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import {absoluteFrom} from '../../file_system';
|
|||||||
import {runInEachFileSystem} from '../../file_system/testing';
|
import {runInEachFileSystem} from '../../file_system/testing';
|
||||||
import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../imports';
|
import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../imports';
|
||||||
import {InjectableClassRegistry} from '../../metadata';
|
import {InjectableClassRegistry} from '../../metadata';
|
||||||
|
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {getDeclaration, makeProgram} from '../../testing';
|
import {getDeclaration, makeProgram} from '../../testing';
|
||||||
import {InjectableDecoratorHandler} from '../src/injectable';
|
import {InjectableDecoratorHandler} from '../src/injectable';
|
||||||
@ -70,7 +71,7 @@ function setupHandler(errorOnDuplicateProv: boolean) {
|
|||||||
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
|
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
|
||||||
const handler = new InjectableDecoratorHandler(
|
const handler = new InjectableDecoratorHandler(
|
||||||
reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, /* isCore */ false,
|
reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, /* isCore */ false,
|
||||||
/* strictCtorDeps */ false, injectableRegistry, errorOnDuplicateProv);
|
/* strictCtorDeps */ false, injectableRegistry, NOOP_PERF_RECORDER, errorOnDuplicateProv);
|
||||||
const TestClass = getDeclaration(program, ENTRY_FILE, 'TestClass', isNamedClassDeclaration);
|
const TestClass = getDeclaration(program, ENTRY_FILE, 'TestClass', isNamedClassDeclaration);
|
||||||
const ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
|
const ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
|
||||||
if (ɵprov === undefined) {
|
if (ɵprov === undefined) {
|
||||||
|
@ -14,6 +14,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
|
|||||||
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||||
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
|
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||||
import {getDeclaration, makeProgram} from '../../testing';
|
import {getDeclaration, makeProgram} from '../../testing';
|
||||||
@ -71,7 +72,8 @@ runInEachFileSystem(() => {
|
|||||||
const handler = new NgModuleDecoratorHandler(
|
const handler = new NgModuleDecoratorHandler(
|
||||||
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
|
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
|
||||||
/* isCore */ false, /* routeAnalyzer */ null, refEmitter, /* factoryTracker */ null,
|
/* isCore */ false, /* routeAnalyzer */ null, refEmitter, /* factoryTracker */ null,
|
||||||
NOOP_DEFAULT_IMPORT_RECORDER, /* annotateForClosureCompiler */ false, injectableRegistry);
|
NOOP_DEFAULT_IMPORT_RECORDER, /* annotateForClosureCompiler */ false, injectableRegistry,
|
||||||
|
NOOP_PERF_RECORDER);
|
||||||
const TestModule =
|
const TestModule =
|
||||||
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
|
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
|
||||||
const detected =
|
const detected =
|
||||||
|
@ -28,9 +28,8 @@ export interface TestOnlyOptions {
|
|||||||
/**
|
/**
|
||||||
* An option to enable ngtsc's internal performance tracing.
|
* An option to enable ngtsc's internal performance tracing.
|
||||||
*
|
*
|
||||||
* This should be a path to a JSON file where trace information will be written. An optional 'ts:'
|
* This should be a path to a JSON file where trace information will be written. This is sensitive
|
||||||
* prefix will cause the trace to be written via the TS host instead of directly to the filesystem
|
* to the compiler's working directory, and should likely be an absolute path.
|
||||||
* (not all hosts support this mode of operation).
|
|
||||||
*
|
*
|
||||||
* This is currently not exposed to users as the trace format is still unstable.
|
* This is currently not exposed to users as the trace format is still unstable.
|
||||||
*/
|
*/
|
||||||
|
@ -21,7 +21,9 @@ import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer
|
|||||||
import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, ResourceRegistry} from '../../metadata';
|
import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, ResourceRegistry} from '../../metadata';
|
||||||
import {ModuleWithProvidersScanner} from '../../modulewithproviders';
|
import {ModuleWithProvidersScanner} from '../../modulewithproviders';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {NOOP_PERF_RECORDER, PerfRecorder} from '../../perf';
|
import {ActivePerfRecorder} from '../../perf';
|
||||||
|
import {PerfCheckpoint, PerfEvent, PerfPhase} from '../../perf/src/api';
|
||||||
|
import {DelegatingPerfRecorder} from '../../perf/src/recorder';
|
||||||
import {DeclarationNode, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
import {DeclarationNode, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {AdapterResourceLoader} from '../../resource';
|
import {AdapterResourceLoader} from '../../resource';
|
||||||
import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
|
import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
|
||||||
@ -80,6 +82,7 @@ export interface FreshCompilationTicket {
|
|||||||
enableTemplateTypeChecker: boolean;
|
enableTemplateTypeChecker: boolean;
|
||||||
usePoisonedData: boolean;
|
usePoisonedData: boolean;
|
||||||
tsProgram: ts.Program;
|
tsProgram: ts.Program;
|
||||||
|
perfRecorder: ActivePerfRecorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,12 +98,14 @@ export interface IncrementalTypeScriptCompilationTicket {
|
|||||||
newDriver: IncrementalDriver;
|
newDriver: IncrementalDriver;
|
||||||
enableTemplateTypeChecker: boolean;
|
enableTemplateTypeChecker: boolean;
|
||||||
usePoisonedData: boolean;
|
usePoisonedData: boolean;
|
||||||
|
perfRecorder: ActivePerfRecorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IncrementalResourceCompilationTicket {
|
export interface IncrementalResourceCompilationTicket {
|
||||||
kind: CompilationTicketKind.IncrementalResource;
|
kind: CompilationTicketKind.IncrementalResource;
|
||||||
compiler: NgCompiler;
|
compiler: NgCompiler;
|
||||||
modifiedResourceFiles: Set<string>;
|
modifiedResourceFiles: Set<string>;
|
||||||
|
perfRecorder: ActivePerfRecorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,8 +124,8 @@ export type CompilationTicket = FreshCompilationTicket|IncrementalTypeScriptComp
|
|||||||
export function freshCompilationTicket(
|
export function freshCompilationTicket(
|
||||||
tsProgram: ts.Program, options: NgCompilerOptions,
|
tsProgram: ts.Program, options: NgCompilerOptions,
|
||||||
incrementalBuildStrategy: IncrementalBuildStrategy,
|
incrementalBuildStrategy: IncrementalBuildStrategy,
|
||||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, enableTemplateTypeChecker: boolean,
|
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, perfRecorder: ActivePerfRecorder|null,
|
||||||
usePoisonedData: boolean): CompilationTicket {
|
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): CompilationTicket {
|
||||||
return {
|
return {
|
||||||
kind: CompilationTicketKind.Fresh,
|
kind: CompilationTicketKind.Fresh,
|
||||||
tsProgram,
|
tsProgram,
|
||||||
@ -129,6 +134,7 @@ export function freshCompilationTicket(
|
|||||||
typeCheckingProgramStrategy,
|
typeCheckingProgramStrategy,
|
||||||
enableTemplateTypeChecker,
|
enableTemplateTypeChecker,
|
||||||
usePoisonedData,
|
usePoisonedData,
|
||||||
|
perfRecorder: perfRecorder ?? ActivePerfRecorder.zeroedToNow(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,8 +145,8 @@ export function freshCompilationTicket(
|
|||||||
export function incrementalFromCompilerTicket(
|
export function incrementalFromCompilerTicket(
|
||||||
oldCompiler: NgCompiler, newProgram: ts.Program,
|
oldCompiler: NgCompiler, newProgram: ts.Program,
|
||||||
incrementalBuildStrategy: IncrementalBuildStrategy,
|
incrementalBuildStrategy: IncrementalBuildStrategy,
|
||||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy,
|
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>,
|
||||||
modifiedResourceFiles: Set<string>): CompilationTicket {
|
perfRecorder: ActivePerfRecorder|null): CompilationTicket {
|
||||||
const oldProgram = oldCompiler.getNextProgram();
|
const oldProgram = oldCompiler.getNextProgram();
|
||||||
const oldDriver = oldCompiler.incrementalStrategy.getIncrementalDriver(oldProgram);
|
const oldDriver = oldCompiler.incrementalStrategy.getIncrementalDriver(oldProgram);
|
||||||
if (oldDriver === null) {
|
if (oldDriver === null) {
|
||||||
@ -148,11 +154,15 @@ export function incrementalFromCompilerTicket(
|
|||||||
// program.
|
// program.
|
||||||
return freshCompilationTicket(
|
return freshCompilationTicket(
|
||||||
newProgram, oldCompiler.options, incrementalBuildStrategy, typeCheckingProgramStrategy,
|
newProgram, oldCompiler.options, incrementalBuildStrategy, typeCheckingProgramStrategy,
|
||||||
oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
|
perfRecorder, oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newDriver =
|
if (perfRecorder === null) {
|
||||||
IncrementalDriver.reconcile(oldProgram, oldDriver, newProgram, modifiedResourceFiles);
|
perfRecorder = ActivePerfRecorder.zeroedToNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDriver = IncrementalDriver.reconcile(
|
||||||
|
oldProgram, oldDriver, newProgram, modifiedResourceFiles, perfRecorder);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kind: CompilationTicketKind.IncrementalTypeScript,
|
kind: CompilationTicketKind.IncrementalTypeScript,
|
||||||
@ -164,6 +174,7 @@ export function incrementalFromCompilerTicket(
|
|||||||
newDriver,
|
newDriver,
|
||||||
oldProgram,
|
oldProgram,
|
||||||
newProgram,
|
newProgram,
|
||||||
|
perfRecorder,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,9 +186,14 @@ export function incrementalFromDriverTicket(
|
|||||||
oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program,
|
oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program,
|
||||||
options: NgCompilerOptions, incrementalBuildStrategy: IncrementalBuildStrategy,
|
options: NgCompilerOptions, incrementalBuildStrategy: IncrementalBuildStrategy,
|
||||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>,
|
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>,
|
||||||
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): CompilationTicket {
|
perfRecorder: ActivePerfRecorder|null, enableTemplateTypeChecker: boolean,
|
||||||
const newDriver =
|
usePoisonedData: boolean): CompilationTicket {
|
||||||
IncrementalDriver.reconcile(oldProgram, oldDriver, newProgram, modifiedResourceFiles);
|
if (perfRecorder === null) {
|
||||||
|
perfRecorder = ActivePerfRecorder.zeroedToNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDriver = IncrementalDriver.reconcile(
|
||||||
|
oldProgram, oldDriver, newProgram, modifiedResourceFiles, perfRecorder);
|
||||||
return {
|
return {
|
||||||
kind: CompilationTicketKind.IncrementalTypeScript,
|
kind: CompilationTicketKind.IncrementalTypeScript,
|
||||||
oldProgram,
|
oldProgram,
|
||||||
@ -188,6 +204,7 @@ export function incrementalFromDriverTicket(
|
|||||||
typeCheckingProgramStrategy,
|
typeCheckingProgramStrategy,
|
||||||
enableTemplateTypeChecker,
|
enableTemplateTypeChecker,
|
||||||
usePoisonedData,
|
usePoisonedData,
|
||||||
|
perfRecorder,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +214,7 @@ export function resourceChangeTicket(compiler: NgCompiler, modifiedResourceFiles
|
|||||||
kind: CompilationTicketKind.IncrementalResource,
|
kind: CompilationTicketKind.IncrementalResource,
|
||||||
compiler,
|
compiler,
|
||||||
modifiedResourceFiles,
|
modifiedResourceFiles,
|
||||||
|
perfRecorder: ActivePerfRecorder.zeroedToNow(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,16 +263,23 @@ export class NgCompiler {
|
|||||||
readonly ignoreForDiagnostics: Set<ts.SourceFile>;
|
readonly ignoreForDiagnostics: Set<ts.SourceFile>;
|
||||||
readonly ignoreForEmit: Set<ts.SourceFile>;
|
readonly ignoreForEmit: Set<ts.SourceFile>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
|
||||||
|
* new compilation uses a fresh `PerfRecorder`. Thus, classes created with a lifespan of the
|
||||||
|
* `NgCompiler` use a `DelegatingPerfRecorder` so the `PerfRecorder` they write to can be updated
|
||||||
|
* with each fresh compilation.
|
||||||
|
*/
|
||||||
|
private delegatingPerfRecorder = new DelegatingPerfRecorder(this.perfRecorder);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a `CompilationTicket` into an `NgCompiler` instance for the requested compilation.
|
* Convert a `CompilationTicket` into an `NgCompiler` instance for the requested compilation.
|
||||||
*
|
*
|
||||||
* Depending on the nature of the compilation request, the `NgCompiler` instance may be reused
|
* Depending on the nature of the compilation request, the `NgCompiler` instance may be reused
|
||||||
* from a previous compilation and updated with any changes, it may be a new instance which
|
* from a previous compilation and updated with any changes, it may be a new instance which
|
||||||
* incrementally reuses state from a previous compilation, or it may represent a fresh compilation
|
* incrementally reuses state from a previous compilation, or it may represent a fresh
|
||||||
* entirely.
|
* compilation entirely.
|
||||||
*/
|
*/
|
||||||
static fromTicket(
|
static fromTicket(ticket: CompilationTicket, adapter: NgCompilerAdapter) {
|
||||||
ticket: CompilationTicket, adapter: NgCompilerAdapter, perfRecorder?: PerfRecorder) {
|
|
||||||
switch (ticket.kind) {
|
switch (ticket.kind) {
|
||||||
case CompilationTicketKind.Fresh:
|
case CompilationTicketKind.Fresh:
|
||||||
return new NgCompiler(
|
return new NgCompiler(
|
||||||
@ -266,7 +291,7 @@ export class NgCompiler {
|
|||||||
IncrementalDriver.fresh(ticket.tsProgram),
|
IncrementalDriver.fresh(ticket.tsProgram),
|
||||||
ticket.enableTemplateTypeChecker,
|
ticket.enableTemplateTypeChecker,
|
||||||
ticket.usePoisonedData,
|
ticket.usePoisonedData,
|
||||||
perfRecorder,
|
ticket.perfRecorder,
|
||||||
);
|
);
|
||||||
case CompilationTicketKind.IncrementalTypeScript:
|
case CompilationTicketKind.IncrementalTypeScript:
|
||||||
return new NgCompiler(
|
return new NgCompiler(
|
||||||
@ -278,11 +303,11 @@ export class NgCompiler {
|
|||||||
ticket.newDriver,
|
ticket.newDriver,
|
||||||
ticket.enableTemplateTypeChecker,
|
ticket.enableTemplateTypeChecker,
|
||||||
ticket.usePoisonedData,
|
ticket.usePoisonedData,
|
||||||
perfRecorder,
|
ticket.perfRecorder,
|
||||||
);
|
);
|
||||||
case CompilationTicketKind.IncrementalResource:
|
case CompilationTicketKind.IncrementalResource:
|
||||||
const compiler = ticket.compiler;
|
const compiler = ticket.compiler;
|
||||||
compiler.updateWithChangedResources(ticket.modifiedResourceFiles);
|
compiler.updateWithChangedResources(ticket.modifiedResourceFiles, ticket.perfRecorder);
|
||||||
return compiler;
|
return compiler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,7 +321,7 @@ export class NgCompiler {
|
|||||||
readonly incrementalDriver: IncrementalDriver,
|
readonly incrementalDriver: IncrementalDriver,
|
||||||
readonly enableTemplateTypeChecker: boolean,
|
readonly enableTemplateTypeChecker: boolean,
|
||||||
readonly usePoisonedData: boolean,
|
readonly usePoisonedData: boolean,
|
||||||
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER,
|
private livePerfRecorder: ActivePerfRecorder,
|
||||||
) {
|
) {
|
||||||
this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics);
|
this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics);
|
||||||
const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options);
|
const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options);
|
||||||
@ -312,9 +337,7 @@ export class NgCompiler {
|
|||||||
|
|
||||||
const moduleResolutionCache = ts.createModuleResolutionCache(
|
const moduleResolutionCache = ts.createModuleResolutionCache(
|
||||||
this.adapter.getCurrentDirectory(),
|
this.adapter.getCurrentDirectory(),
|
||||||
// Note: this used to be an arrow-function closure. However, JS engines like v8 have some
|
// doen't retain a reference to `this`, if other closures in the constructor here reference
|
||||||
// strange behaviors with retaining the lexical scope of the closure. Even if this function
|
|
||||||
// doesn't retain a reference to `this`, if other closures in the constructor here reference
|
|
||||||
// `this` internally then a closure created here would retain them. This can cause major
|
// `this` internally then a closure created here would retain them. This can cause major
|
||||||
// memory leak issues since the `moduleResolutionCache` is a long-lived object and finds its
|
// memory leak issues since the `moduleResolutionCache` is a long-lived object and finds its
|
||||||
// way into all kinds of places inside TS internal objects.
|
// way into all kinds of places inside TS internal objects.
|
||||||
@ -322,42 +345,66 @@ export class NgCompiler {
|
|||||||
this.moduleResolver =
|
this.moduleResolver =
|
||||||
new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache);
|
new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache);
|
||||||
this.resourceManager = new AdapterResourceLoader(adapter, this.options);
|
this.resourceManager = new AdapterResourceLoader(adapter, this.options);
|
||||||
this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(tsProgram.getTypeChecker()));
|
this.cycleAnalyzer =
|
||||||
|
new CycleAnalyzer(new ImportGraph(tsProgram.getTypeChecker(), this.delegatingPerfRecorder));
|
||||||
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, tsProgram);
|
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, tsProgram);
|
||||||
|
|
||||||
this.ignoreForDiagnostics =
|
this.ignoreForDiagnostics =
|
||||||
new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf)));
|
new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf)));
|
||||||
this.ignoreForEmit = this.adapter.ignoreForEmit;
|
this.ignoreForEmit = this.adapter.ignoreForEmit;
|
||||||
|
|
||||||
|
let dtsFileCount = 0;
|
||||||
|
let nonDtsFileCount = 0;
|
||||||
|
for (const sf of tsProgram.getSourceFiles()) {
|
||||||
|
if (sf.isDeclarationFile) {
|
||||||
|
dtsFileCount++;
|
||||||
|
} else {
|
||||||
|
nonDtsFileCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
livePerfRecorder.eventCount(PerfEvent.InputDtsFile, dtsFileCount);
|
||||||
|
livePerfRecorder.eventCount(PerfEvent.InputTsFile, nonDtsFileCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateWithChangedResources(changedResources: Set<string>): void {
|
get perfRecorder(): ActivePerfRecorder {
|
||||||
if (this.compilation === null) {
|
return this.livePerfRecorder;
|
||||||
// Analysis hasn't happened yet, so no update is necessary - any changes to resources will be
|
}
|
||||||
// captured by the inital analysis pass itself.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resourceManager.invalidate();
|
private updateWithChangedResources(
|
||||||
|
changedResources: Set<string>, perfRecorder: ActivePerfRecorder): void {
|
||||||
|
this.livePerfRecorder = perfRecorder;
|
||||||
|
this.delegatingPerfRecorder.target = perfRecorder;
|
||||||
|
|
||||||
const classesToUpdate = new Set<DeclarationNode>();
|
perfRecorder.inPhase(PerfPhase.ResourceUpdate, () => {
|
||||||
for (const resourceFile of changedResources) {
|
if (this.compilation === null) {
|
||||||
for (const templateClass of this.getComponentsWithTemplateFile(resourceFile)) {
|
// Analysis hasn't happened yet, so no update is necessary - any changes to resources will
|
||||||
classesToUpdate.add(templateClass);
|
// be captured by the inital analysis pass itself.
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const styleClass of this.getComponentsWithStyleFile(resourceFile)) {
|
this.resourceManager.invalidate();
|
||||||
classesToUpdate.add(styleClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const clazz of classesToUpdate) {
|
const classesToUpdate = new Set<DeclarationNode>();
|
||||||
this.compilation.traitCompiler.updateResources(clazz);
|
for (const resourceFile of changedResources) {
|
||||||
if (!ts.isClassDeclaration(clazz)) {
|
for (const templateClass of this.getComponentsWithTemplateFile(resourceFile)) {
|
||||||
continue;
|
classesToUpdate.add(templateClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const styleClass of this.getComponentsWithStyleFile(resourceFile)) {
|
||||||
|
classesToUpdate.add(styleClass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.compilation.templateTypeChecker.invalidateClass(clazz);
|
for (const clazz of classesToUpdate) {
|
||||||
}
|
this.compilation.traitCompiler.updateResources(clazz);
|
||||||
|
if (!ts.isClassDeclaration(clazz)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compilation.templateTypeChecker.invalidateClass(clazz);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -481,33 +528,28 @@ export class NgCompiler {
|
|||||||
if (this.compilation !== null) {
|
if (this.compilation !== null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.compilation = this.makeCompilation();
|
|
||||||
|
|
||||||
const analyzeSpan = this.perfRecorder.start('analyze');
|
await this.perfRecorder.inPhase(PerfPhase.Analysis, async () => {
|
||||||
const promises: Promise<void>[] = [];
|
this.compilation = this.makeCompilation();
|
||||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
|
||||||
if (sf.isDeclarationFile) {
|
const promises: Promise<void>[] = [];
|
||||||
continue;
|
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||||
|
if (sf.isDeclarationFile) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let analysisPromise = this.compilation.traitCompiler.analyzeAsync(sf);
|
||||||
|
this.scanForMwp(sf);
|
||||||
|
if (analysisPromise !== undefined) {
|
||||||
|
promises.push(analysisPromise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', sf);
|
await Promise.all(promises);
|
||||||
let analysisPromise = this.compilation.traitCompiler.analyzeAsync(sf);
|
|
||||||
this.scanForMwp(sf);
|
|
||||||
if (analysisPromise === undefined) {
|
|
||||||
this.perfRecorder.stop(analyzeFileSpan);
|
|
||||||
} else if (this.perfRecorder.enabled) {
|
|
||||||
analysisPromise = analysisPromise.then(() => this.perfRecorder.stop(analyzeFileSpan));
|
|
||||||
}
|
|
||||||
if (analysisPromise !== undefined) {
|
|
||||||
promises.push(analysisPromise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
this.perfRecorder.memory(PerfCheckpoint.Analysis);
|
||||||
|
this.resolveCompilation(this.compilation.traitCompiler);
|
||||||
this.perfRecorder.stop(analyzeSpan);
|
});
|
||||||
|
|
||||||
this.resolveCompilation(this.compilation.traitCompiler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -517,9 +559,7 @@ export class NgCompiler {
|
|||||||
*/
|
*/
|
||||||
listLazyRoutes(entryRoute?: string): LazyRoute[] {
|
listLazyRoutes(entryRoute?: string): LazyRoute[] {
|
||||||
if (entryRoute) {
|
if (entryRoute) {
|
||||||
// Note:
|
// htts://github.com/angular/angular/blob/50732e156/packages/compiler-cli/src/transformers/compiler_host.ts#L175-L188).
|
||||||
// This resolution step is here to match the implementation of the old `AotCompilerHost` (see
|
|
||||||
// https://github.com/angular/angular/blob/50732e156/packages/compiler-cli/src/transformers/compiler_host.ts#L175-L188).
|
|
||||||
//
|
//
|
||||||
// `@angular/cli` will always call this API with an absolute path, so the resolution step is
|
// `@angular/cli` will always call this API with an absolute path, so the resolution step is
|
||||||
// not necessary, but keeping it backwards compatible in case someone else is using the API.
|
// not necessary, but keeping it backwards compatible in case someone else is using the API.
|
||||||
@ -573,7 +613,8 @@ export class NgCompiler {
|
|||||||
const before = [
|
const before = [
|
||||||
ivyTransformFactory(
|
ivyTransformFactory(
|
||||||
compilation.traitCompiler, compilation.reflector, importRewriter,
|
compilation.traitCompiler, compilation.reflector, importRewriter,
|
||||||
compilation.defaultImportTracker, compilation.isCore, this.closureCompilerEnabled),
|
compilation.defaultImportTracker, this.delegatingPerfRecorder, compilation.isCore,
|
||||||
|
this.closureCompilerEnabled),
|
||||||
aliasTransformFactory(compilation.traitCompiler.exportStatements),
|
aliasTransformFactory(compilation.traitCompiler.exportStatements),
|
||||||
compilation.defaultImportTracker.importPreservingTransformer(),
|
compilation.defaultImportTracker.importPreservingTransformer(),
|
||||||
];
|
];
|
||||||
@ -618,28 +659,32 @@ export class NgCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private analyzeSync(): void {
|
private analyzeSync(): void {
|
||||||
const analyzeSpan = this.perfRecorder.start('analyze');
|
this.perfRecorder.inPhase(PerfPhase.Analysis, () => {
|
||||||
this.compilation = this.makeCompilation();
|
this.compilation = this.makeCompilation();
|
||||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||||
if (sf.isDeclarationFile) {
|
if (sf.isDeclarationFile) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
this.compilation.traitCompiler.analyzeSync(sf);
|
||||||
|
this.scanForMwp(sf);
|
||||||
}
|
}
|
||||||
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', sf);
|
|
||||||
this.compilation.traitCompiler.analyzeSync(sf);
|
|
||||||
this.scanForMwp(sf);
|
|
||||||
this.perfRecorder.stop(analyzeFileSpan);
|
|
||||||
}
|
|
||||||
this.perfRecorder.stop(analyzeSpan);
|
|
||||||
|
|
||||||
this.resolveCompilation(this.compilation.traitCompiler);
|
this.perfRecorder.memory(PerfCheckpoint.Analysis);
|
||||||
|
|
||||||
|
this.resolveCompilation(this.compilation.traitCompiler);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveCompilation(traitCompiler: TraitCompiler): void {
|
private resolveCompilation(traitCompiler: TraitCompiler): void {
|
||||||
traitCompiler.resolve();
|
this.perfRecorder.inPhase(PerfPhase.Resolve, () => {
|
||||||
|
traitCompiler.resolve();
|
||||||
|
|
||||||
// At this point, analysis is complete and the compiler can now calculate which files need to
|
// At this point, analysis is complete and the compiler can now calculate which files need to
|
||||||
// be emitted, so do that.
|
// be emitted, so do that.
|
||||||
this.incrementalDriver.recordSuccessfulAnalysis(traitCompiler);
|
this.incrementalDriver.recordSuccessfulAnalysis(traitCompiler);
|
||||||
|
|
||||||
|
this.perfRecorder.memory(PerfCheckpoint.Resolve);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private get fullTemplateTypeCheck(): boolean {
|
private get fullTemplateTypeCheck(): boolean {
|
||||||
@ -770,7 +815,6 @@ export class NgCompiler {
|
|||||||
const compilation = this.ensureAnalyzed();
|
const compilation = this.ensureAnalyzed();
|
||||||
|
|
||||||
// Get the diagnostics.
|
// Get the diagnostics.
|
||||||
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
|
|
||||||
const diagnostics: ts.Diagnostic[] = [];
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||||
if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
|
if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
|
||||||
@ -782,7 +826,6 @@ export class NgCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const program = this.typeCheckingProgramStrategy.getProgram();
|
const program = this.typeCheckingProgramStrategy.getProgram();
|
||||||
this.perfRecorder.stop(typeCheckSpan);
|
|
||||||
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
||||||
this.nextProgram = program;
|
this.nextProgram = program;
|
||||||
|
|
||||||
@ -794,14 +837,12 @@ export class NgCompiler {
|
|||||||
const compilation = this.ensureAnalyzed();
|
const compilation = this.ensureAnalyzed();
|
||||||
|
|
||||||
// Get the diagnostics.
|
// Get the diagnostics.
|
||||||
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
|
|
||||||
const diagnostics: ts.Diagnostic[] = [];
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
if (!sf.isDeclarationFile && !this.adapter.isShim(sf)) {
|
if (!sf.isDeclarationFile && !this.adapter.isShim(sf)) {
|
||||||
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
|
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
|
||||||
}
|
}
|
||||||
|
|
||||||
const program = this.typeCheckingProgramStrategy.getProgram();
|
const program = this.typeCheckingProgramStrategy.getProgram();
|
||||||
this.perfRecorder.stop(typeCheckSpan);
|
|
||||||
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
||||||
this.nextProgram = program;
|
this.nextProgram = program;
|
||||||
|
|
||||||
@ -950,7 +991,8 @@ export class NgCompiler {
|
|||||||
this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData,
|
this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData,
|
||||||
this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer,
|
this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer,
|
||||||
cycleHandlingStrategy, refEmitter, defaultImportTracker, this.incrementalDriver.depGraph,
|
cycleHandlingStrategy, refEmitter, defaultImportTracker, this.incrementalDriver.depGraph,
|
||||||
injectableRegistry, semanticDepGraphUpdater, this.closureCompilerEnabled),
|
injectableRegistry, semanticDepGraphUpdater, this.closureCompilerEnabled,
|
||||||
|
this.delegatingPerfRecorder),
|
||||||
|
|
||||||
// TODO(alxhub): understand why the cast here is necessary (something to do with `null`
|
// TODO(alxhub): understand why the cast here is necessary (something to do with `null`
|
||||||
// not being assignable to `unknown` when wrapped in `Readonly`).
|
// not being assignable to `unknown` when wrapped in `Readonly`).
|
||||||
@ -959,31 +1001,33 @@ export class NgCompiler {
|
|||||||
reflector, evaluator, metaRegistry, scopeRegistry, metaReader,
|
reflector, evaluator, metaRegistry, scopeRegistry, metaReader,
|
||||||
defaultImportTracker, injectableRegistry, isCore, semanticDepGraphUpdater,
|
defaultImportTracker, injectableRegistry, isCore, semanticDepGraphUpdater,
|
||||||
this.closureCompilerEnabled, compileUndecoratedClassesWithAngularFeatures,
|
this.closureCompilerEnabled, compileUndecoratedClassesWithAngularFeatures,
|
||||||
|
this.delegatingPerfRecorder,
|
||||||
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null,unknown>>,
|
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null,unknown>>,
|
||||||
// clang-format on
|
// clang-format on
|
||||||
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
||||||
// before injectable factories (so injectable factories can delegate to them)
|
// before injectable factories (so injectable factories can delegate to them)
|
||||||
new PipeDecoratorHandler(
|
new PipeDecoratorHandler(
|
||||||
reflector, evaluator, metaRegistry, scopeRegistry, defaultImportTracker,
|
reflector, evaluator, metaRegistry, scopeRegistry, defaultImportTracker,
|
||||||
injectableRegistry, isCore),
|
injectableRegistry, isCore, this.delegatingPerfRecorder),
|
||||||
new InjectableDecoratorHandler(
|
new InjectableDecoratorHandler(
|
||||||
reflector, defaultImportTracker, isCore, this.options.strictInjectionParameters || false,
|
reflector, defaultImportTracker, isCore, this.options.strictInjectionParameters || false,
|
||||||
injectableRegistry),
|
injectableRegistry, this.delegatingPerfRecorder),
|
||||||
new NgModuleDecoratorHandler(
|
new NgModuleDecoratorHandler(
|
||||||
reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore,
|
reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore,
|
||||||
routeAnalyzer, refEmitter, this.adapter.factoryTracker, defaultImportTracker,
|
routeAnalyzer, refEmitter, this.adapter.factoryTracker, defaultImportTracker,
|
||||||
this.closureCompilerEnabled, injectableRegistry, this.options.i18nInLocale),
|
this.closureCompilerEnabled, injectableRegistry, this.delegatingPerfRecorder,
|
||||||
|
this.options.i18nInLocale),
|
||||||
];
|
];
|
||||||
|
|
||||||
const traitCompiler = new TraitCompiler(
|
const traitCompiler = new TraitCompiler(
|
||||||
handlers, reflector, this.perfRecorder, this.incrementalDriver,
|
handlers, reflector, this.delegatingPerfRecorder, this.incrementalDriver,
|
||||||
this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms,
|
this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms,
|
||||||
semanticDepGraphUpdater);
|
semanticDepGraphUpdater);
|
||||||
|
|
||||||
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
||||||
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
|
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
|
||||||
this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver,
|
this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver,
|
||||||
scopeRegistry, typeCheckScopeRegistry);
|
scopeRegistry, typeCheckScopeRegistry, this.delegatingPerfRecorder);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isCore,
|
isCore,
|
||||||
|
@ -25,8 +25,8 @@ function makeFreshCompiler(
|
|||||||
programStrategy: TypeCheckingProgramStrategy, incrementalStrategy: IncrementalBuildStrategy,
|
programStrategy: TypeCheckingProgramStrategy, incrementalStrategy: IncrementalBuildStrategy,
|
||||||
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): NgCompiler {
|
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): NgCompiler {
|
||||||
const ticket = freshCompilationTicket(
|
const ticket = freshCompilationTicket(
|
||||||
program, options, incrementalStrategy, programStrategy, enableTemplateTypeChecker,
|
program, options, incrementalStrategy, programStrategy, /* perfRecorder */ null,
|
||||||
usePoisonedData);
|
enableTemplateTypeChecker, usePoisonedData);
|
||||||
return NgCompiler.fromTicket(ticket, host);
|
return NgCompiler.fromTicket(ticket, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ ts_library(
|
|||||||
]),
|
]),
|
||||||
module_name = "@angular/compiler-cli/src/ngtsc/cycles",
|
module_name = "@angular/compiler-cli/src/ngtsc/cycles",
|
||||||
deps = [
|
deps = [
|
||||||
|
"//packages/compiler-cli/src/ngtsc/perf",
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {PerfPhase, PerfRecorder} from '../../perf';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cached graph of imports in the `ts.Program`.
|
* A cached graph of imports in the `ts.Program`.
|
||||||
*
|
*
|
||||||
@ -17,7 +19,7 @@ import * as ts from 'typescript';
|
|||||||
export class ImportGraph {
|
export class ImportGraph {
|
||||||
private map = new Map<ts.SourceFile, Set<ts.SourceFile>>();
|
private map = new Map<ts.SourceFile, Set<ts.SourceFile>>();
|
||||||
|
|
||||||
constructor(private checker: ts.TypeChecker) {}
|
constructor(private checker: ts.TypeChecker, private perf: PerfRecorder) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List the direct (not transitive) imports of a given `ts.SourceFile`.
|
* List the direct (not transitive) imports of a given `ts.SourceFile`.
|
||||||
@ -99,26 +101,28 @@ export class ImportGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private scanImports(sf: ts.SourceFile): Set<ts.SourceFile> {
|
private scanImports(sf: ts.SourceFile): Set<ts.SourceFile> {
|
||||||
const imports = new Set<ts.SourceFile>();
|
return this.perf.inPhase(PerfPhase.CycleDetection, () => {
|
||||||
// Look through the source file for import and export statements.
|
const imports = new Set<ts.SourceFile>();
|
||||||
for (const stmt of sf.statements) {
|
// Look through the source file for import and export statements.
|
||||||
if ((!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt)) ||
|
for (const stmt of sf.statements) {
|
||||||
stmt.moduleSpecifier === undefined) {
|
if ((!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt)) ||
|
||||||
continue;
|
stmt.moduleSpecifier === undefined) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const symbol = this.checker.getSymbolAtLocation(stmt.moduleSpecifier);
|
const symbol = this.checker.getSymbolAtLocation(stmt.moduleSpecifier);
|
||||||
if (symbol === undefined || symbol.valueDeclaration === undefined) {
|
if (symbol === undefined || symbol.valueDeclaration === undefined) {
|
||||||
// No symbol could be found to skip over this import/export.
|
// No symbol could be found to skip over this import/export.
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
const moduleFile = symbol.valueDeclaration;
|
||||||
|
if (ts.isSourceFile(moduleFile) && isLocalFile(moduleFile)) {
|
||||||
|
// Record this local import.
|
||||||
|
imports.add(moduleFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const moduleFile = symbol.valueDeclaration;
|
return imports;
|
||||||
if (ts.isSourceFile(moduleFile) && isLocalFile(moduleFile)) {
|
});
|
||||||
// Record this local import.
|
|
||||||
imports.add(moduleFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imports;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/cycles",
|
"//packages/compiler-cli/src/ngtsc/cycles",
|
||||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/perf",
|
||||||
"//packages/compiler-cli/src/ngtsc/testing",
|
"//packages/compiler-cli/src/ngtsc/testing",
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
],
|
],
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
|
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
|
||||||
import {runInEachFileSystem} from '../../file_system/testing';
|
import {runInEachFileSystem} from '../../file_system/testing';
|
||||||
|
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||||
import {Cycle, CycleAnalyzer} from '../src/analyzer';
|
import {Cycle, CycleAnalyzer} from '../src/analyzer';
|
||||||
import {ImportGraph} from '../src/imports';
|
import {ImportGraph} from '../src/imports';
|
||||||
import {importPath, makeProgramFromGraph} from './util';
|
import {importPath, makeProgramFromGraph} from './util';
|
||||||
@ -75,7 +76,7 @@ runInEachFileSystem(() => {
|
|||||||
const {program} = makeProgramFromGraph(getFileSystem(), graph);
|
const {program} = makeProgramFromGraph(getFileSystem(), graph);
|
||||||
return {
|
return {
|
||||||
program,
|
program,
|
||||||
analyzer: new CycleAnalyzer(new ImportGraph(program.getTypeChecker())),
|
analyzer: new CycleAnalyzer(new ImportGraph(program.getTypeChecker(), NOOP_PERF_RECORDER)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
|
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
|
||||||
import {runInEachFileSystem} from '../../file_system/testing';
|
import {runInEachFileSystem} from '../../file_system/testing';
|
||||||
|
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||||
import {ImportGraph} from '../src/imports';
|
import {ImportGraph} from '../src/imports';
|
||||||
import {importPath, makeProgramFromGraph} from './util';
|
import {importPath, makeProgramFromGraph} from './util';
|
||||||
|
|
||||||
@ -86,7 +87,7 @@ runInEachFileSystem(() => {
|
|||||||
const {program} = makeProgramFromGraph(getFileSystem(), graph);
|
const {program} = makeProgramFromGraph(getFileSystem(), graph);
|
||||||
return {
|
return {
|
||||||
program,
|
program,
|
||||||
graph: new ImportGraph(program.getTypeChecker()),
|
graph: new ImportGraph(program.getTypeChecker(), NOOP_PERF_RECORDER),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/incremental/semantic_graph",
|
"//packages/compiler-cli/src/ngtsc/incremental/semantic_graph",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//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/perf",
|
||||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||||
"//packages/compiler-cli/src/ngtsc/scope",
|
"//packages/compiler-cli/src/ngtsc/scope",
|
||||||
"//packages/compiler-cli/src/ngtsc/transform",
|
"//packages/compiler-cli/src/ngtsc/transform",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
||||||
|
import {PerfEvent, PerfPhase, PerfRecorder} from '../../perf';
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
import {ClassRecord, TraitCompiler} from '../../transform';
|
import {ClassRecord, TraitCompiler} from '../../transform';
|
||||||
import {FileTypeCheckingData} from '../../typecheck/src/checker';
|
import {FileTypeCheckingData} from '../../typecheck/src/checker';
|
||||||
@ -43,118 +44,122 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord, FileType
|
|||||||
*/
|
*/
|
||||||
static reconcile(
|
static reconcile(
|
||||||
oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program,
|
oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program,
|
||||||
modifiedResourceFiles: Set<string>|null): IncrementalDriver {
|
modifiedResourceFiles: Set<string>|null, perf: PerfRecorder): IncrementalDriver {
|
||||||
// Initialize the state of the current build based on the previous one.
|
return perf.inPhase(PerfPhase.Reconciliation, () => {
|
||||||
let state: PendingBuildState;
|
// Initialize the state of the current build based on the previous one.
|
||||||
if (oldDriver.state.kind === BuildStateKind.Pending) {
|
let state: PendingBuildState;
|
||||||
// The previous build never made it past the pending state. Reuse it as the starting state for
|
if (oldDriver.state.kind === BuildStateKind.Pending) {
|
||||||
// this build.
|
// The previous build never made it past the pending state. Reuse it as the starting state
|
||||||
state = oldDriver.state;
|
// for this build.
|
||||||
} else {
|
state = oldDriver.state;
|
||||||
let priorGraph: SemanticDepGraph|null = null;
|
|
||||||
if (oldDriver.state.lastGood !== null) {
|
|
||||||
priorGraph = oldDriver.state.lastGood.semanticDepGraph;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The previous build was successfully analyzed. `pendingEmit` is the only state carried
|
|
||||||
// forward into this build.
|
|
||||||
state = {
|
|
||||||
kind: BuildStateKind.Pending,
|
|
||||||
pendingEmit: oldDriver.state.pendingEmit,
|
|
||||||
pendingTypeCheckEmit: oldDriver.state.pendingTypeCheckEmit,
|
|
||||||
changedResourcePaths: new Set<AbsoluteFsPath>(),
|
|
||||||
changedTsPaths: new Set<string>(),
|
|
||||||
lastGood: oldDriver.state.lastGood,
|
|
||||||
semanticDepGraphUpdater: new SemanticDepGraphUpdater(priorGraph),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge the freshly modified resource files with any prior ones.
|
|
||||||
if (modifiedResourceFiles !== null) {
|
|
||||||
for (const resFile of modifiedResourceFiles) {
|
|
||||||
state.changedResourcePaths.add(absoluteFrom(resFile));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, process the files in the new program, with a couple of goals:
|
|
||||||
// 1) Determine which TS files have changed, if any, and merge them into `changedTsFiles`.
|
|
||||||
// 2) Produce a list of TS files which no longer exist in the program (they've been deleted
|
|
||||||
// since the previous compilation). These need to be removed from the state tracking to avoid
|
|
||||||
// leaking memory.
|
|
||||||
|
|
||||||
// All files in the old program, for easy detection of changes.
|
|
||||||
const oldFiles = new Set<ts.SourceFile>(oldProgram.getSourceFiles());
|
|
||||||
|
|
||||||
// Assume all the old files were deleted to begin with. Only TS files are tracked.
|
|
||||||
const deletedTsPaths = new Set<string>(tsOnlyFiles(oldProgram).map(sf => sf.fileName));
|
|
||||||
|
|
||||||
for (const newFile of newProgram.getSourceFiles()) {
|
|
||||||
if (!newFile.isDeclarationFile) {
|
|
||||||
// This file exists in the new program, so remove it from `deletedTsPaths`.
|
|
||||||
deletedTsPaths.delete(newFile.fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldFiles.has(newFile)) {
|
|
||||||
// This file hasn't changed; no need to look at it further.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The file has changed since the last successful build. The appropriate reaction depends on
|
|
||||||
// what kind of file it is.
|
|
||||||
if (!newFile.isDeclarationFile) {
|
|
||||||
// It's a .ts file, so track it as a change.
|
|
||||||
state.changedTsPaths.add(newFile.fileName);
|
|
||||||
} else {
|
} else {
|
||||||
// It's a .d.ts file. Currently the compiler does not do a great job of tracking
|
let priorGraph: SemanticDepGraph|null = null;
|
||||||
// dependencies on .d.ts files, so bail out of incremental builds here and do a full build.
|
if (oldDriver.state.lastGood !== null) {
|
||||||
// This usually only happens if something in node_modules changes.
|
priorGraph = oldDriver.state.lastGood.semanticDepGraph;
|
||||||
return IncrementalDriver.fresh(newProgram);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The next step is to remove any deleted files from the state.
|
// The previous build was successfully analyzed. `pendingEmit` is the only state carried
|
||||||
for (const filePath of deletedTsPaths) {
|
// forward into this build.
|
||||||
state.pendingEmit.delete(filePath);
|
state = {
|
||||||
state.pendingTypeCheckEmit.delete(filePath);
|
kind: BuildStateKind.Pending,
|
||||||
|
pendingEmit: oldDriver.state.pendingEmit,
|
||||||
// Even if the file doesn't exist in the current compilation, it still might have been changed
|
pendingTypeCheckEmit: oldDriver.state.pendingTypeCheckEmit,
|
||||||
// in a previous one, so delete it from the set of changed TS files, just in case.
|
changedResourcePaths: new Set<AbsoluteFsPath>(),
|
||||||
state.changedTsPaths.delete(filePath);
|
changedTsPaths: new Set<string>(),
|
||||||
}
|
lastGood: oldDriver.state.lastGood,
|
||||||
|
semanticDepGraphUpdater: new SemanticDepGraphUpdater(priorGraph),
|
||||||
// Now, changedTsPaths contains physically changed TS paths. Use the previous program's logical
|
};
|
||||||
// dependency graph to determine logically changed files.
|
|
||||||
const depGraph = new FileDependencyGraph();
|
|
||||||
|
|
||||||
// If a previous compilation exists, use its dependency graph to determine the set of logically
|
|
||||||
// changed files.
|
|
||||||
let logicalChanges: Set<string>|null = null;
|
|
||||||
if (state.lastGood !== null) {
|
|
||||||
// Extract the set of logically changed files. At the same time, this operation populates the
|
|
||||||
// current (fresh) dependency graph with information about those files which have not
|
|
||||||
// logically changed.
|
|
||||||
logicalChanges = depGraph.updateWithPhysicalChanges(
|
|
||||||
state.lastGood.depGraph, state.changedTsPaths, deletedTsPaths,
|
|
||||||
state.changedResourcePaths);
|
|
||||||
for (const fileName of state.changedTsPaths) {
|
|
||||||
logicalChanges.add(fileName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any logically changed files need to be re-emitted. Most of the time this would happen
|
// Merge the freshly modified resource files with any prior ones.
|
||||||
// regardless because the new dependency graph would _also_ identify the file as stale.
|
if (modifiedResourceFiles !== null) {
|
||||||
// However there are edge cases such as removing a component from an NgModule without adding
|
for (const resFile of modifiedResourceFiles) {
|
||||||
// it to another one, where the previous graph identifies the file as logically changed, but
|
state.changedResourcePaths.add(absoluteFrom(resFile));
|
||||||
// the new graph (which does not have that edge) fails to identify that the file should be
|
}
|
||||||
// re-emitted.
|
|
||||||
for (const change of logicalChanges) {
|
|
||||||
state.pendingEmit.add(change);
|
|
||||||
state.pendingTypeCheckEmit.add(change);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// `state` now reflects the initial pending state of the current compilation.
|
// Next, process the files in the new program, with a couple of goals:
|
||||||
|
// 1) Determine which TS files have changed, if any, and merge them into `changedTsFiles`.
|
||||||
|
// 2) Produce a list of TS files which no longer exist in the program (they've been deleted
|
||||||
|
// since the previous compilation). These need to be removed from the state tracking to
|
||||||
|
// avoid leaking memory.
|
||||||
|
|
||||||
return new IncrementalDriver(state, depGraph, logicalChanges);
|
// All files in the old program, for easy detection of changes.
|
||||||
|
const oldFiles = new Set<ts.SourceFile>(oldProgram.getSourceFiles());
|
||||||
|
|
||||||
|
// Assume all the old files were deleted to begin with. Only TS files are tracked.
|
||||||
|
const deletedTsPaths = new Set<string>(tsOnlyFiles(oldProgram).map(sf => sf.fileName));
|
||||||
|
|
||||||
|
for (const newFile of newProgram.getSourceFiles()) {
|
||||||
|
if (!newFile.isDeclarationFile) {
|
||||||
|
// This file exists in the new program, so remove it from `deletedTsPaths`.
|
||||||
|
deletedTsPaths.delete(newFile.fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldFiles.has(newFile)) {
|
||||||
|
// This file hasn't changed; no need to look at it further.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The file has changed since the last successful build. The appropriate reaction depends on
|
||||||
|
// what kind of file it is.
|
||||||
|
if (!newFile.isDeclarationFile) {
|
||||||
|
// It's a .ts file, so track it as a change.
|
||||||
|
state.changedTsPaths.add(newFile.fileName);
|
||||||
|
} else {
|
||||||
|
// It's a .d.ts file. Currently the compiler does not do a great job of tracking
|
||||||
|
// dependencies on .d.ts files, so bail out of incremental builds here and do a full
|
||||||
|
// build. This usually only happens if something in node_modules changes.
|
||||||
|
return IncrementalDriver.fresh(newProgram);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next step is to remove any deleted files from the state.
|
||||||
|
for (const filePath of deletedTsPaths) {
|
||||||
|
state.pendingEmit.delete(filePath);
|
||||||
|
state.pendingTypeCheckEmit.delete(filePath);
|
||||||
|
|
||||||
|
// Even if the file doesn't exist in the current compilation, it still might have been
|
||||||
|
// changed in a previous one, so delete it from the set of changed TS files, just in case.
|
||||||
|
state.changedTsPaths.delete(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
perf.eventCount(PerfEvent.SourceFilePhysicalChange, state.changedTsPaths.size);
|
||||||
|
|
||||||
|
// Now, changedTsPaths contains physically changed TS paths. Use the previous program's
|
||||||
|
// logical dependency graph to determine logically changed files.
|
||||||
|
const depGraph = new FileDependencyGraph();
|
||||||
|
|
||||||
|
// If a previous compilation exists, use its dependency graph to determine the set of
|
||||||
|
// logically changed files.
|
||||||
|
let logicalChanges: Set<string>|null = null;
|
||||||
|
if (state.lastGood !== null) {
|
||||||
|
// Extract the set of logically changed files. At the same time, this operation populates
|
||||||
|
// the current (fresh) dependency graph with information about those files which have not
|
||||||
|
// logically changed.
|
||||||
|
logicalChanges = depGraph.updateWithPhysicalChanges(
|
||||||
|
state.lastGood.depGraph, state.changedTsPaths, deletedTsPaths,
|
||||||
|
state.changedResourcePaths);
|
||||||
|
perf.eventCount(PerfEvent.SourceFileLogicalChange, logicalChanges.size);
|
||||||
|
for (const fileName of state.changedTsPaths) {
|
||||||
|
logicalChanges.add(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any logically changed files need to be re-emitted. Most of the time this would happen
|
||||||
|
// regardless because the new dependency graph would _also_ identify the file as stale.
|
||||||
|
// However there are edge cases such as removing a component from an NgModule without adding
|
||||||
|
// it to another one, where the previous graph identifies the file as logically changed, but
|
||||||
|
// the new graph (which does not have that edge) fails to identify that the file should be
|
||||||
|
// re-emitted.
|
||||||
|
for (const change of logicalChanges) {
|
||||||
|
state.pendingEmit.add(change);
|
||||||
|
state.pendingTypeCheckEmit.add(change);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `state` now reflects the initial pending state of the current compilation.
|
||||||
|
return new IncrementalDriver(state, depGraph, logicalChanges);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static fresh(program: ts.Program): IncrementalDriver {
|
static fresh(program: ts.Program): IncrementalDriver {
|
||||||
|
@ -9,8 +9,6 @@ ts_library(
|
|||||||
]),
|
]),
|
||||||
deps = [
|
deps = [
|
||||||
"//packages:types",
|
"//packages:types",
|
||||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
|
||||||
"@npm//@types/node",
|
"@npm//@types/node",
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
],
|
],
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {PerfRecorder} from './src/api';
|
export * from './src/api';
|
||||||
export {NOOP_PERF_RECORDER} from './src/noop';
|
export {NOOP_PERF_RECORDER} from './src/noop';
|
||||||
export {PerfTracker} from './src/tracking';
|
export {ActivePerfRecorder, DelegatingPerfRecorder} from './src/recorder';
|
||||||
|
@ -6,12 +6,328 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {DeclarationNode} from '../../reflection';
|
/**
|
||||||
|
* A phase of compilation for which time is tracked in a distinct bucket.
|
||||||
|
*/
|
||||||
|
export enum PerfPhase {
|
||||||
|
/**
|
||||||
|
* The "default" phase which tracks time not spent in any other phase.
|
||||||
|
*/
|
||||||
|
Unaccounted,
|
||||||
|
|
||||||
export interface PerfRecorder {
|
/**
|
||||||
readonly enabled: boolean;
|
* Time spent setting up the compiler, before a TypeScript program is created.
|
||||||
|
*
|
||||||
|
* This includes operations like configuring the `ts.CompilerHost` and any wrappers.
|
||||||
|
*/
|
||||||
|
Setup,
|
||||||
|
|
||||||
mark(name: string, node?: DeclarationNode, category?: string, detail?: string): void;
|
/**
|
||||||
start(name: string, node?: DeclarationNode, category?: string, detail?: string): number;
|
* Time spent in `ts.createProgram`, including reading and parsing `ts.SourceFile`s in the
|
||||||
stop(span: number): void;
|
* `ts.CompilerHost`.
|
||||||
|
*
|
||||||
|
* This might be an incremental program creation operation.
|
||||||
|
*/
|
||||||
|
TypeScriptProgramCreate,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent reconciling the contents of an old `ts.Program` with the new incremental one.
|
||||||
|
*
|
||||||
|
* Only present in incremental compilations.
|
||||||
|
*/
|
||||||
|
Reconciliation,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent updating an `NgCompiler` instance with a resource-only change.
|
||||||
|
*
|
||||||
|
* Only present in incremental compilations where the change was resource-only.
|
||||||
|
*/
|
||||||
|
ResourceUpdate,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent calculating the plain TypeScript diagnostics (structural and semantic).
|
||||||
|
*/
|
||||||
|
TypeScriptDiagnostics,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent in Angular analysis of individual classes in the program.
|
||||||
|
*/
|
||||||
|
Analysis,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent in Angular global analysis (synthesis of analysis information into a complete
|
||||||
|
* understanding of the program).
|
||||||
|
*/
|
||||||
|
Resolve,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent building the import graph of the program in order to perform cycle detection.
|
||||||
|
*/
|
||||||
|
CycleDetection,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent generating the text of Type Check Blocks in order to perform template type checking.
|
||||||
|
*/
|
||||||
|
TcbGeneration,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent updating the `ts.Program` with new Type Check Block code.
|
||||||
|
*/
|
||||||
|
TcbUpdateProgram,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent by TypeScript performing its emit operations, including downleveling and writing
|
||||||
|
* output files.
|
||||||
|
*/
|
||||||
|
TypeScriptEmit,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent by Angular performing code transformations of ASTs as they're about to be emitted.
|
||||||
|
*
|
||||||
|
* This includes the actual code generation step for templates, and occurs during the emit phase
|
||||||
|
* (but is tracked separately from `TypeScriptEmit` time).
|
||||||
|
*/
|
||||||
|
Compile,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent performing a `TemplateTypeChecker` autocompletion operation.
|
||||||
|
*/
|
||||||
|
TtcAutocompletion,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent computing template type-checking diagnostics.
|
||||||
|
*/
|
||||||
|
TtcDiagnostics,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent getting a `Symbol` from the `TemplateTypeChecker`.
|
||||||
|
*/
|
||||||
|
TtcSymbol,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time spent by the Angular Language Service calculating a "get references" or a renaming
|
||||||
|
* operation.
|
||||||
|
*/
|
||||||
|
LsReferencesAndRenames,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the number of `PerfPhase`s, and must appear at the end of the list.
|
||||||
|
*/
|
||||||
|
LAST,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents some occurrence during compilation, and is tracked with a counter.
|
||||||
|
*/
|
||||||
|
export enum PerfEvent {
|
||||||
|
/**
|
||||||
|
* Counts the number of `.d.ts` files in the program.
|
||||||
|
*/
|
||||||
|
InputDtsFile,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of non-`.d.ts` files in the program.
|
||||||
|
*/
|
||||||
|
InputTsFile,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `@Component` class was analyzed.
|
||||||
|
*/
|
||||||
|
AnalyzeComponent,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `@Directive` class was analyzed.
|
||||||
|
*/
|
||||||
|
AnalyzeDirective,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `@Injectable` class was analyzed.
|
||||||
|
*/
|
||||||
|
AnalyzeInjectable,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `@NgModule` class was analyzed.
|
||||||
|
*/
|
||||||
|
AnalyzeNgModule,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `@Pipe` class was analyzed.
|
||||||
|
*/
|
||||||
|
AnalyzePipe,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A trait was analyzed.
|
||||||
|
*
|
||||||
|
* In theory, this should be the sum of the `Analyze` counters for each decorator type.
|
||||||
|
*/
|
||||||
|
TraitAnalyze,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A trait had a prior analysis available from an incremental program, and did not need to be
|
||||||
|
* re-analyzed.
|
||||||
|
*/
|
||||||
|
TraitReuseAnalysis,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `ts.SourceFile` directly changed between the prior program and a new incremental compilation.
|
||||||
|
*/
|
||||||
|
SourceFilePhysicalChange,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `ts.SourceFile` did not physically changed, but according to the file dependency graph, has
|
||||||
|
* logically changed between the prior program and a new incremental compilation.
|
||||||
|
*/
|
||||||
|
SourceFileLogicalChange,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `ts.SourceFile` has not logically changed and all of its analysis results were thus available
|
||||||
|
* for reuse.
|
||||||
|
*/
|
||||||
|
SourceFileReuseAnalysis,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Type Check Block (TCB) was generated.
|
||||||
|
*/
|
||||||
|
GenerateTcb,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Type Check Block (TCB) could not be generated because inlining was disabled, and the block
|
||||||
|
* would've required inlining.
|
||||||
|
*/
|
||||||
|
SkipGenerateTcbNoInline,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `.ngtypecheck.ts` file could be reused from the previous program and did not need to be
|
||||||
|
* regenerated.
|
||||||
|
*/
|
||||||
|
ReuseTypeCheckFile,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The template type-checking program required changes and had to be updated in an incremental
|
||||||
|
* step.
|
||||||
|
*/
|
||||||
|
UpdateTypeCheckProgram,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The compiler was able to prove that a `ts.SourceFile` did not need to be re-emitted.
|
||||||
|
*/
|
||||||
|
EmitSkipSourceFile,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `ts.SourceFile` was emitted.
|
||||||
|
*/
|
||||||
|
EmitSourceFile,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the number of `PrefEvent`s, and must appear at the end of the list.
|
||||||
|
*/
|
||||||
|
LAST,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a checkpoint during compilation at which the memory usage of the compiler should be
|
||||||
|
* recorded.
|
||||||
|
*/
|
||||||
|
export enum PerfCheckpoint {
|
||||||
|
/**
|
||||||
|
* The point at which the `PerfRecorder` was created, and ideally tracks memory used before any
|
||||||
|
* compilation structures are created.
|
||||||
|
*/
|
||||||
|
Initial,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The point just after the `ts.Program` has been created.
|
||||||
|
*/
|
||||||
|
TypeScriptProgramCreate,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The point just before Angular analysis starts.
|
||||||
|
*
|
||||||
|
* In the main usage pattern for the compiler, TypeScript diagnostics have been calculated at this
|
||||||
|
* point, so the `ts.TypeChecker` has fully ingested the current program, all `ts.Type` structures
|
||||||
|
* and `ts.Symbol`s have been created.
|
||||||
|
*/
|
||||||
|
PreAnalysis,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The point just after Angular analysis completes.
|
||||||
|
*/
|
||||||
|
Analysis,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The point just after Angular resolution is complete.
|
||||||
|
*/
|
||||||
|
Resolve,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The point just after Type Check Blocks (TCBs) have been generated.
|
||||||
|
*/
|
||||||
|
TtcGeneration,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The point just after the template type-checking program has been updated with any new TCBs.
|
||||||
|
*/
|
||||||
|
TtcUpdateProgram,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The point just before emit begins.
|
||||||
|
*
|
||||||
|
* In the main usage pattern for the compiler, all template type-checking diagnostics have been
|
||||||
|
* requested at this point.
|
||||||
|
*/
|
||||||
|
PreEmit,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The point just after the program has been fully emitted.
|
||||||
|
*/
|
||||||
|
Emit,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the number of `PerfCheckpoint`s, and must appear at the end of the list.
|
||||||
|
*/
|
||||||
|
LAST,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records timing, memory, or counts at specific points in the compiler's operation.
|
||||||
|
*/
|
||||||
|
export interface PerfRecorder {
|
||||||
|
/**
|
||||||
|
* Set the current phase of compilation.
|
||||||
|
*
|
||||||
|
* Time spent in the previous phase will be accounted to that phase. The caller is responsible for
|
||||||
|
* exiting the phase when work that should be tracked within it is completed, and either returning
|
||||||
|
* to the previous phase or transitioning to the next one directly.
|
||||||
|
*
|
||||||
|
* In general, prefer using `inPhase()` to instrument a section of code, as it automatically
|
||||||
|
* handles entering and exiting the phase. `phase()` should only be used when the former API
|
||||||
|
* cannot be cleanly applied to a particular operation.
|
||||||
|
*
|
||||||
|
* @returns the previous phase
|
||||||
|
*/
|
||||||
|
phase(phase: PerfPhase): PerfPhase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run `fn` in the given `PerfPhase` and return the result.
|
||||||
|
*
|
||||||
|
* Enters `phase` before executing the given `fn`, then exits the phase and returns the result.
|
||||||
|
* Prefer this API to `phase()` where possible.
|
||||||
|
*/
|
||||||
|
inPhase<T>(phase: PerfPhase, fn: () => T): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record the memory usage of the compiler at the given checkpoint.
|
||||||
|
*/
|
||||||
|
memory(after: PerfCheckpoint): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record that a specific event has occurred, possibly more than once.
|
||||||
|
*/
|
||||||
|
eventCount(event: PerfEvent, incrementBy?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the `PerfRecorder` to an empty state (clear all tracked statistics) and reset the zero
|
||||||
|
* point to the current time.
|
||||||
|
*/
|
||||||
|
reset(): void;
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,23 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {DeclarationNode} from '../../reflection';
|
import {PerfPhase, PerfRecorder} from './api';
|
||||||
|
|
||||||
import {PerfRecorder} from './api';
|
class NoopPerfRecorder implements PerfRecorder {
|
||||||
|
eventCount(): void {}
|
||||||
|
|
||||||
export const NOOP_PERF_RECORDER: PerfRecorder = {
|
memory(): void {}
|
||||||
enabled: false,
|
|
||||||
mark: (name: string, node: DeclarationNode, category?: string, detail?: string): void => {},
|
phase(): PerfPhase {
|
||||||
start: (name: string, node: DeclarationNode, category?: string, detail?: string): number => {
|
return PerfPhase.Unaccounted;
|
||||||
return 0;
|
}
|
||||||
},
|
|
||||||
stop: (span: number|false): void => {},
|
inPhase<T>(phase: PerfPhase, fn: () => T): T {
|
||||||
};
|
return fn();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const NOOP_PERF_RECORDER: PerfRecorder = new NoopPerfRecorder();
|
||||||
|
154
packages/compiler-cli/src/ngtsc/perf/src/recorder.ts
Normal file
154
packages/compiler-cli/src/ngtsc/perf/src/recorder.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google LLC 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
|
||||||
|
*/
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import {PerfCheckpoint, PerfEvent, PerfPhase, PerfRecorder} from './api';
|
||||||
|
import {HrTime, mark, timeSinceInMicros} from './clock';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializable performance data for the compilation, using string names.
|
||||||
|
*/
|
||||||
|
export interface PerfResults {
|
||||||
|
events: Record<string, number>;
|
||||||
|
phases: Record<string, number>;
|
||||||
|
memory: Record<string, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `PerfRecorder` that actively tracks performance statistics.
|
||||||
|
*/
|
||||||
|
export class ActivePerfRecorder implements PerfRecorder {
|
||||||
|
private counters: number[];
|
||||||
|
private phaseTime: number[];
|
||||||
|
private bytes: number[];
|
||||||
|
|
||||||
|
private currentPhase = PerfPhase.Unaccounted;
|
||||||
|
private currentPhaseEntered = this.zeroTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an `ActivePerfRecoder` with its zero point set to the current time.
|
||||||
|
*/
|
||||||
|
static zeroedToNow(): ActivePerfRecorder {
|
||||||
|
return new ActivePerfRecorder(mark());
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(private zeroTime: HrTime) {
|
||||||
|
this.counters = Array(PerfEvent.LAST).fill(0);
|
||||||
|
this.phaseTime = Array(PerfPhase.LAST).fill(0);
|
||||||
|
this.bytes = Array(PerfCheckpoint.LAST).fill(0);
|
||||||
|
|
||||||
|
// Take an initial memory snapshot before any other compilation work begins.
|
||||||
|
this.memory(PerfCheckpoint.Initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.counters = Array(PerfEvent.LAST).fill(0);
|
||||||
|
this.phaseTime = Array(PerfPhase.LAST).fill(0);
|
||||||
|
this.bytes = Array(PerfCheckpoint.LAST).fill(0);
|
||||||
|
this.zeroTime = mark();
|
||||||
|
this.currentPhase = PerfPhase.Unaccounted;
|
||||||
|
this.currentPhaseEntered = this.zeroTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
memory(after: PerfCheckpoint): void {
|
||||||
|
this.bytes[after] = process.memoryUsage().heapUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
phase(phase: PerfPhase): PerfPhase {
|
||||||
|
const previous = this.currentPhase;
|
||||||
|
this.phaseTime[this.currentPhase] += timeSinceInMicros(this.currentPhaseEntered);
|
||||||
|
this.currentPhase = phase;
|
||||||
|
this.currentPhaseEntered = mark();
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
inPhase<T>(phase: PerfPhase, fn: () => T): T {
|
||||||
|
const previousPhase = this.phase(phase);
|
||||||
|
try {
|
||||||
|
return fn();
|
||||||
|
} finally {
|
||||||
|
this.phase(previousPhase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventCount(counter: PerfEvent, incrementBy: number = 1): void {
|
||||||
|
this.counters[counter] += incrementBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current performance metrics as a serializable object.
|
||||||
|
*/
|
||||||
|
finalize(): PerfResults {
|
||||||
|
// Track the last segment of time spent in `this.currentPhase` in the time array.
|
||||||
|
this.phase(PerfPhase.Unaccounted);
|
||||||
|
|
||||||
|
const results: PerfResults = {
|
||||||
|
events: {},
|
||||||
|
phases: {},
|
||||||
|
memory: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < this.phaseTime.length; i++) {
|
||||||
|
if (this.phaseTime[i] > 0) {
|
||||||
|
results.phases[PerfPhase[i]] = this.phaseTime[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.phaseTime.length; i++) {
|
||||||
|
if (this.counters[i] > 0) {
|
||||||
|
results.events[PerfEvent[i]] = this.counters[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.bytes.length; i++) {
|
||||||
|
if (this.bytes[i] > 0) {
|
||||||
|
results.memory[PerfCheckpoint[i]] = this.bytes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `PerfRecorder` that delegates to a target `PerfRecorder` which can be updated later.
|
||||||
|
*
|
||||||
|
* `DelegatingPerfRecorder` is useful when a compiler class that needs a `PerfRecorder` can outlive
|
||||||
|
* the current compilation. This is true for most compiler classes as resource-only changes reuse
|
||||||
|
* the same `NgCompiler` for a new compilation.
|
||||||
|
*/
|
||||||
|
export class DelegatingPerfRecorder implements PerfRecorder {
|
||||||
|
constructor(public target: PerfRecorder) {}
|
||||||
|
|
||||||
|
eventCount(counter: PerfEvent, incrementBy?: number): void {
|
||||||
|
this.target.eventCount(counter, incrementBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
phase(phase: PerfPhase): PerfPhase {
|
||||||
|
return this.target.phase(phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
inPhase<T>(phase: PerfPhase, fn: () => T): T {
|
||||||
|
// Note: this doesn't delegate to `this.target.inPhase` but instead is implemented manually here
|
||||||
|
// to avoid adding an additional frame of noise to the stack when debugging.
|
||||||
|
const previousPhase = this.target.phase(phase);
|
||||||
|
try {
|
||||||
|
return fn();
|
||||||
|
} finally {
|
||||||
|
this.target.phase(previousPhase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memory(after: PerfCheckpoint): void {
|
||||||
|
this.target.memory(after);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.target.reset();
|
||||||
|
}
|
||||||
|
}
|
@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC 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
|
|
||||||
*/
|
|
||||||
/// <reference types="node" />
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
import {resolve} from '../../file_system';
|
|
||||||
import {DeclarationNode} from '../../reflection';
|
|
||||||
import {PerfRecorder} from './api';
|
|
||||||
import {HrTime, mark, timeSinceInMicros} from './clock';
|
|
||||||
|
|
||||||
export class PerfTracker implements PerfRecorder {
|
|
||||||
private nextSpanId = 1;
|
|
||||||
private log: PerfLogEvent[] = [];
|
|
||||||
|
|
||||||
readonly enabled = true;
|
|
||||||
|
|
||||||
private constructor(private zeroTime: HrTime) {}
|
|
||||||
|
|
||||||
static zeroedToNow(): PerfTracker {
|
|
||||||
return new PerfTracker(mark());
|
|
||||||
}
|
|
||||||
|
|
||||||
mark(name: string, node?: DeclarationNode, category?: string, detail?: string): void {
|
|
||||||
const msg = this.makeLogMessage(PerfLogEventType.MARK, name, node, category, detail, undefined);
|
|
||||||
this.log.push(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
start(name: string, node?: DeclarationNode, category?: string, detail?: string): number {
|
|
||||||
const span = this.nextSpanId++;
|
|
||||||
const msg = this.makeLogMessage(PerfLogEventType.SPAN_OPEN, name, node, category, detail, span);
|
|
||||||
this.log.push(msg);
|
|
||||||
return span;
|
|
||||||
}
|
|
||||||
|
|
||||||
stop(span: number): void {
|
|
||||||
this.log.push({
|
|
||||||
type: PerfLogEventType.SPAN_CLOSE,
|
|
||||||
span,
|
|
||||||
stamp: timeSinceInMicros(this.zeroTime),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private makeLogMessage(
|
|
||||||
type: PerfLogEventType, name: string, node: DeclarationNode|undefined,
|
|
||||||
category: string|undefined, detail: string|undefined, span: number|undefined): PerfLogEvent {
|
|
||||||
const msg: PerfLogEvent = {
|
|
||||||
type,
|
|
||||||
name,
|
|
||||||
stamp: timeSinceInMicros(this.zeroTime),
|
|
||||||
};
|
|
||||||
if (category !== undefined) {
|
|
||||||
msg.category = category;
|
|
||||||
}
|
|
||||||
if (detail !== undefined) {
|
|
||||||
msg.detail = detail;
|
|
||||||
}
|
|
||||||
if (span !== undefined) {
|
|
||||||
msg.span = span;
|
|
||||||
}
|
|
||||||
if (node !== undefined) {
|
|
||||||
msg.file = node.getSourceFile().fileName;
|
|
||||||
if (!ts.isSourceFile(node)) {
|
|
||||||
const name = ts.getNameOfDeclaration(node);
|
|
||||||
if (name !== undefined && ts.isIdentifier(name)) {
|
|
||||||
msg.declaration = name.text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
asJson(): unknown {
|
|
||||||
return this.log;
|
|
||||||
}
|
|
||||||
|
|
||||||
serializeToFile(target: string, host: ts.CompilerHost): void {
|
|
||||||
const json = JSON.stringify(this.log, null, 2);
|
|
||||||
|
|
||||||
if (target.startsWith('ts:')) {
|
|
||||||
target = target.substr('ts:'.length);
|
|
||||||
const outFile = resolve(host.getCurrentDirectory(), target);
|
|
||||||
host.writeFile(outFile, json, false);
|
|
||||||
} else {
|
|
||||||
const outFile = resolve(host.getCurrentDirectory(), target);
|
|
||||||
fs.writeFileSync(outFile, json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PerfLogEvent {
|
|
||||||
name?: string;
|
|
||||||
span?: number;
|
|
||||||
file?: string;
|
|
||||||
declaration?: string;
|
|
||||||
type: PerfLogEventType;
|
|
||||||
category?: string;
|
|
||||||
detail?: string;
|
|
||||||
stamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PerfLogEventType {
|
|
||||||
SPAN_OPEN,
|
|
||||||
SPAN_CLOSE,
|
|
||||||
MARK,
|
|
||||||
}
|
|
@ -14,10 +14,10 @@ import {verifySupportedTypeScriptVersion} from '../typescript_support';
|
|||||||
|
|
||||||
import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, NgCompilerHost} from './core';
|
import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, NgCompilerHost} from './core';
|
||||||
import {NgCompilerOptions} from './core/api';
|
import {NgCompilerOptions} from './core/api';
|
||||||
import {absoluteFrom, AbsoluteFsPath} from './file_system';
|
import {absoluteFrom, AbsoluteFsPath, getFileSystem} from './file_system';
|
||||||
import {TrackedIncrementalBuildStrategy} from './incremental';
|
import {TrackedIncrementalBuildStrategy} from './incremental';
|
||||||
import {IndexedComponent} from './indexer';
|
import {IndexedComponent} from './indexer';
|
||||||
import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
|
import {ActivePerfRecorder, PerfCheckpoint as PerfCheckpoint, PerfEvent, PerfPhase} from './perf';
|
||||||
import {DeclarationNode} from './reflection';
|
import {DeclarationNode} from './reflection';
|
||||||
import {retagAllTsFiles, untagAllTsFiles} from './shims';
|
import {retagAllTsFiles, untagAllTsFiles} from './shims';
|
||||||
import {ReusedProgramStrategy} from './typecheck';
|
import {ReusedProgramStrategy} from './typecheck';
|
||||||
@ -54,22 +54,20 @@ export class NgtscProgram implements api.Program {
|
|||||||
private reuseTsProgram: ts.Program;
|
private reuseTsProgram: ts.Program;
|
||||||
private closureCompilerEnabled: boolean;
|
private closureCompilerEnabled: boolean;
|
||||||
private host: NgCompilerHost;
|
private host: NgCompilerHost;
|
||||||
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER;
|
|
||||||
private perfTracker: PerfTracker|null = null;
|
|
||||||
private incrementalStrategy: TrackedIncrementalBuildStrategy;
|
private incrementalStrategy: TrackedIncrementalBuildStrategy;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
rootNames: ReadonlyArray<string>, private options: NgCompilerOptions,
|
rootNames: ReadonlyArray<string>, private options: NgCompilerOptions,
|
||||||
delegateHost: api.CompilerHost, oldProgram?: NgtscProgram) {
|
delegateHost: api.CompilerHost, oldProgram?: NgtscProgram) {
|
||||||
|
const perfRecorder = ActivePerfRecorder.zeroedToNow();
|
||||||
|
|
||||||
|
perfRecorder.phase(PerfPhase.Setup);
|
||||||
|
|
||||||
// First, check whether the current TS version is supported.
|
// First, check whether the current TS version is supported.
|
||||||
if (!options.disableTypeScriptVersionCheck) {
|
if (!options.disableTypeScriptVersionCheck) {
|
||||||
verifySupportedTypeScriptVersion();
|
verifySupportedTypeScriptVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.tracePerformance !== undefined) {
|
|
||||||
this.perfTracker = PerfTracker.zeroedToNow();
|
|
||||||
this.perfRecorder = this.perfTracker;
|
|
||||||
}
|
|
||||||
this.closureCompilerEnabled = !!options.annotateForClosureCompiler;
|
this.closureCompilerEnabled = !!options.annotateForClosureCompiler;
|
||||||
|
|
||||||
const reuseProgram = oldProgram?.reuseTsProgram;
|
const reuseProgram = oldProgram?.reuseTsProgram;
|
||||||
@ -83,9 +81,14 @@ export class NgtscProgram implements api.Program {
|
|||||||
retagAllTsFiles(reuseProgram);
|
retagAllTsFiles(reuseProgram);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tsProgram = ts.createProgram(this.host.inputFiles, options, this.host, reuseProgram);
|
this.tsProgram = perfRecorder.inPhase(
|
||||||
|
PerfPhase.TypeScriptProgramCreate,
|
||||||
|
() => ts.createProgram(this.host.inputFiles, options, this.host, reuseProgram));
|
||||||
this.reuseTsProgram = this.tsProgram;
|
this.reuseTsProgram = this.tsProgram;
|
||||||
|
|
||||||
|
perfRecorder.phase(PerfPhase.Unaccounted);
|
||||||
|
perfRecorder.memory(PerfCheckpoint.TypeScriptProgramCreate);
|
||||||
|
|
||||||
this.host.postProgramCreationCleanup();
|
this.host.postProgramCreationCleanup();
|
||||||
|
|
||||||
// Shim tagging has served its purpose, and tags can now be removed from all `ts.SourceFile`s in
|
// Shim tagging has served its purpose, and tags can now be removed from all `ts.SourceFile`s in
|
||||||
@ -111,7 +114,7 @@ export class NgtscProgram implements api.Program {
|
|||||||
let ticket: CompilationTicket;
|
let ticket: CompilationTicket;
|
||||||
if (oldProgram === undefined) {
|
if (oldProgram === undefined) {
|
||||||
ticket = freshCompilationTicket(
|
ticket = freshCompilationTicket(
|
||||||
this.tsProgram, options, this.incrementalStrategy, reusedProgramStrategy,
|
this.tsProgram, options, this.incrementalStrategy, reusedProgramStrategy, perfRecorder,
|
||||||
/* enableTemplateTypeChecker */ false, /* usePoisonedData */ false);
|
/* enableTemplateTypeChecker */ false, /* usePoisonedData */ false);
|
||||||
} else {
|
} else {
|
||||||
ticket = incrementalFromCompilerTicket(
|
ticket = incrementalFromCompilerTicket(
|
||||||
@ -120,12 +123,13 @@ export class NgtscProgram implements api.Program {
|
|||||||
this.incrementalStrategy,
|
this.incrementalStrategy,
|
||||||
reusedProgramStrategy,
|
reusedProgramStrategy,
|
||||||
modifiedResourceFiles,
|
modifiedResourceFiles,
|
||||||
|
perfRecorder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create the NgCompiler which will drive the rest of the compilation.
|
// Create the NgCompiler which will drive the rest of the compilation.
|
||||||
this.compiler = NgCompiler.fromTicket(ticket, this.host, this.perfRecorder);
|
this.compiler = NgCompiler.fromTicket(ticket, this.host);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTsProgram(): ts.Program {
|
getTsProgram(): ts.Program {
|
||||||
@ -138,49 +142,59 @@ export class NgtscProgram implements api.Program {
|
|||||||
|
|
||||||
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||||
undefined): readonly ts.Diagnostic[] {
|
undefined): readonly ts.Diagnostic[] {
|
||||||
return this.tsProgram.getOptionsDiagnostics(cancellationToken);
|
return this.compiler.perfRecorder.inPhase(
|
||||||
|
PerfPhase.TypeScriptDiagnostics,
|
||||||
|
() => this.tsProgram.getOptionsDiagnostics(cancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
getTsSyntacticDiagnostics(
|
getTsSyntacticDiagnostics(
|
||||||
sourceFile?: ts.SourceFile|undefined,
|
sourceFile?: ts.SourceFile|undefined,
|
||||||
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
|
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
|
||||||
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
|
||||||
if (sourceFile !== undefined) {
|
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
||||||
if (ignoredFiles.has(sourceFile)) {
|
let res: readonly ts.Diagnostic[];
|
||||||
return [];
|
if (sourceFile !== undefined) {
|
||||||
}
|
if (ignoredFiles.has(sourceFile)) {
|
||||||
|
return [];
|
||||||
return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
|
|
||||||
} else {
|
|
||||||
const diagnostics: ts.Diagnostic[] = [];
|
|
||||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
|
||||||
if (!ignoredFiles.has(sf)) {
|
|
||||||
diagnostics.push(...this.tsProgram.getSyntacticDiagnostics(sf, cancellationToken));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res = this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
|
||||||
|
} else {
|
||||||
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
|
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||||
|
if (!ignoredFiles.has(sf)) {
|
||||||
|
diagnostics.push(...this.tsProgram.getSyntacticDiagnostics(sf, cancellationToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = diagnostics;
|
||||||
}
|
}
|
||||||
return diagnostics;
|
return res;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTsSemanticDiagnostics(
|
getTsSemanticDiagnostics(
|
||||||
sourceFile?: ts.SourceFile|undefined,
|
sourceFile?: ts.SourceFile|undefined,
|
||||||
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
|
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
|
||||||
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
|
||||||
if (sourceFile !== undefined) {
|
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
||||||
if (ignoredFiles.has(sourceFile)) {
|
let res: readonly ts.Diagnostic[];
|
||||||
return [];
|
if (sourceFile !== undefined) {
|
||||||
}
|
if (ignoredFiles.has(sourceFile)) {
|
||||||
|
return [];
|
||||||
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
|
|
||||||
} else {
|
|
||||||
const diagnostics: ts.Diagnostic[] = [];
|
|
||||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
|
||||||
if (!ignoredFiles.has(sf)) {
|
|
||||||
diagnostics.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res = this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||||
|
} else {
|
||||||
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
|
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||||
|
if (!ignoredFiles.has(sf)) {
|
||||||
|
diagnostics.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = diagnostics;
|
||||||
}
|
}
|
||||||
return diagnostics;
|
return res;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||||
@ -235,73 +249,82 @@ export class NgtscProgram implements api.Program {
|
|||||||
emitCallback?: api.TsEmitCallback | undefined;
|
emitCallback?: api.TsEmitCallback | undefined;
|
||||||
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback | undefined;
|
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback | undefined;
|
||||||
}|undefined): ts.EmitResult {
|
}|undefined): ts.EmitResult {
|
||||||
const {transformers} = this.compiler.prepareEmit();
|
this.compiler.perfRecorder.memory(PerfCheckpoint.PreEmit);
|
||||||
const ignoreFiles = this.compiler.ignoreForEmit;
|
|
||||||
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
|
||||||
|
|
||||||
const writeFile: ts.WriteFileCallback =
|
const res = this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptEmit, () => {
|
||||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
const {transformers} = this.compiler.prepareEmit();
|
||||||
onError: ((message: string) => void)|undefined,
|
const ignoreFiles = this.compiler.ignoreForEmit;
|
||||||
sourceFiles: ReadonlyArray<ts.SourceFile>|undefined) => {
|
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
||||||
if (sourceFiles !== undefined) {
|
|
||||||
// Record successful writes for any `ts.SourceFile` (that's not a declaration file)
|
const writeFile: ts.WriteFileCallback =
|
||||||
// that's an input to this write.
|
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||||
for (const writtenSf of sourceFiles) {
|
onError: ((message: string) => void)|undefined,
|
||||||
if (writtenSf.isDeclarationFile) {
|
sourceFiles: ReadonlyArray<ts.SourceFile>|undefined) => {
|
||||||
continue;
|
if (sourceFiles !== undefined) {
|
||||||
|
// Record successful writes for any `ts.SourceFile` (that's not a declaration file)
|
||||||
|
// that's an input to this write.
|
||||||
|
for (const writtenSf of sourceFiles) {
|
||||||
|
if (writtenSf.isDeclarationFile) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compiler.incrementalDriver.recordSuccessfulEmit(writtenSf);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.compiler.incrementalDriver.recordSuccessfulEmit(writtenSf);
|
|
||||||
}
|
}
|
||||||
}
|
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||||
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const customTransforms = opts && opts.customTransformers;
|
const customTransforms = opts && opts.customTransformers;
|
||||||
const beforeTransforms = transformers.before || [];
|
const beforeTransforms = transformers.before || [];
|
||||||
const afterDeclarationsTransforms = transformers.afterDeclarations;
|
const afterDeclarationsTransforms = transformers.afterDeclarations;
|
||||||
|
|
||||||
if (customTransforms !== undefined && customTransforms.beforeTs !== undefined) {
|
if (customTransforms !== undefined && customTransforms.beforeTs !== undefined) {
|
||||||
beforeTransforms.push(...customTransforms.beforeTs);
|
beforeTransforms.push(...customTransforms.beforeTs);
|
||||||
}
|
|
||||||
|
|
||||||
const emitSpan = this.perfRecorder.start('emit');
|
|
||||||
const emitResults: ts.EmitResult[] = [];
|
|
||||||
|
|
||||||
for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
|
|
||||||
if (targetSourceFile.isDeclarationFile || ignoreFiles.has(targetSourceFile)) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.compiler.incrementalDriver.safeToSkipEmit(targetSourceFile)) {
|
const emitResults: ts.EmitResult[] = [];
|
||||||
continue;
|
|
||||||
|
for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
|
||||||
|
if (targetSourceFile.isDeclarationFile || ignoreFiles.has(targetSourceFile)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.compiler.incrementalDriver.safeToSkipEmit(targetSourceFile)) {
|
||||||
|
this.compiler.perfRecorder.eventCount(PerfEvent.EmitSkipSourceFile);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compiler.perfRecorder.eventCount(PerfEvent.EmitSourceFile);
|
||||||
|
|
||||||
|
emitResults.push(emitCallback({
|
||||||
|
targetSourceFile,
|
||||||
|
program: this.tsProgram,
|
||||||
|
host: this.host,
|
||||||
|
options: this.options,
|
||||||
|
emitOnlyDtsFiles: false,
|
||||||
|
writeFile,
|
||||||
|
customTransformers: {
|
||||||
|
before: beforeTransforms,
|
||||||
|
after: customTransforms && customTransforms.afterTs,
|
||||||
|
afterDeclarations: afterDeclarationsTransforms,
|
||||||
|
} as any,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileEmitSpan = this.perfRecorder.start('emitFile', targetSourceFile);
|
this.compiler.perfRecorder.memory(PerfCheckpoint.Emit);
|
||||||
emitResults.push(emitCallback({
|
|
||||||
targetSourceFile,
|
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in
|
||||||
program: this.tsProgram,
|
// code.
|
||||||
host: this.host,
|
return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
|
||||||
options: this.options,
|
});
|
||||||
emitOnlyDtsFiles: false,
|
|
||||||
writeFile,
|
// Record performance analysis information to disk if we've been asked to do so.
|
||||||
customTransformers: {
|
if (this.options.tracePerformance !== undefined) {
|
||||||
before: beforeTransforms,
|
const perf = this.compiler.perfRecorder.finalize();
|
||||||
after: customTransforms && customTransforms.afterTs,
|
getFileSystem().writeFile(
|
||||||
afterDeclarations: afterDeclarationsTransforms,
|
getFileSystem().resolve(this.options.tracePerformance), JSON.stringify(perf, null, 2));
|
||||||
} as any,
|
|
||||||
}));
|
|
||||||
this.perfRecorder.stop(fileEmitSpan);
|
|
||||||
}
|
}
|
||||||
|
return res;
|
||||||
this.perfRecorder.stop(emitSpan);
|
|
||||||
|
|
||||||
if (this.perfTracker !== null && this.options.tracePerformance !== undefined) {
|
|
||||||
this.perfTracker.serializeToFile(this.options.tracePerformance, this.host);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in code.
|
|
||||||
return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getIndexedComponents(): Map<DeclarationNode, IndexedComponent> {
|
getIndexedComponents(): Map<DeclarationNode, IndexedComponent> {
|
||||||
|
@ -13,7 +13,7 @@ import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
|||||||
import {IncrementalBuild} from '../../incremental/api';
|
import {IncrementalBuild} from '../../incremental/api';
|
||||||
import {SemanticDepGraphUpdater, SemanticSymbol} from '../../incremental/semantic_graph';
|
import {SemanticDepGraphUpdater, SemanticSymbol} from '../../incremental/semantic_graph';
|
||||||
import {IndexingContext} from '../../indexer';
|
import {IndexingContext} from '../../indexer';
|
||||||
import {PerfRecorder} from '../../perf';
|
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||||
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost} from '../../reflection';
|
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost} from '../../reflection';
|
||||||
import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck/api';
|
import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck/api';
|
||||||
import {getSourceFile, isExported} from '../../util/src/typescript';
|
import {getSourceFile, isExported} from '../../util/src/typescript';
|
||||||
@ -124,6 +124,9 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||||||
this.adopt(priorRecord);
|
this.adopt(priorRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.perf.eventCount(PerfEvent.SourceFileReuseAnalysis);
|
||||||
|
this.perf.eventCount(PerfEvent.TraitReuseAnalysis, priorWork.length);
|
||||||
|
|
||||||
// Skip the rest of analysis, as this file's prior traits are being reused.
|
// Skip the rest of analysis, as this file's prior traits are being reused.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -359,6 +362,8 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||||||
TraitState[trait.state]} (expected DETECTED)`);
|
TraitState[trait.state]} (expected DETECTED)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.perf.eventCount(PerfEvent.TraitAnalyze);
|
||||||
|
|
||||||
// Attempt analysis. This could fail with a `FatalDiagnosticError`; catch it if it does.
|
// Attempt analysis. This could fail with a `FatalDiagnosticError`; catch it if it does.
|
||||||
let result: AnalysisOutput<unknown>;
|
let result: AnalysisOutput<unknown>;
|
||||||
try {
|
try {
|
||||||
@ -509,9 +514,6 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const compileSpan = this.perf.start('compileClass', original);
|
|
||||||
|
|
||||||
|
|
||||||
// `trait.resolution` is non-null asserted here because TypeScript does not recognize that
|
// `trait.resolution` is non-null asserted here because TypeScript does not recognize that
|
||||||
// `Readonly<unknown>` is nullable (as `unknown` itself is nullable) due to the way that
|
// `Readonly<unknown>` is nullable (as `unknown` itself is nullable) due to the way that
|
||||||
// `Readonly` works.
|
// `Readonly` works.
|
||||||
@ -526,7 +528,6 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const compileMatchRes = compileRes;
|
const compileMatchRes = compileRes;
|
||||||
this.perf.stop(compileSpan);
|
|
||||||
if (Array.isArray(compileMatchRes)) {
|
if (Array.isArray(compileMatchRes)) {
|
||||||
for (const result of compileMatchRes) {
|
for (const result of compileMatchRes) {
|
||||||
if (!res.some(r => r.name === result.name)) {
|
if (!res.some(r => r.name === result.name)) {
|
||||||
|
@ -10,6 +10,7 @@ import {ConstantPool} from '@angular/compiler';
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {DefaultImportRecorder, ImportRewriter} from '../../imports';
|
import {DefaultImportRecorder, ImportRewriter} from '../../imports';
|
||||||
|
import {PerfPhase, PerfRecorder} from '../../perf';
|
||||||
import {Decorator, ReflectionHost} from '../../reflection';
|
import {Decorator, ReflectionHost} from '../../reflection';
|
||||||
import {ImportManager, RecordWrappedNodeExprFn, translateExpression, translateStatement, TranslatorOptions} from '../../translator';
|
import {ImportManager, RecordWrappedNodeExprFn, translateExpression, translateStatement, TranslatorOptions} from '../../translator';
|
||||||
import {visit, VisitListEntryResult, Visitor} from '../../util/src/visitor';
|
import {visit, VisitListEntryResult, Visitor} from '../../util/src/visitor';
|
||||||
@ -33,14 +34,16 @@ interface FileOverviewMeta {
|
|||||||
|
|
||||||
export function ivyTransformFactory(
|
export function ivyTransformFactory(
|
||||||
compilation: TraitCompiler, reflector: ReflectionHost, importRewriter: ImportRewriter,
|
compilation: TraitCompiler, reflector: ReflectionHost, importRewriter: ImportRewriter,
|
||||||
defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
defaultImportRecorder: DefaultImportRecorder, perf: PerfRecorder, isCore: boolean,
|
||||||
isClosureCompilerEnabled: boolean): ts.TransformerFactory<ts.SourceFile> {
|
isClosureCompilerEnabled: boolean): ts.TransformerFactory<ts.SourceFile> {
|
||||||
const recordWrappedNodeExpr = createRecorderFn(defaultImportRecorder);
|
const recordWrappedNodeExpr = createRecorderFn(defaultImportRecorder);
|
||||||
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
||||||
return (file: ts.SourceFile): ts.SourceFile => {
|
return (file: ts.SourceFile): ts.SourceFile => {
|
||||||
return transformIvySourceFile(
|
return perf.inPhase(
|
||||||
compilation, context, reflector, importRewriter, file, isCore, isClosureCompilerEnabled,
|
PerfPhase.Compile,
|
||||||
recordWrappedNodeExpr);
|
() => transformIvySourceFile(
|
||||||
|
compilation, context, reflector, importRewriter, file, isCore,
|
||||||
|
isClosureCompilerEnabled, recordWrappedNodeExpr));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import {CompilationTicket, freshCompilationTicket, incrementalFromDriverTicket,
|
|||||||
import {NgCompilerOptions, UnifiedModulesHost} from './core/api';
|
import {NgCompilerOptions, UnifiedModulesHost} from './core/api';
|
||||||
import {NodeJSFileSystem, setFileSystem} from './file_system';
|
import {NodeJSFileSystem, setFileSystem} from './file_system';
|
||||||
import {PatchedProgramIncrementalBuildStrategy} from './incremental';
|
import {PatchedProgramIncrementalBuildStrategy} from './incremental';
|
||||||
import {NOOP_PERF_RECORDER} from './perf';
|
import {ActivePerfRecorder, NOOP_PERF_RECORDER, PerfPhase} from './perf';
|
||||||
import {untagAllTsFiles} from './shims';
|
import {untagAllTsFiles} from './shims';
|
||||||
import {OptimizeFor} from './typecheck/api';
|
import {OptimizeFor} from './typecheck/api';
|
||||||
import {ReusedProgramStrategy} from './typecheck/src/augmented_program';
|
import {ReusedProgramStrategy} from './typecheck/src/augmented_program';
|
||||||
@ -94,6 +94,13 @@ export class NgTscPlugin implements TscPlugin {
|
|||||||
ignoreForDiagnostics: Set<ts.SourceFile>,
|
ignoreForDiagnostics: Set<ts.SourceFile>,
|
||||||
ignoreForEmit: Set<ts.SourceFile>,
|
ignoreForEmit: Set<ts.SourceFile>,
|
||||||
} {
|
} {
|
||||||
|
// TODO(alxhub): we provide a `PerfRecorder` to the compiler, but because we're not driving the
|
||||||
|
// compilation, the information captured within it is incomplete, and may not include timings
|
||||||
|
// for phases such as emit.
|
||||||
|
//
|
||||||
|
// Additionally, nothing actually captures the perf results here, so recording stats at all is
|
||||||
|
// somewhat moot for now :)
|
||||||
|
const perfRecorder = ActivePerfRecorder.zeroedToNow();
|
||||||
if (this.host === null || this.options === null) {
|
if (this.host === null || this.options === null) {
|
||||||
throw new Error('Lifecycle error: setupCompilation() before wrapHost().');
|
throw new Error('Lifecycle error: setupCompilation() before wrapHost().');
|
||||||
}
|
}
|
||||||
@ -115,15 +122,15 @@ export class NgTscPlugin implements TscPlugin {
|
|||||||
|
|
||||||
if (oldProgram === undefined || oldDriver === null) {
|
if (oldProgram === undefined || oldDriver === null) {
|
||||||
ticket = freshCompilationTicket(
|
ticket = freshCompilationTicket(
|
||||||
program, this.options, strategy, typeCheckStrategy,
|
program, this.options, strategy, typeCheckStrategy, perfRecorder,
|
||||||
/* enableTemplateTypeChecker */ false, /* usePoisonedData */ false);
|
/* enableTemplateTypeChecker */ false, /* usePoisonedData */ false);
|
||||||
} else {
|
} else {
|
||||||
strategy.toNextBuildStrategy().getIncrementalDriver(oldProgram);
|
strategy.toNextBuildStrategy().getIncrementalDriver(oldProgram);
|
||||||
ticket = incrementalFromDriverTicket(
|
ticket = incrementalFromDriverTicket(
|
||||||
oldProgram, oldDriver, program, this.options, strategy, typeCheckStrategy,
|
oldProgram, oldDriver, program, this.options, strategy, typeCheckStrategy,
|
||||||
modifiedResourceFiles, false, false);
|
modifiedResourceFiles, perfRecorder, false, false);
|
||||||
}
|
}
|
||||||
this._compiler = NgCompiler.fromTicket(ticket, this.host, NOOP_PERF_RECORDER);
|
this._compiler = NgCompiler.fromTicket(ticket, this.host);
|
||||||
return {
|
return {
|
||||||
ignoreForDiagnostics: this._compiler.ignoreForDiagnostics,
|
ignoreForDiagnostics: this._compiler.ignoreForDiagnostics,
|
||||||
ignoreForEmit: this._compiler.ignoreForEmit,
|
ignoreForEmit: this._compiler.ignoreForEmit,
|
||||||
@ -146,6 +153,9 @@ export class NgTscPlugin implements TscPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createTransformers(): ts.CustomTransformers {
|
createTransformers(): ts.CustomTransformers {
|
||||||
|
// The plugin consumer doesn't know about our perf tracing system, so we consider the emit phase
|
||||||
|
// as beginning now.
|
||||||
|
this.compiler.perfRecorder.phase(PerfPhase.TypeScriptEmit);
|
||||||
return this.compiler.prepareEmit().transformers;
|
return this.compiler.prepareEmit().transformers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/imports",
|
"//packages/compiler-cli/src/ngtsc/imports",
|
||||||
"//packages/compiler-cli/src/ngtsc/incremental:api",
|
"//packages/compiler-cli/src/ngtsc/incremental:api",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||||
|
"//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/scope",
|
||||||
"//packages/compiler-cli/src/ngtsc/shims",
|
"//packages/compiler-cli/src/ngtsc/shims",
|
||||||
|
@ -12,6 +12,7 @@ import * as ts from 'typescript';
|
|||||||
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
|
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
|
||||||
import {Reference, ReferenceEmitter} from '../../imports';
|
import {Reference, ReferenceEmitter} from '../../imports';
|
||||||
import {IncrementalBuild} from '../../incremental/api';
|
import {IncrementalBuild} from '../../incremental/api';
|
||||||
|
import {PerfCheckpoint, PerfEvent, PerfPhase, PerfRecorder} from '../../perf';
|
||||||
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
|
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
|
||||||
import {ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope';
|
import {ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope';
|
||||||
import {isShim} from '../../shims';
|
import {isShim} from '../../shims';
|
||||||
@ -82,7 +83,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
|
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
|
||||||
private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>,
|
private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>,
|
||||||
private readonly componentScopeReader: ComponentScopeReader,
|
private readonly componentScopeReader: ComponentScopeReader,
|
||||||
private readonly typeCheckScopeRegistry: TypeCheckScopeRegistry) {}
|
private readonly typeCheckScopeRegistry: TypeCheckScopeRegistry,
|
||||||
|
private readonly perf: PerfRecorder) {}
|
||||||
|
|
||||||
getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null {
|
getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null {
|
||||||
const {data} = this.getLatestComponentState(component);
|
const {data} = this.getLatestComponentState(component);
|
||||||
@ -181,19 +183,60 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sfPath = absoluteFromSourceFile(sf);
|
return this.perf.inPhase(PerfPhase.TtcDiagnostics, () => {
|
||||||
const fileRecord = this.state.get(sfPath)!;
|
const sfPath = absoluteFromSourceFile(sf);
|
||||||
|
const fileRecord = this.state.get(sfPath)!;
|
||||||
|
|
||||||
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
|
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
|
||||||
|
|
||||||
const diagnostics: (ts.Diagnostic|null)[] = [];
|
const diagnostics: (ts.Diagnostic|null)[] = [];
|
||||||
if (fileRecord.hasInlines) {
|
if (fileRecord.hasInlines) {
|
||||||
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
|
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
|
||||||
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
|
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
|
||||||
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const [shimPath, shimRecord] of fileRecord.shimData) {
|
||||||
|
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
|
||||||
|
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
|
||||||
|
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
||||||
|
diagnostics.push(...shimRecord.genesisDiagnostics);
|
||||||
|
|
||||||
|
for (const templateData of shimRecord.templates.values()) {
|
||||||
|
diagnostics.push(...templateData.templateDiagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[] {
|
||||||
|
this.ensureShimForComponent(component);
|
||||||
|
|
||||||
|
return this.perf.inPhase(PerfPhase.TtcDiagnostics, () => {
|
||||||
|
const sf = component.getSourceFile();
|
||||||
|
const sfPath = absoluteFromSourceFile(sf);
|
||||||
|
const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
|
||||||
|
|
||||||
|
const fileRecord = this.getFileData(sfPath);
|
||||||
|
|
||||||
|
if (!fileRecord.shimData.has(shimPath)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateId = fileRecord.sourceManager.getTemplateId(component);
|
||||||
|
const shimRecord = fileRecord.shimData.get(shimPath)!;
|
||||||
|
|
||||||
|
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
|
||||||
|
|
||||||
|
const diagnostics: (TemplateDiagnostic|null)[] = [];
|
||||||
|
if (shimRecord.hasInlines) {
|
||||||
|
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
|
||||||
|
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
|
||||||
|
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
||||||
|
}
|
||||||
|
|
||||||
for (const [shimPath, shimRecord] of fileRecord.shimData) {
|
|
||||||
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
|
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
|
||||||
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
|
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
|
||||||
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
||||||
@ -202,48 +245,11 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
for (const templateData of shimRecord.templates.values()) {
|
for (const templateData of shimRecord.templates.values()) {
|
||||||
diagnostics.push(...templateData.templateDiagnostics);
|
diagnostics.push(...templateData.templateDiagnostics);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null);
|
return diagnostics.filter(
|
||||||
}
|
(diag: TemplateDiagnostic|null): diag is TemplateDiagnostic =>
|
||||||
|
diag !== null && diag.templateId === templateId);
|
||||||
getDiagnosticsForComponent(component: ts.ClassDeclaration): ts.Diagnostic[] {
|
});
|
||||||
this.ensureShimForComponent(component);
|
|
||||||
|
|
||||||
const sf = component.getSourceFile();
|
|
||||||
const sfPath = absoluteFromSourceFile(sf);
|
|
||||||
const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
|
|
||||||
|
|
||||||
const fileRecord = this.getFileData(sfPath);
|
|
||||||
|
|
||||||
if (!fileRecord.shimData.has(shimPath)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateId = fileRecord.sourceManager.getTemplateId(component);
|
|
||||||
const shimRecord = fileRecord.shimData.get(shimPath)!;
|
|
||||||
|
|
||||||
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
|
|
||||||
|
|
||||||
const diagnostics: (TemplateDiagnostic|null)[] = [];
|
|
||||||
if (shimRecord.hasInlines) {
|
|
||||||
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
|
|
||||||
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
|
|
||||||
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
|
|
||||||
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
|
|
||||||
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
|
||||||
diagnostics.push(...shimRecord.genesisDiagnostics);
|
|
||||||
|
|
||||||
for (const templateData of shimRecord.templates.values()) {
|
|
||||||
diagnostics.push(...templateData.templateDiagnostics);
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics.filter(
|
|
||||||
(diag: TemplateDiagnostic|null): diag is TemplateDiagnostic =>
|
|
||||||
diag !== null && diag.templateId === templateId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTypeCheckBlock(component: ts.ClassDeclaration): ts.Node|null {
|
getTypeCheckBlock(component: ts.ClassDeclaration): ts.Node|null {
|
||||||
@ -256,7 +262,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
if (engine === null) {
|
if (engine === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return engine.getGlobalCompletions(context);
|
return this.perf.inPhase(
|
||||||
|
PerfPhase.TtcAutocompletion, () => engine.getGlobalCompletions(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
getExpressionCompletionLocation(
|
getExpressionCompletionLocation(
|
||||||
@ -266,7 +273,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
if (engine === null) {
|
if (engine === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return engine.getExpressionCompletionLocation(ast);
|
return this.perf.inPhase(
|
||||||
|
PerfPhase.TtcAutocompletion, () => engine.getExpressionCompletionLocation(ast));
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateClass(clazz: ts.ClassDeclaration): void {
|
invalidateClass(clazz: ts.ClassDeclaration): void {
|
||||||
@ -318,6 +326,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.perf.eventCount(PerfEvent.ReuseTypeCheckFile);
|
||||||
this.state.set(sfPath, previousResults);
|
this.state.set(sfPath, previousResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,50 +335,55 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const host = new WholeProgramTypeCheckingHost(this);
|
this.perf.inPhase(PerfPhase.TcbGeneration, () => {
|
||||||
const ctx = this.newContext(host);
|
const host = new WholeProgramTypeCheckingHost(this);
|
||||||
|
const ctx = this.newContext(host);
|
||||||
|
|
||||||
for (const sf of this.originalProgram.getSourceFiles()) {
|
for (const sf of this.originalProgram.getSourceFiles()) {
|
||||||
if (sf.isDeclarationFile || isShim(sf)) {
|
if (sf.isDeclarationFile || isShim(sf)) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.maybeAdoptPriorResultsForFile(sf);
|
||||||
|
|
||||||
|
const sfPath = absoluteFromSourceFile(sf);
|
||||||
|
const fileData = this.getFileData(sfPath);
|
||||||
|
if (fileData.isComplete) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.typeCheckAdapter.typeCheck(sf, ctx);
|
||||||
|
|
||||||
|
fileData.isComplete = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateFromContext(ctx);
|
||||||
|
this.isComplete = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureAllShimsForOneFile(sf: ts.SourceFile): void {
|
||||||
|
this.perf.inPhase(PerfPhase.TcbGeneration, () => {
|
||||||
this.maybeAdoptPriorResultsForFile(sf);
|
this.maybeAdoptPriorResultsForFile(sf);
|
||||||
|
|
||||||
const sfPath = absoluteFromSourceFile(sf);
|
const sfPath = absoluteFromSourceFile(sf);
|
||||||
|
|
||||||
const fileData = this.getFileData(sfPath);
|
const fileData = this.getFileData(sfPath);
|
||||||
if (fileData.isComplete) {
|
if (fileData.isComplete) {
|
||||||
continue;
|
// All data for this file is present and accounted for already.
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const host =
|
||||||
|
new SingleFileTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this);
|
||||||
|
const ctx = this.newContext(host);
|
||||||
|
|
||||||
this.typeCheckAdapter.typeCheck(sf, ctx);
|
this.typeCheckAdapter.typeCheck(sf, ctx);
|
||||||
|
|
||||||
fileData.isComplete = true;
|
fileData.isComplete = true;
|
||||||
}
|
|
||||||
|
|
||||||
this.updateFromContext(ctx);
|
this.updateFromContext(ctx);
|
||||||
this.isComplete = true;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
private ensureAllShimsForOneFile(sf: ts.SourceFile): void {
|
|
||||||
this.maybeAdoptPriorResultsForFile(sf);
|
|
||||||
|
|
||||||
const sfPath = absoluteFromSourceFile(sf);
|
|
||||||
|
|
||||||
const fileData = this.getFileData(sfPath);
|
|
||||||
if (fileData.isComplete) {
|
|
||||||
// All data for this file is present and accounted for already.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const host = new SingleFileTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this);
|
|
||||||
const ctx = this.newContext(host);
|
|
||||||
|
|
||||||
this.typeCheckAdapter.typeCheck(sf, ctx);
|
|
||||||
|
|
||||||
fileData.isComplete = true;
|
|
||||||
|
|
||||||
this.updateFromContext(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureShimForComponent(component: ts.ClassDeclaration): void {
|
private ensureShimForComponent(component: ts.ClassDeclaration): void {
|
||||||
@ -399,7 +413,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
InliningMode.Error;
|
InliningMode.Error;
|
||||||
return new TypeCheckContextImpl(
|
return new TypeCheckContextImpl(
|
||||||
this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector,
|
this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector,
|
||||||
host, inlining);
|
host, inlining, this.perf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -428,8 +442,14 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
|
|
||||||
private updateFromContext(ctx: TypeCheckContextImpl): void {
|
private updateFromContext(ctx: TypeCheckContextImpl): void {
|
||||||
const updates = ctx.finalize();
|
const updates = ctx.finalize();
|
||||||
this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental);
|
return this.perf.inPhase(PerfPhase.TcbUpdateProgram, () => {
|
||||||
this.priorBuild.recordSuccessfulTypeCheck(this.state);
|
if (updates.size > 0) {
|
||||||
|
this.perf.eventCount(PerfEvent.UpdateTypeCheckProgram);
|
||||||
|
}
|
||||||
|
this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental);
|
||||||
|
this.priorBuild.recordSuccessfulTypeCheck(this.state);
|
||||||
|
this.perf.memory(PerfCheckpoint.TtcUpdateProgram);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileData(path: AbsoluteFsPath): FileTypeCheckingData {
|
getFileData(path: AbsoluteFsPath): FileTypeCheckingData {
|
||||||
@ -450,7 +470,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
if (builder === null) {
|
if (builder === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return builder.getSymbol(node);
|
return this.perf.inPhase(PerfPhase.TtcSymbol, () => builder.getSymbol(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getOrCreateSymbolBuilder(component: ts.ClassDeclaration): SymbolBuilder|null {
|
private getOrCreateSymbolBuilder(component: ts.ClassDeclaration): SymbolBuilder|null {
|
||||||
|
@ -12,6 +12,7 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
||||||
import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
|
import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
|
||||||
|
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||||
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
||||||
import {ImportManager} from '../../translator';
|
import {ImportManager} from '../../translator';
|
||||||
import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api';
|
import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api';
|
||||||
@ -179,7 +180,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
|||||||
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
|
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
|
||||||
private componentMappingStrategy: ComponentToShimMappingStrategy,
|
private componentMappingStrategy: ComponentToShimMappingStrategy,
|
||||||
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost,
|
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost,
|
||||||
private host: TypeCheckingHost, private inlining: InliningMode) {
|
private host: TypeCheckingHost, private inlining: InliningMode, private perf: PerfRecorder) {
|
||||||
if (inlining === InliningMode.Error && config.useInlineTypeConstructors) {
|
if (inlining === InliningMode.Error && config.useInlineTypeConstructors) {
|
||||||
// We cannot use inlining for type checking since this environment does not support it.
|
// We cannot use inlining for type checking since this environment does not support it.
|
||||||
throw new Error(`AssertionError: invalid inlining configuration.`);
|
throw new Error(`AssertionError: invalid inlining configuration.`);
|
||||||
@ -273,6 +274,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
|||||||
shimData.oobRecorder.requiresInlineTcb(templateId, ref.node);
|
shimData.oobRecorder.requiresInlineTcb(templateId, ref.node);
|
||||||
|
|
||||||
// Checking this template would be unsupported, so don't try.
|
// Checking this template would be unsupported, so don't try.
|
||||||
|
this.perf.eventCount(PerfEvent.SkipGenerateTcbNoInline);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,6 +284,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
|||||||
pipes,
|
pipes,
|
||||||
schemas,
|
schemas,
|
||||||
};
|
};
|
||||||
|
this.perf.eventCount(PerfEvent.GenerateTcb);
|
||||||
if (tcbRequiresInline) {
|
if (tcbRequiresInline) {
|
||||||
// This class didn't meet the requirements for external type checking, so generate an inline
|
// This class didn't meet the requirements for external type checking, so generate an inline
|
||||||
// TCB for the class.
|
// TCB for the class.
|
||||||
|
@ -17,6 +17,7 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/imports",
|
"//packages/compiler-cli/src/ngtsc/imports",
|
||||||
"//packages/compiler-cli/src/ngtsc/incremental",
|
"//packages/compiler-cli/src/ngtsc/incremental",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||||
|
"//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/scope",
|
||||||
"//packages/compiler-cli/src/ngtsc/shims",
|
"//packages/compiler-cli/src/ngtsc/shims",
|
||||||
|
@ -14,6 +14,7 @@ import {TestFile} from '../../file_system/testing';
|
|||||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reexport, Reference, ReferenceEmitter, RelativePathStrategy} from '../../imports';
|
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reexport, Reference, ReferenceEmitter, RelativePathStrategy} from '../../imports';
|
||||||
import {NOOP_INCREMENTAL_BUILD} from '../../incremental';
|
import {NOOP_INCREMENTAL_BUILD} from '../../incremental';
|
||||||
import {ClassPropertyMapping, CompoundMetadataReader} from '../../metadata';
|
import {ClassPropertyMapping, CompoundMetadataReader} from '../../metadata';
|
||||||
|
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||||
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {ComponentScopeReader, LocalModuleScope, ScopeData, TypeCheckScopeRegistry} from '../../scope';
|
import {ComponentScopeReader, LocalModuleScope, ScopeData, TypeCheckScopeRegistry} from '../../scope';
|
||||||
import {makeProgram} from '../../testing';
|
import {makeProgram} from '../../testing';
|
||||||
@ -515,7 +516,7 @@ export function setup(targets: TypeCheckingTarget[], overrides: {
|
|||||||
|
|
||||||
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
||||||
program, programStrategy, checkAdapter, fullConfig, emitter, reflectionHost, host,
|
program, programStrategy, checkAdapter, fullConfig, emitter, reflectionHost, host,
|
||||||
NOOP_INCREMENTAL_BUILD, fakeScopeReader, typeCheckScopeRegistry);
|
NOOP_INCREMENTAL_BUILD, fakeScopeReader, typeCheckScopeRegistry, NOOP_PERF_RECORDER);
|
||||||
return {
|
return {
|
||||||
templateTypeChecker,
|
templateTypeChecker,
|
||||||
program,
|
program,
|
||||||
|
@ -10,6 +10,7 @@ import * as ts from 'typescript';
|
|||||||
import {absoluteFrom, AbsoluteFsPath, getFileSystem, getSourceFileOrError, LogicalFileSystem, NgtscCompilerHost} from '../../file_system';
|
import {absoluteFrom, AbsoluteFsPath, getFileSystem, getSourceFileOrError, LogicalFileSystem, NgtscCompilerHost} from '../../file_system';
|
||||||
import {runInEachFileSystem, TestFile} from '../../file_system/testing';
|
import {runInEachFileSystem, TestFile} from '../../file_system/testing';
|
||||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||||
|
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {getDeclaration, makeProgram} from '../../testing';
|
import {getDeclaration, makeProgram} from '../../testing';
|
||||||
import {getRootDirs} from '../../util/src/typescript';
|
import {getRootDirs} from '../../util/src/typescript';
|
||||||
@ -74,7 +75,7 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||||||
]);
|
]);
|
||||||
const ctx = new TypeCheckContextImpl(
|
const ctx = new TypeCheckContextImpl(
|
||||||
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
|
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
|
||||||
new TestTypeCheckingHost(), InliningMode.InlineOps);
|
new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER);
|
||||||
const TestClass =
|
const TestClass =
|
||||||
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||||
const pendingFile = makePendingFile();
|
const pendingFile = makePendingFile();
|
||||||
@ -113,7 +114,7 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||||||
const pendingFile = makePendingFile();
|
const pendingFile = makePendingFile();
|
||||||
const ctx = new TypeCheckContextImpl(
|
const ctx = new TypeCheckContextImpl(
|
||||||
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
|
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
|
||||||
new TestTypeCheckingHost(), InliningMode.InlineOps);
|
new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER);
|
||||||
const TestClass =
|
const TestClass =
|
||||||
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||||
ctx.addInlineTypeCtor(
|
ctx.addInlineTypeCtor(
|
||||||
@ -158,7 +159,7 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||||||
const pendingFile = makePendingFile();
|
const pendingFile = makePendingFile();
|
||||||
const ctx = new TypeCheckContextImpl(
|
const ctx = new TypeCheckContextImpl(
|
||||||
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
|
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
|
||||||
new TestTypeCheckingHost(), InliningMode.InlineOps);
|
new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER);
|
||||||
const TestClass =
|
const TestClass =
|
||||||
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||||
ctx.addInlineTypeCtor(
|
ctx.addInlineTypeCtor(
|
||||||
|
@ -15,6 +15,7 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/imports",
|
"//packages/compiler-cli/src/ngtsc/imports",
|
||||||
"//packages/compiler-cli/src/ngtsc/incremental",
|
"//packages/compiler-cli/src/ngtsc/incremental",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/perf",
|
||||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||||
"//packages/compiler-cli/src/ngtsc/shims",
|
"//packages/compiler-cli/src/ngtsc/shims",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, resourceChangeTicket} from '@angular/compiler-cli/src/ngtsc/core';
|
import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, resourceChangeTicket} from '@angular/compiler-cli/src/ngtsc/core';
|
||||||
import {NgCompilerOptions} from '@angular/compiler-cli/src/ngtsc/core/api';
|
import {NgCompilerOptions} from '@angular/compiler-cli/src/ngtsc/core/api';
|
||||||
import {TrackedIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental';
|
import {TrackedIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental';
|
||||||
|
import {ActivePerfRecorder} from '@angular/compiler-cli/src/ngtsc/perf';
|
||||||
import {TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
import {TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
||||||
import * as ts from 'typescript/lib/tsserverlibrary';
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
|
||||||
@ -44,6 +45,10 @@ export class CompilerFactory {
|
|||||||
// Only resource files have changed since the last NgCompiler was created.
|
// Only resource files have changed since the last NgCompiler was created.
|
||||||
const ticket = resourceChangeTicket(this.compiler, modifiedResourceFiles);
|
const ticket = resourceChangeTicket(this.compiler, modifiedResourceFiles);
|
||||||
this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
|
this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
|
||||||
|
} else {
|
||||||
|
// The previous NgCompiler is being reused, but we still want to reset its performance
|
||||||
|
// tracker to capture only the operations that are needed to service the current request.
|
||||||
|
this.compiler.perfRecorder.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.compiler;
|
return this.compiler;
|
||||||
@ -52,11 +57,12 @@ export class CompilerFactory {
|
|||||||
let ticket: CompilationTicket;
|
let ticket: CompilationTicket;
|
||||||
if (this.compiler === null || this.lastKnownProgram === null) {
|
if (this.compiler === null || this.lastKnownProgram === null) {
|
||||||
ticket = freshCompilationTicket(
|
ticket = freshCompilationTicket(
|
||||||
program, this.options, this.incrementalStrategy, this.programStrategy, true, true);
|
program, this.options, this.incrementalStrategy, this.programStrategy,
|
||||||
|
/* perfRecorder */ null, true, true);
|
||||||
} else {
|
} else {
|
||||||
ticket = incrementalFromCompilerTicket(
|
ticket = incrementalFromCompilerTicket(
|
||||||
this.compiler, program, this.incrementalStrategy, this.programStrategy,
|
this.compiler, program, this.incrementalStrategy, this.programStrategy,
|
||||||
modifiedResourceFiles);
|
modifiedResourceFiles, /* perfRecorder */ null);
|
||||||
}
|
}
|
||||||
this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
|
this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
|
||||||
this.lastKnownProgram = program;
|
this.lastKnownProgram = program;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import {AbsoluteSourceSpan, AST, BindingPipe, LiteralPrimitive, MethodCall, ParseSourceSpan, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstNode, TmplAstReference, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
|
import {AbsoluteSourceSpan, AST, BindingPipe, LiteralPrimitive, MethodCall, ParseSourceSpan, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstNode, TmplAstReference, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
|
||||||
import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core';
|
import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core';
|
||||||
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
|
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
|
import {PerfPhase} from '@angular/compiler-cli/src/ngtsc/perf';
|
||||||
import {DirectiveSymbol, ShimLocation, SymbolKind, TemplateTypeChecker, TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
import {DirectiveSymbol, ShimLocation, SymbolKind, TemplateTypeChecker, TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
||||||
import {ExpressionIdentifier, hasExpressionIdentifier} from '@angular/compiler-cli/src/ngtsc/typecheck/src/comments';
|
import {ExpressionIdentifier, hasExpressionIdentifier} from '@angular/compiler-cli/src/ngtsc/typecheck/src/comments';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
@ -66,46 +67,53 @@ export class ReferencesAndRenameBuilder {
|
|||||||
|
|
||||||
getRenameInfo(filePath: string, position: number):
|
getRenameInfo(filePath: string, position: number):
|
||||||
Omit<ts.RenameInfoSuccess, 'kind'|'kindModifiers'>|ts.RenameInfoFailure {
|
Omit<ts.RenameInfoSuccess, 'kind'|'kindModifiers'>|ts.RenameInfoFailure {
|
||||||
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
|
||||||
// We could not get a template at position so we assume the request came from outside the
|
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
||||||
// template.
|
// We could not get a template at position so we assume the request came from outside the
|
||||||
if (templateInfo === undefined) {
|
// template.
|
||||||
return this.tsLS.getRenameInfo(filePath, position);
|
if (templateInfo === undefined) {
|
||||||
}
|
return this.tsLS.getRenameInfo(filePath, position);
|
||||||
|
}
|
||||||
|
|
||||||
const allTargetDetails = this.getTargetDetailsAtTemplatePosition(templateInfo, position);
|
const allTargetDetails = this.getTargetDetailsAtTemplatePosition(templateInfo, position);
|
||||||
if (allTargetDetails === null) {
|
if (allTargetDetails === null) {
|
||||||
return {canRename: false, localizedErrorMessage: 'Could not find template node at position.'};
|
return {
|
||||||
}
|
canRename: false,
|
||||||
const {templateTarget} = allTargetDetails[0];
|
localizedErrorMessage: 'Could not find template node at position.',
|
||||||
const templateTextAndSpan = getRenameTextAndSpanAtPosition(templateTarget, position);
|
};
|
||||||
if (templateTextAndSpan === null) {
|
}
|
||||||
return {canRename: false, localizedErrorMessage: 'Could not determine template node text.'};
|
const {templateTarget} = allTargetDetails[0];
|
||||||
}
|
const templateTextAndSpan = getRenameTextAndSpanAtPosition(templateTarget, position);
|
||||||
const {text, span} = templateTextAndSpan;
|
if (templateTextAndSpan === null) {
|
||||||
return {
|
return {canRename: false, localizedErrorMessage: 'Could not determine template node text.'};
|
||||||
canRename: true,
|
}
|
||||||
displayName: text,
|
const {text, span} = templateTextAndSpan;
|
||||||
fullDisplayName: text,
|
return {
|
||||||
triggerSpan: span,
|
canRename: true,
|
||||||
};
|
displayName: text,
|
||||||
|
fullDisplayName: text,
|
||||||
|
triggerSpan: span,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
findRenameLocations(filePath: string, position: number): readonly ts.RenameLocation[]|undefined {
|
findRenameLocations(filePath: string, position: number): readonly ts.RenameLocation[]|undefined {
|
||||||
this.ttc.generateAllTypeCheckBlocks();
|
this.ttc.generateAllTypeCheckBlocks();
|
||||||
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
|
||||||
// We could not get a template at position so we assume the request came from outside the
|
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
||||||
// template.
|
// We could not get a template at position so we assume the request came from outside the
|
||||||
if (templateInfo === undefined) {
|
// template.
|
||||||
const requestNode = this.getTsNodeAtPosition(filePath, position);
|
if (templateInfo === undefined) {
|
||||||
if (requestNode === null) {
|
const requestNode = this.getTsNodeAtPosition(filePath, position);
|
||||||
return undefined;
|
if (requestNode === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const requestOrigin: TypeScriptRequest = {kind: RequestKind.TypeScript, requestNode};
|
||||||
|
return this.findRenameLocationsAtTypescriptPosition(filePath, position, requestOrigin);
|
||||||
}
|
}
|
||||||
const requestOrigin: TypeScriptRequest = {kind: RequestKind.TypeScript, requestNode};
|
|
||||||
return this.findRenameLocationsAtTypescriptPosition(filePath, position, requestOrigin);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.findRenameLocationsAtTemplatePosition(templateInfo, position);
|
return this.findRenameLocationsAtTemplatePosition(templateInfo, position);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private findRenameLocationsAtTemplatePosition(templateInfo: TemplateInfo, position: number):
|
private findRenameLocationsAtTemplatePosition(templateInfo: TemplateInfo, position: number):
|
||||||
@ -148,55 +156,60 @@ export class ReferencesAndRenameBuilder {
|
|||||||
findRenameLocationsAtTypescriptPosition(
|
findRenameLocationsAtTypescriptPosition(
|
||||||
filePath: string, position: number,
|
filePath: string, position: number,
|
||||||
requestOrigin: RequestOrigin): readonly ts.RenameLocation[]|undefined {
|
requestOrigin: RequestOrigin): readonly ts.RenameLocation[]|undefined {
|
||||||
let originalNodeText: string;
|
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
|
||||||
if (requestOrigin.kind === RequestKind.TypeScript) {
|
let originalNodeText: string;
|
||||||
originalNodeText = requestOrigin.requestNode.getText();
|
if (requestOrigin.kind === RequestKind.TypeScript) {
|
||||||
} else {
|
originalNodeText = requestOrigin.requestNode.getText();
|
||||||
const templateNodeText =
|
} else {
|
||||||
getRenameTextAndSpanAtPosition(requestOrigin.requestNode, requestOrigin.position);
|
const templateNodeText =
|
||||||
if (templateNodeText === null) {
|
getRenameTextAndSpanAtPosition(requestOrigin.requestNode, requestOrigin.position);
|
||||||
|
if (templateNodeText === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
originalNodeText = templateNodeText.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
const locations = this.tsLS.findRenameLocations(
|
||||||
|
filePath, position, /*findInStrings*/ false, /*findInComments*/ false);
|
||||||
|
if (locations === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
originalNodeText = templateNodeText.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
const locations = this.tsLS.findRenameLocations(
|
const entries: Map<string, ts.RenameLocation> = new Map();
|
||||||
filePath, position, /*findInStrings*/ false, /*findInComments*/ false);
|
for (const location of locations) {
|
||||||
if (locations === undefined) {
|
// TODO(atscott): Determine if a file is a shim file in a more robust way and make the API
|
||||||
return undefined;
|
// available in an appropriate location.
|
||||||
}
|
if (this.ttc.isTrackedTypeCheckFile(absoluteFrom(location.fileName))) {
|
||||||
|
const entry = this.convertToTemplateDocumentSpan(location, this.ttc, originalNodeText);
|
||||||
const entries: Map<string, ts.RenameLocation> = new Map();
|
// There is no template node whose text matches the original rename request. Bail on
|
||||||
for (const location of locations) {
|
// renaming completely rather than providing incomplete results.
|
||||||
// TODO(atscott): Determine if a file is a shim file in a more robust way and make the API
|
if (entry === null) {
|
||||||
// available in an appropriate location.
|
return undefined;
|
||||||
if (this.ttc.isTrackedTypeCheckFile(absoluteFrom(location.fileName))) {
|
}
|
||||||
const entry = this.convertToTemplateDocumentSpan(location, this.ttc, originalNodeText);
|
entries.set(createLocationKey(entry), entry);
|
||||||
// There is no template node whose text matches the original rename request. Bail on
|
} else {
|
||||||
// renaming completely rather than providing incomplete results.
|
// Ensure we only allow renaming a TS result with matching text
|
||||||
if (entry === null) {
|
const refNode = this.getTsNodeAtPosition(location.fileName, location.textSpan.start);
|
||||||
return undefined;
|
if (refNode === null || refNode.getText() !== originalNodeText) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
entries.set(createLocationKey(location), location);
|
||||||
}
|
}
|
||||||
entries.set(createLocationKey(entry), entry);
|
|
||||||
} else {
|
|
||||||
// Ensure we only allow renaming a TS result with matching text
|
|
||||||
const refNode = this.getTsNodeAtPosition(location.fileName, location.textSpan.start);
|
|
||||||
if (refNode === null || refNode.getText() !== originalNodeText) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
entries.set(createLocationKey(location), location);
|
|
||||||
}
|
}
|
||||||
}
|
return Array.from(entries.values());
|
||||||
return Array.from(entries.values());
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getReferencesAtPosition(filePath: string, position: number): ts.ReferenceEntry[]|undefined {
|
getReferencesAtPosition(filePath: string, position: number): ts.ReferenceEntry[]|undefined {
|
||||||
this.ttc.generateAllTypeCheckBlocks();
|
this.ttc.generateAllTypeCheckBlocks();
|
||||||
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
|
||||||
if (templateInfo === undefined) {
|
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
|
||||||
return this.getReferencesAtTypescriptPosition(filePath, position);
|
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
||||||
}
|
if (templateInfo === undefined) {
|
||||||
return this.getReferencesAtTemplatePosition(templateInfo, position);
|
return this.getReferencesAtTypescriptPosition(filePath, position);
|
||||||
|
}
|
||||||
|
return this.getReferencesAtTemplatePosition(templateInfo, position);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getReferencesAtTemplatePosition(templateInfo: TemplateInfo, position: number):
|
private getReferencesAtTemplatePosition(templateInfo: TemplateInfo, position: number):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user