mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(compiler-cli): extract ModuleWithProviders analysis out of NgModule handler (#46009)
This commit moves the foreign function resolver logic for detecting a `ModuleWithProviders` in a return type position of a function call, as the logic can then be reused for standalone components in a subsequent commit. PR Close #46009
This commit is contained in:
parent
a99f2eb38d
commit
3d19f32942
2 changed files with 143 additions and 123 deletions
|
|
@ -6,16 +6,17 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareInjectorFromMetadata, compileDeclareNgModuleFromMetadata, compileInjector, compileNgModule, CUSTOM_ELEMENTS_SCHEMA, Expression, ExternalExpr, FactoryTarget, InvokeFunctionExpr, LiteralArrayExpr, NO_ERRORS_SCHEMA, R3ClassMetadata, R3CompiledExpression, R3FactoryMetadata, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, R3SelectorScopeMode, SchemaMetadata, Statement, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareInjectorFromMetadata, compileDeclareNgModuleFromMetadata, compileInjector, compileNgModule, Expression, ExternalExpr, FactoryTarget, InvokeFunctionExpr, LiteralArrayExpr, R3ClassMetadata, R3CompiledExpression, R3FactoryMetadata, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, R3SelectorScopeMode, SchemaMetadata, Statement, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {createModuleWithProvidersResolver, isResolvedModuleWithProviders} from '@angular/compiler-cli/src/ngtsc/annotations/ng_module/src/module_with_providers';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../../diagnostics';
|
||||
import {assertSuccessfulReferenceEmit, Reference, ReferenceEmitter} from '../../../imports';
|
||||
import {isArrayEqual, isReferenceEqual, isSymbolEqual, SemanticReference, SemanticSymbol} from '../../../incremental/semantic_graph';
|
||||
import {InjectableClassRegistry, MetadataReader, MetadataRegistry, MetaKind} from '../../../metadata';
|
||||
import {DynamicValue, PartialEvaluator, ResolvedValue, SyntheticValue} from '../../../partial_evaluator';
|
||||
import {PartialEvaluator, ResolvedValue, SyntheticValue} from '../../../partial_evaluator';
|
||||
import {PerfEvent, PerfRecorder} from '../../../perf';
|
||||
import {ClassDeclaration, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../../reflection';
|
||||
import {ClassDeclaration, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
|
||||
import {LocalModuleScopeRegistry, ScopeData} from '../../../scope';
|
||||
import {getDiagnosticNode} from '../../../scope/src/util';
|
||||
import {FactoryTracker} from '../../../shims/api';
|
||||
|
|
@ -179,8 +180,7 @@ export class NgModuleDecoratorHandler implements
|
|||
}
|
||||
|
||||
const moduleResolvers = combineResolvers([
|
||||
(fn, call, resolve, unresolvable) =>
|
||||
this._extractModuleFromModuleWithProvidersFn(fn, call, resolve, unresolvable),
|
||||
createModuleWithProvidersResolver(this.reflector, this.isCore),
|
||||
forwardRefResolver,
|
||||
]);
|
||||
|
||||
|
|
@ -644,112 +644,6 @@ export class NgModuleDecoratorHandler implements
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `FunctionDeclaration`, `MethodDeclaration` or `FunctionExpression`, check if it is
|
||||
* typed as a `ModuleWithProviders` and return an expression referencing the module if available.
|
||||
*/
|
||||
private _extractModuleFromModuleWithProvidersFn(
|
||||
fn: Reference<ts.FunctionDeclaration|ts.MethodDeclaration|ts.FunctionExpression>,
|
||||
node: ts.CallExpression, resolve: (expr: ts.Expression) => ResolvedValue,
|
||||
unresolvable: DynamicValue): SyntheticValue<ResolvedModuleWithProviders>|DynamicValue {
|
||||
const rawType = fn.node.type || null;
|
||||
|
||||
const type = rawType &&
|
||||
(this._reflectModuleFromTypeParam(rawType, fn.node) ||
|
||||
this._reflectModuleFromLiteralType(rawType));
|
||||
if (type === null) {
|
||||
return unresolvable;
|
||||
}
|
||||
const ngModule = resolve(type);
|
||||
if (!(ngModule instanceof Reference) || !isNamedClassDeclaration(ngModule.node)) {
|
||||
return unresolvable;
|
||||
}
|
||||
|
||||
return new SyntheticValue({
|
||||
ngModule: ngModule as Reference<ClassDeclaration>,
|
||||
mwpCall: node,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
|
||||
* `ModuleWithProviders<T>`
|
||||
* @param type The type to reflect on.
|
||||
* @returns the identifier of the NgModule type if found, or null otherwise.
|
||||
*/
|
||||
private _reflectModuleFromTypeParam(
|
||||
type: ts.TypeNode,
|
||||
node: ts.FunctionDeclaration|ts.MethodDeclaration|ts.FunctionExpression): ts.Expression|null {
|
||||
// Examine the type of the function to see if it's a ModuleWithProviders reference.
|
||||
if (!ts.isTypeReferenceNode(type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const typeName = type &&
|
||||
(ts.isIdentifier(type.typeName) && type.typeName ||
|
||||
ts.isQualifiedName(type.typeName) && type.typeName.right) ||
|
||||
null;
|
||||
if (typeName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look at the type itself to see where it comes from.
|
||||
const id = this.reflector.getImportOfIdentifier(typeName);
|
||||
|
||||
// If it's not named ModuleWithProviders, bail.
|
||||
if (id === null || id.name !== 'ModuleWithProviders') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If it's not from @angular/core, bail.
|
||||
if (!this.isCore && id.from !== '@angular/core') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If there's no type parameter specified, bail.
|
||||
if (type.typeArguments === undefined || type.typeArguments.length !== 1) {
|
||||
const parent =
|
||||
ts.isMethodDeclaration(node) && ts.isClassDeclaration(node.parent) ? node.parent : null;
|
||||
const symbolName = (parent && parent.name ? parent.name.getText() + '.' : '') +
|
||||
(node.name ? node.name.getText() : 'anonymous');
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC, type,
|
||||
`${symbolName} returns a ModuleWithProviders type without a generic type argument. ` +
|
||||
`Please add a generic type argument to the ModuleWithProviders type. If this ` +
|
||||
`occurrence is in library code you don't control, please contact the library authors.`);
|
||||
}
|
||||
|
||||
const arg = type.typeArguments[0];
|
||||
|
||||
return typeNodeToValueExpr(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
|
||||
* `A|B|{ngModule: T}|C`.
|
||||
* @param type The type to reflect on.
|
||||
* @returns the identifier of the NgModule type if found, or null otherwise.
|
||||
*/
|
||||
private _reflectModuleFromLiteralType(type: ts.TypeNode): ts.Expression|null {
|
||||
if (!ts.isIntersectionTypeNode(type)) {
|
||||
return null;
|
||||
}
|
||||
for (const t of type.types) {
|
||||
if (ts.isTypeLiteralNode(t)) {
|
||||
for (const m of t.members) {
|
||||
const ngModuleType = ts.isPropertySignature(m) && ts.isIdentifier(m.name) &&
|
||||
m.name.text === 'ngModule' && m.type ||
|
||||
null;
|
||||
const ngModuleExpression = ngModuleType && typeNodeToValueExpr(ngModuleType);
|
||||
if (ngModuleExpression) {
|
||||
return ngModuleExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify that a "Declaration" reference is a `ClassDeclaration` reference.
|
||||
private isClassDeclarationReference(ref: Reference): ref is Reference<ClassDeclaration> {
|
||||
return this.reflector.isClass(ref.node);
|
||||
|
|
@ -826,24 +720,12 @@ function isModuleIdExpression(expr: ts.Expression): boolean {
|
|||
expr.expression.text === 'module' && expr.name.text === 'id';
|
||||
}
|
||||
|
||||
interface ResolvedModuleWithProviders {
|
||||
ngModule: Reference<ClassDeclaration>;
|
||||
mwpCall: ts.CallExpression;
|
||||
}
|
||||
|
||||
export interface TopLevelImportedExpression {
|
||||
expression: ts.Expression;
|
||||
resolvedReferences: Array<Reference<ClassDeclaration>>;
|
||||
hasModuleWithProviders: boolean;
|
||||
}
|
||||
|
||||
function isResolvedModuleWithProviders(sv: SyntheticValue<unknown>):
|
||||
sv is SyntheticValue<ResolvedModuleWithProviders> {
|
||||
return typeof sv.value === 'object' && sv.value != null &&
|
||||
sv.value.hasOwnProperty('ngModule' as keyof ResolvedModuleWithProviders) &&
|
||||
sv.value.hasOwnProperty('mwpCall' as keyof ResolvedModuleWithProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to produce a diagnostics for a situation when a standalone component
|
||||
* is referenced in the `@NgModule.bootstrap` array.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* @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 ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
|
||||
import {Reference} from '../../../imports';
|
||||
import {ForeignFunctionResolver, SyntheticValue} from '../../../partial_evaluator';
|
||||
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost, typeNodeToValueExpr} from '../../../reflection';
|
||||
|
||||
/**
|
||||
* Creates a foreign function resolver to detect a `ModuleWithProviders<T>` type in a return type
|
||||
* position of a function or method declaration. A `SyntheticValue` is produced if such a return
|
||||
* type is recognized.
|
||||
*
|
||||
* @param reflector The reflection host to use for analyzing the syntax.
|
||||
* @param isCore Whether the @angular/core package is being compiled.
|
||||
*/
|
||||
export function createModuleWithProvidersResolver(
|
||||
reflector: ReflectionHost, isCore: boolean): ForeignFunctionResolver {
|
||||
/**
|
||||
* Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
|
||||
* `ModuleWithProviders<T>`
|
||||
* @param type The type to reflect on.
|
||||
* @returns the identifier of the NgModule type if found, or null otherwise.
|
||||
*/
|
||||
function _reflectModuleFromTypeParam(
|
||||
type: ts.TypeNode,
|
||||
node: ts.FunctionDeclaration|ts.MethodDeclaration|ts.FunctionExpression): ts.Expression|null {
|
||||
// Examine the type of the function to see if it's a ModuleWithProviders reference.
|
||||
if (!ts.isTypeReferenceNode(type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const typeName = type &&
|
||||
(ts.isIdentifier(type.typeName) && type.typeName ||
|
||||
ts.isQualifiedName(type.typeName) && type.typeName.right) ||
|
||||
null;
|
||||
if (typeName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look at the type itself to see where it comes from.
|
||||
const id = reflector.getImportOfIdentifier(typeName);
|
||||
|
||||
// If it's not named ModuleWithProviders, bail.
|
||||
if (id === null || id.name !== 'ModuleWithProviders') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If it's not from @angular/core, bail.
|
||||
if (!isCore && id.from !== '@angular/core') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If there's no type parameter specified, bail.
|
||||
if (type.typeArguments === undefined || type.typeArguments.length !== 1) {
|
||||
const parent =
|
||||
ts.isMethodDeclaration(node) && ts.isClassDeclaration(node.parent) ? node.parent : null;
|
||||
const symbolName = (parent && parent.name ? parent.name.getText() + '.' : '') +
|
||||
(node.name ? node.name.getText() : 'anonymous');
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC, type,
|
||||
`${symbolName} returns a ModuleWithProviders type without a generic type argument. ` +
|
||||
`Please add a generic type argument to the ModuleWithProviders type. If this ` +
|
||||
`occurrence is in library code you don't control, please contact the library authors.`);
|
||||
}
|
||||
|
||||
const arg = type.typeArguments[0];
|
||||
|
||||
return typeNodeToValueExpr(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
|
||||
* `A|B|{ngModule: T}|C`.
|
||||
* @param type The type to reflect on.
|
||||
* @returns the identifier of the NgModule type if found, or null otherwise.
|
||||
*/
|
||||
function _reflectModuleFromLiteralType(type: ts.TypeNode): ts.Expression|null {
|
||||
if (!ts.isIntersectionTypeNode(type)) {
|
||||
return null;
|
||||
}
|
||||
for (const t of type.types) {
|
||||
if (ts.isTypeLiteralNode(t)) {
|
||||
for (const m of t.members) {
|
||||
const ngModuleType = ts.isPropertySignature(m) && ts.isIdentifier(m.name) &&
|
||||
m.name.text === 'ngModule' && m.type ||
|
||||
null;
|
||||
const ngModuleExpression = ngModuleType && typeNodeToValueExpr(ngModuleType);
|
||||
if (ngModuleExpression) {
|
||||
return ngModuleExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return (fn, callExpr, resolve, unresolvable) => {
|
||||
const rawType = fn.node.type;
|
||||
if (rawType === undefined) {
|
||||
return unresolvable;
|
||||
}
|
||||
|
||||
const type =
|
||||
_reflectModuleFromTypeParam(rawType, fn.node) ?? _reflectModuleFromLiteralType(rawType);
|
||||
if (type === null) {
|
||||
return unresolvable;
|
||||
}
|
||||
const ngModule = resolve(type);
|
||||
if (!(ngModule instanceof Reference) || !isNamedClassDeclaration(ngModule.node)) {
|
||||
return unresolvable;
|
||||
}
|
||||
|
||||
return new SyntheticValue({
|
||||
ngModule: ngModule as Reference<ClassDeclaration>,
|
||||
mwpCall: callExpr,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResolvedModuleWithProviders {
|
||||
ngModule: Reference<ClassDeclaration>;
|
||||
mwpCall: ts.CallExpression;
|
||||
}
|
||||
|
||||
export function isResolvedModuleWithProviders(sv: SyntheticValue<unknown>):
|
||||
sv is SyntheticValue<ResolvedModuleWithProviders> {
|
||||
return typeof sv.value === 'object' && sv.value != null &&
|
||||
sv.value.hasOwnProperty('ngModule' as keyof ResolvedModuleWithProviders) &&
|
||||
sv.value.hasOwnProperty('mwpCall' as keyof ResolvedModuleWithProviders);
|
||||
}
|
||||
Loading…
Reference in a new issue