feat(ivy): support NgModule metadata from calls that do not return ModuleWithProviders types (#27326)

Normally functions that return `ModuleWithProvider` objects should parameterize
the return type to include the type of `NgModule` that is being returned. For
example `forRoot(): ModuleWithProviders<RouterModule>`.

But in some cases, especially those generated by nccc, these functions to not
explicitly declare `ModuleWithProviders` as their return type. Instead they
return a "intersection" type, one of whose members is a type literal that
declares the `NgModule` type returned. For example:
`forRoot(): CustomType&{ngModule:RouterModule}`.

This commit changes the `NgModuleDecoratorHandler` so that it can extract
the `NgModule` type from either kind of declaration.

PR Close #27326
This commit is contained in:
Pete Bacon Darwin
2018-12-11 12:14:21 +00:00
committed by Matias Niemelä
parent f2a1c66031
commit 4b70a4e905
2 changed files with 72 additions and 2 deletions

View File

@ -197,9 +197,20 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
*/
private _extractModuleFromModuleWithProvidersFn(node: ts.FunctionDeclaration|
ts.MethodDeclaration): ts.Expression|null {
const type = node.type;
const type = node.type || null;
return type &&
(this._reflectModuleFromTypeParam(type) || this._reflectModuleFromLiteralType(type));
}
/**
* 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): ts.Expression|null {
// Examine the type of the function to see if it's a ModuleWithProviders reference.
if (type === undefined || !ts.isTypeReferenceNode(type) || !ts.isIdentifier(type.typeName)) {
if (!ts.isTypeReferenceNode(type) || !ts.isIdentifier(type.typeName)) {
return null;
}
@ -226,6 +237,32 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
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;
}
/**
* Compute a list of `Reference`s from a resolved metadata value.
*/