refactor(compiler-cli): introduce the TemplateTypeChecker abstraction (#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 #38105
This commit is contained in:
		
							parent
							
								
									736f6337b2
								
							
						
					
					
						commit
						16c7441c2f
					
				| @ -90,4 +90,6 @@ class NoIncrementalBuild implements IncrementalBuild<any, any> { | ||||
|   priorTypeCheckingResultsFor(): null { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   recordSuccessfulTypeCheck(): void {} | ||||
| } | ||||
|  | ||||
| @ -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", | ||||
|  | ||||
| @ -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'; | ||||
| 
 | ||||
| @ -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>): | ||||
|  | ||||
| @ -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", | ||||
|     ], | ||||
|  | ||||
| @ -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'; | ||||
| @ -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'; | ||||
| 
 | ||||
| @ -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. | ||||
| @ -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[] = []; | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
| @ -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'; | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -11,4 +11,5 @@ import {IncrementalBuild} from '../api'; | ||||
| export const NOOP_INCREMENTAL_BUILD: IncrementalBuild<any, any> = { | ||||
|   priorWorkFor: () => null, | ||||
|   priorTypeCheckingResultsFor: () => null, | ||||
|   recordSuccessfulTypeCheck: () => {}, | ||||
| }; | ||||
|  | ||||
| @ -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'; | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
| @ -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", | ||||
|     ], | ||||
|  | ||||
| @ -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", | ||||
|     ], | ||||
|  | ||||
| @ -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 { | ||||
|   /** | ||||
|  | ||||
| @ -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'; | ||||
|  | ||||
| @ -4,7 +4,9 @@ package(default_visibility = ["//visibility:public"]) | ||||
| 
 | ||||
| ts_library( | ||||
|     name = "typecheck", | ||||
|     srcs = glob(["**/*.ts"]), | ||||
|     srcs = glob( | ||||
|         ["**/*.ts"], | ||||
|     ), | ||||
|     deps = [ | ||||
|         "//packages:types", | ||||
|         "//packages/compiler", | ||||
| @ -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", | ||||
|  | ||||
							
								
								
									
										18
									
								
								packages/compiler-cli/src/ngtsc/typecheck/api/BUILD.bazel
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/compiler-cli/src/ngtsc/typecheck/api/BUILD.bazel
									
									
									
									
									
										Normal file
									
								
							| @ -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", | ||||
|     ], | ||||
| ) | ||||
| @ -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'; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
| @ -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. | ||||
							
								
								
									
										43
									
								
								packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								packages/compiler-cli/src/ngtsc/typecheck/api/context.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								packages/compiler-cli/src/ngtsc/typecheck/api/context.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								packages/compiler-cli/src/ngtsc/typecheck/api/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/compiler-cli/src/ngtsc/typecheck/api/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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'; | ||||
| @ -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'; | ||||
|  | ||||
| @ -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'; | ||||
| 
 | ||||
