From 4b70a4e905b7a38b64f235412150cafd30d9559e Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 11 Dec 2018 12:14:21 +0000 Subject: [PATCH] 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`. 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 --- .../src/ngtsc/annotations/src/ng_module.ts | 41 ++++++++++++++++++- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 33 +++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index a19261665d..54cb3a65cb 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -197,9 +197,20 @@ export class NgModuleDecoratorHandler implements DecoratorHandler` + * @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 { }); }); + it('should unwrap a ModuleWithProviders-like function if a matching literal type is provided for it', + () => { + env.tsconfig(); + env.write(`test.ts`, ` + import {NgModule} from '@angular/core'; + import {RouterModule} from 'router'; + + @NgModule({imports: [RouterModule.forRoot()]}) + export class TestModule {} + `); + + env.write('node_modules/router/index.d.ts', ` + import {ModuleWithProviders} from '@angular/core'; + + export interface MyType extends ModuleWithProviders {} + + declare class RouterModule { + static forRoot(): (MyType)&{ngModule:RouterModule}; + } + `); + + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain('imports: [[RouterModule.forRoot()]]'); + + const dtsContents = env.getContents('test.d.ts'); + expect(dtsContents).toContain(`import * as i1 from 'router';`); + expect(dtsContents) + .toContain( + 'i0.ɵNgModuleDefWithMeta'); + }); + it('should inject special types according to the metadata', () => { env.tsconfig(); env.write(`test.ts`, `