refactor(ivy): include generic type for ModuleWithProviders
in .d.ts files (#34235)
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
This commit is contained in:
@ -13,6 +13,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/incremental",
|
||||
"//packages/compiler-cli/src/ngtsc/indexer",
|
||||
"//packages/compiler-cli/src/ngtsc/modulewithproviders",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/scope",
|
||||
|
@ -8,5 +8,5 @@
|
||||
|
||||
export * from './src/api';
|
||||
export {IvyCompilation} from './src/compilation';
|
||||
export {declarationTransformFactory, DtsTransformRegistry, IvyDeclarationDtsTransform} from './src/declaration';
|
||||
export {declarationTransformFactory, DtsTransformRegistry, IvyDeclarationDtsTransform, ReturnTypeTransform} from './src/declaration';
|
||||
export {ivyTransformFactory} from './src/transform';
|
||||
|
@ -6,13 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool} from '@angular/compiler';
|
||||
import {ConstantPool, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {ImportRewriter} from '../../imports';
|
||||
import {IncrementalDriver} from '../../incremental';
|
||||
import {IndexingContext} from '../../indexer';
|
||||
import {ModuleWithProvidersScanner} from '../../modulewithproviders';
|
||||
import {PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, ReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry} from '../../scope';
|
||||
@ -71,7 +72,8 @@ export class IvyCompilation {
|
||||
private importRewriter: ImportRewriter, private incrementalDriver: IncrementalDriver,
|
||||
private perf: PerfRecorder, private sourceToFactorySymbols: Map<string, Set<string>>|null,
|
||||
private scopeRegistry: LocalModuleScopeRegistry, private compileNonExportedClasses: boolean,
|
||||
private dtsTransforms: DtsTransformRegistry) {}
|
||||
private dtsTransforms: DtsTransformRegistry, private mwpScanner: ModuleWithProvidersScanner) {
|
||||
}
|
||||
|
||||
get exportStatements(): Map<string, Map<string, [string, string]>> { return this.reexportMap; }
|
||||
|
||||
@ -235,6 +237,14 @@ export class IvyCompilation {
|
||||
|
||||
visit(sf);
|
||||
|
||||
this.mwpScanner.scan(sf, {
|
||||
addTypeReplacement: (node: ts.Declaration, type: Type): void => {
|
||||
// Only obtain the return type transform for the source file once there's a type to replace,
|
||||
// so that no transform is allocated when there's nothing to do.
|
||||
this.dtsTransforms.getReturnTypeTransform(sf).addTypeReplacement(node, type);
|
||||
}
|
||||
});
|
||||
|
||||
if (preanalyze && promises.length > 0) {
|
||||
return Promise.all(promises).then(() => undefined);
|
||||
} else {
|
||||
|
@ -21,13 +21,21 @@ import {addImports} from './utils';
|
||||
* have their declaration file transformed.
|
||||
*/
|
||||
export class DtsTransformRegistry {
|
||||
private ivyDeclarationTransforms = new Map<string, IvyDeclarationDtsTransform>();
|
||||
private ivyDeclarationTransforms = new Map<ts.SourceFile, IvyDeclarationDtsTransform>();
|
||||
private returnTypeTransforms = new Map<ts.SourceFile, ReturnTypeTransform>();
|
||||
|
||||
getIvyDeclarationTransform(sf: ts.SourceFile): IvyDeclarationDtsTransform {
|
||||
if (!this.ivyDeclarationTransforms.has(sf.fileName)) {
|
||||
this.ivyDeclarationTransforms.set(sf.fileName, new IvyDeclarationDtsTransform());
|
||||
if (!this.ivyDeclarationTransforms.has(sf)) {
|
||||
this.ivyDeclarationTransforms.set(sf, new IvyDeclarationDtsTransform());
|
||||
}
|
||||
return this.ivyDeclarationTransforms.get(sf.fileName) !;
|
||||
return this.ivyDeclarationTransforms.get(sf) !;
|
||||
}
|
||||
|
||||
getReturnTypeTransform(sf: ts.SourceFile): ReturnTypeTransform {
|
||||
if (!this.returnTypeTransforms.has(sf)) {
|
||||
this.returnTypeTransforms.set(sf, new ReturnTypeTransform());
|
||||
}
|
||||
return this.returnTypeTransforms.get(sf) !;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,11 +50,16 @@ export class DtsTransformRegistry {
|
||||
if (!sf.isDeclarationFile) {
|
||||
return null;
|
||||
}
|
||||
const originalSf = ts.getOriginalNode(sf) as ts.SourceFile;
|
||||
|
||||
let transforms: DtsTransform[]|null = null;
|
||||
if (this.ivyDeclarationTransforms.has(sf.fileName)) {
|
||||
if (this.ivyDeclarationTransforms.has(originalSf)) {
|
||||
transforms = [];
|
||||
transforms.push(this.ivyDeclarationTransforms.get(sf.fileName) !);
|
||||
transforms.push(this.ivyDeclarationTransforms.get(originalSf) !);
|
||||
}
|
||||
if (this.returnTypeTransforms.has(originalSf)) {
|
||||
transforms = transforms || [];
|
||||
transforms.push(this.returnTypeTransforms.get(originalSf) !);
|
||||
}
|
||||
return transforms;
|
||||
}
|
||||
@ -211,3 +224,61 @@ export class IvyDeclarationDtsTransform implements DtsTransform {
|
||||
/* members */[...members, ...newMembers]);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReturnTypeTransform implements DtsTransform {
|
||||
private typeReplacements = new Map<ts.Declaration, Type>();
|
||||
|
||||
addTypeReplacement(declaration: ts.Declaration, type: Type): void {
|
||||
this.typeReplacements.set(declaration, type);
|
||||
}
|
||||
|
||||
transformClassElement(element: ts.ClassElement, imports: ImportManager): ts.ClassElement {
|
||||
if (!ts.isMethodSignature(element)) {
|
||||
return element;
|
||||
}
|
||||
|
||||
const original = ts.getOriginalNode(element) as ts.MethodDeclaration;
|
||||
if (!this.typeReplacements.has(original)) {
|
||||
return element;
|
||||
}
|
||||
const returnType = this.typeReplacements.get(original) !;
|
||||
const tsReturnType = translateType(returnType, imports);
|
||||
|
||||
const methodSignature = ts.updateMethodSignature(
|
||||
/* node */ element,
|
||||
/* typeParameters */ element.typeParameters,
|
||||
/* parameters */ element.parameters,
|
||||
/* type */ tsReturnType,
|
||||
/* name */ element.name,
|
||||
/* questionToken */ element.questionToken);
|
||||
|
||||
// Copy over any modifiers, these cannot be set during the `ts.updateMethodSignature` call.
|
||||
methodSignature.modifiers = element.modifiers;
|
||||
|
||||
// A bug in the TypeScript declaration causes `ts.MethodSignature` not to be assignable to
|
||||
// `ts.ClassElement`. Since `element` was a `ts.MethodSignature` already, transforming it into
|
||||
// this type is actually correct.
|
||||
return methodSignature as unknown as ts.ClassElement;
|
||||
}
|
||||
|
||||
transformFunctionDeclaration(element: ts.FunctionDeclaration, imports: ImportManager):
|
||||
ts.FunctionDeclaration {
|
||||
const original = ts.getOriginalNode(element) as ts.FunctionDeclaration;
|
||||
if (!this.typeReplacements.has(original)) {
|
||||
return element;
|
||||
}
|
||||
const returnType = this.typeReplacements.get(original) !;
|
||||
const tsReturnType = translateType(returnType, imports);
|
||||
|
||||
return ts.updateFunctionDeclaration(
|
||||
/* node */ element,
|
||||
/* decorators */ element.decorators,
|
||||
/* modifiers */ element.modifiers,
|
||||
/* asteriskToken */ element.asteriskToken,
|
||||
/* name */ element.name,
|
||||
/* typeParameters */ element.typeParameters,
|
||||
/* parameters */ element.parameters,
|
||||
/* type */ tsReturnType,
|
||||
/* body */ element.body);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user