diff --git a/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts b/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts index bc9a4fc646..aa4a619fd8 100644 --- a/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts +++ b/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts @@ -16,7 +16,7 @@ import {absoluteFrom, absoluteFromSourceFile, dirname, FileSystem, LogicalFileSy import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports'; import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../../src/ngtsc/metadata'; import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator'; -import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope'; +import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../../src/ngtsc/scope'; import {DecoratorHandler} from '../../../src/ngtsc/transform'; import {NgccReflectionHost} from '../host/ngcc_host'; import {Migration} from '../migrations/migration'; @@ -91,11 +91,13 @@ export class DecorationAnalyzer { importGraph = new ImportGraph(this.moduleResolver); cycleAnalyzer = new CycleAnalyzer(this.importGraph); injectableRegistry = new InjectableClassRegistry(this.reflectionHost); + typeCheckScopeRegistry = new TypeCheckScopeRegistry(this.scopeRegistry, this.fullMetaReader); handlers: DecoratorHandler[] = [ new ComponentDecoratorHandler( this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader, - this.scopeRegistry, this.scopeRegistry, new ResourceRegistry(), this.isCore, - this.resourceManager, this.rootDirs, !!this.compilerOptions.preserveWhitespaces, + this.scopeRegistry, this.scopeRegistry, this.typeCheckScopeRegistry, new ResourceRegistry(), + this.isCore, this.resourceManager, this.rootDirs, + !!this.compilerOptions.preserveWhitespaces, /* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat, /* usePoisonedData */ false, /* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer, diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 2a88fb2bec..172276dee0 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -18,7 +18,7 @@ import {IndexingContext} from '../../indexer'; import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, Resource, ResourceRegistry} from '../../metadata'; import {EnumValue, PartialEvaluator} from '../../partial_evaluator'; import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection'; -import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope'; +import {ComponentScopeReader, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../scope'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform'; import {TemplateSourceMapping, TypeCheckContext} from '../../typecheck/api'; import {getTemplateId, makeTemplateDiagnostic} from '../../typecheck/diagnostics'; @@ -30,7 +30,6 @@ import {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagno import {extractDirectiveMetadata, parseFieldArrayValue} from './directive'; import {compileNgFactoryDefField} from './factory'; import {generateSetClassMetadataCall} from './metadata'; -import {TypeCheckScopes} from './typecheck_scopes'; import {findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, readBaseClass, resolveProvidersRequiringFactory, unwrapExpression, wrapFunctionExpressionsInParens} from './util'; const EMPTY_MAP = new Map(); @@ -87,6 +86,7 @@ export class ComponentDecoratorHandler implements private reflector: ReflectionHost, private evaluator: PartialEvaluator, private metaRegistry: MetadataRegistry, private metaReader: MetadataReader, private scopeReader: ComponentScopeReader, private scopeRegistry: LocalModuleScopeRegistry, + private typeCheckScopeRegistry: TypeCheckScopeRegistry, private resourceRegistry: ResourceRegistry, private isCore: boolean, private resourceLoader: ResourceLoader, private rootDirs: ReadonlyArray, private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean, @@ -100,7 +100,6 @@ export class ComponentDecoratorHandler implements private literalCache = new Map(); private elementSchemaRegistry = new DomElementSchemaRegistry(); - private typeCheckScopes = new TypeCheckScopes(this.scopeReader, this.metaReader); /** * During the asynchronous preanalyze phase, it's necessary to parse the template to extract @@ -440,15 +439,14 @@ export class ComponentDecoratorHandler implements typeCheck(ctx: TypeCheckContext, node: ClassDeclaration, meta: Readonly): void { - if (!ts.isClassDeclaration(node)) { + if (this.typeCheckScopeRegistry === null || !ts.isClassDeclaration(node)) { return; } if (meta.isPoisoned && !this.usePoisonedData) { return; } - - const scope = this.typeCheckScopes.getTypeCheckScope(node); + const scope = this.typeCheckScopeRegistry.getTypeCheckScope(node); if (scope.isPoisoned && !this.usePoisonedData) { // Don't type-check components that had errors in their scopes, unless requested. return; @@ -492,17 +490,17 @@ export class ComponentDecoratorHandler implements // Determining this is challenging, because the TemplateDefinitionBuilder is responsible for // matching directives and pipes in the template; however, that doesn't run until the actual // compile() step. It's not possible to run template compilation sooner as it requires the - // ConstantPool for the overall file being compiled (which isn't available until the transform - // step). + // ConstantPool for the overall file being compiled (which isn't available until the + // transform step). // - // Instead, directives/pipes are matched independently here, using the R3TargetBinder. This is - // an alternative implementation of template matching which is used for template type-checking - // and will eventually replace matching in the TemplateDefinitionBuilder. + // Instead, directives/pipes are matched independently here, using the R3TargetBinder. This + // is an alternative implementation of template matching which is used for template + // type-checking and will eventually replace matching in the TemplateDefinitionBuilder. - // Set up the R3TargetBinder, as well as a 'directives' array and a 'pipes' map that are later - // fed to the TemplateDefinitionBuilder. First, a SelectorMatcher is constructed to match - // directives that are in scope. + // Set up the R3TargetBinder, as well as a 'directives' array and a 'pipes' map that are + // later fed to the TemplateDefinitionBuilder. First, a SelectorMatcher is constructed to + // match directives that are in scope. type MatchedDirective = DirectiveMeta&{selector: string}; const matcher = new SelectorMatcher(); @@ -563,8 +561,8 @@ export class ComponentDecoratorHandler implements } // Check whether the directive/pipe arrays in ɵcmp need to be wrapped in closures. - // This is required if any directive/pipe reference is to a declaration in the same file but - // declared after this component. + // This is required if any directive/pipe reference is to a declaration in the same file + // but declared after this component. const wrapDirectivesAndPipesInClosure = usedDirectives.some( dir => isExpressionForwardReference(dir.type, node.name, context)) || @@ -890,8 +888,8 @@ export class ComponentDecoratorHandler implements // 2. By default, the template parser strips leading trivia characters (like spaces, tabs, and // newlines). This also destroys source mapping information. // - // In order to guarantee the correctness of diagnostics, templates are parsed a second time with - // the above options set to preserve source mappings. + // In order to guarantee the correctness of diagnostics, templates are parsed a second time + // with the above options set to preserve source mappings. const {nodes: diagNodes} = parseTemplate(templateStr, templateUrl, { preserveWhitespaces: true, diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/component_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/component_spec.ts index b3c8134e0d..51d139cee7 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/test/component_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/test/component_spec.ts @@ -5,6 +5,9 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + +import * as ts from 'typescript'; + import {CycleAnalyzer, ImportGraph} from '../../cycles'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {absoluteFrom} from '../../file_system'; @@ -13,7 +16,7 @@ import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '.. import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../metadata'; import {PartialEvaluator} from '../../partial_evaluator'; import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; -import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; +import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../scope'; import {getDeclaration, makeProgram} from '../../testing'; import {ResourceLoader} from '../src/api'; import {ComponentDecoratorHandler} from '../src/component'; @@ -48,17 +51,33 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil const refEmitter = new ReferenceEmitter([]); const injectableRegistry = new InjectableClassRegistry(reflectionHost); const resourceRegistry = new ResourceRegistry(); + const typeCheckScopeRegistry = new TypeCheckScopeRegistry(scopeRegistry, metaReader); const handler = new ComponentDecoratorHandler( - reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, scopeRegistry, + reflectionHost, + evaluator, + metaRegistry, + metaReader, + scopeRegistry, + scopeRegistry, + typeCheckScopeRegistry, resourceRegistry, - /* isCore */ false, new StubResourceLoader(), /* rootDirs */['/'], - /* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true, + /* isCore */ false, + new StubResourceLoader(), + /* rootDirs */['/'], + /* defaultPreserveWhitespaces */ false, + /* i18nUseExternalIds */ true, /* enableI18nLegacyMessageIdFormat */ false, /* usePoisonedData */ false, - /* i18nNormalizeLineEndingsInICUs */ undefined, moduleResolver, cycleAnalyzer, refEmitter, - NOOP_DEFAULT_IMPORT_RECORDER, /* depTracker */ null, injectableRegistry, - /* annotateForClosureCompiler */ false); + /* i18nNormalizeLineEndingsInICUs */ undefined, + moduleResolver, + cycleAnalyzer, + refEmitter, + NOOP_DEFAULT_IMPORT_RECORDER, + /* depTracker */ null, + injectableRegistry, + /* annotateForClosureCompiler */ false, + ); return {reflectionHost, handler}; } diff --git a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts index 04f48a5da1..a7ebb7c143 100644 --- a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts +++ b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts @@ -24,7 +24,7 @@ import {NOOP_PERF_RECORDER, PerfRecorder} from '../../perf'; import {DeclarationNode, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {AdapterResourceLoader} from '../../resource'; import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing'; -import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; +import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver, TypeCheckScopeRegistry} from '../../scope'; import {generatedFactoryTransform} from '../../shims'; import {ivySwitchTransform} from '../../switch'; import {aliasTransformFactory, CompilationMode, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform'; @@ -44,6 +44,7 @@ interface LazyCompilationState { reflector: TypeScriptReflectionHost; metaReader: MetadataReader; scopeRegistry: LocalModuleScopeRegistry; + typeCheckScopeRegistry: TypeCheckScopeRegistry; exportReferenceGraph: ReferenceGraph|null; routeAnalyzer: NgModuleRouteAnalyzer; dtsTransforms: DtsTransformRegistry; @@ -732,6 +733,7 @@ export class NgCompiler { const injectableRegistry = new InjectableClassRegistry(reflector); const metaReader = new CompoundMetadataReader([localMetaReader, dtsReader]); + const typeCheckScopeRegistry = new TypeCheckScopeRegistry(scopeReader, metaReader); // If a flat module entrypoint was specified, then track references via a `ReferenceGraph` in @@ -761,8 +763,9 @@ export class NgCompiler { const handlers: DecoratorHandler[] = [ new ComponentDecoratorHandler( reflector, evaluator, metaRegistry, metaReader, scopeReader, scopeRegistry, - resourceRegistry, isCore, this.resourceManager, this.adapter.rootDirs, - this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false, + typeCheckScopeRegistry, resourceRegistry, isCore, this.resourceManager, + this.adapter.rootDirs, this.options.preserveWhitespaces || false, + this.options.i18nUseExternalIds !== false, this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData, this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer, refEmitter, defaultImportTracker, this.incrementalDriver.depGraph, injectableRegistry, @@ -814,6 +817,7 @@ export class NgCompiler { routeAnalyzer, mwpScanner, metaReader, + typeCheckScopeRegistry, defaultImportTracker, aliasingHost, refEmitter, diff --git a/packages/compiler-cli/src/ngtsc/scope/index.ts b/packages/compiler-cli/src/ngtsc/scope/index.ts index 814ae3a8f0..920f8dba8a 100644 --- a/packages/compiler-cli/src/ngtsc/scope/index.ts +++ b/packages/compiler-cli/src/ngtsc/scope/index.ts @@ -10,3 +10,4 @@ export {ExportScope, ScopeData} from './src/api'; export {ComponentScopeReader, CompoundComponentScopeReader} from './src/component_scope'; export {DtsModuleScopeResolver, MetadataDtsModuleScopeResolver} from './src/dependency'; export {DeclarationData, LocalModuleScope, LocalModuleScopeRegistry, LocalNgModuleData} from './src/local'; +export {TypeCheckScope, TypeCheckScopeRegistry} from './src/typecheck'; \ No newline at end of file diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/typecheck_scopes.ts b/packages/compiler-cli/src/ngtsc/scope/src/typecheck.ts similarity index 88% rename from packages/compiler-cli/src/ngtsc/annotations/src/typecheck_scopes.ts rename to packages/compiler-cli/src/ngtsc/scope/src/typecheck.ts index f3319f9e57..a162a9a728 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/typecheck_scopes.ts +++ b/packages/compiler-cli/src/ngtsc/scope/src/typecheck.ts @@ -12,7 +12,8 @@ import * as ts from 'typescript'; import {Reference} from '../../imports'; import {DirectiveMeta, flattenInheritedDirectiveMetadata, MetadataReader} from '../../metadata'; import {ClassDeclaration} from '../../reflection'; -import {ComponentScopeReader} from '../../scope'; + +import {ComponentScopeReader} from './component_scope'; /** * The scope that is used for type-check code generation of a component template. @@ -24,6 +25,11 @@ export interface TypeCheckScope { */ matcher: SelectorMatcher; + /** + * All of the directives available in the compilation scope of the declaring NgModule. + */ + directives: DirectiveMeta[]; + /** * The pipes that are available in the compilation scope. */ @@ -44,7 +50,7 @@ export interface TypeCheckScope { /** * Computes scope information to be used in template type checking. */ -export class TypeCheckScopes { +export class TypeCheckScopeRegistry { /** * Cache of flattened directive metadata. Because flattened metadata is scope-invariant it's * cached individually, such that all scopes refer to the same flattened metadata. @@ -65,12 +71,14 @@ export class TypeCheckScopes { */ getTypeCheckScope(node: ClassDeclaration): TypeCheckScope { const matcher = new SelectorMatcher(); + const directives: DirectiveMeta[] = []; const pipes = new Map>>(); const scope = this.scopeReader.getScopeForComponent(node); if (scope === null) { return { matcher, + directives, pipes, schemas: [], isPoisoned: false, @@ -83,8 +91,9 @@ export class TypeCheckScopes { for (const meta of scope.compilation.directives) { if (meta.selector !== null) { - const extMeta = this.getInheritedDirectiveMetadata(meta.ref); + const extMeta = this.getTypeCheckDirectiveMetadata(meta.ref); matcher.addSelectables(CssSelector.parse(meta.selector), extMeta); + directives.push(extMeta); } } @@ -98,6 +107,7 @@ export class TypeCheckScopes { const typeCheckScope: TypeCheckScope = { matcher, + directives, pipes, schemas: scope.schemas, isPoisoned: scope.compilation.isPoisoned || scope.exported.isPoisoned, @@ -106,7 +116,7 @@ export class TypeCheckScopes { return typeCheckScope; } - private getInheritedDirectiveMetadata(ref: Reference): DirectiveMeta { + getTypeCheckDirectiveMetadata(ref: Reference): DirectiveMeta { const clazz = ref.node; if (this.flattenedDirectiveMetaCache.has(clazz)) { return this.flattenedDirectiveMetaCache.get(clazz)!;