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 = [
|
||||
"//packages/compiler-cli",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"@npm//@bazel/typescript",
|
||||
"@npm//@types/node",
|
||||
"@npm//tsickle",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
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 * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
@ -515,6 +516,12 @@ function gatherDiagnosticsForInputsOnly(
|
|||
options: ng.CompilerOptions, bazelOpts: BazelOptions,
|
||||
ngProgram: ng.Program): (ng.Diagnostic|ts.Diagnostic)[] {
|
||||
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)[] = [];
|
||||
// These checks mirror ts.getPreEmitDiagnostics, with the important
|
||||
// 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.getSemanticDiagnostics(sf));
|
||||
}
|
||||
|
||||
if (ngProgram instanceof ng.NgtscProgram) {
|
||||
ngProgram.compiler.perfRecorder.phase(previousPhase);
|
||||
}
|
||||
|
||||
if (!diagnostics.length) {
|
||||
// only gather the angular diagnostics if we have no diagnostics
|
||||
// in any other files.
|
||||
|
|
|
@ -22,5 +22,6 @@ export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api'
|
|||
|
||||
export {ngToTsDiagnostic} from './src/transformers/util';
|
||||
export {NgTscPlugin} from './src/ngtsc/tsc_plugin';
|
||||
export {NgtscProgram} from './src/ngtsc/program';
|
||||
|
||||
setFileSystem(new NodeJSFileSystem());
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ConstantPool} from '@angular/compiler';
|
||||
import {NOOP_PERF_RECORDER} from '@angular/compiler-cli/src/ngtsc/perf';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ParsedConfiguration} from '../../..';
|
||||
|
@ -89,7 +90,7 @@ export class DecorationAnalyzer {
|
|||
fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]);
|
||||
evaluator =
|
||||
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);
|
||||
injectableRegistry = new InjectableClassRegistry(this.reflectionHost);
|
||||
typeCheckScopeRegistry = new TypeCheckScopeRegistry(this.scopeRegistry, this.fullMetaReader);
|
||||
|
@ -104,7 +105,8 @@ export class DecorationAnalyzer {
|
|||
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
|
||||
CycleHandlingStrategy.UseRemoteScoping, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||
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.
|
||||
// clang-format off
|
||||
|
@ -117,23 +119,26 @@ export class DecorationAnalyzer {
|
|||
// 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
|
||||
// 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>,
|
||||
// clang-format on
|
||||
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
||||
// before injectable factories (so injectable factories can delegate to them)
|
||||
new PipeDecoratorHandler(
|
||||
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(
|
||||
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(
|
||||
this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
|
||||
this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
|
||||
this.refEmitter,
|
||||
/* 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);
|
||||
migrations: Migration[] = [
|
||||
|
|
|
@ -18,6 +18,7 @@ ts_library(
|
|||
"//packages/compiler-cli/src/ngtsc/indexer",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/routing",
|
||||
"//packages/compiler-cli/src/ngtsc/scope",
|
||||
|
|
|
@ -18,6 +18,7 @@ import {extractSemanticTypeParameters, isArrayEqual, isReferenceEqual, SemanticD
|
|||
import {IndexingContext} from '../../indexer';
|
||||
import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, Resource, ResourceRegistry} from '../../metadata';
|
||||
import {EnumValue, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {ComponentScopeReader, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../scope';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||
|
@ -208,7 +209,7 @@ export class ComponentDecoratorHandler implements
|
|||
private depTracker: DependencyTracker|null,
|
||||
private injectableRegistry: InjectableClassRegistry,
|
||||
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
|
||||
private annotateForClosureCompiler: boolean) {}
|
||||
private annotateForClosureCompiler: boolean, private perf: PerfRecorder) {}
|
||||
|
||||
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
||||
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
|
@ -309,6 +310,7 @@ export class ComponentDecoratorHandler implements
|
|||
analyze(
|
||||
node: ClassDeclaration, decorator: Readonly<Decorator>,
|
||||
flags: HandlerFlags = HandlerFlags.NONE): AnalysisOutput<ComponentAnalysisData> {
|
||||
this.perf.eventCount(PerfEvent.AnalyzeComponent);
|
||||
const containingFile = node.getSourceFile().fileName;
|
||||
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 {extractDirectiveTypeCheckMeta} from '../../metadata/src/util';
|
||||
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry} from '../../scope';
|
||||
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 semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
|
||||
private annotateForClosureCompiler: boolean,
|
||||
private compileUndecoratedClassesWithAngularFeatures: boolean) {}
|
||||
private compileUndecoratedClassesWithAngularFeatures: boolean, private perf: PerfRecorder) {}
|
||||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
readonly name = DirectiveDecoratorHandler.name;
|
||||
|
@ -211,6 +212,8 @@ export class DirectiveDecoratorHandler implements
|
|||
return {diagnostics: [getUndecoratedClassWithAngularFeaturesDiagnostic(node)]};
|
||||
}
|
||||
|
||||
this.perf.eventCount(PerfEvent.AnalyzeDirective);
|
||||
|
||||
const directiveResult = extractDirectiveMetadata(
|
||||
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
|
||||
flags, this.annotateForClosureCompiler);
|
||||
|
|
|
@ -12,6 +12,7 @@ import * as ts from 'typescript';
|
|||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder} from '../../imports';
|
||||
import {InjectableClassRegistry} from '../../metadata';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
||||
|
||||
|
@ -34,7 +35,7 @@ export class InjectableDecoratorHandler implements
|
|||
constructor(
|
||||
private reflector: ReflectionHost, private defaultImportRecorder: DefaultImportRecorder,
|
||||
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.
|
||||
*
|
||||
|
@ -64,6 +65,8 @@ export class InjectableDecoratorHandler implements
|
|||
|
||||
analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
|
||||
AnalysisOutput<InjectableHandlerData> {
|
||||
this.perf.eventCount(PerfEvent.AnalyzeInjectable);
|
||||
|
||||
const meta = extractInjectableMetadata(node, decorator, this.reflector);
|
||||
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 {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
|
||||
import {NgModuleRouteAnalyzer} from '../../routing';
|
||||
import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
|
||||
|
@ -131,7 +132,8 @@ export class NgModuleDecoratorHandler implements
|
|||
private factoryTracker: FactoryTracker|null,
|
||||
private defaultImportRecorder: DefaultImportRecorder,
|
||||
private annotateForClosureCompiler: boolean,
|
||||
private injectableRegistry: InjectableClassRegistry, private localeId?: string) {}
|
||||
private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
|
||||
private localeId?: string) {}
|
||||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
readonly name = NgModuleDecoratorHandler.name;
|
||||
|
@ -154,6 +156,8 @@ export class NgModuleDecoratorHandler implements
|
|||
|
||||
analyze(node: ClassDeclaration, decorator: Readonly<Decorator>):
|
||||
AnalysisOutput<NgModuleAnalysis> {
|
||||
this.perf.eventCount(PerfEvent.AnalyzeNgModule);
|
||||
|
||||
const name = node.name.text;
|
||||
if (decorator.args === null || decorator.args.length > 1) {
|
||||
throw new FatalDiagnosticError(
|
||||
|
|
|
@ -14,6 +14,7 @@ import {DefaultImportRecorder, Reference} from '../../imports';
|
|||
import {SemanticSymbol} from '../../incremental/semantic_graph';
|
||||
import {InjectableClassRegistry, MetadataRegistry} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry} from '../../scope';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||
|
@ -55,7 +56,8 @@ export class PipeDecoratorHandler implements
|
|||
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
||||
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
||||
private defaultImportRecorder: DefaultImportRecorder,
|
||||
private injectableRegistry: InjectableClassRegistry, private isCore: boolean) {}
|
||||
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
|
||||
private perf: PerfRecorder) {}
|
||||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
readonly name = PipeDecoratorHandler.name;
|
||||
|
@ -78,6 +80,8 @@ export class PipeDecoratorHandler implements
|
|||
|
||||
analyze(clazz: ClassDeclaration, decorator: Readonly<Decorator>):
|
||||
AnalysisOutput<PipeHandlerData> {
|
||||
this.perf.eventCount(PerfEvent.AnalyzePipe);
|
||||
|
||||
const name = clazz.name.text;
|
||||
const type = wrapTypeReference(this.reflector, 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/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/scope",
|
||||
"//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 {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../scope';
|
||||
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 moduleResolver =
|
||||
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 metaRegistry = new LocalMetadataRegistry();
|
||||
const dtsReader = new DtsMetadataReader(checker, reflectionHost);
|
||||
|
@ -80,6 +81,7 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
|
|||
injectableRegistry,
|
||||
/* semanticDepGraphUpdater */ null,
|
||||
/* annotateForClosureCompiler */ false,
|
||||
NOOP_PERF_RECORDER,
|
||||
);
|
||||
return {reflectionHost, handler};
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
|
|||
import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||
import {DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
|
@ -171,7 +172,7 @@ runInEachFileSystem(() => {
|
|||
NOOP_DEFAULT_IMPORT_RECORDER, injectableRegistry, /*isCore*/ false,
|
||||
/*semanticDepGraphUpdater*/ null,
|
||||
/*annotateForClosureCompiler*/ false,
|
||||
/*detectUndecoratedClassesWithAngularFeatures*/ false);
|
||||
/*detectUndecoratedClassesWithAngularFeatures*/ false, NOOP_PERF_RECORDER);
|
||||
|
||||
const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import {absoluteFrom} from '../../file_system';
|
|||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../imports';
|
||||
import {InjectableClassRegistry} from '../../metadata';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {InjectableDecoratorHandler} from '../src/injectable';
|
||||
|
@ -70,7 +71,7 @@ function setupHandler(errorOnDuplicateProv: boolean) {
|
|||
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
|
||||
const handler = new InjectableDecoratorHandler(
|
||||
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 ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
|
||||
if (ɵprov === undefined) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {runInEachFileSystem} from '../../file_system/testing';
|
|||
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
|
@ -71,7 +72,8 @@ runInEachFileSystem(() => {
|
|||
const handler = new NgModuleDecoratorHandler(
|
||||
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
|
||||
/* 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 =
|
||||
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
|
||||
const detected =
|
||||
|
|
|
@ -28,9 +28,8 @@ export interface TestOnlyOptions {
|
|||
/**
|
||||
* 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:'
|
||||
* prefix will cause the trace to be written via the TS host instead of directly to the filesystem
|
||||
* (not all hosts support this mode of operation).
|
||||
* This should be a path to a JSON file where trace information will be written. This is sensitive
|
||||
* to the compiler's working directory, and should likely be an absolute path.
|
||||
*
|
||||
* 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 {ModuleWithProvidersScanner} from '../../modulewithproviders';
|
||||
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 {AdapterResourceLoader} from '../../resource';
|
||||
import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
|
||||
|
@ -80,6 +82,7 @@ export interface FreshCompilationTicket {
|
|||
enableTemplateTypeChecker: boolean;
|
||||
usePoisonedData: boolean;
|
||||
tsProgram: ts.Program;
|
||||
perfRecorder: ActivePerfRecorder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,12 +98,14 @@ export interface IncrementalTypeScriptCompilationTicket {
|
|||
newDriver: IncrementalDriver;
|
||||
enableTemplateTypeChecker: boolean;
|
||||
usePoisonedData: boolean;
|
||||
perfRecorder: ActivePerfRecorder;
|
||||
}
|
||||
|
||||
export interface IncrementalResourceCompilationTicket {
|
||||
kind: CompilationTicketKind.IncrementalResource;
|
||||
compiler: NgCompiler;
|
||||
modifiedResourceFiles: Set<string>;
|
||||
perfRecorder: ActivePerfRecorder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,8 +124,8 @@ export type CompilationTicket = FreshCompilationTicket|IncrementalTypeScriptComp
|
|||
export function freshCompilationTicket(
|
||||
tsProgram: ts.Program, options: NgCompilerOptions,
|
||||
incrementalBuildStrategy: IncrementalBuildStrategy,
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, enableTemplateTypeChecker: boolean,
|
||||
usePoisonedData: boolean): CompilationTicket {
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, perfRecorder: ActivePerfRecorder|null,
|
||||
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): CompilationTicket {
|
||||
return {
|
||||
kind: CompilationTicketKind.Fresh,
|
||||
tsProgram,
|
||||
|
@ -129,6 +134,7 @@ export function freshCompilationTicket(
|
|||
typeCheckingProgramStrategy,
|
||||
enableTemplateTypeChecker,
|
||||
usePoisonedData,
|
||||
perfRecorder: perfRecorder ?? ActivePerfRecorder.zeroedToNow(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -139,8 +145,8 @@ export function freshCompilationTicket(
|
|||
export function incrementalFromCompilerTicket(
|
||||
oldCompiler: NgCompiler, newProgram: ts.Program,
|
||||
incrementalBuildStrategy: IncrementalBuildStrategy,
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy,
|
||||
modifiedResourceFiles: Set<string>): CompilationTicket {
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>,
|
||||
perfRecorder: ActivePerfRecorder|null): CompilationTicket {
|
||||
const oldProgram = oldCompiler.getNextProgram();
|
||||
const oldDriver = oldCompiler.incrementalStrategy.getIncrementalDriver(oldProgram);
|
||||
if (oldDriver === null) {
|
||||
|
@ -148,11 +154,15 @@ export function incrementalFromCompilerTicket(
|
|||
// program.
|
||||
return freshCompilationTicket(
|
||||
newProgram, oldCompiler.options, incrementalBuildStrategy, typeCheckingProgramStrategy,
|
||||
oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
|
||||
perfRecorder, oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
|
||||
}
|
||||
|
||||
const newDriver =
|
||||
IncrementalDriver.reconcile(oldProgram, oldDriver, newProgram, modifiedResourceFiles);
|
||||
if (perfRecorder === null) {
|
||||
perfRecorder = ActivePerfRecorder.zeroedToNow();
|
||||
}
|
||||
|
||||
const newDriver = IncrementalDriver.reconcile(
|
||||
oldProgram, oldDriver, newProgram, modifiedResourceFiles, perfRecorder);
|
||||
|
||||
return {
|
||||
kind: CompilationTicketKind.IncrementalTypeScript,
|
||||
|
@ -164,6 +174,7 @@ export function incrementalFromCompilerTicket(
|
|||
newDriver,
|
||||
oldProgram,
|
||||
newProgram,
|
||||
perfRecorder,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -175,9 +186,14 @@ export function incrementalFromDriverTicket(
|
|||
oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program,
|
||||
options: NgCompilerOptions, incrementalBuildStrategy: IncrementalBuildStrategy,
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>,
|
||||
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): CompilationTicket {
|
||||
const newDriver =
|
||||
IncrementalDriver.reconcile(oldProgram, oldDriver, newProgram, modifiedResourceFiles);
|
||||
perfRecorder: ActivePerfRecorder|null, enableTemplateTypeChecker: boolean,
|
||||
usePoisonedData: boolean): CompilationTicket {
|
||||
if (perfRecorder === null) {
|
||||
perfRecorder = ActivePerfRecorder.zeroedToNow();
|
||||
}
|
||||
|
||||
const newDriver = IncrementalDriver.reconcile(
|
||||
oldProgram, oldDriver, newProgram, modifiedResourceFiles, perfRecorder);
|
||||
return {
|
||||
kind: CompilationTicketKind.IncrementalTypeScript,
|
||||
oldProgram,
|
||||
|
@ -188,6 +204,7 @@ export function incrementalFromDriverTicket(
|
|||
typeCheckingProgramStrategy,
|
||||
enableTemplateTypeChecker,
|
||||
usePoisonedData,
|
||||
perfRecorder,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -197,6 +214,7 @@ export function resourceChangeTicket(compiler: NgCompiler, modifiedResourceFiles
|
|||
kind: CompilationTicketKind.IncrementalResource,
|
||||
compiler,
|
||||
modifiedResourceFiles,
|
||||
perfRecorder: ActivePerfRecorder.zeroedToNow(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -245,16 +263,23 @@ export class NgCompiler {
|
|||
readonly ignoreForDiagnostics: 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.
|
||||
*
|
||||
* 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
|
||||
* incrementally reuses state from a previous compilation, or it may represent a fresh compilation
|
||||
* entirely.
|
||||
* incrementally reuses state from a previous compilation, or it may represent a fresh
|
||||
* compilation entirely.
|
||||
*/
|
||||
static fromTicket(
|
||||
ticket: CompilationTicket, adapter: NgCompilerAdapter, perfRecorder?: PerfRecorder) {
|
||||
static fromTicket(ticket: CompilationTicket, adapter: NgCompilerAdapter) {
|
||||
switch (ticket.kind) {
|
||||
case CompilationTicketKind.Fresh:
|
||||
return new NgCompiler(
|
||||
|
@ -266,7 +291,7 @@ export class NgCompiler {
|
|||
IncrementalDriver.fresh(ticket.tsProgram),
|
||||
ticket.enableTemplateTypeChecker,
|
||||
ticket.usePoisonedData,
|
||||
perfRecorder,
|
||||
ticket.perfRecorder,
|
||||
);
|
||||
case CompilationTicketKind.IncrementalTypeScript:
|
||||
return new NgCompiler(
|
||||
|
@ -278,11 +303,11 @@ export class NgCompiler {
|
|||
ticket.newDriver,
|
||||
ticket.enableTemplateTypeChecker,
|
||||
ticket.usePoisonedData,
|
||||
perfRecorder,
|
||||
ticket.perfRecorder,
|
||||
);
|
||||
case CompilationTicketKind.IncrementalResource:
|
||||
const compiler = ticket.compiler;
|
||||
compiler.updateWithChangedResources(ticket.modifiedResourceFiles);
|
||||
compiler.updateWithChangedResources(ticket.modifiedResourceFiles, ticket.perfRecorder);
|
||||
return compiler;
|
||||
}
|
||||
}
|
||||
|
@ -296,7 +321,7 @@ export class NgCompiler {
|
|||
readonly incrementalDriver: IncrementalDriver,
|
||||
readonly enableTemplateTypeChecker: boolean,
|
||||
readonly usePoisonedData: boolean,
|
||||
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER,
|
||||
private livePerfRecorder: ActivePerfRecorder,
|
||||
) {
|
||||
this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics);
|
||||
const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options);
|
||||
|
@ -312,9 +337,7 @@ export class NgCompiler {
|
|||
|
||||
const moduleResolutionCache = ts.createModuleResolutionCache(
|
||||
this.adapter.getCurrentDirectory(),
|
||||
// Note: this used to be an arrow-function closure. However, JS engines like v8 have some
|
||||
// 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
|
||||
// doen'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
|
||||
// memory leak issues since the `moduleResolutionCache` is a long-lived object and finds its
|
||||
// way into all kinds of places inside TS internal objects.
|
||||
|
@ -322,42 +345,66 @@ export class NgCompiler {
|
|||
this.moduleResolver =
|
||||
new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache);
|
||||
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.ignoreForDiagnostics =
|
||||
new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf)));
|
||||
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 {
|
||||
if (this.compilation === null) {
|
||||
// Analysis hasn't happened yet, so no update is necessary - any changes to resources will be
|
||||
// captured by the inital analysis pass itself.
|
||||
return;
|
||||
}
|
||||
get perfRecorder(): ActivePerfRecorder {
|
||||
return this.livePerfRecorder;
|
||||
}
|
||||
|
||||
this.resourceManager.invalidate();
|
||||
private updateWithChangedResources(
|
||||
changedResources: Set<string>, perfRecorder: ActivePerfRecorder): void {
|
||||
this.livePerfRecorder = perfRecorder;
|
||||
this.delegatingPerfRecorder.target = perfRecorder;
|
||||
|
||||
const classesToUpdate = new Set<DeclarationNode>();
|
||||
for (const resourceFile of changedResources) {
|
||||
for (const templateClass of this.getComponentsWithTemplateFile(resourceFile)) {
|
||||
classesToUpdate.add(templateClass);
|
||||
perfRecorder.inPhase(PerfPhase.ResourceUpdate, () => {
|
||||
if (this.compilation === null) {
|
||||
// Analysis hasn't happened yet, so no update is necessary - any changes to resources will
|
||||
// be captured by the inital analysis pass itself.
|
||||
return;
|
||||
}
|
||||
|
||||
for (const styleClass of this.getComponentsWithStyleFile(resourceFile)) {
|
||||
classesToUpdate.add(styleClass);
|
||||
}
|
||||
}
|
||||
this.resourceManager.invalidate();
|
||||
|
||||
for (const clazz of classesToUpdate) {
|
||||
this.compilation.traitCompiler.updateResources(clazz);
|
||||
if (!ts.isClassDeclaration(clazz)) {
|
||||
continue;
|
||||
const classesToUpdate = new Set<DeclarationNode>();
|
||||
for (const resourceFile of changedResources) {
|
||||
for (const templateClass of this.getComponentsWithTemplateFile(resourceFile)) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
this.compilation = this.makeCompilation();
|
||||
|
||||
const analyzeSpan = this.perfRecorder.start('analyze');
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile) {
|
||||
continue;
|
||||
await this.perfRecorder.inPhase(PerfPhase.Analysis, async () => {
|
||||
this.compilation = this.makeCompilation();
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
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);
|
||||
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);
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
this.perfRecorder.stop(analyzeSpan);
|
||||
|
||||
this.resolveCompilation(this.compilation.traitCompiler);
|
||||
this.perfRecorder.memory(PerfCheckpoint.Analysis);
|
||||
this.resolveCompilation(this.compilation.traitCompiler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -517,9 +559,7 @@ export class NgCompiler {
|
|||
*/
|
||||
listLazyRoutes(entryRoute?: string): LazyRoute[] {
|
||||
if (entryRoute) {
|
||||
// Note:
|
||||
// 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).
|
||||
// htts://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
|
||||
// not necessary, but keeping it backwards compatible in case someone else is using the API.
|
||||
|
@ -573,7 +613,8 @@ export class NgCompiler {
|
|||
const before = [
|
||||
ivyTransformFactory(
|
||||
compilation.traitCompiler, compilation.reflector, importRewriter,
|
||||
compilation.defaultImportTracker, compilation.isCore, this.closureCompilerEnabled),
|
||||
compilation.defaultImportTracker, this.delegatingPerfRecorder, compilation.isCore,
|
||||
this.closureCompilerEnabled),
|
||||
aliasTransformFactory(compilation.traitCompiler.exportStatements),
|
||||
compilation.defaultImportTracker.importPreservingTransformer(),
|
||||
];
|
||||
|
@ -618,28 +659,32 @@ export class NgCompiler {
|
|||
}
|
||||
|
||||
private analyzeSync(): void {
|
||||
const analyzeSpan = this.perfRecorder.start('analyze');
|
||||
this.compilation = this.makeCompilation();
|
||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile) {
|
||||
continue;
|
||||
this.perfRecorder.inPhase(PerfPhase.Analysis, () => {
|
||||
this.compilation = this.makeCompilation();
|
||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile) {
|
||||
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 {
|
||||
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
|
||||
// be emitted, so do that.
|
||||
this.incrementalDriver.recordSuccessfulAnalysis(traitCompiler);
|
||||
// At this point, analysis is complete and the compiler can now calculate which files need to
|
||||
// be emitted, so do that.
|
||||
this.incrementalDriver.recordSuccessfulAnalysis(traitCompiler);
|
||||
|
||||
this.perfRecorder.memory(PerfCheckpoint.Resolve);
|
||||
});
|
||||
}
|
||||
|
||||
private get fullTemplateTypeCheck(): boolean {
|
||||
|
@ -770,7 +815,6 @@ export class NgCompiler {
|
|||
const compilation = this.ensureAnalyzed();
|
||||
|
||||
// Get the diagnostics.
|
||||
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
|
||||
|
@ -782,7 +826,6 @@ export class NgCompiler {
|
|||
}
|
||||
|
||||
const program = this.typeCheckingProgramStrategy.getProgram();
|
||||
this.perfRecorder.stop(typeCheckSpan);
|
||||
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
||||
this.nextProgram = program;
|
||||
|
||||
|
@ -794,14 +837,12 @@ export class NgCompiler {
|
|||
const compilation = this.ensureAnalyzed();
|
||||
|
||||
// Get the diagnostics.
|
||||
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
if (!sf.isDeclarationFile && !this.adapter.isShim(sf)) {
|
||||
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
|
||||
}
|
||||
|
||||
const program = this.typeCheckingProgramStrategy.getProgram();
|
||||
this.perfRecorder.stop(typeCheckSpan);
|
||||
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
||||
this.nextProgram = program;
|
||||
|
||||
|
@ -950,7 +991,8 @@ export class NgCompiler {
|
|||
this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData,
|
||||
this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer,
|
||||
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`
|
||||
// not being assignable to `unknown` when wrapped in `Readonly`).
|
||||
|
@ -959,31 +1001,33 @@ export class NgCompiler {
|
|||
reflector, evaluator, metaRegistry, scopeRegistry, metaReader,
|
||||
defaultImportTracker, injectableRegistry, isCore, semanticDepGraphUpdater,
|
||||
this.closureCompilerEnabled, compileUndecoratedClassesWithAngularFeatures,
|
||||
this.delegatingPerfRecorder,
|
||||
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null,unknown>>,
|
||||
// clang-format on
|
||||
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
||||
// before injectable factories (so injectable factories can delegate to them)
|
||||
new PipeDecoratorHandler(
|
||||
reflector, evaluator, metaRegistry, scopeRegistry, defaultImportTracker,
|
||||
injectableRegistry, isCore),
|
||||
injectableRegistry, isCore, this.delegatingPerfRecorder),
|
||||
new InjectableDecoratorHandler(
|
||||
reflector, defaultImportTracker, isCore, this.options.strictInjectionParameters || false,
|
||||
injectableRegistry),
|
||||
injectableRegistry, this.delegatingPerfRecorder),
|
||||
new NgModuleDecoratorHandler(
|
||||
reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore,
|
||||
routeAnalyzer, refEmitter, this.adapter.factoryTracker, defaultImportTracker,
|
||||
this.closureCompilerEnabled, injectableRegistry, this.options.i18nInLocale),
|
||||
this.closureCompilerEnabled, injectableRegistry, this.delegatingPerfRecorder,
|
||||
this.options.i18nInLocale),
|
||||
];
|
||||
|
||||
const traitCompiler = new TraitCompiler(
|
||||
handlers, reflector, this.perfRecorder, this.incrementalDriver,
|
||||
handlers, reflector, this.delegatingPerfRecorder, this.incrementalDriver,
|
||||
this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms,
|
||||
semanticDepGraphUpdater);
|
||||
|
||||
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
||||
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
|
||||
this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver,
|
||||
scopeRegistry, typeCheckScopeRegistry);
|
||||
scopeRegistry, typeCheckScopeRegistry, this.delegatingPerfRecorder);
|
||||
|
||||
return {
|
||||
isCore,
|
||||
|
|
|
@ -25,8 +25,8 @@ function makeFreshCompiler(
|
|||
programStrategy: TypeCheckingProgramStrategy, incrementalStrategy: IncrementalBuildStrategy,
|
||||
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): NgCompiler {
|
||||
const ticket = freshCompilationTicket(
|
||||
program, options, incrementalStrategy, programStrategy, enableTemplateTypeChecker,
|
||||
usePoisonedData);
|
||||
program, options, incrementalStrategy, programStrategy, /* perfRecorder */ null,
|
||||
enableTemplateTypeChecker, usePoisonedData);
|
||||
return NgCompiler.fromTicket(ticket, host);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ ts_library(
|
|||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/cycles",
|
||||
deps = [
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {PerfPhase, PerfRecorder} from '../../perf';
|
||||
|
||||
/**
|
||||
* A cached graph of imports in the `ts.Program`.
|
||||
*
|
||||
|
@ -17,7 +19,7 @@ import * as ts from 'typescript';
|
|||
export class ImportGraph {
|
||||
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`.
|
||||
|
@ -99,26 +101,28 @@ export class ImportGraph {
|
|||
}
|
||||
|
||||
private scanImports(sf: ts.SourceFile): Set<ts.SourceFile> {
|
||||
const imports = new Set<ts.SourceFile>();
|
||||
// Look through the source file for import and export statements.
|
||||
for (const stmt of sf.statements) {
|
||||
if ((!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt)) ||
|
||||
stmt.moduleSpecifier === undefined) {
|
||||
continue;
|
||||
}
|
||||
return this.perf.inPhase(PerfPhase.CycleDetection, () => {
|
||||
const imports = new Set<ts.SourceFile>();
|
||||
// Look through the source file for import and export statements.
|
||||
for (const stmt of sf.statements) {
|
||||
if ((!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt)) ||
|
||||
stmt.moduleSpecifier === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const symbol = this.checker.getSymbolAtLocation(stmt.moduleSpecifier);
|
||||
if (symbol === undefined || symbol.valueDeclaration === undefined) {
|
||||
// No symbol could be found to skip over this import/export.
|
||||
continue;
|
||||
const symbol = this.checker.getSymbolAtLocation(stmt.moduleSpecifier);
|
||||
if (symbol === undefined || symbol.valueDeclaration === undefined) {
|
||||
// No symbol could be found to skip over this import/export.
|
||||
continue;
|
||||
}
|
||||
const moduleFile = symbol.valueDeclaration;
|
||||
if (ts.isSourceFile(moduleFile) && isLocalFile(moduleFile)) {
|
||||
// Record this local import.
|
||||
imports.add(moduleFile);
|
||||
}
|
||||
}
|
||||
const moduleFile = symbol.valueDeclaration;
|
||||
if (ts.isSourceFile(moduleFile) && isLocalFile(moduleFile)) {
|
||||
// Record this local import.
|
||||
imports.add(moduleFile);
|
||||
}
|
||||
}
|
||||
return imports;
|
||||
return imports;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ ts_library(
|
|||
"//packages/compiler-cli/src/ngtsc/cycles",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/testing",
|
||||
"@npm//typescript",
|
||||
],
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import * as ts from 'typescript';
|
||||
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {Cycle, CycleAnalyzer} from '../src/analyzer';
|
||||
import {ImportGraph} from '../src/imports';
|
||||
import {importPath, makeProgramFromGraph} from './util';
|
||||
|
@ -75,7 +76,7 @@ runInEachFileSystem(() => {
|
|||
const {program} = makeProgramFromGraph(getFileSystem(), graph);
|
||||
return {
|
||||
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 {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {ImportGraph} from '../src/imports';
|
||||
import {importPath, makeProgramFromGraph} from './util';
|
||||
|
||||
|
@ -86,7 +87,7 @@ runInEachFileSystem(() => {
|
|||
const {program} = makeProgramFromGraph(getFileSystem(), graph);
|
||||
return {
|
||||
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/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/scope",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
||||
import {PerfEvent, PerfPhase, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {ClassRecord, TraitCompiler} from '../../transform';
|
||||
import {FileTypeCheckingData} from '../../typecheck/src/checker';
|
||||
|
@ -43,118 +44,122 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord, FileType
|
|||
*/
|
||||
static reconcile(
|
||||
oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program,
|
||||
modifiedResourceFiles: Set<string>|null): IncrementalDriver {
|
||||
// Initialize the state of the current build based on the previous one.
|
||||
let state: PendingBuildState;
|
||||
if (oldDriver.state.kind === BuildStateKind.Pending) {
|
||||
// The previous build never made it past the pending state. Reuse it as the starting state for
|
||||
// this build.
|
||||
state = oldDriver.state;
|
||||
} else {
|
||||
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);
|
||||
modifiedResourceFiles: Set<string>|null, perf: PerfRecorder): IncrementalDriver {
|
||||
return perf.inPhase(PerfPhase.Reconciliation, () => {
|
||||
// Initialize the state of the current build based on the previous one.
|
||||
let state: PendingBuildState;
|
||||
if (oldDriver.state.kind === BuildStateKind.Pending) {
|
||||
// The previous build never made it past the pending state. Reuse it as the starting state
|
||||
// for this build.
|
||||
state = oldDriver.state;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
let priorGraph: SemanticDepGraph|null = null;
|
||||
if (oldDriver.state.lastGood !== null) {
|
||||
priorGraph = oldDriver.state.lastGood.semanticDepGraph;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 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),
|
||||
};
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Merge the freshly modified resource files with any prior ones.
|
||||
if (modifiedResourceFiles !== null) {
|
||||
for (const resFile of modifiedResourceFiles) {
|
||||
state.changedResourcePaths.add(absoluteFrom(resFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `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 {
|
||||
|
|
|
@ -9,8 +9,6 @@ ts_library(
|
|||
]),
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"@npm//@types/node",
|
||||
"@npm//typescript",
|
||||
],
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
* 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 {PerfTracker} from './src/tracking';
|
||||
export {ActivePerfRecorder, DelegatingPerfRecorder} from './src/recorder';
|
||||
|
|
|
@ -6,12 +6,328 @@
|
|||
* 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;
|
||||
stop(span: number): void;
|
||||
/**
|
||||
* Time spent in `ts.createProgram`, including reading and parsing `ts.SourceFile`s in the
|
||||
* `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
|
||||
* 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 = {
|
||||
enabled: false,
|
||||
mark: (name: string, node: DeclarationNode, category?: string, detail?: string): void => {},
|
||||
start: (name: string, node: DeclarationNode, category?: string, detail?: string): number => {
|
||||
return 0;
|
||||
},
|
||||
stop: (span: number|false): void => {},
|
||||
};
|
||||
memory(): void {}
|
||||
|
||||
phase(): PerfPhase {
|
||||
return PerfPhase.Unaccounted;
|
||||
}
|
||||
|
||||
inPhase<T>(phase: PerfPhase, fn: () => T): T {
|
||||
return fn();
|
||||
}
|
||||
|
||||
reset(): void {}
|
||||
}
|
||||
|
||||
|
||||
export const NOOP_PERF_RECORDER: PerfRecorder = new NoopPerfRecorder();
|
||||
|
|
|
@ -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 {NgCompilerOptions} from './core/api';
|
||||
import {absoluteFrom, AbsoluteFsPath} from './file_system';
|
||||
import {absoluteFrom, AbsoluteFsPath, getFileSystem} from './file_system';
|
||||
import {TrackedIncrementalBuildStrategy} from './incremental';
|
||||
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 {retagAllTsFiles, untagAllTsFiles} from './shims';
|
||||
import {ReusedProgramStrategy} from './typecheck';
|
||||
|
@ -54,22 +54,20 @@ export class NgtscProgram implements api.Program {
|
|||
private reuseTsProgram: ts.Program;
|
||||
private closureCompilerEnabled: boolean;
|
||||
private host: NgCompilerHost;
|
||||
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER;
|
||||
private perfTracker: PerfTracker|null = null;
|
||||
private incrementalStrategy: TrackedIncrementalBuildStrategy;
|
||||
|
||||
constructor(
|
||||
rootNames: ReadonlyArray<string>, private options: NgCompilerOptions,
|
||||
delegateHost: api.CompilerHost, oldProgram?: NgtscProgram) {
|
||||
const perfRecorder = ActivePerfRecorder.zeroedToNow();
|
||||
|
||||
perfRecorder.phase(PerfPhase.Setup);
|
||||
|
||||
// First, check whether the current TS version is supported.
|
||||
if (!options.disableTypeScriptVersionCheck) {
|
||||
verifySupportedTypeScriptVersion();
|
||||
}
|
||||
|
||||
if (options.tracePerformance !== undefined) {
|
||||
this.perfTracker = PerfTracker.zeroedToNow();
|
||||
this.perfRecorder = this.perfTracker;
|
||||
}
|
||||
this.closureCompilerEnabled = !!options.annotateForClosureCompiler;
|
||||
|
||||
const reuseProgram = oldProgram?.reuseTsProgram;
|
||||
|
@ -83,9 +81,14 @@ export class NgtscProgram implements api.Program {
|
|||
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;
|
||||
|
||||
perfRecorder.phase(PerfPhase.Unaccounted);
|
||||
perfRecorder.memory(PerfCheckpoint.TypeScriptProgramCreate);
|
||||
|
||||
this.host.postProgramCreationCleanup();
|
||||
|
||||
// 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;
|
||||
if (oldProgram === undefined) {
|
||||
ticket = freshCompilationTicket(
|
||||
this.tsProgram, options, this.incrementalStrategy, reusedProgramStrategy,
|
||||
this.tsProgram, options, this.incrementalStrategy, reusedProgramStrategy, perfRecorder,
|
||||
/* enableTemplateTypeChecker */ false, /* usePoisonedData */ false);
|
||||
} else {
|
||||
ticket = incrementalFromCompilerTicket(
|
||||
|
@ -120,12 +123,13 @@ export class NgtscProgram implements api.Program {
|
|||
this.incrementalStrategy,
|
||||
reusedProgramStrategy,
|
||||
modifiedResourceFiles,
|
||||
perfRecorder,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// 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 {
|
||||
|
@ -138,49 +142,59 @@ export class NgtscProgram implements api.Program {
|
|||
|
||||
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||
undefined): readonly ts.Diagnostic[] {
|
||||
return this.tsProgram.getOptionsDiagnostics(cancellationToken);
|
||||
return this.compiler.perfRecorder.inPhase(
|
||||
PerfPhase.TypeScriptDiagnostics,
|
||||
() => this.tsProgram.getOptionsDiagnostics(cancellationToken));
|
||||
}
|
||||
|
||||
getTsSyntacticDiagnostics(
|
||||
sourceFile?: ts.SourceFile|undefined,
|
||||
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
|
||||
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
||||
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));
|
||||
return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
|
||||
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
||||
let res: readonly ts.Diagnostic[];
|
||||
if (sourceFile !== undefined) {
|
||||
if (ignoredFiles.has(sourceFile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
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(
|
||||
sourceFile?: ts.SourceFile|undefined,
|
||||
cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] {
|
||||
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
||||
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));
|
||||
return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
|
||||
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
||||
let res: readonly ts.Diagnostic[];
|
||||
if (sourceFile !== undefined) {
|
||||
if (ignoredFiles.has(sourceFile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
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|
|
||||
|
@ -235,73 +249,82 @@ export class NgtscProgram implements api.Program {
|
|||
emitCallback?: api.TsEmitCallback | undefined;
|
||||
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback | undefined;
|
||||
}|undefined): ts.EmitResult {
|
||||
const {transformers} = this.compiler.prepareEmit();
|
||||
const ignoreFiles = this.compiler.ignoreForEmit;
|
||||
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
||||
this.compiler.perfRecorder.memory(PerfCheckpoint.PreEmit);
|
||||
|
||||
const writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError: ((message: string) => void)|undefined,
|
||||
sourceFiles: ReadonlyArray<ts.SourceFile>|undefined) => {
|
||||
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;
|
||||
const res = this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptEmit, () => {
|
||||
const {transformers} = this.compiler.prepareEmit();
|
||||
const ignoreFiles = this.compiler.ignoreForEmit;
|
||||
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
||||
|
||||
const writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError: ((message: string) => void)|undefined,
|
||||
sourceFiles: ReadonlyArray<ts.SourceFile>|undefined) => {
|
||||
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 beforeTransforms = transformers.before || [];
|
||||
const afterDeclarationsTransforms = transformers.afterDeclarations;
|
||||
const customTransforms = opts && opts.customTransformers;
|
||||
const beforeTransforms = transformers.before || [];
|
||||
const afterDeclarationsTransforms = transformers.afterDeclarations;
|
||||
|
||||
if (customTransforms !== undefined && customTransforms.beforeTs !== undefined) {
|
||||
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 (customTransforms !== undefined && customTransforms.beforeTs !== undefined) {
|
||||
beforeTransforms.push(...customTransforms.beforeTs);
|
||||
}
|
||||
|
||||
if (this.compiler.incrementalDriver.safeToSkipEmit(targetSourceFile)) {
|
||||
continue;
|
||||
const emitResults: ts.EmitResult[] = [];
|
||||
|
||||
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);
|
||||
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,
|
||||
}));
|
||||
this.perfRecorder.stop(fileEmitSpan);
|
||||
this.compiler.perfRecorder.memory(PerfCheckpoint.Emit);
|
||||
|
||||
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in
|
||||
// code.
|
||||
return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
|
||||
});
|
||||
|
||||
// Record performance analysis information to disk if we've been asked to do so.
|
||||
if (this.options.tracePerformance !== undefined) {
|
||||
const perf = this.compiler.perfRecorder.finalize();
|
||||
getFileSystem().writeFile(
|
||||
getFileSystem().resolve(this.options.tracePerformance), JSON.stringify(perf, null, 2));
|
||||
}
|
||||
|
||||
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);
|
||||
return res;
|
||||
}
|
||||
|
||||
getIndexedComponents(): Map<DeclarationNode, IndexedComponent> {
|
||||
|
|
|
@ -13,7 +13,7 @@ import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
|||
import {IncrementalBuild} from '../../incremental/api';
|
||||
import {SemanticDepGraphUpdater, SemanticSymbol} from '../../incremental/semantic_graph';
|
||||
import {IndexingContext} from '../../indexer';
|
||||
import {PerfRecorder} from '../../perf';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost} from '../../reflection';
|
||||
import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck/api';
|
||||
import {getSourceFile, isExported} from '../../util/src/typescript';
|
||||
|
@ -124,6 +124,9 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||
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.
|
||||
return;
|
||||
}
|
||||
|
@ -359,6 +362,8 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||
TraitState[trait.state]} (expected DETECTED)`);
|
||||
}
|
||||
|
||||
this.perf.eventCount(PerfEvent.TraitAnalyze);
|
||||
|
||||
// Attempt analysis. This could fail with a `FatalDiagnosticError`; catch it if it does.
|
||||
let result: AnalysisOutput<unknown>;
|
||||
try {
|
||||
|
@ -509,9 +514,6 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||
continue;
|
||||
}
|
||||
|
||||
const compileSpan = this.perf.start('compileClass', original);
|
||||
|
||||
|
||||
// `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` works.
|
||||
|
@ -526,7 +528,6 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||
}
|
||||
|
||||
const compileMatchRes = compileRes;
|
||||
this.perf.stop(compileSpan);
|
||||
if (Array.isArray(compileMatchRes)) {
|
||||
for (const result of compileMatchRes) {
|
||||
if (!res.some(r => r.name === result.name)) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {ConstantPool} from '@angular/compiler';
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {DefaultImportRecorder, ImportRewriter} from '../../imports';
|
||||
import {PerfPhase, PerfRecorder} from '../../perf';
|
||||
import {Decorator, ReflectionHost} from '../../reflection';
|
||||
import {ImportManager, RecordWrappedNodeExprFn, translateExpression, translateStatement, TranslatorOptions} from '../../translator';
|
||||
import {visit, VisitListEntryResult, Visitor} from '../../util/src/visitor';
|
||||
|
@ -33,14 +34,16 @@ interface FileOverviewMeta {
|
|||
|
||||
export function ivyTransformFactory(
|
||||
compilation: TraitCompiler, reflector: ReflectionHost, importRewriter: ImportRewriter,
|
||||
defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||
defaultImportRecorder: DefaultImportRecorder, perf: PerfRecorder, isCore: boolean,
|
||||
isClosureCompilerEnabled: boolean): ts.TransformerFactory<ts.SourceFile> {
|
||||
const recordWrappedNodeExpr = createRecorderFn(defaultImportRecorder);
|
||||
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
||||
return (file: ts.SourceFile): ts.SourceFile => {
|
||||
return transformIvySourceFile(
|
||||
compilation, context, reflector, importRewriter, file, isCore, isClosureCompilerEnabled,
|
||||
recordWrappedNodeExpr);
|
||||
return perf.inPhase(
|
||||
PerfPhase.Compile,
|
||||
() => transformIvySourceFile(
|
||||
compilation, context, reflector, importRewriter, file, isCore,
|
||||
isClosureCompilerEnabled, recordWrappedNodeExpr));
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import {CompilationTicket, freshCompilationTicket, incrementalFromDriverTicket,
|
|||
import {NgCompilerOptions, UnifiedModulesHost} from './core/api';
|
||||
import {NodeJSFileSystem, setFileSystem} from './file_system';
|
||||
import {PatchedProgramIncrementalBuildStrategy} from './incremental';
|
||||
import {NOOP_PERF_RECORDER} from './perf';
|
||||
import {ActivePerfRecorder, NOOP_PERF_RECORDER, PerfPhase} from './perf';
|
||||
import {untagAllTsFiles} from './shims';
|
||||
import {OptimizeFor} from './typecheck/api';
|
||||
import {ReusedProgramStrategy} from './typecheck/src/augmented_program';
|
||||
|
@ -94,6 +94,13 @@ export class NgTscPlugin implements TscPlugin {
|
|||
ignoreForDiagnostics: 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) {
|
||||
throw new Error('Lifecycle error: setupCompilation() before wrapHost().');
|
||||
}
|
||||
|
@ -115,15 +122,15 @@ export class NgTscPlugin implements TscPlugin {
|
|||
|
||||
if (oldProgram === undefined || oldDriver === null) {
|
||||
ticket = freshCompilationTicket(
|
||||
program, this.options, strategy, typeCheckStrategy,
|
||||
program, this.options, strategy, typeCheckStrategy, perfRecorder,
|
||||
/* enableTemplateTypeChecker */ false, /* usePoisonedData */ false);
|
||||
} else {
|
||||
strategy.toNextBuildStrategy().getIncrementalDriver(oldProgram);
|
||||
ticket = incrementalFromDriverTicket(
|
||||
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 {
|
||||
ignoreForDiagnostics: this._compiler.ignoreForDiagnostics,
|
||||
ignoreForEmit: this._compiler.ignoreForEmit,
|
||||
|
@ -146,6 +153,9 @@ export class NgTscPlugin implements TscPlugin {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ ts_library(
|
|||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/incremental:api",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/scope",
|
||||
"//packages/compiler-cli/src/ngtsc/shims",
|
||||
|
|
|
@ -12,6 +12,7 @@ import * as ts from 'typescript';
|
|||
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
|
||||
import {Reference, ReferenceEmitter} from '../../imports';
|
||||
import {IncrementalBuild} from '../../incremental/api';
|
||||
import {PerfCheckpoint, PerfEvent, PerfPhase, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
|
||||
import {ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope';
|
||||
import {isShim} from '../../shims';
|
||||
|
@ -82,7 +83,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
|
||||
private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>,
|
||||
private readonly componentScopeReader: ComponentScopeReader,
|
||||
private readonly typeCheckScopeRegistry: TypeCheckScopeRegistry) {}
|
||||
private readonly typeCheckScopeRegistry: TypeCheckScopeRegistry,
|
||||
private readonly perf: PerfRecorder) {}
|
||||
|
||||
getTemplate(component: ts.ClassDeclaration): TmplAstNode[]|null {
|
||||
const {data} = this.getLatestComponentState(component);
|
||||
|
@ -181,19 +183,60 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
break;
|
||||
}
|
||||
|
||||
const sfPath = absoluteFromSourceFile(sf);
|
||||
const fileRecord = this.state.get(sfPath)!;
|
||||
return this.perf.inPhase(PerfPhase.TtcDiagnostics, () => {
|
||||
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)[] = [];
|
||||
if (fileRecord.hasInlines) {
|
||||
const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
|
||||
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
|
||||
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
||||
}
|
||||
const diagnostics: (ts.Diagnostic|null)[] = [];
|
||||
if (fileRecord.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);
|
||||
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);
|
||||
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
|
||||
diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
||||
|
@ -202,48 +245,11 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
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);
|
||||
|
||||
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);
|
||||
return diagnostics.filter(
|
||||
(diag: TemplateDiagnostic|null): diag is TemplateDiagnostic =>
|
||||
diag !== null && diag.templateId === templateId);
|
||||
});
|
||||
}
|
||||
|
||||
getTypeCheckBlock(component: ts.ClassDeclaration): ts.Node|null {
|
||||
|
@ -256,7 +262,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
if (engine === null) {
|
||||
return null;
|
||||
}
|
||||
return engine.getGlobalCompletions(context);
|
||||
return this.perf.inPhase(
|
||||
PerfPhase.TtcAutocompletion, () => engine.getGlobalCompletions(context));
|
||||
}
|
||||
|
||||
getExpressionCompletionLocation(
|
||||
|
@ -266,7 +273,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
if (engine === null) {
|
||||
return null;
|
||||
}
|
||||
return engine.getExpressionCompletionLocation(ast);
|
||||
return this.perf.inPhase(
|
||||
PerfPhase.TtcAutocompletion, () => engine.getExpressionCompletionLocation(ast));
|
||||
}
|
||||
|
||||
invalidateClass(clazz: ts.ClassDeclaration): void {
|
||||
|
@ -318,6 +326,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
return;
|
||||
}
|
||||
|
||||
this.perf.eventCount(PerfEvent.ReuseTypeCheckFile);
|
||||
this.state.set(sfPath, previousResults);
|
||||
}
|
||||
|
||||
|
@ -326,50 +335,55 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
return;
|
||||
}
|
||||
|
||||
const host = new WholeProgramTypeCheckingHost(this);
|
||||
const ctx = this.newContext(host);
|
||||
this.perf.inPhase(PerfPhase.TcbGeneration, () => {
|
||||
const host = new WholeProgramTypeCheckingHost(this);
|
||||
const ctx = this.newContext(host);
|
||||
|
||||
for (const sf of this.originalProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile || isShim(sf)) {
|
||||
continue;
|
||||
for (const sf of this.originalProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile || isShim(sf)) {
|
||||
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);
|
||||
|
||||
const sfPath = absoluteFromSourceFile(sf);
|
||||
|
||||
const fileData = this.getFileData(sfPath);
|
||||
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);
|
||||
|
||||
fileData.isComplete = true;
|
||||
}
|
||||
|
||||
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);
|
||||
this.updateFromContext(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
private ensureShimForComponent(component: ts.ClassDeclaration): void {
|
||||
|
@ -399,7 +413,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
InliningMode.Error;
|
||||
return new TypeCheckContextImpl(
|
||||
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 {
|
||||
const updates = ctx.finalize();
|
||||
this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental);
|
||||
this.priorBuild.recordSuccessfulTypeCheck(this.state);
|
||||
return this.perf.inPhase(PerfPhase.TcbUpdateProgram, () => {
|
||||
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 {
|
||||
|
@ -450,7 +470,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||
if (builder === null) {
|
||||
return null;
|
||||
}
|
||||
return builder.getSymbol(node);
|
||||
return this.perf.inPhase(PerfPhase.TtcSymbol, () => builder.getSymbol(node));
|
||||
}
|
||||
|
||||
private getOrCreateSymbolBuilder(component: ts.ClassDeclaration): SymbolBuilder|null {
|
||||
|
|
|
@ -12,6 +12,7 @@ import * as ts from 'typescript';
|
|||
|
||||
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
||||
import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
||||
import {ImportManager} from '../../translator';
|
||||
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 componentMappingStrategy: ComponentToShimMappingStrategy,
|
||||
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) {
|
||||
// We cannot use inlining for type checking since this environment does not support it.
|
||||
throw new Error(`AssertionError: invalid inlining configuration.`);
|
||||
|
@ -273,6 +274,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
|||
shimData.oobRecorder.requiresInlineTcb(templateId, ref.node);
|
||||
|
||||
// Checking this template would be unsupported, so don't try.
|
||||
this.perf.eventCount(PerfEvent.SkipGenerateTcbNoInline);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -282,6 +284,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
|||
pipes,
|
||||
schemas,
|
||||
};
|
||||
this.perf.eventCount(PerfEvent.GenerateTcb);
|
||||
if (tcbRequiresInline) {
|
||||
// This class didn't meet the requirements for external type checking, so generate an inline
|
||||
// TCB for the class.
|
||||
|
|
|
@ -17,6 +17,7 @@ ts_library(
|
|||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/incremental",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/scope",
|
||||
"//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 {NOOP_INCREMENTAL_BUILD} from '../../incremental';
|
||||
import {ClassPropertyMapping, CompoundMetadataReader} from '../../metadata';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {ComponentScopeReader, LocalModuleScope, ScopeData, TypeCheckScopeRegistry} from '../../scope';
|
||||
import {makeProgram} from '../../testing';
|
||||
|
@ -515,7 +516,7 @@ export function setup(targets: TypeCheckingTarget[], overrides: {
|
|||
|
||||
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
||||
program, programStrategy, checkAdapter, fullConfig, emitter, reflectionHost, host,
|
||||
NOOP_INCREMENTAL_BUILD, fakeScopeReader, typeCheckScopeRegistry);
|
||||
NOOP_INCREMENTAL_BUILD, fakeScopeReader, typeCheckScopeRegistry, NOOP_PERF_RECORDER);
|
||||
return {
|
||||
templateTypeChecker,
|
||||
program,
|
||||
|
|
|
@ -10,6 +10,7 @@ import * as ts from 'typescript';
|
|||
import {absoluteFrom, AbsoluteFsPath, getFileSystem, getSourceFileOrError, LogicalFileSystem, NgtscCompilerHost} from '../../file_system';
|
||||
import {runInEachFileSystem, TestFile} from '../../file_system/testing';
|
||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {getRootDirs} from '../../util/src/typescript';
|
||||
|
@ -74,7 +75,7 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
]);
|
||||
const ctx = new TypeCheckContextImpl(
|
||||
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
|
||||
new TestTypeCheckingHost(), InliningMode.InlineOps);
|
||||
new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER);
|
||||
const TestClass =
|
||||
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
const pendingFile = makePendingFile();
|
||||
|
@ -113,7 +114,7 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
const pendingFile = makePendingFile();
|
||||
const ctx = new TypeCheckContextImpl(
|
||||
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
|
||||
new TestTypeCheckingHost(), InliningMode.InlineOps);
|
||||
new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER);
|
||||
const TestClass =
|
||||
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
ctx.addInlineTypeCtor(
|
||||
|
@ -158,7 +159,7 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
const pendingFile = makePendingFile();
|
||||
const ctx = new TypeCheckContextImpl(
|
||||
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost,
|
||||
new TestTypeCheckingHost(), InliningMode.InlineOps);
|
||||
new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER);
|
||||
const TestClass =
|
||||
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
ctx.addInlineTypeCtor(
|
||||
|
|
|
@ -15,6 +15,7 @@ ts_library(
|
|||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/incremental",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/shims",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, resourceChangeTicket} from '@angular/compiler-cli/src/ngtsc/core';
|
||||
import {NgCompilerOptions} from '@angular/compiler-cli/src/ngtsc/core/api';
|
||||
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 * as ts from 'typescript/lib/tsserverlibrary';
|
||||
|
||||
|
@ -44,6 +45,10 @@ export class CompilerFactory {
|
|||
// Only resource files have changed since the last NgCompiler was created.
|
||||
const ticket = resourceChangeTicket(this.compiler, modifiedResourceFiles);
|
||||
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;
|
||||
|
@ -52,11 +57,12 @@ export class CompilerFactory {
|
|||
let ticket: CompilationTicket;
|
||||
if (this.compiler === null || this.lastKnownProgram === null) {
|
||||
ticket = freshCompilationTicket(
|
||||
program, this.options, this.incrementalStrategy, this.programStrategy, true, true);
|
||||
program, this.options, this.incrementalStrategy, this.programStrategy,
|
||||
/* perfRecorder */ null, true, true);
|
||||
} else {
|
||||
ticket = incrementalFromCompilerTicket(
|
||||
this.compiler, program, this.incrementalStrategy, this.programStrategy,
|
||||
modifiedResourceFiles);
|
||||
modifiedResourceFiles, /* perfRecorder */ null);
|
||||
}
|
||||
this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
|
||||
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 {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core';
|
||||
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 {ExpressionIdentifier, hasExpressionIdentifier} from '@angular/compiler-cli/src/ngtsc/typecheck/src/comments';
|
||||
import * as ts from 'typescript';
|
||||
|
@ -66,46 +67,53 @@ export class ReferencesAndRenameBuilder {
|
|||
|
||||
getRenameInfo(filePath: string, position: number):
|
||||
Omit<ts.RenameInfoSuccess, 'kind'|'kindModifiers'>|ts.RenameInfoFailure {
|
||||
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
||||
// We could not get a template at position so we assume the request came from outside the
|
||||
// template.
|
||||
if (templateInfo === undefined) {
|
||||
return this.tsLS.getRenameInfo(filePath, position);
|
||||
}
|
||||
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
|
||||
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
||||
// We could not get a template at position so we assume the request came from outside the
|
||||
// template.
|
||||
if (templateInfo === undefined) {
|
||||
return this.tsLS.getRenameInfo(filePath, position);
|
||||
}
|
||||
|
||||
const allTargetDetails = this.getTargetDetailsAtTemplatePosition(templateInfo, position);
|
||||
if (allTargetDetails === null) {
|
||||
return {canRename: false, localizedErrorMessage: 'Could not find template node at position.'};
|
||||
}
|
||||
const {templateTarget} = allTargetDetails[0];
|
||||
const templateTextAndSpan = getRenameTextAndSpanAtPosition(templateTarget, position);
|
||||
if (templateTextAndSpan === null) {
|
||||
return {canRename: false, localizedErrorMessage: 'Could not determine template node text.'};
|
||||
}
|
||||
const {text, span} = templateTextAndSpan;
|
||||
return {
|
||||
canRename: true,
|
||||
displayName: text,
|
||||
fullDisplayName: text,
|
||||
triggerSpan: span,
|
||||
};
|
||||
const allTargetDetails = this.getTargetDetailsAtTemplatePosition(templateInfo, position);
|
||||
if (allTargetDetails === null) {
|
||||
return {
|
||||
canRename: false,
|
||||
localizedErrorMessage: 'Could not find template node at position.',
|
||||
};
|
||||
}
|
||||
const {templateTarget} = allTargetDetails[0];
|
||||
const templateTextAndSpan = getRenameTextAndSpanAtPosition(templateTarget, position);
|
||||
if (templateTextAndSpan === null) {
|
||||
return {canRename: false, localizedErrorMessage: 'Could not determine template node text.'};
|
||||
}
|
||||
const {text, span} = templateTextAndSpan;
|
||||
return {
|
||||
canRename: true,
|
||||
displayName: text,
|
||||
fullDisplayName: text,
|
||||
triggerSpan: span,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
findRenameLocations(filePath: string, position: number): readonly ts.RenameLocation[]|undefined {
|
||||
this.ttc.generateAllTypeCheckBlocks();
|
||||
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
||||
// We could not get a template at position so we assume the request came from outside the
|
||||
// template.
|
||||
if (templateInfo === undefined) {
|
||||
const requestNode = this.getTsNodeAtPosition(filePath, position);
|
||||
if (requestNode === null) {
|
||||
return undefined;
|
||||
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
|
||||
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
||||
// We could not get a template at position so we assume the request came from outside the
|
||||
// template.
|
||||
if (templateInfo === undefined) {
|
||||
const requestNode = this.getTsNodeAtPosition(filePath, position);
|
||||
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):
|
||||
|
@ -148,55 +156,60 @@ export class ReferencesAndRenameBuilder {
|
|||
findRenameLocationsAtTypescriptPosition(
|
||||
filePath: string, position: number,
|
||||
requestOrigin: RequestOrigin): readonly ts.RenameLocation[]|undefined {
|
||||
let originalNodeText: string;
|
||||
if (requestOrigin.kind === RequestKind.TypeScript) {
|
||||
originalNodeText = requestOrigin.requestNode.getText();
|
||||
} else {
|
||||
const templateNodeText =
|
||||
getRenameTextAndSpanAtPosition(requestOrigin.requestNode, requestOrigin.position);
|
||||
if (templateNodeText === null) {
|
||||
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
|
||||
let originalNodeText: string;
|
||||
if (requestOrigin.kind === RequestKind.TypeScript) {
|
||||
originalNodeText = requestOrigin.requestNode.getText();
|
||||
} else {
|
||||
const templateNodeText =
|
||||
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;
|
||||
}
|
||||
originalNodeText = templateNodeText.text;
|
||||
}
|
||||
|
||||
const locations = this.tsLS.findRenameLocations(
|
||||
filePath, position, /*findInStrings*/ false, /*findInComments*/ false);
|
||||
if (locations === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const entries: Map<string, ts.RenameLocation> = new Map();
|
||||
for (const location of locations) {
|
||||
// TODO(atscott): Determine if a file is a shim file in a more robust way and make the API
|
||||
// available in an appropriate location.
|
||||
if (this.ttc.isTrackedTypeCheckFile(absoluteFrom(location.fileName))) {
|
||||
const entry = this.convertToTemplateDocumentSpan(location, this.ttc, originalNodeText);
|
||||
// There is no template node whose text matches the original rename request. Bail on
|
||||
// renaming completely rather than providing incomplete results.
|
||||
if (entry === null) {
|
||||
return undefined;
|
||||
const entries: Map<string, ts.RenameLocation> = new Map();
|
||||
for (const location of locations) {
|
||||
// TODO(atscott): Determine if a file is a shim file in a more robust way and make the API
|
||||
// available in an appropriate location.
|
||||
if (this.ttc.isTrackedTypeCheckFile(absoluteFrom(location.fileName))) {
|
||||
const entry = this.convertToTemplateDocumentSpan(location, this.ttc, originalNodeText);
|
||||
// There is no template node whose text matches the original rename request. Bail on
|
||||
// renaming completely rather than providing incomplete results.
|
||||
if (entry === null) {
|
||||
return undefined;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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 {
|
||||
this.ttc.generateAllTypeCheckBlocks();
|
||||
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
||||
if (templateInfo === undefined) {
|
||||
return this.getReferencesAtTypescriptPosition(filePath, position);
|
||||
}
|
||||
return this.getReferencesAtTemplatePosition(templateInfo, position);
|
||||
|
||||
return this.compiler.perfRecorder.inPhase(PerfPhase.LsReferencesAndRenames, () => {
|
||||
const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
||||
if (templateInfo === undefined) {
|
||||
return this.getReferencesAtTypescriptPosition(filePath, position);
|
||||
}
|
||||
return this.getReferencesAtTemplatePosition(templateInfo, position);
|
||||
});
|
||||
}
|
||||
|
||||
private getReferencesAtTemplatePosition(templateInfo: TemplateInfo, position: number):
|
||||
|
|
Loading…
Reference in New Issue