feat(ivy): resolve forwardRef() for queries (#25080)
@ContentChild[ren] and @ViewChild[ren] can contain a forwardRef() to a type. This commit allows ngtsc to unwrap the forward reference and deal with the node inside. It includes two modes of support for forward reference resolution - a foreign function resolver which understands deeply nested forward references in expressions that are being statically evaluated, and an unwrapForwardRef() function which deals only with top-level nodes. Both will be useful in the future, but for now only unwrapForwardRef() is used. PR Close #25080
This commit is contained in:

committed by
Igor Minar

parent
48d7205873
commit
f902b5ec59
@ -14,7 +14,7 @@ import {Reference, filterToMembersWithDecorator, reflectObjectLiteral, staticall
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {getConstructorDependencies, isAngularCore, unwrapExpression} from './util';
|
||||
import {getConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwardRef} from './util';
|
||||
|
||||
const EMPTY_OBJECT: {[key: string]: string} = {};
|
||||
|
||||
@ -156,12 +156,13 @@ export function extractQueryMetadata(
|
||||
throw new Error(`@${name} must have arguments`);
|
||||
}
|
||||
const first = name === 'ViewChild' || name === 'ContentChild';
|
||||
const arg = staticallyResolve(args[0], reflector, checker);
|
||||
const node = unwrapForwardRef(args[0], reflector);
|
||||
const arg = staticallyResolve(node, reflector, checker);
|
||||
|
||||
// Extract the predicate
|
||||
let predicate: Expression|string[]|null = null;
|
||||
if (arg instanceof Reference) {
|
||||
predicate = new WrappedNodeExpr(args[0]);
|
||||
predicate = new WrappedNodeExpr(node);
|
||||
} else if (typeof arg === 'string') {
|
||||
predicate = [arg];
|
||||
} else if (isStringArrayOrDie(arg, '@' + name)) {
|
||||
|
@ -67,14 +67,14 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
if (ngModule.has('imports')) {
|
||||
const importsMeta = staticallyResolve(
|
||||
ngModule.get('imports') !, this.reflector, this.checker,
|
||||
node => this._extractModuleFromModuleWithProvidersFn(node));
|
||||
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
|
||||
imports = resolveTypeList(importsMeta, 'imports');
|
||||
}
|
||||
let exports: Reference[] = [];
|
||||
if (ngModule.has('exports')) {
|
||||
const exportsMeta = staticallyResolve(
|
||||
ngModule.get('exports') !, this.reflector, this.checker,
|
||||
node => this._extractModuleFromModuleWithProvidersFn(node));
|
||||
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
|
||||
exports = resolveTypeList(exportsMeta, 'exports');
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {Expression, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeE
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
import {Reference} from '../../metadata';
|
||||
import {AbsoluteReference, Reference} from '../../metadata';
|
||||
|
||||
export function getConstructorDependencies(
|
||||
clazz: ts.ClassDeclaration, reflector: ReflectionHost,
|
||||
@ -103,3 +103,69 @@ export function unwrapExpression(node: ts.Expression): ts.Expression {
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function expandForwardRef(arg: ts.Expression): ts.Expression|null {
|
||||
if (!ts.isArrowFunction(arg) && !ts.isFunctionExpression(arg)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const body = arg.body;
|
||||
// Either the body is a ts.Expression directly, or a block with a single return statement.
|
||||
if (ts.isBlock(body)) {
|
||||
// Block body - look for a single return statement.
|
||||
if (body.statements.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
const stmt = body.statements[0];
|
||||
if (!ts.isReturnStatement(stmt) || stmt.expression === undefined) {
|
||||
return null;
|
||||
}
|
||||
return stmt.expression;
|
||||
} else {
|
||||
// Shorthand body - return as an expression.
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Possibly resolve a forwardRef() expression into the inner value.
|
||||
*
|
||||
* @param node the forwardRef() expression to resolve
|
||||
* @param reflector a ReflectionHost
|
||||
* @returns the resolved expression, if the original expression was a forwardRef(), or the original
|
||||
* expression otherwise
|
||||
*/
|
||||
export function unwrapForwardRef(node: ts.Expression, reflector: ReflectionHost): ts.Expression {
|
||||
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression) ||
|
||||
node.arguments.length !== 1) {
|
||||
return node;
|
||||
}
|
||||
const expr = expandForwardRef(node.arguments[0]);
|
||||
if (expr === null) {
|
||||
return node;
|
||||
}
|
||||
const imp = reflector.getImportOfIdentifier(node.expression);
|
||||
if (imp === null || imp.from !== '@angular/core' || imp.name !== 'forwardRef') {
|
||||
return node;
|
||||
} else {
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A foreign function resolver for `staticallyResolve` which unwraps forwardRef() expressions.
|
||||
*
|
||||
* @param ref a Reference to the declaration of the function being called (which might be
|
||||
* forwardRef)
|
||||
* @param args the arguments to the invocation of the forwardRef expression
|
||||
* @returns an unwrapped argument if `ref` pointed to forwardRef, or null otherwise
|
||||
*/
|
||||
export function forwardRefResolver(
|
||||
ref: Reference<ts.FunctionDeclaration|ts.MethodDeclaration>,
|
||||
args: ts.Expression[]): ts.Expression|null {
|
||||
if (!(ref instanceof AbsoluteReference) || ref.moduleName !== '@angular/core' ||
|
||||
ref.symbolName !== 'forwardRef' || args.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
return expandForwardRef(args[0]);
|
||||
}
|
||||
|
Reference in New Issue
Block a user