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:
parent
f2a1c66031
commit
4b70a4e905
|
@ -197,9 +197,20 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
*/
|
*/
|
||||||
private _extractModuleFromModuleWithProvidersFn(node: ts.FunctionDeclaration|
|
private _extractModuleFromModuleWithProvidersFn(node: ts.FunctionDeclaration|
|
||||||
ts.MethodDeclaration): ts.Expression|null {
|
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.
|
// 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +237,32 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
return typeNodeToValueExpr(arg);
|
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.
|
* Compute a list of `Reference`s from a resolved metadata value.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -441,6 +441,39 @@ describe('ngtsc behavioral tests', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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<TestModule, never, [typeof i1.RouterModule], never>');
|
||||||
|
});
|
||||||
|
|
||||||
it('should inject special types according to the metadata', () => {
|
it('should inject special types according to the metadata', () => {
|
||||||
env.tsconfig();
|
env.tsconfig();
|
||||||
env.write(`test.ts`, `
|
env.write(`test.ts`, `
|
||||||
|
|
Loading…
Reference in New Issue