Skip to content

Commit 087b60a

Browse files
committed
feat(type-compiler): support trivially inferred types
This makes types of all properties of code like this ``` class A { a = 1; b = '2'; c = true; d = 2n; e = Symbol('e'); f = new Date(); g = new Uint8Array(); } ``` available in runtime.
1 parent b4b8a28 commit 087b60a

File tree

4 files changed

+159
-24
lines changed

4 files changed

+159
-24
lines changed

packages/type-compiler/install-transformer.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,22 @@ function getCode(deepkitDistPath: string, varName: string, id: string): string {
2828
if (typeTransformer) {
2929
if (!customTransformers) ${varName} = {};
3030
if (!${varName}.before) ${varName}.before = [];
31-
if (!${varName}.before.includes(typeTransformer.transformer)) ${varName}.before.push(typeTransformer.transformer);
32-
33-
if (!${varName}.afterDeclarations) ${varName}.afterDeclarations = [];
34-
if (!${varName}.afterDeclarations.includes(typeTransformer.declarationTransformer)) ${varName}.afterDeclarations.push(typeTransformer.declarationTransformer);
31+
let alreadyPatched = false;
32+
for (let fn of ${varName}.before) {
33+
if (fn && fn.name === 'deepkitTransformer') alreadyPatched = true;
34+
}
35+
if (!alreadyPatched) {
36+
if (!${varName}.before.includes(typeTransformer.transformer)) ${varName}.before.push(typeTransformer.transformer);
37+
38+
if (!${varName}.afterDeclarations) ${varName}.afterDeclarations = [];
39+
if (!${varName}.afterDeclarations.includes(typeTransformer.declarationTransformer)) {
40+
${varName}.afterDeclarations.push(typeTransformer.declarationTransformer);
41+
}
42+
}
3543
}
3644
} catch (e) {
3745
}
46+
//${getPatchId(id)}-end
3847
`;
3948
}
4049

packages/type-compiler/src/compiler.ts

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
ArrowFunction,
1515
Block,
1616
Bundle,
17+
CallExpression,
1718
CallSignatureDeclaration,
1819
ClassDeclaration,
1920
ClassElement,
@@ -49,6 +50,7 @@ import type {
4950
MethodSignature,
5051
Modifier,
5152
ModuleDeclaration,
53+
NewExpression,
5254
Node,
5355
NodeFactory,
5456
ParseConfigHost,
@@ -1183,6 +1185,65 @@ export class ReflectionTransformer implements CustomTransformer {
11831185
return [variable];
11841186
}
11851187

1188+
protected extractPackStructOfExpression(node: Expression, program: CompilerProgram): void {
1189+
switch (node.kind) {
1190+
case SyntaxKind.StringLiteral: {
1191+
program.pushOp(ReflectionOp.string);
1192+
return;
1193+
}
1194+
case SyntaxKind.NumericLiteral: {
1195+
program.pushOp(ReflectionOp.number);
1196+
return;
1197+
}
1198+
case SyntaxKind.FalseKeyword:
1199+
case SyntaxKind.TrueKeyword: {
1200+
program.pushOp(ReflectionOp.boolean);
1201+
return;
1202+
}
1203+
case SyntaxKind.BigIntLiteral: {
1204+
program.pushOp(ReflectionOp.bigint);
1205+
return;
1206+
}
1207+
//Symbol() is a function call, so we need to check for that
1208+
case SyntaxKind.CallExpression: {
1209+
const call = node as CallExpression;
1210+
if (isIdentifier(call.expression) && getIdentifierName(call.expression) === 'Symbol') {
1211+
program.pushOp(ReflectionOp.symbol);
1212+
return;
1213+
}
1214+
break;
1215+
}
1216+
//new Date()
1217+
case SyntaxKind.NewExpression: {
1218+
const call = node as NewExpression;
1219+
if (isIdentifier(call.expression)) {
1220+
const map: {[name: string]: ReflectionOp} = {
1221+
'Date': ReflectionOp.date,
1222+
'RegExp': ReflectionOp.regexp,
1223+
'Uint8Array': ReflectionOp.uint8Array,
1224+
'Uint8ClampedArray': ReflectionOp.uint8ClampedArray,
1225+
'Uint16Array': ReflectionOp.uint16Array,
1226+
'Uint32Array': ReflectionOp.uint32Array,
1227+
'Int8Array': ReflectionOp.int8Array,
1228+
'Int16Array': ReflectionOp.int16Array,
1229+
'Int32Array': ReflectionOp.int32Array,
1230+
'Float32Array': ReflectionOp.float32Array,
1231+
'Float64Array': ReflectionOp.float64Array,
1232+
'ArrayBuffer': ReflectionOp.arrayBuffer,
1233+
};
1234+
const op = map[getIdentifierName(call.expression)];
1235+
if (op) {
1236+
program.pushOp(op);
1237+
return;
1238+
}
1239+
}
1240+
break;
1241+
}
1242+
}
1243+
1244+
program.pushOp(ReflectionOp.never);
1245+
}
1246+
11861247
protected extractPackStructOfType(node: Node | Declaration | ClassDeclaration | ClassExpression, program: CompilerProgram): void {
11871248
if (isParenthesizedTypeNode(node)) return this.extractPackStructOfType(node.type, program);
11881249

@@ -1500,32 +1561,36 @@ export class ReflectionTransformer implements CustomTransformer {
15001561
//TypeScript does not narrow types down
15011562
const narrowed = node as PropertyDeclaration;
15021563

1503-
if (narrowed.type) {
1504-
// if the property was explicitly marked as `@reflection no`, we ignore it
1505-
if (false === this.getExplicitReflectionMode(program.sourceFile, narrowed)) return;
1564+
// if the property was explicitly marked as `@reflection no`, we ignore it
1565+
if (false === this.getExplicitReflectionMode(program.sourceFile, narrowed)) return;
15061566

1567+
if (narrowed.type) {
15071568
this.extractPackStructOfType(narrowed.type, program);
1508-
const name = getPropertyName(this.f, narrowed.name);
1509-
program.pushOp(ReflectionOp.property, program.findOrAddStackEntry(name));
1569+
} else if (narrowed.initializer) {
1570+
this.extractPackStructOfExpression(narrowed.initializer, program);
1571+
}
15101572

1511-
if (narrowed.questionToken) program.pushOp(ReflectionOp.optional);
1512-
if (hasModifier(narrowed, SyntaxKind.ReadonlyKeyword)) program.pushOp(ReflectionOp.readonly);
1513-
if (hasModifier(narrowed, SyntaxKind.PrivateKeyword)) program.pushOp(ReflectionOp.private);
1514-
if (hasModifier(narrowed, SyntaxKind.ProtectedKeyword)) program.pushOp(ReflectionOp.protected);
1515-
if (hasModifier(narrowed, SyntaxKind.AbstractKeyword)) program.pushOp(ReflectionOp.abstract);
1516-
if (hasModifier(narrowed, SyntaxKind.StaticKeyword)) program.pushOp(ReflectionOp.static);
1573+
const name = getPropertyName(this.f, narrowed.name);
1574+
program.pushOp(ReflectionOp.property, program.findOrAddStackEntry(name));
15171575

1518-
if (narrowed.initializer) {
1519-
//important to use Function, since it will be called using a different `this`
1520-
program.pushOp(ReflectionOp.defaultValue, program.findOrAddStackEntry(
1521-
this.f.createFunctionExpression(undefined, undefined, undefined, undefined, undefined, undefined,
1522-
this.f.createBlock([this.f.createReturnStatement(narrowed.initializer)])),
1523-
));
1524-
}
1576+
if (narrowed.questionToken) program.pushOp(ReflectionOp.optional);
1577+
if (hasModifier(narrowed, SyntaxKind.ReadonlyKeyword)) program.pushOp(ReflectionOp.readonly);
1578+
if (hasModifier(narrowed, SyntaxKind.PrivateKeyword)) program.pushOp(ReflectionOp.private);
1579+
if (hasModifier(narrowed, SyntaxKind.ProtectedKeyword)) program.pushOp(ReflectionOp.protected);
1580+
if (hasModifier(narrowed, SyntaxKind.AbstractKeyword)) program.pushOp(ReflectionOp.abstract);
1581+
if (hasModifier(narrowed, SyntaxKind.StaticKeyword)) program.pushOp(ReflectionOp.static);
15251582

1526-
const description = extractJSDocAttribute(this.sourceFile, narrowed, 'description');
1527-
if (description) program.pushOp(ReflectionOp.description, program.findOrAddStackEntry(description));
1583+
if (narrowed.initializer) {
1584+
//important to use Function, since it will be called using a different `this`
1585+
program.pushOp(ReflectionOp.defaultValue, program.findOrAddStackEntry(
1586+
this.f.createFunctionExpression(undefined, undefined, undefined, undefined, undefined, undefined,
1587+
this.f.createBlock([this.f.createReturnStatement(narrowed.initializer)])),
1588+
));
15281589
}
1590+
1591+
const description = extractJSDocAttribute(this.sourceFile, narrowed, 'description');
1592+
if (description) program.pushOp(ReflectionOp.description, program.findOrAddStackEntry(description));
1593+
15291594
break;
15301595
}
15311596
case SyntaxKind.ConditionalType: {

packages/type-compiler/tests/transpile.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,3 +580,15 @@ test('symbol function name', () => {
580580
console.log(res.app);
581581
expect(res.app).toContain(`() => Symbol.iterator`);
582582
});
583+
584+
test('infer type', () => {
585+
const res = transpile({
586+
'app': `
587+
class A {
588+
a = 1;
589+
}
590+
`
591+
});
592+
console.log(res.app);
593+
expect(res.app).toContain(`'a'`);
594+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { expect, test } from '@jest/globals';
2+
import { typeOf } from '../../src/reflection/reflection';
3+
import { assertType, findMember, ReflectionKind, stringifyResolvedType } from '../../src/reflection/type';
4+
5+
test('basics', () => {
6+
class A {
7+
a = 1;
8+
b = '2';
9+
c = true;
10+
d = 2n;
11+
e = Symbol('e');
12+
f = new Date();
13+
g = new Uint8Array();
14+
}
15+
16+
const type = typeOf<A>();
17+
console.log(stringifyResolvedType(type));
18+
19+
assertType(type, ReflectionKind.class);
20+
const a = findMember('a', type.types);
21+
assertType(a, ReflectionKind.property);
22+
assertType(a.type, ReflectionKind.number);
23+
24+
const b = findMember('b', type.types);
25+
assertType(b, ReflectionKind.property);
26+
assertType(b.type, ReflectionKind.string);
27+
28+
const c = findMember('c', type.types);
29+
assertType(c, ReflectionKind.property);
30+
assertType(c.type, ReflectionKind.boolean);
31+
32+
const d = findMember('d', type.types);
33+
assertType(d, ReflectionKind.property);
34+
assertType(d.type, ReflectionKind.bigint);
35+
36+
const e = findMember('e', type.types);
37+
assertType(e, ReflectionKind.property);
38+
assertType(e.type, ReflectionKind.symbol);
39+
40+
const f = findMember('f', type.types);
41+
assertType(f, ReflectionKind.property);
42+
assertType(f.type, ReflectionKind.class);
43+
expect(f.type.classType).toBe(Date);
44+
45+
const g = findMember('g', type.types);
46+
assertType(g, ReflectionKind.property);
47+
assertType(g.type, ReflectionKind.class);
48+
expect(g.type.classType).toBe(Uint8Array);
49+
});

0 commit comments

Comments
 (0)