fix(ivy): support complex type nodes in ModuleWithProviders (#27562)

With ngcc's ability to fixup pre-Ivy ModuleWithProviders such that they
include a reference to the NgModule type, the type may become a qualified
name:

```
import {ModuleWithProviders} from '@angular/core';
import * as ngcc0 from './module';

export declare provide(): ModuleWithProviders<ngcc0.Module>;
```

ngtsc now takes this situation into account when reflecting a
ModuleWithProvider's type argument.

PR Close #27562
This commit is contained in:
JoostK 2018-12-09 15:20:31 +01:00 committed by Miško Hevery
parent 7fae9114c8
commit a8ebc837ea
4 changed files with 52 additions and 20 deletions

View File

@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {Decorator, ReflectionHost} from '../../host';
import {Reference, ResolvedReference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
import {Reference, ResolvedReference, ResolvedValue, reflectObjectLiteral, staticallyResolve, typeNodeToValueExpr} from '../../metadata';
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {generateSetClassMetadataCall} from './metadata';
@ -223,12 +223,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const arg = type.typeArguments[0];
// If the argument isn't an Identifier, bail.
if (!ts.isTypeReferenceNode(arg) || !ts.isIdentifier(arg.typeName)) {
return null;
}
return arg.typeName;
return typeNodeToValueExpr(arg);
}
/**

View File

@ -8,5 +8,5 @@
/// <reference types="node" />
export {TypeScriptReflectionHost, filterToMembersWithDecorator, findMember, reflectObjectLiteral, reflectTypeEntityToDeclaration} from './src/reflector';
export {TypeScriptReflectionHost, filterToMembersWithDecorator, findMember, reflectObjectLiteral, reflectTypeEntityToDeclaration, typeNodeToValueExpr} from './src/reflector';
export {AbsoluteReference, EnumValue, ImportMode, Reference, ResolvedReference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';

View File

@ -436,7 +436,7 @@ function parameterName(name: ts.BindingName): string|null {
}
}
function typeNodeToValueExpr(node: ts.TypeNode): ts.Expression|null {
export function typeNodeToValueExpr(node: ts.TypeNode): ts.Expression|null {
if (ts.isTypeReferenceNode(node)) {
return entityNameToValue(node.typeName);
} else {

View File

@ -375,9 +375,10 @@ describe('ngtsc behavioral tests', () => {
'i0.ɵNgModuleDefWithMeta<TestModule, [typeof TestPipe, typeof TestCmp], never, never>');
});
it('should unwrap a ModuleWithProviders function if a generic type is provided for it', () => {
env.tsconfig();
env.write(`test.ts`, `
describe('unwrapping ModuleWithProviders functions', () => {
it('should extract the generic type and include it in the module\'s declaration', () => {
env.tsconfig();
env.write(`test.ts`, `
import {NgModule} from '@angular/core';
import {RouterModule} from 'router';
@ -385,7 +386,7 @@ describe('ngtsc behavioral tests', () => {
export class TestModule {}
`);
env.write('node_modules/router/index.d.ts', `
env.write('node_modules/router/index.d.ts', `
import {ModuleWithProviders} from '@angular/core';
declare class RouterModule {
@ -393,15 +394,51 @@ describe('ngtsc behavioral tests', () => {
}
`);
env.driveMain();
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents).toContain('imports: [[RouterModule.forRoot()]]');
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<TestModule, never, [typeof i1.RouterModule], never>');
const dtsContents = env.getContents('test.d.ts');
expect(dtsContents).toContain(`import * as i1 from 'router';`);
expect(dtsContents)
.toContain('i0.ɵNgModuleDefWithMeta<TestModule, never, [typeof i1.RouterModule], never>');
});
it('should extract the generic type if it is provided as qualified type name', () => {
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';
import * as internal from './internal';
declare class RouterModule {
static forRoot(): ModuleWithProviders<internal.InternalRouterModule>;
}
`);
env.write('node_modules/router/internal.d.ts', `
export declare class InternalRouterModule {}
`);
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<TestModule, never, [typeof i1.InternalRouterModule], never>');
});
});
it('should inject special types according to the metadata', () => {