The `ModuleWithProviders` type has an optional type parameter that should be specified to indicate what NgModule class will be provided. This enables the Ivy compiler to statically determine the NgModule type from the declaration files. This type parameter will become required in the future, however to aid in the migration the compiler will detect code patterns where using `ModuleWithProviders` as return type is appropriate, in which case it transforms the emitted .d.ts files to include the generic type argument. This should reduce the number of occurrences where `ModuleWithProviders` is referenced without its generic type argument. Resolves FW-389 PR Close #34235
299 lines
8.4 KiB
TypeScript
299 lines
8.4 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
|
import {loadStandardTestFiles} from '../helpers/src/mock_file_loading';
|
|
|
|
import {NgtscTestEnvironment} from './env';
|
|
|
|
const trim = (input: string): string => input.replace(/\s+/g, ' ').trim();
|
|
|
|
const testFiles = loadStandardTestFiles();
|
|
|
|
runInEachFileSystem(() => {
|
|
describe('ModuleWithProviders generic type transform', () => {
|
|
let env !: NgtscTestEnvironment;
|
|
|
|
beforeEach(() => {
|
|
env = NgtscTestEnvironment.setup(testFiles);
|
|
env.tsconfig();
|
|
});
|
|
|
|
it('should add a generic type for static methods on exported classes', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
|
|
@NgModule()
|
|
export class TestModule {
|
|
static forRoot() {
|
|
return {
|
|
ngModule: TestModule,
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('import * as i0 from "@angular/core";');
|
|
expect(dtsContents).toContain('static forRoot(): i0.ModuleWithProviders<TestModule>;');
|
|
});
|
|
|
|
it('should not add a generic type for non-static methods', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
|
|
@NgModule()
|
|
export class TestModule {
|
|
forRoot() {
|
|
return {
|
|
ngModule: TestModule,
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('import * as i0 from "@angular/core";');
|
|
expect(dtsContents).toContain('forRoot(): { ngModule: typeof TestModule; };');
|
|
expect(dtsContents).not.toContain('static forRoot()');
|
|
});
|
|
|
|
it('should add a generic type for exported functions', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
|
|
export function forRoot() {
|
|
return {
|
|
ngModule: TestModule,
|
|
};
|
|
}
|
|
|
|
@NgModule()
|
|
export class TestModule {}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('import * as i0 from "@angular/core";');
|
|
expect(dtsContents)
|
|
.toContain('export declare function forRoot(): i0.ModuleWithProviders<TestModule>;');
|
|
});
|
|
|
|
it('should not add a generic type when already present', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule, ModuleWithProviders} from '@angular/core';
|
|
|
|
export class TestModule {
|
|
forRoot(): ModuleWithProviders<InternalTestModule> {
|
|
return {
|
|
ngModule: TestModule,
|
|
};
|
|
}
|
|
}
|
|
|
|
@NgModule()
|
|
export class InternalTestModule {}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('forRoot(): ModuleWithProviders<InternalTestModule>;');
|
|
});
|
|
|
|
it('should add a generic type when missing the generic type parameter', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule, ModuleWithProviders} from '@angular/core';
|
|
|
|
@NgModule()
|
|
export class TestModule {
|
|
static forRoot(): ModuleWithProviders {
|
|
return {
|
|
ngModule: TestModule,
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('static forRoot(): i0.ModuleWithProviders<TestModule>;');
|
|
});
|
|
|
|
it('should add a generic type when missing the generic type parameter (qualified name)', () => {
|
|
env.write('test.ts', `
|
|
import * as ng from '@angular/core';
|
|
|
|
@ng.NgModule()
|
|
export class TestModule {
|
|
static forRoot(): ng.ModuleWithProviders {
|
|
return {
|
|
ngModule: TestModule,
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('static forRoot(): i0.ModuleWithProviders<TestModule>;');
|
|
});
|
|
|
|
it('should add a generic type and add an import for external references', () => {
|
|
env.write('test.ts', `
|
|
import {ModuleWithProviders} from '@angular/core';
|
|
import {InternalTestModule} from './internal';
|
|
|
|
export class TestModule {
|
|
static forRoot(): ModuleWithProviders {
|
|
return {
|
|
ngModule: InternalTestModule,
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
env.write('internal.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
|
|
@NgModule()
|
|
export class InternalTestModule {}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('import * as i1 from "./internal";');
|
|
expect(dtsContents)
|
|
.toContain('static forRoot(): i0.ModuleWithProviders<i1.InternalTestModule>;');
|
|
});
|
|
|
|
it('should not add a generic type if the return type is not ModuleWithProviders', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
|
|
@NgModule()
|
|
export class TestModule {
|
|
static forRoot(): { ngModule: typeof TestModule } {
|
|
return {
|
|
ngModule: TestModule,
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('static forRoot(): { ngModule: typeof TestModule; };');
|
|
});
|
|
|
|
it('should not add a generic type if the return type is not ModuleWithProviders from @angular/core',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
import {ModuleWithProviders} from './mwp';
|
|
|
|
@NgModule()
|
|
export class TestModule {
|
|
static forRoot(): ModuleWithProviders {
|
|
return {
|
|
ngModule: TestModule,
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
env.write('mwp.ts', `
|
|
export type ModuleWithProviders = { ngModule: any };
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('static forRoot(): ModuleWithProviders;');
|
|
});
|
|
|
|
it('should not add a generic type when the "ngModule" property is not a reference', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
|
|
@NgModule()
|
|
export class TestModule {
|
|
static forRoot() {
|
|
return {
|
|
ngModule: 'test',
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).toContain('static forRoot(): { ngModule: string; };');
|
|
});
|
|
|
|
it('should not add a generic type when the class is not exported', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
|
|
@NgModule()
|
|
class TestModule {
|
|
static forRoot() {
|
|
return {
|
|
ngModule: TestModule,
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
// The TestModule class is not exported so doesn't even show up in the declaration file
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents).not.toContain('static forRoot()');
|
|
});
|
|
|
|
it('should add a generic type only when ngModule/providers are present', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule, ModuleWithProviders} from '@angular/core';
|
|
|
|
@NgModule()
|
|
export class TestModule {
|
|
static hasNgModuleAndProviders() {
|
|
return {
|
|
ngModule: TestModule,
|
|
providers: [],
|
|
};
|
|
}
|
|
static hasNgModuleAndFoo() {
|
|
return {
|
|
ngModule: TestModule,
|
|
foo: 'test',
|
|
};
|
|
}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
|
|
const dtsContents = trim(env.getContents('test.d.ts'));
|
|
expect(dtsContents)
|
|
.toContain('static hasNgModuleAndProviders(): i0.ModuleWithProviders<TestModule>;');
|
|
expect(dtsContents)
|
|
.toContain('static hasNgModuleAndFoo(): { ngModule: typeof TestModule; foo: string; };');
|
|
});
|
|
});
|
|
});
|