Skip to content

Commit 0f40756

Browse files
crisbetoalxhub
authored andcommitted
refactor(compiler): introduce internal transplanted type (#50104)
Adds a new AST for a `TransplantedType` in the compiler which will be used for some upcoming work. A transplanted type is a type node that is defined in one place in the app, but needs to be copied to a different one (e.g. the generated .d.ts). These changes also include updates to the type translator that will rewrite any type references within the type to point to the new context file. PR Close #50104
1 parent 570114e commit 0f40756

File tree

8 files changed

+143
-20
lines changed

8 files changed

+143
-20
lines changed

packages/compiler-cli/src/ngtsc/core/src/compiler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,9 @@ export class NgCompiler {
625625

626626
const afterDeclarations: ts.TransformerFactory<ts.SourceFile>[] = [];
627627
if (compilation.dtsTransforms !== null) {
628-
afterDeclarations.push(
629-
declarationTransformFactory(compilation.dtsTransforms, importRewriter));
628+
afterDeclarations.push(declarationTransformFactory(
629+
compilation.dtsTransforms, compilation.reflector, compilation.refEmitter,
630+
importRewriter));
630631
}
631632

632633
// Only add aliasing re-exports to the .d.ts output if the `AliasingHost` requests it.

packages/compiler-cli/src/ngtsc/transform/src/api.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
import {ConstantPool, Expression, Statement, Type} from '@angular/compiler';
1010
import ts from 'typescript';
1111

12-
import {Reexport} from '../../imports';
12+
import {Reexport, ReferenceEmitter} from '../../imports';
1313
import {SemanticSymbol} from '../../incremental/semantic_graph';
1414
import {IndexingContext} from '../../indexer';
15-
import {ClassDeclaration, Decorator} from '../../reflection';
15+
import {ClassDeclaration, Decorator, ReflectionHost} from '../../reflection';
1616
import {ImportManager} from '../../translator';
1717
import {TypeCheckContext} from '../../typecheck/api';
1818
import {ExtendedTemplateChecker} from '../../typecheck/extended/api';
@@ -278,5 +278,6 @@ export interface DtsTransform {
278278
(element: ts.FunctionDeclaration, imports: ImportManager): ts.FunctionDeclaration;
279279
transformClass?
280280
(clazz: ts.ClassDeclaration, elements: ReadonlyArray<ts.ClassElement>,
281+
reflector: ReflectionHost, refEmitter: ReferenceEmitter,
281282
imports: ImportManager): ts.ClassDeclaration;
282283
}

packages/compiler-cli/src/ngtsc/transform/src/declaration.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import {Type} from '@angular/compiler';
1010
import ts from 'typescript';
1111

12-
import {ImportRewriter} from '../../imports';
13-
import {ClassDeclaration} from '../../reflection';
12+
import {ImportRewriter, ReferenceEmitter} from '../../imports';
13+
import {ClassDeclaration, ReflectionHost} from '../../reflection';
1414
import {ImportManager, translateType} from '../../translator';
1515

1616
import {DtsTransform} from './api';
@@ -54,10 +54,12 @@ export class DtsTransformRegistry {
5454
}
5555

5656
export function declarationTransformFactory(
57-
transformRegistry: DtsTransformRegistry, importRewriter: ImportRewriter,
57+
transformRegistry: DtsTransformRegistry, reflector: ReflectionHost,
58+
refEmitter: ReferenceEmitter, importRewriter: ImportRewriter,
5859
importPrefix?: string): ts.TransformerFactory<ts.SourceFile> {
5960
return (context: ts.TransformationContext) => {
60-
const transformer = new DtsTransformer(context, importRewriter, importPrefix);
61+
const transformer =
62+
new DtsTransformer(context, reflector, refEmitter, importRewriter, importPrefix);
6163
return (fileOrBundle) => {
6264
if (ts.isBundle(fileOrBundle)) {
6365
// Only attempt to transform source files.
@@ -77,7 +79,8 @@ export function declarationTransformFactory(
7779
*/
7880
class DtsTransformer {
7981
constructor(
80-
private ctx: ts.TransformationContext, private importRewriter: ImportRewriter,
82+
private ctx: ts.TransformationContext, private reflector: ReflectionHost,
83+
private refEmitter: ReferenceEmitter, private importRewriter: ImportRewriter,
8184
private importPrefix?: string) {}
8285

8386
/**
@@ -133,7 +136,8 @@ class DtsTransformer {
133136
// not yet been incorporated. Otherwise, `newClazz.members` holds the latest class members.
134137
const inputMembers = (clazz === newClazz ? elements : newClazz.members);
135138

136-
newClazz = transform.transformClass(newClazz, inputMembers, imports);
139+
newClazz = transform.transformClass(
140+
newClazz, inputMembers, this.reflector, this.refEmitter, imports);
137141
}
138142
}
139143

@@ -181,6 +185,7 @@ export class IvyDeclarationDtsTransform implements DtsTransform {
181185

182186
transformClass(
183187
clazz: ts.ClassDeclaration, members: ReadonlyArray<ts.ClassElement>,
188+
reflector: ReflectionHost, refEmitter: ReferenceEmitter,
184189
imports: ImportManager): ts.ClassDeclaration {
185190
const original = ts.getOriginalNode(clazz) as ClassDeclaration;
186191

@@ -191,7 +196,8 @@ export class IvyDeclarationDtsTransform implements DtsTransform {
191196

192197
const newMembers = fields.map(decl => {
193198
const modifiers = [ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)];
194-
const typeRef = translateType(decl.type, imports);
199+
const typeRef =
200+
translateType(decl.type, original.getSourceFile(), reflector, refEmitter, imports);
195201
markForEmitAsSingleLine(typeRef);
196202
return ts.factory.createPropertyDeclaration(
197203
/* modifiers */ modifiers,

packages/compiler-cli/src/ngtsc/translator/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ ts_library(
99
"//packages:types",
1010
"//packages/compiler",
1111
"//packages/compiler-cli/src/ngtsc/imports",
12+
"//packages/compiler-cli/src/ngtsc/reflection",
1213
"//packages/compiler-cli/src/ngtsc/util",
1314
"@npm//typescript",
1415
],

packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@
99
import * as o from '@angular/compiler';
1010
import ts from 'typescript';
1111

12+
import {assertSuccessfulReferenceEmit, ImportFlags, Reference, ReferenceEmitter} from '../../imports';
13+
import {ReflectionHost} from '../../reflection';
14+
1215
import {Context} from './context';
1316
import {ImportManager} from './import_manager';
1417

1518

16-
export function translateType(type: o.Type, imports: ImportManager): ts.TypeNode {
17-
return type.visitType(new TypeTranslatorVisitor(imports), new Context(false));
19+
export function translateType(
20+
type: o.Type, contextFile: ts.SourceFile, reflector: ReflectionHost,
21+
refEmitter: ReferenceEmitter, imports: ImportManager): ts.TypeNode {
22+
return type.visitType(
23+
new TypeTranslatorVisitor(imports, contextFile, reflector, refEmitter), new Context(false));
1824
}
1925

20-
export class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor {
21-
constructor(private imports: ImportManager) {}
26+
class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor {
27+
constructor(
28+
private imports: ImportManager, private contextFile: ts.SourceFile,
29+
private reflector: ReflectionHost, private refEmitter: ReferenceEmitter) {}
2230

2331
visitBuiltinType(type: o.BuiltinType, context: Context): ts.KeywordTypeNode {
2432
switch (type.name) {
@@ -71,6 +79,14 @@ export class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor
7179
return ts.factory.createTypeLiteralNode([indexSignature]);
7280
}
7381

82+
visitTransplantedType(ast: o.TransplantedType<ts.Node>, context: any) {
83+
if (!ts.isTypeNode(ast.type)) {
84+
throw new Error(`A TransplantedType must wrap a TypeNode`);
85+
}
86+
87+
return this.translateTransplantedTypeNode(ast.type, context);
88+
}
89+
7490
visitReadVarExpr(ast: o.ReadVarExpr, context: Context): ts.TypeQueryNode {
7591
if (ast.name === null) {
7692
throw new Error(`ReadVarExpr with no variable name in type`);
@@ -228,4 +244,71 @@ export class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor
228244
}
229245
return typeNode;
230246
}
247+
248+
/**
249+
* Translates a type reference node so that all of its references
250+
* are imported into the context file.
251+
*/
252+
private translateTransplantedTypeReferenceNode(
253+
node: ts.TypeReferenceNode&{typeName: ts.Identifier}, context: any): ts.TypeReferenceNode {
254+
const declaration = this.reflector.getDeclarationOfIdentifier(node.typeName);
255+
256+
if (declaration === null) {
257+
throw new Error(
258+
`Unable to statically determine the declaration file of type node ${node.typeName.text}`);
259+
}
260+
261+
const emittedType = this.refEmitter.emit(
262+
new Reference(declaration.node), this.contextFile,
263+
ImportFlags.NoAliasing | ImportFlags.AllowTypeImports |
264+
ImportFlags.AllowRelativeDtsImports);
265+
266+
assertSuccessfulReferenceEmit(emittedType, node, 'type');
267+
268+
const result = emittedType.expression.visitExpression(this, context);
269+
270+
if (!ts.isTypeReferenceNode(result)) {
271+
throw new Error(`Expected TypeReferenceNode when referencing the type for ${
272+
node.typeName.text}, but received ${ts.SyntaxKind[result.kind]}`);
273+
}
274+
275+
// If the original node doesn't have any generic parameters we return the results.
276+
if (node.typeArguments === undefined || node.typeArguments.length === 0) {
277+
return result;
278+
}
279+
280+
// If there are any generics, we have to reflect them as well.
281+
const translatedArgs =
282+
node.typeArguments.map(arg => this.translateTransplantedTypeNode(arg, context));
283+
284+
return ts.factory.updateTypeReferenceNode(
285+
result, result.typeName, ts.factory.createNodeArray(translatedArgs));
286+
}
287+
288+
/**
289+
* Translates a type node so that all of the type references it
290+
* contains are imported and can be referenced in the context file.
291+
*/
292+
private translateTransplantedTypeNode(rootNode: ts.TypeNode, context: any): ts.TypeNode {
293+
const factory: ts.TransformerFactory<ts.Node> = transformContext => root => {
294+
const walk = (node: ts.Node): ts.Node => {
295+
if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {
296+
const translated =
297+
this.translateTransplantedTypeReferenceNode(node as ts.TypeReferenceNode & {
298+
typeName: ts.Identifier;
299+
}, context);
300+
301+
if (translated !== node) {
302+
return translated;
303+
}
304+
}
305+
306+
return ts.visitEachChild(node, walk, transformContext);
307+
};
308+
309+
return ts.visitNode(root, walk);
310+
};
311+
312+
return ts.transform(rootNode, [factory]).transformed[0] as ts.TypeNode;
313+
}
231314
}

packages/compiler-cli/src/ngtsc/typecheck/src/environment.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ExpressionType, ExternalExpr, Type, TypeModifier} from '@angular/compiler';
9+
import {ExpressionType, ExternalExpr, TransplantedType, Type, TypeModifier} from '@angular/compiler';
1010
import ts from 'typescript';
1111

1212
import {assertSuccessfulReferenceEmit, ImportFlags, Reference, ReferenceEmitKind, ReferenceEmitter} from '../../imports';
@@ -149,7 +149,9 @@ export class Environment implements ReferenceEmitEnvironment {
149149

150150
// Create an `ExpressionType` from the `Expression` and translate it via `translateType`.
151151
// TODO(alxhub): support references to types with generic arguments in a clean way.
152-
return translateType(new ExpressionType(ngExpr.expression), this.importManager);
152+
return translateType(
153+
new ExpressionType(ngExpr.expression), this.contextFile, this.reflector, this.refEmitter,
154+
this.importManager);
153155
}
154156

155157
private emitTypeParameters(declaration: ClassDeclaration<ts.ClassDeclaration>):
@@ -167,8 +169,18 @@ export class Environment implements ReferenceEmitEnvironment {
167169
referenceExternalType(moduleName: string, name: string, typeParams?: Type[]): ts.TypeNode {
168170
const external = new ExternalExpr({moduleName, name});
169171
return translateType(
170-
new ExpressionType(external, /* modifiers */ TypeModifier.None, typeParams),
171-
this.importManager);
172+
new ExpressionType(external, TypeModifier.None, typeParams), this.contextFile,
173+
this.reflector, this.refEmitter, this.importManager);
174+
}
175+
176+
/**
177+
* Generates a `ts.TypeNode` representing a type that is being referenced from a different place
178+
* in the program. Any type references inside the transplanted type will be rewritten so that
179+
* they can be imported in the context fiel.
180+
*/
181+
referenceTransplantedType(type: TransplantedType<ts.TypeNode>): ts.TypeNode {
182+
return translateType(
183+
type, this.contextFile, this.reflector, this.refEmitter, this.importManager);
172184
}
173185

174186
getPreludeStatements(): ts.Statement[] {

packages/compiler/src/compiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export * from './ml_parser/tags';
5252
export {ParseTreeResult, TreeError} from './ml_parser/parser';
5353
export {LexerRange} from './ml_parser/lexer';
5454
export * from './ml_parser/xml_parser';
55-
export {ArrayType, DYNAMIC_TYPE, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CommaExpr, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, literalMap, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, NONE_TYPE, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, TaggedTemplateExpr, TemplateLiteral, TemplateLiteralElement, Type, TypeModifier, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, STRING_TYPE, TypeofExpr, jsDocComment, leadingComment, LeadingComment, JSDocComment, UnaryOperator, UnaryOperatorExpr, LocalizedString} from './output/output_ast';
55+
export {ArrayType, DYNAMIC_TYPE, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CommaExpr, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, literalMap, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, NONE_TYPE, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, TaggedTemplateExpr, TemplateLiteral, TemplateLiteralElement, Type, TypeModifier, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, STRING_TYPE, TypeofExpr, jsDocComment, leadingComment, LeadingComment, JSDocComment, UnaryOperator, UnaryOperatorExpr, LocalizedString, TransplantedType} from './output/output_ast';
5656
export {EmitterVisitorContext} from './output/abstract_emitter';
5757
export {JitEvaluator} from './output/output_jit';
5858
export * from './parse_util';

packages/compiler/src/output/output_ast.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ export class MapType extends Type {
7878
}
7979
}
8080

81+
82+
export class TransplantedType<T> extends Type {
83+
constructor(readonly type: T, modifiers?: TypeModifier) {
84+
super(modifiers);
85+
}
86+
override visitType(visitor: TypeVisitor, context: any): any {
87+
return visitor.visitTransplantedType(this, context);
88+
}
89+
}
90+
91+
8192
export const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
8293
export const INFERRED_TYPE = new BuiltinType(BuiltinTypeName.Inferred);
8394
export const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
@@ -92,6 +103,7 @@ export interface TypeVisitor {
92103
visitExpressionType(type: ExpressionType, context: any): any;
93104
visitArrayType(type: ArrayType, context: any): any;
94105
visitMapType(type: MapType, context: any): any;
106+
visitTransplantedType(type: TransplantedType<unknown>, context: any): any;
95107
}
96108

97109
///// Expressions
@@ -1111,6 +1123,9 @@ export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor
11111123
visitMapType(type: MapType, context: any): any {
11121124
return this.visitType(type, context);
11131125
}
1126+
visitTransplantedType(type: TransplantedType<unknown>, context: any): any {
1127+
return type;
1128+
}
11141129
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: any): any {
11151130
return ast;
11161131
}
@@ -1276,6 +1291,10 @@ export function expressionType(
12761291
return new ExpressionType(expr, typeModifiers, typeParams);
12771292
}
12781293

1294+
export function transplantedType<T>(type: T, typeModifiers?: TypeModifier): TransplantedType<T> {
1295+
return new TransplantedType(type, typeModifiers);
1296+
}
1297+
12791298
export function typeofExpr(expr: Expression) {
12801299
return new TypeofExpr(expr);
12811300
}

0 commit comments

Comments
 (0)