Skip to content

Commit 5cf7b32

Browse files
committed
improve 2nd iteration
1 parent beab438 commit 5cf7b32

File tree

4 files changed

+127
-101
lines changed

4 files changed

+127
-101
lines changed

packages/type-compiler/src/compiler.ts

+43-48
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
ArrayTypeNode,
1414
ArrowFunction,
1515
Bundle,
16+
CallExpression,
1617
CallSignatureDeclaration,
1718
ClassDeclaration,
1819
ClassElement,
@@ -56,7 +57,7 @@ import type {
5657
QualifiedName,
5758
RestTypeNode,
5859
SignatureDeclaration,
59-
Statement,
60+
Statement, StringLiteral,
6061
TemplateLiteralTypeNode,
6162
TransformationContext,
6263
TupleTypeNode,
@@ -70,7 +71,7 @@ import type {
7071
TypeReferenceNode,
7172
UnionTypeNode,
7273
} from 'typescript';
73-
import ts from 'typescript';
74+
import ts, { ResolvedModule } from 'typescript';
7475

7576
import {
7677
ensureImportIsEmitted,
@@ -85,6 +86,7 @@ import {
8586
isNodeWithLocals,
8687
NodeConverter,
8788
PackExpression,
89+
SerializedEntityNameAsExpression,
8890
serializeEntityNameAsExpression,
8991
} from './reflection-ast.js';
9092
import { SourceFile } from './ts-types.js';
@@ -511,23 +513,6 @@ export interface ReflectionTransformerConfig {
511513
deepkitTypeCompilerOptions?: DeepkitTypeCompilerOptions;
512514
}
513515

514-
export interface EmbedDeclaration {
515-
name: EntityName,
516-
sourceFile: SourceFile,
517-
assignType?: boolean;
518-
}
519-
520-
export class EmbedDeclarations extends Map<Node, EmbedDeclaration> {
521-
getByName(name: string): EmbedDeclaration | null {
522-
for (const [,d] of this) {
523-
if (getEntityName(d.name) === name) {
524-
return d;
525-
}
526-
}
527-
return null;
528-
}
529-
}
530-
531516
/**
532517
* Read the TypeScript AST and generate pack struct (instructions + pre-defined stack).
533518
*
@@ -564,7 +549,7 @@ export class ReflectionTransformer implements CustomTransformer {
564549
* Types added to this map will get a type program at the top root level of the program.
565550
* This is for imported types, which need to be inlined into the current file, as we do not emit type imports (TS will omit them).
566551
*/
567-
public embedDeclarations = new EmbedDeclarations();
552+
protected embedDeclarations = new Map<Node, { name: EntityName, sourceFile: SourceFile }>();
568553

569554
/**
570555
* When a node was embedded or compiled (from the maps above), we store it here to know to not add it again.
@@ -594,7 +579,7 @@ export class ReflectionTransformer implements CustomTransformer {
594579
protected context: TransformationContext,
595580
) {
596581
this.f = context.factory;
597-
this.nodeConverter = new NodeConverter(this.f, this);
582+
this.nodeConverter = new NodeConverter(this.f);
598583
//it is important to not have undefined values like {paths: undefined} because it would override the read tsconfig.json
599584
this.compilerOptions = filterUndefined(context.getCompilerOptions());
600585
this.host = createCompilerHost(this.compilerOptions);
@@ -1819,14 +1804,13 @@ export class ReflectionTransformer implements CustomTransformer {
18191804
// this.addImports.push({ identifier: narrowed.exprName, from: originImportStatement.moduleSpecifier });
18201805
// }
18211806
// }
1807+
let expression: SerializedEntityNameAsExpression | CallExpression = serializeEntityNameAsExpression(this.f, narrowed.exprName);
18221808
if (isIdentifier(narrowed.exprName)) {
18231809
const resolved = this.resolveDeclaration(narrowed.exprName);
18241810
if (resolved && findSourceFile(resolved.declaration) !== this.sourceFile && resolved.importDeclaration) {
1825-
this.resolveImport(resolved.declaration, resolved.importDeclaration, narrowed.exprName, program);
1811+
expression = this.resolveImport(resolved.declaration, resolved.importDeclaration, narrowed.exprName, expression, program);
18261812
}
18271813
}
1828-
1829-
const expression = serializeEntityNameAsExpression(this.f, narrowed.exprName);
18301814
program.pushOp(ReflectionOp.typeof, program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, expression)));
18311815
break;
18321816
}
@@ -1935,11 +1919,12 @@ export class ReflectionTransformer implements CustomTransformer {
19351919
* This is a custom resolver based on populated `locals` from the binder. It uses a custom resolution algorithm since
19361920
* we have no access to the binder/TypeChecker directly and instantiating a TypeChecker per file/transformer is incredible slow.
19371921
*/
1938-
protected resolveDeclaration(typeName: EntityName): { declaration: Node, importDeclaration?: ImportDeclaration, typeOnly?: boolean } | void {
1939-
let current: Node = typeName.parent;
1922+
protected resolveDeclaration(typeName: EntityName): { declaration: Node, importDeclaration?: ImportDeclaration, sourceFile: SourceFile, typeOnly: boolean } | void {
1923+
let current: Node = typeName.parent
19401924
if (typeName.kind === SyntaxKind.QualifiedName) return; //namespace access not supported yet, e.g. type a = Namespace.X;
19411925

19421926
let declaration: Node | undefined = undefined;
1927+
let sourceFile: SourceFile = this.sourceFile;
19431928

19441929
while (current) {
19451930
if (isNodeWithLocals(current) && current.locals) {
@@ -1981,11 +1966,13 @@ export class ReflectionTransformer implements CustomTransformer {
19811966
importDeclaration = declaration.parent;
19821967
}
19831968

1984-
// TODO: check if it's a built type here?
1985-
19861969
if (importDeclaration) {
19871970
if (importDeclaration.importClause && importDeclaration.importClause.isTypeOnly) typeOnly = true;
19881971
declaration = this.resolveImportSpecifier(typeName.escapedText, importDeclaration, this.sourceFile);
1972+
if (!declaration) {
1973+
sourceFile = importDeclaration.getSourceFile();
1974+
declaration = this.resolveImportSpecifier(typeName.escapedText, importDeclaration, sourceFile);
1975+
}
19891976
}
19901977

19911978
if (declaration && declaration.kind === SyntaxKind.TypeParameter && declaration.parent.kind === SyntaxKind.TypeAliasDeclaration) {
@@ -1995,7 +1982,7 @@ export class ReflectionTransformer implements CustomTransformer {
19951982

19961983
if (!declaration) return;
19971984

1998-
return { declaration, importDeclaration, typeOnly };
1985+
return { declaration, importDeclaration, typeOnly, sourceFile };
19991986
}
20001987

20011988
// protected resolveType(node: TypeNode): Declaration | Node {
@@ -2031,17 +2018,11 @@ export class ReflectionTransformer implements CustomTransformer {
20312018
return this.f.createIdentifier('__Ω' + joinQualifiedName(typeName));
20322019
}
20332020

2034-
protected resolveImport(declaration: Node, importDeclaration: ImportDeclaration, typeName: Identifier, program: CompilerProgram) {
2035-
if (isVariableDeclaration(declaration)) {
2036-
if (declaration.type) {
2037-
declaration = declaration.type;
2038-
} else if (declaration.initializer) {
2039-
declaration = declaration.initializer;
2040-
}
2041-
}
2042-
2021+
protected resolveImport<T extends Expression>(declaration: Node, importDeclaration: ImportDeclaration, typeName: Identifier, expression: T, program: CompilerProgram): CallExpression | T {
20432022
ensureImportIsEmitted(importDeclaration, typeName);
20442023

2024+
if (isTypeAliasDeclaration(declaration)) return expression;
2025+
20452026
// check if the referenced declaration has reflection disabled
20462027
const declarationReflection = this.findReflectionConfig(declaration, program);
20472028
if (declarationReflection.mode !== 'never') {
@@ -2055,19 +2036,33 @@ export class ReflectionTransformer implements CustomTransformer {
20552036
this.embedDeclarations.set(declaration, {
20562037
name: typeName,
20572038
sourceFile: declarationSourceFile,
2058-
assignType: true,
20592039
});
2040+
// if (!isTypeAliasDeclaration(declaration)) {
2041+
return this.wrapWithAssignType(expression, runtimeTypeName);
2042+
// }
20602043
}
20612044
}
2045+
2046+
return expression;
20622047
}
20632048

2064-
// TODO: what to do when the external type depends on another external type? should that be automatically resolved, or should the user explicitly specify that as well?
20652049
protected shouldInlineExternalImport(importDeclaration: ImportDeclaration, entityName: EntityName, config: ReflectionConfig): boolean {
2066-
if (!ts.isStringLiteral(importDeclaration.moduleSpecifier)) return false;
2050+
if (!isStringLiteral(importDeclaration.moduleSpecifier)) return false;
20672051
if (config.options.inlineExternalImports === true) return true;
2068-
const externalImport = config.options.inlineExternalImports?.[importDeclaration.moduleSpecifier.text];
2052+
const resolvedModule = this.resolver.resolveImpl(importDeclaration.moduleSpecifier.text, importDeclaration.getSourceFile().fileName);
2053+
if (!resolvedModule) {
2054+
throw new Error('Cannot resolve module');
2055+
}
2056+
if (!resolvedModule.packageId) {
2057+
throw new Error('Missing package id for resolved module');
2058+
}
2059+
if (!resolvedModule.isExternalLibraryImport) {
2060+
throw new Error('Resolved module is not an external library import');
2061+
}
2062+
const externalImport = config.options.inlineExternalImports?.[resolvedModule.packageId.name];
20692063
if (!externalImport) return false;
20702064
if (externalImport === true) return true;
2065+
if (!importDeclaration.moduleSpecifier.text.startsWith(resolvedModule.packageId.name)) return true;
20712066
const typeName = getEntityName(entityName);
20722067
return externalImport.includes(typeName);
20732068
}
@@ -2169,14 +2164,15 @@ export class ReflectionTransformer implements CustomTransformer {
21692164
}
21702165

21712166
if (isModuleDeclaration(declaration) && resolved.importDeclaration) {
2167+
let expression: SerializedEntityNameAsExpression | CallExpression = serializeEntityNameAsExpression(this.f, typeName);
21722168
if (isIdentifier(typeName)) {
2173-
this.resolveImport(declaration, resolved.importDeclaration, typeName, program);
2169+
expression = this.resolveImport(declaration, resolved.importDeclaration, typeName, expression, program)
21742170
}
21752171

21762172
//we can not infer from module declaration, so do `typeof T` in runtime
21772173
program.pushOp(
21782174
ReflectionOp.typeof,
2179-
program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, serializeEntityNameAsExpression(this.f, typeName)))
2175+
program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, expression))
21802176
);
21812177
} else if (isTypeAliasDeclaration(declaration) || isInterfaceDeclaration(declaration) || isEnumDeclaration(declaration)) {
21822178
//Set/Map are interface declarations
@@ -2232,7 +2228,6 @@ export class ReflectionTransformer implements CustomTransformer {
22322228
}
22332229

22342230
if (isGlobal) {
2235-
//we don't embed non-global imported declarations anymore, only globals
22362231
this.embedDeclarations.set(declaration, {
22372232
name: typeName,
22382233
sourceFile: declarationSourceFile
@@ -2252,7 +2247,7 @@ export class ReflectionTransformer implements CustomTransformer {
22522247
return;
22532248
}
22542249

2255-
const found = this.resolver.resolve(this.sourceFile, resolved.importDeclaration);
2250+
const found = this.resolver.resolve(resolved.sourceFile, resolved.importDeclaration);
22562251
if (!found) {
22572252
debug('module not found');
22582253
program.pushOp(ReflectionOp.any);
@@ -2326,16 +2321,16 @@ export class ReflectionTransformer implements CustomTransformer {
23262321
return;
23272322
}
23282323

2324+
let body: Identifier | PropertyAccessExpression | CallExpression = isIdentifier(typeName) ? typeName : this.createAccessorForEntityName(typeName);
23292325
if (resolved.importDeclaration && isIdentifier(typeName)) {
2330-
this.resolveImport(resolved.declaration, resolved.importDeclaration, typeName, program);
2326+
body = this.resolveImport(resolved.declaration, resolved.importDeclaration, typeName, body, program);
23312327
}
23322328
program.pushFrame();
23332329
if (type.typeArguments) {
23342330
for (const typeArgument of type.typeArguments) {
23352331
this.extractPackStructOfType(typeArgument, program);
23362332
}
23372333
}
2338-
const body = isIdentifier(typeName) ? typeName : this.createAccessorForEntityName(typeName);
23392334
const index = program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, body));
23402335
program.pushOp(isClassDeclaration(declaration) ? ReflectionOp.classReference : ReflectionOp.functionReference, index);
23412336
program.popFrameImplicit();

packages/type-compiler/src/reflection-ast.ts

+5-9
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import type {
3333
import ts from 'typescript';
3434
import { cloneNode as tsNodeClone, CloneNodeHook } from '@marcj/ts-clone-node';
3535
import { SourceFile } from './ts-types.js';
36-
import { ReflectionTransformer } from './compiler.js';
3736

3837
const {
3938
isArrowFunction,
@@ -123,7 +122,7 @@ const cloneHook = <T extends Node>(node: T, payload: { depth: number }): CloneNo
123122
};
124123

125124
export class NodeConverter {
126-
constructor(protected f: NodeFactory, protected transformer: ReflectionTransformer) {}
125+
constructor(protected f: NodeFactory) {}
127126

128127
toExpression<T extends PackExpression | PackExpression[]>(node?: T): Expression {
129128
if (node === undefined) return this.f.createIdentifier('undefined');
@@ -139,18 +138,15 @@ export class NodeConverter {
139138

140139
if (node.pos === -1 && node.end === -1 && node.parent === undefined) {
141140
if (isArrowFunction(node)) {
141+
if (node.body.pos === -1 && node.body.end === -1 && node.body.parent === undefined) return node;
142142
return this.f.createArrowFunction(node.modifiers, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, this.toExpression(node.body as Expression));
143143
}
144+
return node;
144145
}
145146

146147
switch (node.kind) {
147148
case SyntaxKind.Identifier:
148-
const typeName = getIdentifierName(node as Identifier);
149-
const embedDeclaration = this.transformer.embedDeclarations.getByName(typeName);
150-
if (embedDeclaration?.assignType) {
151-
return this.transformer.wrapWithAssignType(finish(node, this.f.createIdentifier(typeName)), this.transformer.getDeclarationVariableName(node as Identifier));
152-
}
153-
return finish(node, this.f.createIdentifier(typeName));
149+
return finish(node, this.f.createIdentifier(getIdentifierName(node as Identifier)));
154150
case SyntaxKind.StringLiteral:
155151
return finish(node, this.f.createStringLiteral((node as StringLiteral).text));
156152
case SyntaxKind.NumericLiteral:
@@ -249,7 +245,7 @@ export function serializeEntityNameAsExpression(f: NodeFactory, node: EntityName
249245
return node;
250246
}
251247

252-
type SerializedEntityNameAsExpression = Identifier | BinaryExpression | PropertyAccessExpression;
248+
export type SerializedEntityNameAsExpression = Identifier | BinaryExpression | PropertyAccessExpression;
253249

254250
/**
255251
* Serializes an qualified name as an expression for decorator type metadata.

packages/type-compiler/src/resolver.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import type { CompilerHost, CompilerOptions, ExportDeclaration, Expression, ImportDeclaration, ResolvedModule, SourceFile, StringLiteral, } from 'typescript';
1+
import type {
2+
CompilerHost,
3+
CompilerOptions,
4+
ExportDeclaration,
5+
Expression,
6+
ImportDeclaration,
7+
ResolvedModuleFull,
8+
SourceFile,
9+
StringLiteral,
10+
} from 'typescript';
211
import ts from 'typescript';
312
import * as micromatch from 'micromatch';
413
import { isAbsolute } from 'path';
@@ -61,12 +70,12 @@ export class Resolver {
6170
return this.resolveSourceFile(from.fileName, (moduleSpecifier as StringLiteral).text);
6271
}
6372

64-
resolveImpl(modulePath: string, fromPath: string): ResolvedModule | undefined {
73+
resolveImpl(modulePath: string, fromPath: string): ResolvedModuleFull | undefined {
6574
if (this.host.resolveModuleNames !== undefined) {
66-
return this.host.resolveModuleNames([modulePath], fromPath, /*reusedNames*/ undefined, /*redirectedReference*/ undefined, this.compilerOptions)[0];
75+
return this.host.resolveModuleNames([modulePath], fromPath, /*reusedNames*/ undefined, /*redirectedReference*/ undefined, this.compilerOptions)[0] as ResolvedModuleFull | undefined;
6776
}
6877
const result = resolveModuleName(modulePath, fromPath, this.compilerOptions, this.host);
69-
return result.resolvedModule;
78+
return result.resolvedModule as ResolvedModuleFull;
7079
}
7180

7281
/**

0 commit comments

Comments
 (0)