|  | ||||
| @ -13,99 +13,160 @@ import {ReferenceEmitter} from '../../imports'; | ||||
| import {IncrementalBuild} from '../../incremental/api'; | ||||
| import {ReflectionHost} from '../../reflection'; | ||||
| import {isShim} from '../../shims'; | ||||
| import {getSourceFileOrNull} from '../../util/src/typescript'; | ||||
| import {ProgramTypeCheckAdapter, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from '../api'; | ||||
| 
 | ||||
| import {TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from './api'; | ||||
| import {FileTypeCheckingData, TypeCheckContext, TypeCheckRequest} from './context'; | ||||
| import {shouldReportDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics'; | ||||
| 
 | ||||
| /** | ||||
|  * 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; | ||||
| } | ||||
| import {ShimTypeCheckingData, TypeCheckContextImpl, TypeCheckingHost} from './context'; | ||||
| import {findTypeCheckBlock, shouldReportDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics'; | ||||
| import {TemplateSourceManager} from './source'; | ||||
| 
 | ||||
| /** | ||||
|  * Primary template type-checking engine, which performs type-checking using a | ||||
|  * `TypeCheckingProgramStrategy` for type-checking program maintenance, and the | ||||
|  * `ProgramTypeCheckAdapter` for generation of template type-checking code. | ||||
|  */ | ||||
| export class TemplateTypeChecker { | ||||
|   private files = new Map<AbsoluteFsPath, FileTypeCheckingData>(); | ||||
| export class TemplateTypeCheckerImpl implements TemplateTypeChecker { | ||||
|   private state = new Map<AbsoluteFsPath, FileTypeCheckingData>(); | ||||
|   private isComplete = false; | ||||
| 
 | ||||
|   constructor( | ||||
|       private originalProgram: ts.Program, | ||||
|       private typeCheckingStrategy: TypeCheckingProgramStrategy, | ||||
|       readonly typeCheckingStrategy: TypeCheckingProgramStrategy, | ||||
|       private typeCheckAdapter: ProgramTypeCheckAdapter, private config: TypeCheckingConfig, | ||||
|       private refEmitter: ReferenceEmitter, private reflector: ReflectionHost, | ||||
|       private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>, | ||||
|       private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>) {} | ||||
| 
 | ||||
|   /** | ||||
|    * Reset the internal type-checking program by generating type-checking code from the user's | ||||
|    * program. | ||||
|    */ | ||||
|   refresh(): TypeCheckRequest { | ||||
|     this.files.clear(); | ||||
| 
 | ||||
|     const ctx = new TypeCheckContext( | ||||
|         this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector); | ||||
| 
 | ||||
|     // Typecheck all the files.
 | ||||
|     for (const sf of this.originalProgram.getSourceFiles()) { | ||||
|       if (sf.isDeclarationFile || isShim(sf)) { | ||||
|         continue; | ||||
|       } | ||||
| 
 | ||||
|       const previousResults = this.priorBuild.priorTypeCheckingResultsFor(sf); | ||||
|       if (previousResults === null) { | ||||
|         // Previous results were not available, so generate new type-checking code for this file.
 | ||||
|         this.typeCheckAdapter.typeCheck(sf, ctx); | ||||
|       } else { | ||||
|         // Previous results were available, and can be adopted into the current build.
 | ||||
|         ctx.adoptPriorResults(sf, previousResults); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const results = ctx.finalize(); | ||||
|     this.typeCheckingStrategy.updateFiles(results.updates, UpdateMode.Complete); | ||||
|     for (const [file, fileData] of results.perFileData) { | ||||
|       this.files.set(file, fileData); | ||||
|     } | ||||
| 
 | ||||
|     return results; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Retrieve type-checking diagnostics from the given `ts.SourceFile` using the most recent | ||||
|    * type-checking program. | ||||
|    */ | ||||
|   getDiagnosticsForFile(sf: ts.SourceFile): ts.Diagnostic[] { | ||||
|     const path = absoluteFromSourceFile(sf); | ||||
|     if (!this.files.has(path)) { | ||||
|       return []; | ||||
|     } | ||||
|     const fileRecord = this.files.get(path)!; | ||||
|     this.ensureAllShimsForAllFiles(); | ||||
| 
 | ||||
|     const sfPath = absoluteFromSourceFile(sf); | ||||
|     const fileRecord = this.state.get(sfPath)!; | ||||
| 
 | ||||
|     const typeCheckProgram = this.typeCheckingStrategy.getProgram(); | ||||
| 
 | ||||
|     const diagnostics: (ts.Diagnostic|null)[] = []; | ||||
|     if (fileRecord.hasInlines) { | ||||
|       const inlineSf = getSourceFileOrError(typeCheckProgram, path); | ||||
|       const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath); | ||||
|       diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map( | ||||
|           diag => convertDiagnostic(diag, fileRecord.sourceResolver))); | ||||
|           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.sourceResolver))); | ||||
|           diag => convertDiagnostic(diag, fileRecord.sourceManager))); | ||||
|       diagnostics.push(...shimRecord.genesisDiagnostics); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null); | ||||
|   } | ||||
| 
 | ||||
|   getTypeCheckBlock(component: ts.ClassDeclaration): ts.Node|null { | ||||
|     this.ensureAllShimsForAllFiles(); | ||||
| 
 | ||||
|     const program = this.typeCheckingStrategy.getProgram(); | ||||
|     const filePath = absoluteFromSourceFile(component.getSourceFile()); | ||||
|     const shimPath = this.typeCheckingStrategy.shimPathForComponent(component); | ||||
| 
 | ||||
|     if (!this.state.has(filePath)) { | ||||
|       throw new Error(`Error: no data for source file: ${filePath}`); | ||||
|     } | ||||
|     const fileRecord = this.state.get(filePath)!; | ||||
|     const id = fileRecord.sourceManager.getTemplateId(component); | ||||
| 
 | ||||
|     const shimSf = getSourceFileOrNull(program, shimPath); | ||||
|     if (shimSf === null) { | ||||
|       throw new Error(`Error: no shim file in program: ${shimPath}`); | ||||
|     } | ||||
| 
 | ||||
|     let node: ts.Node|null = findTypeCheckBlock(shimSf, id); | ||||
|     if (node === null) { | ||||
|       // Try for an inline block.
 | ||||
|       const inlineSf = getSourceFileOrError(program, filePath); | ||||
|       node = findTypeCheckBlock(inlineSf, id); | ||||
|     } | ||||
| 
 | ||||
|     return node; | ||||
|   } | ||||
| 
 | ||||
|   private maybeAdoptPriorResultsForFile(sf: ts.SourceFile): void { | ||||
|     const sfPath = absoluteFromSourceFile(sf); | ||||
|     if (this.state.has(sfPath)) { | ||||
|       const existingResults = this.state.get(sfPath)!; | ||||
| 
 | ||||
|       if (existingResults.isComplete) { | ||||
|         // All data for this file has already been generated, so no need to adopt anything.
 | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const previousResults = this.priorBuild.priorTypeCheckingResultsFor(sf); | ||||
|     if (previousResults === null || !previousResults.isComplete) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this.state.set(sfPath, previousResults); | ||||
|   } | ||||
| 
 | ||||
|   private ensureAllShimsForAllFiles(): void { | ||||
|     if (this.isComplete) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const host = new WholeProgramTypeCheckingHost(this); | ||||
|     const ctx = this.newContext(host); | ||||
| 
 | ||||
|     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 newContext(host: TypeCheckingHost): TypeCheckContextImpl { | ||||
|     return new TypeCheckContextImpl( | ||||
|         this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector, | ||||
|         host); | ||||
|   } | ||||
| 
 | ||||
|   private updateFromContext(ctx: TypeCheckContextImpl): void { | ||||
|     const updates = ctx.finalize(); | ||||
|     this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental); | ||||
|     this.priorBuild.recordSuccessfulTypeCheck(this.state); | ||||
|   } | ||||
| 
 | ||||
|   getFileData(path: AbsoluteFsPath): FileTypeCheckingData { | ||||
|     if (!this.state.has(path)) { | ||||
|       this.state.set(path, { | ||||
|         hasInlines: false, | ||||
|         sourceManager: new TemplateSourceManager(), | ||||
|         isComplete: false, | ||||
|         shimData: new Map(), | ||||
|       }); | ||||
|     } | ||||
|     return this.state.get(path)!; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function convertDiagnostic( | ||||
| @ -115,3 +176,65 @@ function convertDiagnostic( | ||||
|   } | ||||
|   return translateDiagnostic(diag, sourceResolver); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data for template type-checking related to a specific input file in the user's program (which | ||||
|  * contains components to be checked). | ||||
|  */ | ||||
| export interface FileTypeCheckingData { | ||||
|   /** | ||||
|    * Whether the type-checking shim required any inline changes to the original file, which affects | ||||
|    * whether the shim can be reused. | ||||
|    */ | ||||
|   hasInlines: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * Source mapping information for mapping diagnostics from inlined type check blocks back to the | ||||
|    * original template. | ||||
|    */ | ||||
|   sourceManager: TemplateSourceManager; | ||||
| 
 | ||||
|   /** | ||||
|    * Data for each shim generated from this input file. | ||||
|    * | ||||
|    * A single input file will generate one or more shim files that actually contain template | ||||
|    * type-checking code. | ||||
|    */ | ||||
|   shimData: Map<AbsoluteFsPath, ShimTypeCheckingData>; | ||||
| 
 | ||||
|   /** | ||||
|    * Whether the template type-checker is certain that all components from this input file have had | ||||
|    * type-checking code generated into shims. | ||||
|    */ | ||||
|   isComplete: boolean; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Drives a `TypeCheckContext` to generate type-checking code for every component in the program. | ||||
|  */ | ||||
| class WholeProgramTypeCheckingHost implements TypeCheckingHost { | ||||
|   constructor(private impl: TemplateTypeCheckerImpl) {} | ||||
| 
 | ||||
|   getSourceManager(sfPath: AbsoluteFsPath): TemplateSourceManager { | ||||
|     return this.impl.getFileData(sfPath).sourceManager; | ||||
|   } | ||||
| 
 | ||||
|   shouldCheckComponent(node: ts.ClassDeclaration): boolean { | ||||
|     const fileData = this.impl.getFileData(absoluteFromSourceFile(node.getSourceFile())); | ||||
|     const shimPath = this.impl.typeCheckingStrategy.shimPathForComponent(node); | ||||
|     // The component needs to be checked unless the shim which would contain it already exists.
 | ||||
|     return !fileData.shimData.has(shimPath); | ||||
|   } | ||||
| 
 | ||||
|   recordShimData(sfPath: AbsoluteFsPath, data: ShimTypeCheckingData): void { | ||||
|     const fileData = this.impl.getFileData(sfPath); | ||||
|     fileData.shimData.set(data.path, data); | ||||
|     if (data.hasInlines) { | ||||
|       fileData.hasInlines = true; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   recordComplete(sfPath: AbsoluteFsPath): void { | ||||
|     this.impl.getFileData(sfPath).isComplete = true; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -6,16 +6,15 @@ | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {BoundTarget, ParseSourceFile, SchemaMetadata} from '@angular/compiler'; | ||||
| import {ParseSourceFile, R3TargetBinder, SchemaMetadata, TmplAstNode} from '@angular/compiler'; | ||||
| import * as ts from 'typescript'; | ||||
| 
 | ||||
| import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system'; | ||||
| import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports'; | ||||
| import {ClassDeclaration, ReflectionHost} from '../../reflection'; | ||||
| import {ImportManager} from '../../translator'; | ||||
| import {ComponentToShimMappingStrategy, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api'; | ||||
| 
 | ||||
| import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, TypeCtorMetadata} from './api'; | ||||
| import {TemplateSourceResolver} from './diagnostics'; | ||||
| import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom'; | ||||
| import {Environment} from './environment'; | ||||
| import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob'; | ||||
| @ -24,55 +23,6 @@ import {generateTypeCheckBlock, requiresInlineTypeCheckBlock} from './type_check | ||||
| import {TypeCheckFile} from './type_check_file'; | ||||
| import {generateInlineTypeCtor, requiresInlineTypeCtor} from './type_constructor'; | ||||
| 
 | ||||
| /** | ||||
|  * Complete type-checking code generated for the user's program, ready for input into the | ||||
|  * type-checking engine. | ||||
|  */ | ||||
| export interface TypeCheckRequest { | ||||
|   /** | ||||
|    * Map of source filenames to new contents for those files. | ||||
|    * | ||||
|    * This includes both contents of type-checking shim files, as well as changes to any user files | ||||
|    * which needed to be made to support template type-checking. | ||||
|    */ | ||||
|   updates: Map<AbsoluteFsPath, string>; | ||||
| 
 | ||||
|   /** | ||||
|    * Map containing additional data for each type-checking shim that is required to support | ||||
|    * generation of diagnostics. | ||||
|    */ | ||||
|   perFileData: Map<AbsoluteFsPath, FileTypeCheckingData>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data for template type-checking related to a specific input file in the user's program (which | ||||
|  * contains components to be checked). | ||||
|  */ | ||||
| export interface FileTypeCheckingData { | ||||
|   /** | ||||
|    * Whether the type-checking shim required any inline changes to the original file, which affects | ||||
|    * whether the shim can be reused. | ||||
|    */ | ||||
|   hasInlines: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * Source mapping information for mapping diagnostics from inlined type check blocks back to the | ||||
|    * original template. | ||||
|    */ | ||||
|   sourceResolver: TemplateSourceResolver; | ||||
| 
 | ||||
|   /** | ||||
|    * Data for each shim generated from this input file. | ||||
|    * | ||||
|    * A single input file will generate one or more shim files that actually contain template | ||||
|    * type-checking code. | ||||
|    */ | ||||
|   shimData: Map<AbsoluteFsPath, ShimTypeCheckingData>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data specific to a single shim generated from an input file. | ||||
|  */ | ||||
| export interface ShimTypeCheckingData { | ||||
|   /** | ||||
|    * Path to the shim file. | ||||
| @ -85,6 +35,11 @@ export interface ShimTypeCheckingData { | ||||
|    * Some diagnostics are produced during creation time and are tracked here. | ||||
|    */ | ||||
|   genesisDiagnostics: ts.Diagnostic[]; | ||||
| 
 | ||||
|   /** | ||||
|    * Whether any inline operations for the input file were required to generate this shim. | ||||
|    */ | ||||
|   hasInlines: boolean; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -126,38 +81,55 @@ export interface PendingShimData { | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Abstracts the operation of determining which shim file will host a particular component's | ||||
|  * template type-checking code. | ||||
|  * Adapts the `TypeCheckContextImpl` to the larger template type-checking system. | ||||
|  * | ||||
|  * 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). | ||||
|  * Through this interface, a single `TypeCheckContextImpl` (which represents one "pass" of template | ||||
|  * type-checking) requests information about the larger state of type-checking, as well as reports | ||||
|  * back its results once finalized. | ||||
|  */ | ||||
| export interface ComponentToShimMappingStrategy { | ||||
| export interface TypeCheckingHost { | ||||
|   /** | ||||
|    * 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. | ||||
|    * Retrieve the `TemplateSourceManager` responsible for components in the given input file path. | ||||
|    */ | ||||
|   shimPathForComponent(node: ts.ClassDeclaration): AbsoluteFsPath; | ||||
|   getSourceManager(sfPath: AbsoluteFsPath): TemplateSourceManager; | ||||
| 
 | ||||
|   /** | ||||
|    * Whether a particular component class should be included in the current type-checking pass. | ||||
|    * | ||||
|    * Not all components offered to the `TypeCheckContext` for checking may require processing. For | ||||
|    * example, the component may have results already available from a prior pass or from a previous | ||||
|    * program. | ||||
|    */ | ||||
|   shouldCheckComponent(node: ts.ClassDeclaration): boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * Report data from a shim generated from the given input file path. | ||||
|    */ | ||||
|   recordShimData(sfPath: AbsoluteFsPath, data: ShimTypeCheckingData): void; | ||||
| 
 | ||||
|   /** | ||||
|    * Record that all of the components within the given input file path had code generated - that | ||||
|    * is, coverage for the file can be considered complete. | ||||
|    */ | ||||
|   recordComplete(sfPath: AbsoluteFsPath): void; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * A template type checking context for a program. | ||||
|  * | ||||
|  * The `TypeCheckContext` allows registration of components and their templates which need to be | ||||
|  * type checked. | ||||
|  */ | ||||
| export class TypeCheckContext { | ||||
| export class TypeCheckContextImpl implements TypeCheckContext { | ||||
|   private fileMap = new Map<AbsoluteFsPath, PendingFileTypeCheckingData>(); | ||||
| 
 | ||||
|   constructor( | ||||
|       private config: TypeCheckingConfig, | ||||
|       private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>, | ||||
|       private componentMappingStrategy: ComponentToShimMappingStrategy, | ||||
|       private refEmitter: ReferenceEmitter, private reflector: ReflectionHost) {} | ||||
|       private refEmitter: ReferenceEmitter, private reflector: ReflectionHost, | ||||
|       private host: TypeCheckingHost) {} | ||||
| 
 | ||||
|   /** | ||||
|    * A `Map` of `ts.SourceFile`s that the context has seen to the operations (additions of methods | ||||
| @ -171,22 +143,6 @@ export class TypeCheckContext { | ||||
|    */ | ||||
|   private typeCtorPending = new Set<ts.ClassDeclaration>(); | ||||
| 
 | ||||
|   /** | ||||
|    * Map of data for file paths which was adopted from a prior compilation. | ||||
|    * | ||||
|    * This data allows the `TypeCheckContext` to generate a `TypeCheckRequest` which can interpret | ||||
|    * diagnostics from type-checking shims included in the prior compilation. | ||||
|    */ | ||||
|   private adoptedFiles = new Map<AbsoluteFsPath, FileTypeCheckingData>(); | ||||
| 
 | ||||
|   /** | ||||
|    * Record the `FileTypeCheckingData` from a previous program that's associated with a particular | ||||
|    * source file. | ||||
|    */ | ||||
|   adoptPriorResults(sf: ts.SourceFile, data: FileTypeCheckingData): void { | ||||
|     this.adoptedFiles.set(absoluteFromSourceFile(sf), data); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Record a template for the given component `node`, with a `SelectorMatcher` for directive | ||||
|    * matching. | ||||
| @ -197,12 +153,17 @@ export class TypeCheckContext { | ||||
|    */ | ||||
|   addTemplate( | ||||
|       ref: Reference<ClassDeclaration<ts.ClassDeclaration>>, | ||||
|       boundTarget: BoundTarget<TypeCheckableDirectiveMeta>, | ||||
|       binder: R3TargetBinder<TypeCheckableDirectiveMeta>, template: TmplAstNode[], | ||||
|       pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>, | ||||
|       schemas: SchemaMetadata[], sourceMapping: TemplateSourceMapping, | ||||
|       file: ParseSourceFile): void { | ||||
|     if (!this.host.shouldCheckComponent(ref.node)) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const fileData = this.dataForFile(ref.node.getSourceFile()); | ||||
|     const shimData = this.pendingShimForComponent(ref.node); | ||||
|     const boundTarget = binder.bind({template}); | ||||
|     // Get all of the directives used in the template and record type constructors for all of them.
 | ||||
|     for (const dir of boundTarget.getUsedDirectives()) { | ||||
|       const dirRef = dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>; | ||||
| @ -308,7 +269,7 @@ export class TypeCheckContext { | ||||
|     return code; | ||||
|   } | ||||
| 
 | ||||
|   finalize(): TypeCheckRequest { | ||||
|   finalize(): Map<AbsoluteFsPath, string> { | ||||
|     // First, build the map of updates to source files.
 | ||||
|     const updates = new Map<AbsoluteFsPath, string>(); | ||||
|     for (const originalSf of this.opMap.keys()) { | ||||
| @ -318,39 +279,23 @@ export class TypeCheckContext { | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const results: TypeCheckRequest = { | ||||
|       updates: updates, | ||||
|       perFileData: new Map<AbsoluteFsPath, FileTypeCheckingData>(), | ||||
|     }; | ||||
| 
 | ||||
|     // Then go through each input file that has pending code generation operations.
 | ||||
|     for (const [sfPath, pendingFileData] of this.fileMap) { | ||||
|       const fileData: FileTypeCheckingData = { | ||||
|         hasInlines: pendingFileData.hasInlines, | ||||
|         shimData: new Map(), | ||||
|         sourceResolver: pendingFileData.sourceManager, | ||||
|       }; | ||||
| 
 | ||||
|       // For each input file, consider generation operations for each of its shims.
 | ||||
|       for (const [shimPath, pendingShimData] of pendingFileData.shimData) { | ||||
|         updates.set(pendingShimData.file.fileName, pendingShimData.file.render()); | ||||
|         fileData.shimData.set(shimPath, { | ||||
|       for (const pendingShimData of pendingFileData.shimData.values()) { | ||||
|         this.host.recordShimData(sfPath, { | ||||
|           genesisDiagnostics: [ | ||||
|             ...pendingShimData.domSchemaChecker.diagnostics, | ||||
|             ...pendingShimData.oobRecorder.diagnostics, | ||||
|           ], | ||||
|           hasInlines: pendingFileData.hasInlines, | ||||
|           path: pendingShimData.file.fileName, | ||||
|         }); | ||||
|         updates.set(pendingShimData.file.fileName, pendingShimData.file.render()); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|       results.perFileData.set(sfPath, fileData); | ||||
|     } | ||||
| 
 | ||||
|     for (const [sfPath, fileData] of this.adoptedFiles.entries()) { | ||||
|       results.perFileData.set(sfPath, fileData); | ||||
|     } | ||||
| 
 | ||||
|     return results; | ||||
|     return updates; | ||||
|   } | ||||
| 
 | ||||
|   private addInlineTypeCheckBlock( | ||||
| @ -385,12 +330,10 @@ export class TypeCheckContext { | ||||
|   private dataForFile(sf: ts.SourceFile): PendingFileTypeCheckingData { | ||||
|     const sfPath = absoluteFromSourceFile(sf); | ||||
| 
 | ||||
|     const sourceManager = new TemplateSourceManager(); | ||||
| 
 | ||||
|     if (!this.fileMap.has(sfPath)) { | ||||
|       const data: PendingFileTypeCheckingData = { | ||||
|         hasInlines: false, | ||||
|         sourceManager, | ||||
|         sourceManager: this.host.getSourceManager(sfPath), | ||||
|         shimData: new Map(), | ||||
|       }; | ||||
|       this.fileMap.set(sfPath, data); | ||||
|  | ||||
| @ -9,8 +9,8 @@ import {AbsoluteSourceSpan, ParseSourceSpan} from '@angular/compiler'; | ||||
| import * as ts from 'typescript'; | ||||
| 
 | ||||
| import {getTokenAtPosition} from '../../util/src/typescript'; | ||||
| import {ExternalTemplateSourceMapping, TemplateId, TemplateSourceMapping} from '../api'; | ||||
| 
 | ||||
| import {ExternalTemplateSourceMapping, TemplateId, TemplateSourceMapping} from './api'; | ||||
| 
 | ||||
| /** | ||||
|  * A `ts.Diagnostic` with additional information about the diagnostic related to template | ||||
| @ -28,6 +28,8 @@ export interface TemplateDiagnostic extends ts.Diagnostic { | ||||
|  * in a TCB and map them back to original locations in the template. | ||||
|  */ | ||||
| export interface TemplateSourceResolver { | ||||
|   getTemplateId(node: ts.ClassDeclaration): TemplateId; | ||||
| 
 | ||||
|   /** | ||||
|    * For the given template id, retrieve the original source mapping which describes how the offsets | ||||
|    * in the template should be interpreted. | ||||
| @ -140,6 +142,15 @@ export function translateDiagnostic( | ||||
|       mapping, span, diagnostic.category, diagnostic.code, diagnostic.messageText); | ||||
| } | ||||
| 
 | ||||
| export function findTypeCheckBlock(file: ts.SourceFile, id: TemplateId): ts.Node|null { | ||||
|   for (const stmt of file.statements) { | ||||
|     if (ts.isFunctionDeclaration(stmt) && getTemplateId(stmt, file) === id) { | ||||
|       return stmt; | ||||
|     } | ||||
|   } | ||||
|   return null; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template. | ||||
|  */ | ||||
|  | ||||
| @ -10,8 +10,8 @@ import {DomElementSchemaRegistry, ParseSourceSpan, SchemaMetadata, TmplAstElemen | ||||
| import * as ts from 'typescript'; | ||||
| 
 | ||||
| import {ErrorCode, ngErrorCode} from '../../diagnostics'; | ||||
| import {TemplateId} from '../api'; | ||||
| 
 | ||||
| import {TemplateId} from './api'; | ||||
| import {makeTemplateDiagnostic, TemplateSourceResolver} from './diagnostics'; | ||||
| 
 | ||||
| const REGISTRY = new DomElementSchemaRegistry(); | ||||
|  | ||||
| @ -12,8 +12,8 @@ import * as ts from 'typescript'; | ||||
| import {ImportFlags, NOOP_DEFAULT_IMPORT_RECORDER, Reference, ReferenceEmitter} from '../../imports'; | ||||
| import {ClassDeclaration, ReflectionHost} from '../../reflection'; | ||||
| import {ImportManager, translateExpression, translateType} from '../../translator'; | ||||
| import {TypeCheckableDirectiveMeta, TypeCheckingConfig, TypeCtorMetadata} from '../api'; | ||||
| 
 | ||||
| import {TypeCheckableDirectiveMeta, TypeCheckingConfig, TypeCtorMetadata} from './api'; | ||||
| import {tsDeclareVariable} from './ts_util'; | ||||
| import {generateTypeCtorDeclarationFn, requiresInlineTypeCtor} from './type_constructor'; | ||||
| import {TypeParameterEmitter} from './type_parameter_emitter'; | ||||
|  | ||||
| @ -8,8 +8,8 @@ | ||||
| 
 | ||||
| import {AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '@angular/compiler'; | ||||
| import * as ts from 'typescript'; | ||||
| import {TypeCheckingConfig} from '../api'; | ||||
| 
 | ||||
| import {TypeCheckingConfig} from './api'; | ||||
| import {addParseSpanInfo, wrapForDiagnostics} from './diagnostics'; | ||||
| import {tsCastToAny} from './ts_util'; | ||||
| 
 | ||||
|  | ||||
| @ -10,8 +10,8 @@ import {BindingPipe, PropertyWrite, TmplAstReference, TmplAstVariable} from '@an | ||||
| import * as ts from 'typescript'; | ||||
| 
 | ||||
| import {ErrorCode, ngErrorCode} from '../../diagnostics'; | ||||
| import {TemplateId} from '../api'; | ||||
| 
 | ||||
| import {TemplateId} from './api'; | ||||
| import {makeTemplateDiagnostic, TemplateSourceResolver} from './diagnostics'; | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -8,8 +8,8 @@ | ||||
| 
 | ||||
| import {AbsoluteSourceSpan, ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler'; | ||||
| import * as ts from 'typescript'; | ||||
| import {TemplateId, TemplateSourceMapping} from '../api'; | ||||
| 
 | ||||
| import {TemplateId, TemplateSourceMapping} from './api'; | ||||
| import {TemplateSourceResolver} from './diagnostics'; | ||||
| import {computeLineStartsMap, getLineAndCharacterFromPosition} from './line_mappings'; | ||||
| 
 | ||||
| @ -55,6 +55,10 @@ export class TemplateSourceManager implements TemplateSourceResolver { | ||||
|    */ | ||||
|   private templateSources = new Map<TemplateId, TemplateSource>(); | ||||
| 
 | ||||
|   getTemplateId(node: ts.ClassDeclaration): TemplateId { | ||||
|     return getTemplateId(node); | ||||
|   } | ||||
| 
 | ||||
|   captureSource(node: ts.ClassDeclaration, mapping: TemplateSourceMapping, file: ParseSourceFile): | ||||
|       TemplateId { | ||||
|     const id = getTemplateId(node); | ||||
|  | ||||
| @ -8,7 +8,8 @@ | ||||
| 
 | ||||
| import {AST, BoundTarget, ImplicitReceiver, PropertyWrite, RecursiveAstVisitor, TmplAstVariable} from '@angular/compiler'; | ||||
| 
 | ||||
| import {TemplateId} from './api'; | ||||
| import {TemplateId} from '../api'; | ||||
| 
 | ||||
| import {OutOfBandDiagnosticRecorder} from './oob'; | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -11,8 +11,8 @@ import * as ts from 'typescript'; | ||||
| 
 | ||||
| import {Reference} from '../../imports'; | ||||
| import {ClassDeclaration} from '../../reflection'; | ||||
| import {TemplateId, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata} from '../api'; | ||||
| 
 | ||||
| import {TemplateId, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata} from './api'; | ||||
| import {addParseSpanInfo, addTemplateId, ignoreDiagnostics, wrapForDiagnostics} from './diagnostics'; | ||||
| import {DomSchemaChecker} from './dom'; | ||||
| import {Environment} from './environment'; | ||||
|  | ||||
| @ -11,8 +11,8 @@ import {AbsoluteFsPath, join} from '../../file_system'; | ||||
| import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports'; | ||||
| import {ClassDeclaration, ReflectionHost} from '../../reflection'; | ||||
| import {ImportManager} from '../../translator'; | ||||
| import {TypeCheckBlockMetadata, TypeCheckingConfig} from '../api'; | ||||
| 
 | ||||
| import {TypeCheckBlockMetadata, TypeCheckingConfig} from './api'; | ||||
| import {DomSchemaChecker} from './dom'; | ||||
| import {Environment} from './environment'; | ||||
| import {OutOfBandDiagnosticRecorder} from './oob'; | ||||
|  | ||||
| @ -9,8 +9,8 @@ | ||||
| import * as ts from 'typescript'; | ||||
| 
 | ||||
| import {ClassDeclaration, ReflectionHost} from '../../reflection'; | ||||
| import {TypeCtorMetadata} from '../api'; | ||||
| 
 | ||||
| import {TypeCtorMetadata} from './api'; | ||||
| import {TypeParameterEmitter} from './type_parameter_emitter'; | ||||
| 
 | ||||
| export function generateTypeCtorDeclarationFn( | ||||
|  | ||||
| @ -19,6 +19,7 @@ ts_library( | ||||
|         "//packages/compiler-cli/src/ngtsc/shims", | ||||
|         "//packages/compiler-cli/src/ngtsc/testing", | ||||
|         "//packages/compiler-cli/src/ngtsc/typecheck", | ||||
|         "//packages/compiler-cli/src/ngtsc/typecheck/api", | ||||
|         "//packages/compiler-cli/src/ngtsc/util", | ||||
|         "@npm//typescript", | ||||
|     ], | ||||
|  | ||||
| @ -8,10 +8,11 @@ | ||||
| 
 | ||||
| import * as ts from 'typescript'; | ||||
| 
 | ||||
| import {absoluteFrom, getSourceFileOrError} from '../../file_system'; | ||||
| import {runInEachFileSystem, TestFile} from '../../file_system/testing'; | ||||
| import {TypeCheckingConfig} from '../src/api'; | ||||
| import {TypeCheckingConfig} from '../api'; | ||||
| 
 | ||||
| import {ngForDeclaration, ngForDts, TestDeclaration, typecheck} from './test_utils'; | ||||
| import {ngForDeclaration, ngForDts, setup, TestDeclaration} from './test_utils'; | ||||
| 
 | ||||
| runInEachFileSystem(() => { | ||||
|   describe('template diagnostics', () => { | ||||
| @ -35,7 +36,7 @@ runInEachFileSystem(() => { | ||||
|           }]); | ||||
| 
 | ||||
|       expect(messages).toEqual( | ||||
|           [`synthetic.html(1, 10): Type 'string' is not assignable to type 'number'.`]); | ||||
|           [`TestComponent.html(1, 10): Type 'string' is not assignable to type 'number'.`]); | ||||
|     }); | ||||
| 
 | ||||
|     it('infers type of template variables', () => { | ||||
| @ -49,7 +50,7 @@ runInEachFileSystem(() => { | ||||
|           [ngForDeclaration()], [ngForDts()]); | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 62): Argument of type 'number' is not assignable to parameter of type 'string'.`, | ||||
|         `TestComponent.html(1, 62): Argument of type 'number' is not assignable to parameter of type 'string'.`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -83,7 +84,7 @@ runInEachFileSystem(() => { | ||||
|           }]); | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 24): Argument of type 'HTMLDivElement' is not assignable to parameter of type 'string'.`, | ||||
|         `TestComponent.html(1, 24): Argument of type 'HTMLDivElement' is not assignable to parameter of type 'string'.`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -104,7 +105,7 @@ runInEachFileSystem(() => { | ||||
|           }]); | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 31): Argument of type 'Dir' is not assignable to parameter of type 'string'.`, | ||||
|         `TestComponent.html(1, 31): Argument of type 'Dir' is not assignable to parameter of type 'string'.`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -115,7 +116,7 @@ runInEachFileSystem(() => { | ||||
|       }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 30): Argument of type 'TemplateRef<any>' is not assignable to parameter of type 'string'.`, | ||||
|         `TestComponent.html(1, 30): Argument of type 'TemplateRef<any>' is not assignable to parameter of type 'string'.`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -130,7 +131,7 @@ runInEachFileSystem(() => { | ||||
|           [ngForDeclaration()], [ngForDts()]); | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 47): Property 'namme' does not exist on type '{ name: string; }'. Did you mean 'name'?`, | ||||
|         `TestComponent.html(1, 47): Property 'namme' does not exist on type '{ name: string; }'. Did you mean 'name'?`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -151,8 +152,8 @@ runInEachFileSystem(() => { | ||||
|       }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 29): Property 'heihgt' does not exist on type 'TestComponent'. Did you mean 'height'?`, | ||||
|         `synthetic.html(1, 6): Can't bind to 'srcc' since it isn't a known property of 'img'.`, | ||||
|         `TestComponent.html(1, 29): Property 'heihgt' does not exist on type 'TestComponent'. Did you mean 'height'?`, | ||||
|         `TestComponent.html(1, 6): Can't bind to 'srcc' since it isn't a known property of 'img'.`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -171,7 +172,7 @@ runInEachFileSystem(() => { | ||||
|           }]); | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 10): Type '"drak"' is not assignable to type '"dark" | "light"'.`, | ||||
|         `TestComponent.html(1, 10): Type '"drak"' is not assignable to type '"dark" | "light"'.`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -190,7 +191,7 @@ runInEachFileSystem(() => { | ||||
|           [{type: 'pipe', name: 'Pipe', pipeName: 'pipe'}]); | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 28): Argument of type 'number' is not assignable to parameter of type 'string'.`, | ||||
|         `TestComponent.html(1, 28): Argument of type 'number' is not assignable to parameter of type 'string'.`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -204,8 +205,8 @@ runInEachFileSystem(() => { | ||||
|          }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 4): Property 'personn' does not exist on type 'TestComponent'. Did you mean 'person'?`, | ||||
|         `synthetic.html(1, 24): Property 'personn' does not exist on type 'TestComponent'. Did you mean 'person'?`, | ||||
|         `TestComponent.html(1, 4): Property 'personn' does not exist on type 'TestComponent'. Did you mean 'person'?`, | ||||
|         `TestComponent.html(1, 24): Property 'personn' does not exist on type 'TestComponent'. Did you mean 'person'?`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -230,7 +231,7 @@ runInEachFileSystem(() => { | ||||
|           }]); | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 14): Property 'personn' does not exist on type 'TestComponent'. Did you mean 'person'?`, | ||||
|         `TestComponent.html(1, 14): Property 'personn' does not exist on type 'TestComponent'. Did you mean 'person'?`, | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -260,7 +261,7 @@ runInEachFileSystem(() => { | ||||
|             [{type: 'directive', name: 'Dir', selector: '[dir]', outputs: {'out': 'event'}}]); | ||||
| 
 | ||||
|         expect(messages).toEqual([ | ||||
|           `synthetic.html(1, 31): Argument of type 'number' is not assignable to parameter of type 'string'.`, | ||||
|           `TestComponent.html(1, 31): Argument of type 'number' is not assignable to parameter of type 'string'.`, | ||||
|         ]); | ||||
|       }); | ||||
| 
 | ||||
| @ -271,7 +272,7 @@ runInEachFileSystem(() => { | ||||
|           }`);
 | ||||
| 
 | ||||
|         expect(messages).toEqual([ | ||||
|           `synthetic.html(1, 41): Argument of type 'AnimationEvent' is not assignable to parameter of type 'string'.`, | ||||
|           `TestComponent.html(1, 41): Argument of type 'AnimationEvent' is not assignable to parameter of type 'string'.`, | ||||
|         ]); | ||||
|       }); | ||||
| 
 | ||||
| @ -283,7 +284,7 @@ runInEachFileSystem(() => { | ||||
|           }`);
 | ||||
| 
 | ||||
|         expect(messages).toEqual([ | ||||
|           `synthetic.html(1, 27): Argument of type 'MouseEvent' is not assignable to parameter of type 'string'.`, | ||||
|           `TestComponent.html(1, 27): Argument of type 'MouseEvent' is not assignable to parameter of type 'string'.`, | ||||
|         ]); | ||||
|       }); | ||||
| 
 | ||||
| @ -329,7 +330,7 @@ runInEachFileSystem(() => { | ||||
|           }; | ||||
|         }`);
 | ||||
| 
 | ||||
|         expect(messages).toEqual([`synthetic.html(1, 41): Object is possibly 'undefined'.`]); | ||||
|         expect(messages).toEqual([`TestComponent.html(1, 41): Object is possibly 'undefined'.`]); | ||||
|       }); | ||||
| 
 | ||||
|       it('does not produce diagnostic for checked property access', () => { | ||||
| @ -362,8 +363,8 @@ class TestComponent { | ||||
| }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(3, 15): Property 'srcc' does not exist on type 'TestComponent'. Did you mean 'src'?`, | ||||
|         `synthetic.html(4, 18): Property 'heihgt' does not exist on type 'TestComponent'. Did you mean 'height'?`, | ||||
|         `TestComponent.html(3, 15): Property 'srcc' does not exist on type 'TestComponent'. Did you mean 'src'?`, | ||||
|         `TestComponent.html(4, 18): Property 'heihgt' does not exist on type 'TestComponent'. Did you mean 'height'?`, | ||||
|       ]); | ||||
|     }); | ||||
|   }); | ||||
| @ -378,7 +379,7 @@ class TestComponent { | ||||
|         }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 11): Property 'getNName' does not exist on type '{ getName(): string; }'. Did you mean 'getName'?` | ||||
|         `TestComponent.html(1, 11): Property 'getNName' does not exist on type '{ getName(): string; }'. Did you mean 'getName'?` | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -390,7 +391,7 @@ class TestComponent { | ||||
|           }; | ||||
|         }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual([`synthetic.html(1, 19): Expected 0 arguments, but got 1.`]); | ||||
|       expect(messages).toEqual([`TestComponent.html(1, 19): Expected 0 arguments, but got 1.`]); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
| @ -404,7 +405,7 @@ class TestComponent { | ||||
|         }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 12): Property 'getNName' does not exist on type '{ getName(): string; }'. Did you mean 'getName'?` | ||||
|         `TestComponent.html(1, 12): Property 'getNName' does not exist on type '{ getName(): string; }'. Did you mean 'getName'?` | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -416,7 +417,7 @@ class TestComponent { | ||||
|           }; | ||||
|         }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual([`synthetic.html(1, 20): Expected 0 arguments, but got 1.`]); | ||||
|       expect(messages).toEqual([`TestComponent.html(1, 20): Expected 0 arguments, but got 1.`]); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
| @ -430,7 +431,7 @@ class TestComponent { | ||||
|         }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual([ | ||||
|         `synthetic.html(1, 22): Property 'nname' does not exist on type '{ name: string; }'. Did you mean 'name'?` | ||||
|         `TestComponent.html(1, 22): Property 'nname' does not exist on type '{ name: string; }'. Did you mean 'name'?` | ||||
|       ]); | ||||
|     }); | ||||
| 
 | ||||
| @ -443,7 +444,7 @@ class TestComponent { | ||||
|         }`);
 | ||||
| 
 | ||||
|       expect(messages).toEqual( | ||||
|           [`synthetic.html(1, 15): Type '2' is not assignable to type 'string'.`]); | ||||
|           [`TestComponent.html(1, 15): Type '2' is not assignable to type 'string'.`]); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @ -452,7 +453,26 @@ function diagnose( | ||||
|     template: string, source: string, declarations?: TestDeclaration[], | ||||
|     additionalSources: TestFile[] = [], config?: Partial<TypeCheckingConfig>, | ||||
|     options?: ts.CompilerOptions): string[] { | ||||
|   const diagnostics = typecheck(template, source, declarations, additionalSources, config, options); | ||||
|   const sfPath = absoluteFrom('/main.ts'); | ||||
|   const {program, templateTypeChecker} = setup( | ||||
|       [ | ||||
|         { | ||||
|           fileName: sfPath, | ||||
|           templates: { | ||||
|             'TestComponent': template, | ||||
|           }, | ||||
|           source, | ||||
|           declarations, | ||||
|         }, | ||||
|         ...additionalSources.map(testFile => ({ | ||||
|                                    fileName: testFile.name, | ||||
|                                    source: testFile.contents, | ||||
|                                    templates: {}, | ||||
|                                  })), | ||||
|       ], | ||||
|       {config, options}); | ||||
|   const sf = getSourceFileOrError(program, sfPath); | ||||
|   const diagnostics = templateTypeChecker.getDiagnosticsForFile(sf); | ||||
|   return diagnostics.map(diag => { | ||||
|     const text = | ||||
|         typeof diag.messageText === 'string' ? diag.messageText : diag.messageText.messageText; | ||||
|  | ||||
| @ -12,16 +12,23 @@ import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError} from '../../file_sys | ||||
| import {runInEachFileSystem} from '../../file_system/testing'; | ||||
| import {sfExtensionData, ShimReferenceTagger} from '../../shims'; | ||||
| import {expectCompleteReuse, makeProgram} from '../../testing'; | ||||
| import {UpdateMode} from '../src/api'; | ||||
| import {UpdateMode} from '../api'; | ||||
| import {ReusedProgramStrategy} from '../src/augmented_program'; | ||||
| 
 | ||||
| import {createProgramWithNoTemplates} from './test_utils'; | ||||
| import {setup} from './test_utils'; | ||||
| 
 | ||||
| runInEachFileSystem(() => { | ||||
|   describe('template type-checking program', () => { | ||||
|     it('should not be created if no components need to be checked', () => { | ||||
|       const {program, templateTypeChecker, programStrategy} = createProgramWithNoTemplates(); | ||||
|       templateTypeChecker.refresh(); | ||||
|       const fileName = absoluteFrom('/main.ts'); | ||||
|       const {program, templateTypeChecker, programStrategy} = setup([{ | ||||
|         fileName, | ||||
|         templates: {}, | ||||
|         source: `export class NotACmp {}`, | ||||
|       }]); | ||||
|       const sf = getSourceFileOrError(program, fileName); | ||||
| 
 | ||||
|       templateTypeChecker.getDiagnosticsForFile(sf); | ||||
|       // expect() here would create a really long error message, so this is checked manually.
 | ||||
|       if (programStrategy.getProgram() !== program) { | ||||
|         fail('Template type-checking created a new ts.Program even though it had no changes.'); | ||||
|  | ||||
| @ -9,20 +9,22 @@ | ||||
| import {CssSelector, ParseSourceFile, ParseSourceSpan, parseTemplate, R3TargetBinder, SchemaMetadata, SelectorMatcher, TmplAstElement, TmplAstReference, Type} from '@angular/compiler'; | ||||
| import * as ts from 'typescript'; | ||||
| 
 | ||||
| import {absoluteFrom, AbsoluteFsPath, LogicalFileSystem} from '../../file_system'; | ||||
| import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError, LogicalFileSystem} from '../../file_system'; | ||||
| import {TestFile} from '../../file_system/testing'; | ||||
| import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports'; | ||||
| import {NOOP_INCREMENTAL_BUILD} from '../../incremental'; | ||||
| import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; | ||||
| import {makeProgram} from '../../testing'; | ||||
| import {getRootDirs} from '../../util/src/typescript'; | ||||
| import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, UpdateMode} from '../src/api'; | ||||
| import {ProgramTypeCheckAdapter, TemplateTypeChecker, TypeCheckContext} from '../api'; | ||||
| 
 | ||||
| import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, UpdateMode} from '../api/api'; | ||||
| import {ReusedProgramStrategy} from '../src/augmented_program'; | ||||
| import {ProgramTypeCheckAdapter, TemplateTypeChecker} from '../src/checker'; | ||||
| import {TypeCheckContext} from '../src/context'; | ||||
| import {TemplateTypeCheckerImpl} from '../src/checker'; | ||||
| import {DomSchemaChecker} from '../src/dom'; | ||||
| import {Environment} from '../src/environment'; | ||||
| import {OutOfBandDiagnosticRecorder} from '../src/oob'; | ||||
| import {TypeCheckShimGenerator} from '../src/shim'; | ||||
| import {generateTypeCheckBlock} from '../src/type_check_block'; | ||||
| 
 | ||||
| export function typescriptLibDts(): TestFile { | ||||
| @ -235,32 +237,88 @@ export function tcb( | ||||
|   return res.replace(/\s+/g, ' '); | ||||
| } | ||||
| 
 | ||||
| export interface TemplateTestEnvironment { | ||||
|   sf: ts.SourceFile; | ||||
|   program: ts.Program; | ||||
|   templateTypeChecker: TemplateTypeChecker; | ||||
|   programStrategy: ReusedProgramStrategy; | ||||
| /** | ||||
|  * A file in the test program, along with any template information for components within the file. | ||||
|  */ | ||||
| export interface TypeCheckingTarget { | ||||
|   /** | ||||
|    * Path to the file in the virtual test filesystem. | ||||
|    */ | ||||
|   fileName: AbsoluteFsPath; | ||||
| 
 | ||||
|   /** | ||||
|    * Raw source code for the file. | ||||
|    * | ||||
|    * If this is omitted, source code for the file will be generated based on any expected component | ||||
|    * classes. | ||||
|    */ | ||||
|   source?: string; | ||||
| 
 | ||||
|   /** | ||||
|    * A map of component class names to string templates for that component. | ||||
|    */ | ||||
|   templates: {[className: string]: string}; | ||||
| 
 | ||||
|   /** | ||||
|    * Any declarations (e.g. directives) which should be considered as part of the scope for the | ||||
|    * components in this file. | ||||
|    */ | ||||
|   declarations?: TestDeclaration[]; | ||||
| } | ||||
| 
 | ||||
| function setupTemplateTypeChecking( | ||||
|     source: string, additionalSources: {name: AbsoluteFsPath; contents: string}[], | ||||
|     config: Partial<TypeCheckingConfig>, opts: ts.CompilerOptions, | ||||
|     makeTypeCheckAdapterFn: (program: ts.Program, sf: ts.SourceFile) => | ||||
|         ProgramTypeCheckAdapter): TemplateTestEnvironment { | ||||
|   const typeCheckFilePath = absoluteFrom('/main.ngtypecheck.ts'); | ||||
| /** | ||||
|  * Create a testing environment for template type-checking which contains a number of given test | ||||
|  * targets. | ||||
|  * | ||||
|  * A full Angular environment is not necessary to exercise the template type-checking system. | ||||
|  * Components only need to be classes which exist, with templates specified in the target | ||||
|  * configuration. In many cases, it's not even necessary to include source code for test files, as | ||||
|  * that can be auto-generated based on the provided target configuration. | ||||
|  */ | ||||
| export function setup(targets: TypeCheckingTarget[], overrides: { | ||||
|   config?: Partial<TypeCheckingConfig>, | ||||
|   options?: ts.CompilerOptions, | ||||
| } = {}): { | ||||
|   templateTypeChecker: TemplateTypeChecker, | ||||
|   program: ts.Program, | ||||
|   programStrategy: ReusedProgramStrategy, | ||||
| } { | ||||
|   const files = [ | ||||
|     typescriptLibDts(), | ||||
|     angularCoreDts(), | ||||
|     angularAnimationsDts(), | ||||
|     // Add the typecheck file to the program, as the typecheck program is created with the
 | ||||
|     // assumption that the typecheck file was already a root file in the original program.
 | ||||
|     {name: typeCheckFilePath, contents: 'export const TYPECHECK = true;'}, | ||||
|     {name: absoluteFrom('/main.ts'), contents: source}, | ||||
|     ...additionalSources, | ||||
|   ]; | ||||
|   const {program, host, options} = | ||||
|       makeProgram(files, {strictNullChecks: true, noImplicitAny: true, ...opts}, undefined, false); | ||||
|   const sf = program.getSourceFile(absoluteFrom('/main.ts'))!; | ||||
| 
 | ||||
|   for (const target of targets) { | ||||
|     let contents: string; | ||||
|     if (target.source !== undefined) { | ||||
|       contents = target.source; | ||||
|     } else { | ||||
|       contents = `// generated from templates\n\nexport const MODULE = true;\n\n`; | ||||
|       for (const className of Object.keys(target.templates)) { | ||||
|         contents += `export class ${className} {}\n`; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     files.push({ | ||||
|       name: target.fileName, | ||||
|       contents, | ||||
|     }); | ||||
| 
 | ||||
|     if (!target.fileName.endsWith('.d.ts')) { | ||||
|       files.push({ | ||||
|         name: TypeCheckShimGenerator.shimFor(target.fileName), | ||||
|         contents: 'export const MODULE = true;', | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const opts = overrides.options ?? {}; | ||||
|   const config = overrides.config ?? {}; | ||||
| 
 | ||||
|   const {program, host, options} = makeProgram( | ||||
|       files, {strictNullChecks: true, noImplicitAny: true, ...opts}, /* host */ undefined, | ||||
|       /* checkForErrors */ false); | ||||
|   const checker = program.getTypeChecker(); | ||||
|   const logicalFs = new LogicalFileSystem(getRootDirs(host, options), host); | ||||
|   const reflectionHost = new TypeScriptReflectionHost(checker); | ||||
| @ -274,22 +332,18 @@ function setupTemplateTypeChecking( | ||||
|   ]); | ||||
|   const fullConfig = {...ALL_ENABLED_CONFIG, ...config}; | ||||
| 
 | ||||
|   const checkAdapter = makeTypeCheckAdapterFn(program, sf); | ||||
|   const programStrategy = new ReusedProgramStrategy(program, host, options, []); | ||||
|   const templateTypeChecker = new TemplateTypeChecker( | ||||
|       program, programStrategy, checkAdapter, fullConfig, emitter, reflectionHost, host, | ||||
|       NOOP_INCREMENTAL_BUILD); | ||||
|   const checkAdapter = createTypeCheckAdapter((sf, ctx) => { | ||||
|     for (const target of targets) { | ||||
|       if (getSourceFileOrError(program, target.fileName) !== sf) { | ||||
|         continue; | ||||
|       } | ||||
| 
 | ||||
|   return {program, sf, templateTypeChecker, programStrategy}; | ||||
| } | ||||
|       const declarations = target.declarations ?? []; | ||||
| 
 | ||||
| export function typecheck( | ||||
|     template: string, source: string, declarations: TestDeclaration[] = [], | ||||
|     additionalSources: {name: AbsoluteFsPath; contents: string}[] = [], | ||||
|     config: Partial<TypeCheckingConfig> = {}, opts: ts.CompilerOptions = {}): ts.Diagnostic[] { | ||||
|   const {sf, templateTypeChecker} = | ||||
|       setupTemplateTypeChecking(source, additionalSources, config, opts, (program, sf) => { | ||||
|         const templateUrl = 'synthetic.html'; | ||||
|       for (const className of Object.keys(target.templates)) { | ||||
|         const classDecl = getClass(sf, className); | ||||
|         const template = target.templates[className]; | ||||
|         const templateUrl = `${className}.html`; | ||||
|         const templateFile = new ParseSourceFile(template, templateUrl); | ||||
|         const {nodes, errors} = parseTemplate(template, templateUrl); | ||||
|         if (errors !== undefined) { | ||||
| @ -307,44 +361,38 @@ export function typecheck( | ||||
|           return getClass(declFile, decl.name); | ||||
|         }); | ||||
|         const binder = new R3TargetBinder(matcher); | ||||
|         const boundTarget = binder.bind({template: nodes}); | ||||
|         const clazz = new Reference(getClass(sf, 'TestComponent')); | ||||
|         const classRef = new Reference(classDecl); | ||||
| 
 | ||||
|         const sourceMapping: TemplateSourceMapping = { | ||||
|           type: 'external', | ||||
|           template, | ||||
|           templateUrl, | ||||
|           componentClass: clazz.node, | ||||
|           componentClass: classRef.node, | ||||
|           // Use the class's name for error mappings.
 | ||||
|           node: clazz.node.name, | ||||
|           node: classRef.node.name, | ||||
|         }; | ||||
| 
 | ||||
|         return createTypeCheckAdapter((ctx: TypeCheckContext) => { | ||||
|           ctx.addTemplate(clazz, boundTarget, pipes, [], sourceMapping, templateFile); | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|   templateTypeChecker.refresh(); | ||||
|   return templateTypeChecker.getDiagnosticsForFile(sf); | ||||
| } | ||||
| 
 | ||||
| export function createProgramWithNoTemplates(): TemplateTestEnvironment { | ||||
|   return setupTemplateTypeChecking( | ||||
|       'export const NOT_A_COMPONENT = true;', [], {}, {}, () => createTypeCheckAdapter(() => {})); | ||||
| } | ||||
| 
 | ||||
| function createTypeCheckAdapter(fn: (ctx: TypeCheckContext) => void): ProgramTypeCheckAdapter { | ||||
|   let called = false; | ||||
|   return { | ||||
|     typeCheck: (sf: ts.SourceFile, ctx: TypeCheckContext) => { | ||||
|       if (!called) { | ||||
|         fn(ctx); | ||||
|         ctx.addTemplate(classRef, binder, nodes, pipes, [], sourceMapping, templateFile); | ||||
|       } | ||||
|       called = true; | ||||
|     }, | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   const programStrategy = new ReusedProgramStrategy(program, host, options, ['ngtypecheck']); | ||||
|   const templateTypeChecker = new TemplateTypeCheckerImpl( | ||||
|       program, programStrategy, checkAdapter, fullConfig, emitter, reflectionHost, host, | ||||
|       NOOP_INCREMENTAL_BUILD); | ||||
|   return { | ||||
|     templateTypeChecker, | ||||
|     program, | ||||
|     programStrategy, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function createTypeCheckAdapter(fn: (sf: ts.SourceFile, ctx: TypeCheckContext) => void): | ||||
|     ProgramTypeCheckAdapter { | ||||
|   return {typeCheck: fn}; | ||||
| } | ||||
| 
 | ||||
| function prepareDeclarations( | ||||
|     declarations: TestDeclaration[], | ||||
|     resolveDeclaration: (decl: TestDeclaration) => ClassDeclaration<ts.ClassDeclaration>) { | ||||
| @ -386,7 +434,7 @@ export function getClass(sf: ts.SourceFile, name: string): ClassDeclaration<ts.C | ||||
|       return stmt; | ||||
|     } | ||||
|   } | ||||
|   throw new Error(`Class ${name} not found in file`); | ||||
|   throw new Error(`Class ${name} not found in file: ${sf.fileName}: ${sf.text}`); | ||||
| } | ||||
| 
 | ||||
| class FakeEnvironment /* implements Environment */ { | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {TypeCheckingConfig} from '../src/api'; | ||||
| import {TypeCheckingConfig} from '../api'; | ||||
| 
 | ||||
| import {ALL_ENABLED_CONFIG, tcb, TestDeclaration, TestDirective} from './test_utils'; | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,47 @@ | ||||
| /** | ||||
|  * @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 {absoluteFrom, getSourceFileOrError} from '../../file_system'; | ||||
| import {runInEachFileSystem} from '../../file_system/testing'; | ||||
| 
 | ||||
| import {getClass, setup} from './test_utils'; | ||||
| 
 | ||||
| runInEachFileSystem(() => { | ||||
|   describe('TemplateTypeChecker', () => { | ||||
|     it('should batch diagnostic operations when requested in WholeProgram mode', () => { | ||||
|       const file1 = absoluteFrom('/file1.ts'); | ||||
|       const file2 = absoluteFrom('/file2.ts'); | ||||
|       const {program, templateTypeChecker, programStrategy} = setup([ | ||||
|         {fileName: file1, templates: {'Cmp1': '<div></div>'}}, | ||||
|         {fileName: file2, templates: {'Cmp2': '<span></span>'}} | ||||
|       ]); | ||||
| 
 | ||||
|       templateTypeChecker.getDiagnosticsForFile(getSourceFileOrError(program, file1)); | ||||
|       const ttcProgram1 = programStrategy.getProgram(); | ||||
|       templateTypeChecker.getDiagnosticsForFile(getSourceFileOrError(program, file2)); | ||||
|       const ttcProgram2 = programStrategy.getProgram(); | ||||
| 
 | ||||
|       expect(ttcProgram1).toBe(ttcProgram2); | ||||
|     }); | ||||
| 
 | ||||
|     it('should allow access to the type-check block of a component', () => { | ||||
|       const file1 = absoluteFrom('/file1.ts'); | ||||
|       const file2 = absoluteFrom('/file2.ts'); | ||||
|       const {program, templateTypeChecker, programStrategy} = setup([ | ||||
|         {fileName: file1, templates: {'Cmp1': '<div></div>'}}, | ||||
|         {fileName: file2, templates: {'Cmp2': '<span></span>'}} | ||||
|       ]); | ||||
| 
 | ||||
|       const cmp1 = getClass(getSourceFileOrError(program, file1), 'Cmp1'); | ||||
|       const block = templateTypeChecker.getTypeCheckBlock(cmp1); | ||||
|       expect(block).not.toBeNull(); | ||||
|       expect(block!.getText()).toMatch(/: i[0-9]\.Cmp1/); | ||||
|       expect(block!.getText()).toContain(`document.createElement("div")`); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @ -10,16 +10,16 @@ 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 {isNamedClassDeclaration, ReflectionHost, TypeScriptReflectionHost} from '../../reflection'; | ||||
| import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; | ||||
| import {getDeclaration, makeProgram} from '../../testing'; | ||||
| import {getRootDirs} from '../../util/src/typescript'; | ||||
| import {UpdateMode} from '../src/api'; | ||||
| import {ComponentToShimMappingStrategy, UpdateMode} from '../api'; | ||||
| import {ReusedProgramStrategy} from '../src/augmented_program'; | ||||
| import {ComponentToShimMappingStrategy, PendingFileTypeCheckingData, TypeCheckContext} from '../src/context'; | ||||
| import {PendingFileTypeCheckingData, TypeCheckContextImpl, TypeCheckingHost} from '../src/context'; | ||||
| import {TemplateSourceManager} from '../src/source'; | ||||
| import {TypeCheckFile} from '../src/type_check_file'; | ||||
| 
 | ||||
| import {ALL_ENABLED_CONFIG, NoopOobRecorder} from './test_utils'; | ||||
| import {ALL_ENABLED_CONFIG} from './test_utils'; | ||||
| 
 | ||||
| runInEachFileSystem(() => { | ||||
|   describe('ngtsc typechecking', () => { | ||||
| @ -72,8 +72,9 @@ TestClass.ngTypeCtor({value: 'test'}); | ||||
|           new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost), | ||||
|           new LogicalProjectStrategy(reflectionHost, logicalFs), | ||||
|         ]); | ||||
|         const ctx = new TypeCheckContext( | ||||
|             ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost); | ||||
|         const ctx = new TypeCheckContextImpl( | ||||
|             ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost, | ||||
|             new TestTypeCheckingHost()); | ||||
|         const TestClass = | ||||
|             getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); | ||||
|         const pendingFile = makePendingFile(); | ||||
| @ -110,8 +111,9 @@ TestClass.ngTypeCtor({value: 'test'}); | ||||
|           new LogicalProjectStrategy(reflectionHost, logicalFs), | ||||
|         ]); | ||||
|         const pendingFile = makePendingFile(); | ||||
|         const ctx = new TypeCheckContext( | ||||
|             ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost); | ||||
|         const ctx = new TypeCheckContextImpl( | ||||
|             ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost, | ||||
|             new TestTypeCheckingHost()); | ||||
|         const TestClass = | ||||
|             getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); | ||||
|         ctx.addInlineTypeCtor( | ||||
| @ -126,7 +128,7 @@ TestClass.ngTypeCtor({value: 'test'}); | ||||
|               coercedInputFields: new Set(), | ||||
|             }); | ||||
|         const programStrategy = new ReusedProgramStrategy(program, host, options, []); | ||||
|         programStrategy.updateFiles(ctx.finalize().updates, UpdateMode.Complete); | ||||
|         programStrategy.updateFiles(ctx.finalize(), UpdateMode.Complete); | ||||
|         const TestClassWithCtor = getDeclaration( | ||||
|             programStrategy.getProgram(), _('/main.ts'), 'TestClass', isNamedClassDeclaration); | ||||
|         const typeCtor = TestClassWithCtor.members.find(isTypeCtor)!; | ||||
| @ -154,8 +156,9 @@ TestClass.ngTypeCtor({value: 'test'}); | ||||
|           new LogicalProjectStrategy(reflectionHost, logicalFs), | ||||
|         ]); | ||||
|         const pendingFile = makePendingFile(); | ||||
|         const ctx = new TypeCheckContext( | ||||
|             ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost); | ||||
|         const ctx = new TypeCheckContextImpl( | ||||
|             ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost, | ||||
|             new TestTypeCheckingHost()); | ||||
|         const TestClass = | ||||
|             getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); | ||||
|         ctx.addInlineTypeCtor( | ||||
| @ -170,7 +173,7 @@ TestClass.ngTypeCtor({value: 'test'}); | ||||
|               coercedInputFields: new Set(['bar']), | ||||
|             }); | ||||
|         const programStrategy = new ReusedProgramStrategy(program, host, options, []); | ||||
|         programStrategy.updateFiles(ctx.finalize().updates, UpdateMode.Complete); | ||||
|         programStrategy.updateFiles(ctx.finalize(), UpdateMode.Complete); | ||||
|         const TestClassWithCtor = getDeclaration( | ||||
|             programStrategy.getProgram(), _('/main.ts'), 'TestClass', isNamedClassDeclaration); | ||||
|         const typeCtor = TestClassWithCtor.members.find(isTypeCtor)!; | ||||
| @ -194,6 +197,25 @@ function makePendingFile(): PendingFileTypeCheckingData { | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| class TestTypeCheckingHost implements TypeCheckingHost { | ||||
|   private sourceManager = new TemplateSourceManager(); | ||||
| 
 | ||||
|   getSourceManager(): TemplateSourceManager { | ||||
|     return this.sourceManager; | ||||
|   } | ||||
| 
 | ||||
|   shouldCheckComponent(): boolean { | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   getTemplateOverride(): null { | ||||
|     return null; | ||||
|   } | ||||
|   recordShimData(): void {} | ||||
| 
 | ||||
|   recordComplete(): void {} | ||||
| } | ||||
| 
 | ||||
| class TestMappingStrategy implements ComponentToShimMappingStrategy { | ||||
|   shimPathForComponent(): AbsoluteFsPath { | ||||
|     return absoluteFrom('/typecheck.ts'); | ||||
|  | ||||
| @ -11,6 +11,7 @@ ts_library( | ||||
|         "//packages/compiler-cli/src/ngtsc/file_system", | ||||
|         "//packages/compiler-cli/src/ngtsc/incremental", | ||||
|         "//packages/compiler-cli/src/ngtsc/typecheck", | ||||
|         "//packages/compiler-cli/src/ngtsc/typecheck/api", | ||||
|         "@npm//typescript", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| @ -11,7 +11,8 @@ import {CompilerOptions} from '@angular/compiler-cli'; | ||||
| import {NgCompiler, NgCompilerHost} from '@angular/compiler-cli/src/ngtsc/core'; | ||||
| import {absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system'; | ||||
| import {PatchedProgramIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental'; | ||||
| import {TypeCheckingProgramStrategy, TypeCheckShimGenerator, UpdateMode} from '@angular/compiler-cli/src/ngtsc/typecheck'; | ||||
| import {TypeCheckShimGenerator} from '@angular/compiler-cli/src/ngtsc/typecheck'; | ||||
| import {TypeCheckingProgramStrategy, UpdateMode} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; | ||||
| import * as ts from 'typescript/lib/tsserverlibrary'; | ||||
| 
 | ||||
| import {makeCompilerHostFromProject} from './compiler_host'; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user