Skip to content

Commit

Permalink
refactor(compiler-cli): introduce the TemplateTypeChecker abstraction (
Browse files Browse the repository at this point in the history
…angular#38105)

This commit significantly refactors the 'typecheck' package to introduce a
new abstraction, the `TemplateTypeChecker`. To achieve this:

* a 'typecheck:api' package is introduced, containing common interfaces that
  consumers of the template type-checking infrastructure can depend on
  without incurring a dependency on the template type-checking machinery as
  a whole.
* interfaces for `TemplateTypeChecker` and `TypeCheckContext` are introduced
  which contain the abstract operations supported by the implementation
  classes `TemplateTypeCheckerImpl` and `TypeCheckContextImpl` respectively.
* the `TemplateTypeChecker` interface supports diagnostics on a whole
  program basis to start with, but the implementation is purposefully
  designed to support incremental diagnostics at a per-file or per-component
  level.
* `TemplateTypeChecker` supports direct access to the type check block of a
  component.
* the testing utility is refactored to be a lot more useful, and new tests
  are added for the new abstraction.

PR Close angular#38105
  • Loading branch information
alxhub authored and profanis committed Sep 5, 2020
1 parent ac8fcb5 commit 982dfc2
Show file tree
Hide file tree
Showing 43 changed files with 694 additions and 313 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,6 @@ class NoIncrementalBuild implements IncrementalBuild<any, any> {
priorTypeCheckingResultsFor(): null {
return null;
}

recordSuccessfulTypeCheck(): void {}
}
2 changes: 1 addition & 1 deletion packages/compiler-cli/src/ngtsc/annotations/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/shims:api",
"//packages/compiler-cli/src/ngtsc/transform",
"//packages/compiler-cli/src/ngtsc/typecheck",
"//packages/compiler-cli/src/ngtsc/typecheck/api",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//@types/node",
"@npm//typescript",
Expand Down
8 changes: 4 additions & 4 deletions packages/compiler-cli/src/ngtsc/annotations/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
import {TemplateSourceMapping, TypeCheckContext} from '../../typecheck';
import {TemplateSourceMapping, TypeCheckContext} from '../../typecheck/api';
import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300';
import {SubsetOfKeys} from '../../util/src/typescript';

Expand Down Expand Up @@ -426,10 +426,10 @@ export class ComponentDecoratorHandler implements
schemas = scope.schemas;
}

const bound = new R3TargetBinder(matcher).bind({template: meta.template.diagNodes});
const binder = new R3TargetBinder(matcher);
ctx.addTemplate(
new Reference(node), bound, pipes, schemas, meta.template.sourceMapping,
meta.template.file);
new Reference(node), binder, meta.template.diagNodes, pipes, schemas,
meta.template.sourceMapping, meta.template.file);
}

