refactor(compiler-cli): make `TypeCheckingScopeRegistry` a general utility (#40032)
The `annotations` package in the compiler previously contained a registry which tracks NgModule scopes for template type-checking, including unifying all type-checking metadata across class inheritance lines. This commit generalizes this utility and prepares it for use in the `TemplateTypeChecker` as well, to back APIs used by the language service. PR Close #40032
This commit is contained in:
parent
e42250f139
commit
a543e69497
|
@ -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<unknown, unknown, unknown>[] = [
|
||||
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,
|
||||
|
|
|
@ -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<string, Expression>();
|
||||
|
@ -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<string>,
|
||||
private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean,
|
||||
|
@ -100,7 +100,6 @@ export class ComponentDecoratorHandler implements
|
|||
|
||||
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
||||
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<ComponentAnalysisData>):
|
||||
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<MatchedDirective>();
|
||||
|
||||
|
@ -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,
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
||||
|
|
|
@ -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<unknown, unknown, unknown>[] = [
|
||||
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,
|
||||
|
|
|
@ -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';
|
|
@ -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<DirectiveMeta>;
|
||||
|
||||
/**
|
||||
* 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<DirectiveMeta>();
|
||||
const directives: DirectiveMeta[] = [];
|
||||
const pipes = new Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>();
|
||||
|
||||
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<ClassDeclaration>): DirectiveMeta {
|
||||
getTypeCheckDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta {
|
||||
const clazz = ref.node;
|
||||
if (this.flattenedDirectiveMetaCache.has(clazz)) {
|
||||
return this.flattenedDirectiveMetaCache.get(clazz)!;
|
Loading…
Reference in New Issue