resolve(node: ClassDeclaration, analysis: Readonly<ComponentAnalysisData>):
Expand Down
1 change: 1 addition & 0 deletions packages/compiler-cli/src/ngtsc/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/switch",
"//packages/compiler-cli/src/ngtsc/transform",
"//packages/compiler-cli/src/ngtsc/typecheck",
"//packages/compiler-cli/src/ngtsc/typecheck/api",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//typescript",
],
Expand Down
17 changes: 8 additions & 9 deletions packages/compiler-cli/src/ngtsc/core/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecorato
import {CycleAnalyzer, ImportGraph} from '../../cycles';
import {ErrorCode, ngErrorCode} from '../../diagnostics';
import {checkForPrivateExports, ReferenceGraph} from '../../entry_point';
import {getSourceFileOrError, LogicalFileSystem} from '../../file_system';
import {LogicalFileSystem} from '../../file_system';
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
import {IncrementalBuildStrategy, IncrementalDriver} from '../../incremental';
import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer';
Expand All @@ -28,7 +28,8 @@ import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeRe
import {generatedFactoryTransform} from '../../shims';
import {ivySwitchTransform} from '../../switch';
import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
import {isTemplateDiagnostic, TemplateTypeChecker, TypeCheckContext, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck';
import {isTemplateDiagnostic, TemplateTypeCheckerImpl} from '../../typecheck';
import {TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck/api';
import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript';
import {LazyRoute, NgCompilerAdapter, NgCompilerOptions} from '../api';

Expand Down Expand Up @@ -209,6 +210,10 @@ export class NgCompiler {
return this.nextProgram;
}

getTemplateTypeChecker(): TemplateTypeChecker {
return this.ensureAnalyzed().templateTypeChecker;
}

/**
* Perform Angular's analysis step (as a precursor to `getDiagnostics` or `prepareEmit`)
* asynchronously.
Expand Down Expand Up @@ -494,12 +499,6 @@ export class NgCompiler {

const compilation = this.ensureAnalyzed();

// Execute the typeCheck phase of each decorator in the program.
const prepSpan = this.perfRecorder.start('typeCheckPrep');
const results = compilation.templateTypeChecker.refresh();
this.incrementalDriver.recordSuccessfulTypeCheck(results.perFileData);
this.perfRecorder.stop(prepSpan);

// Get the diagnostics.
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
const diagnostics: ts.Diagnostic[] = [];
Expand Down Expand Up @@ -734,7 +733,7 @@ export class NgCompiler {
handlers, reflector, this.perfRecorder, this.incrementalDriver,
this.options.compileNonExportedClasses !== false, dtsTransforms);

const templateTypeChecker = new TemplateTypeChecker(
const templateTypeChecker = new TemplateTypeCheckerImpl(
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver);

Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-cli/src/ngtsc/core/test/compiler_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {absoluteFrom as _, FileSystem, getFileSystem, getSourceFileOrError, NgtscCompilerHost, setFileSystem} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing';
import {NoopIncrementalBuildStrategy} from '../../incremental';
import {ReusedProgramStrategy} from '../../typecheck/src/augmented_program';
import {ReusedProgramStrategy} from '../../typecheck';
import {NgCompilerOptions} from '../api';
import {NgCompiler} from '../src/compiler';
import {NgCompilerHost} from '../src/host';
Expand Down
6 changes: 6 additions & 0 deletions packages/compiler-cli/src/ngtsc/incremental/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export interface IncrementalBuild<AnalysisT, FileTypeCheckDataT> {
* Retrieve the prior type-checking work, if any, that's been done for the given source file.
*/
priorTypeCheckingResultsFor(fileSf: ts.SourceFile): FileTypeCheckDataT|null;

/**
* Reports that template type-checking has completed successfully, with a map of type-checking
* data for each user file which can be reused in a future incremental iteration.
*/
recordSuccessfulTypeCheck(results: Map<AbsoluteFsPath, FileTypeCheckDataT>): void;
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/compiler-cli/src/ngtsc/incremental/src/noop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ import {IncrementalBuild} from '../api';
export const NOOP_INCREMENTAL_BUILD: IncrementalBuild<any, any> = {
priorWorkFor: () => null,
priorTypeCheckingResultsFor: () => null,
recordSuccessfulTypeCheck: () => {},
};
2 changes: 1 addition & 1 deletion packages/compiler-cli/src/ngtsc/incremental/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as ts from 'typescript';

import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
import {ClassRecord, TraitCompiler} from '../../transform';
import {FileTypeCheckingData} from '../../typecheck/src/context';
import {FileTypeCheckingData} from '../../typecheck/src/checker';
import {IncrementalBuild} from '../api';

import {FileDependencyGraph} from './dependency_tracking';
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-cli/src/ngtsc/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {ReusedProgramStrategy} from './typecheck';
* command-line main() function or the Angular CLI.
*/
export class NgtscProgram implements api.Program {
private compiler: NgCompiler;
readonly compiler: NgCompiler;

/**
* The primary TypeScript program, which is used for analysis and emit.
Expand Down
1 change: 0 additions & 1 deletion packages/compiler-cli/src/ngtsc/scope/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/typecheck",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//typescript",
],
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-cli/src/ngtsc/transform/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/translator",
"//packages/compiler-cli/src/ngtsc/typecheck",
"//packages/compiler-cli/src/ngtsc/typecheck/api",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//typescript",
],
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-cli/src/ngtsc/transform/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {Reexport} from '../../imports';
import {IndexingContext} from '../../indexer';
import {ClassDeclaration, Decorator} from '../../reflection';
import {ImportManager} from '../../translator';
import {TypeCheckContext} from '../../typecheck';
import {TypeCheckContext} from '../../typecheck/api';

export enum HandlerPrecedence {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {IncrementalBuild} from '../../incremental/api';
import {IndexingContext} from '../../indexer';
import {PerfRecorder} from '../../perf';
import {ClassDeclaration, Decorator, ReflectionHost} from '../../reflection';
import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck';
import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck/api';
import {getSourceFile, isExported} from '../../util/src/typescript';

import {AnalysisOutput, CompileResult, DecoratorHandler, HandlerFlags, HandlerPrecedence, ResolveResult} from './api';
Expand Down
5 changes: 4 additions & 1 deletion packages/compiler-cli/src/ngtsc/typecheck/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ package(default_visibility = ["//visibility:public"])

ts_library(
name = "typecheck",
srcs = glob(["**/*.ts"]),
srcs = glob(
["**/*.ts"],
),
deps = [
"//packages:types",
"//packages/compiler",
Expand All @@ -17,6 +19,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/shims",
"//packages/compiler-cli/src/ngtsc/shims:api",
"//packages/compiler-cli/src/ngtsc/translator",
"//packages/compiler-cli/src/ngtsc/typecheck/api",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//@types/node",
"@npm//typescript",
Expand Down
18 changes: 18 additions & 0 deletions packages/compiler-cli/src/ngtsc/typecheck/api/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
load("//tools:defaults.bzl", "ts_library")

package(default_visibility = ["//visibility:public"])

ts_library(
name = "api",
srcs = glob(["**/*.ts"]),
module_name = "@angular/compiler-cli/src/ngtsc/typecheck/api",
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/reflection",
"@npm//typescript",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {AbsoluteFsPath} from '../../file_system';
import {Reference} from '../../imports';
import {TemplateGuardMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection';
import {ComponentToShimMappingStrategy} from './context';


/**
Expand Down Expand Up @@ -278,6 +277,25 @@ export interface ExternalTemplateSourceMapping {
templateUrl: string;
}

/**
* Abstracts the operation of determining which shim file will host a particular component's
* template type-checking code.
*
* Different consumers of the type checking infrastructure may choose different approaches to
* optimize for their specific use case (for example, the command-line compiler optimizes for
* efficient `ts.Program` reuse in watch mode).
*/
export interface ComponentToShimMappingStrategy {
/**
* Given a component, determine a path to the shim file into which that component's type checking
* code will be generated.
*
* A major constraint is that components in different input files must not share the same shim
* file. The behavior of the template type-checking system is undefined if this is violated.
*/
shimPathForComponent(node: ts.ClassDeclaration): AbsoluteFsPath;
}

/**
* Strategy used to manage a `ts.Program` which contains template type-checking code and update it
* over time.
Expand Down
43 changes: 43 additions & 0 deletions packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @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
*/

import {TmplAstNode} from '@angular/compiler';

import * as ts from 'typescript';

/**
* Interface to the Angular Template Type Checker to extract diagnostics and intelligence from the
* compiler's understanding of component templates.
*
* This interface is analogous to TypeScript's own `ts.TypeChecker` API.
*
* In general, this interface supports two kinds of operations:
* - updating Type Check Blocks (TCB)s that capture the template in the form of TypeScript code
* - querying information about available TCBs, including diagnostics
*
* Once a TCB is available, information about it can be queried. If no TCB is available to answer a
* query, depending on the method either `null` will be returned or an error will be thrown.
*/
export interface TemplateTypeChecker {
/**
* Get all `ts.Diagnostic`s currently available for the given `ts.SourceFile`.
*
* This method will fail (throw) if there are components within the `ts.SourceFile` that do not
* have TCBs available.
*/
getDiagnosticsForFile(sf: ts.SourceFile): ts.Diagnostic[];

/**
* Retrieve the top-level node representing the TCB for the given component.
*
* This can return `null` if there is no TCB available for the component.
*
* This method always runs in `OptimizeFor.SingleFile` mode.
*/
getTypeCheckBlock(component: ts.ClassDeclaration): ts.Node|null;
}
52 changes: 52 additions & 0 deletions packages/compiler-cli/src/ngtsc/typecheck/api/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @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
*/

import {ParseSourceFile, R3TargetBinder, SchemaMetadata, TmplAstNode} from '@angular/compiler';
import * as ts from 'typescript';

import {Reference} from '../../imports';
import {ClassDeclaration} from '../../reflection';

import {TemplateSourceMapping, TypeCheckableDirectiveMeta} from './api';

/**
* A currently pending type checking operation, into which templates for type-checking can be
* registered.
*/
export interface TypeCheckContext {
/**
* Register a template to potentially be type-checked.
*
* Templates registered via `addTemplate` are available for checking, but might be skipped if
* checking of that component is not required. This can happen for a few reasons, including if
* the component was previously checked and the prior results are still valid.
*
* @param ref a `Reference` to the component class which yielded this template.
* @param binder an `R3TargetBinder` which encapsulates the scope of this template, including all
* available directives.
* @param template the original template AST of this component.
* @param pipes a `Map` of pipes available within the scope of this template.
* @param schemas any schemas which apply to this template.
* @param sourceMapping a `TemplateSourceMapping` instance which describes the origin of the
* template text described by the AST.
* @param file the `ParseSourceFile` associated with the template.
*/
addTemplate(
ref: Reference<ClassDeclaration<ts.ClassDeclaration>>,
binder: R3TargetBinder<TypeCheckableDirectiveMeta>, template: TmplAstNode[],
pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>,
schemas: SchemaMetadata[], sourceMapping: TemplateSourceMapping, file: ParseSourceFile): void;
}

/**
* Interface to trigger generation of type-checking code for a program given a new
* `TypeCheckContext`.
*/
export interface ProgramTypeCheckAdapter {
typeCheck(sf: ts.SourceFile, ctx: TypeCheckContext): void;
}
11 changes: 11 additions & 0 deletions packages/compiler-cli/src/ngtsc/typecheck/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @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
*/

export * from './api';
export * from './checker';
export * from './context';
9 changes: 4 additions & 5 deletions packages/compiler-cli/src/ngtsc/typecheck/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/

export * from './src/api';
export {ReusedProgramStrategy} from './src/augmented_program';
export {TemplateTypeChecker, ProgramTypeCheckAdapter} from './src/checker';
export {TypeCheckContext} from './src/context';
export {TemplateDiagnostic, isTemplateDiagnostic} from './src/diagnostics';
export {TypeCheckShimGenerator} from './src/shim';
export {FileTypeCheckingData, TemplateTypeCheckerImpl} from './src/checker';
export {TypeCheckContextImpl} from './src/context';
export {isTemplateDiagnostic, TemplateDiagnostic} from './src/diagnostics';
export {TypeCheckProgramHost} from './src/host';
export {TypeCheckShimGenerator} from './src/shim';
export {typeCheckFilePath} from './src/type_check_file';
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import * as ts from 'typescript';

import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
import {retagAllTsFiles, untagAllTsFiles} from '../../shims';
import {TypeCheckingProgramStrategy, UpdateMode} from '../api';

import {TypeCheckingProgramStrategy, UpdateMode} from './api';
import {TypeCheckProgramHost} from './host';
import {TypeCheckShimGenerator} from './shim';

Expand Down
Loading

0 comments on commit 982dfc2

Please sign in to comment.