From 6bfb2466753bb99020d8f429097ad1cb3520e500 Mon Sep 17 00:00:00 2001 From: "Marcus S. Abildskov" <8391194+marcus-sa@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:53:29 +0100 Subject: [PATCH] fix(type-compiler): arrow function receive type (#521) * fix(type-compiler): arrow function receive type * fix(type-compiler): object property assignment arrow function receive type --- package-lock.json | 1 + packages/type-compiler/src/compiler.ts | 52 +++++++++++++++++++++----- packages/type/tests/compiler.spec.ts | 44 +++++++++++++++++++++- 3 files changed, 87 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index be54ebd33..549db92bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31086,6 +31086,7 @@ "strip-json-comments": "^3.1.1" }, "bin": { + "deepkit-compiler-debug": "deepkit-compiler-debug.js", "deepkit-type-install": "deepkit-type-install.js" }, "devDependencies": { diff --git a/packages/type-compiler/src/compiler.ts b/packages/type-compiler/src/compiler.ts index 9b7d68b44..e37914d5f 100644 --- a/packages/type-compiler/src/compiler.ts +++ b/packages/type-compiler/src/compiler.ts @@ -12,6 +12,7 @@ import type { __String, ArrayTypeNode, ArrowFunction, + Block, Bundle, CallSignatureDeclaration, ClassDeclaration, @@ -19,6 +20,7 @@ import type { ClassExpression, CompilerHost, CompilerOptions, + ConciseBody, ConditionalTypeNode, ConstructorDeclaration, ConstructorTypeNode, @@ -104,6 +106,7 @@ export function isObject(obj: any): obj is { [key: string]: any } { const { visitEachChild, visitNode, + isPropertyAssignment, isArrayTypeNode, isArrowFunction, isCallExpression, @@ -762,7 +765,9 @@ export class ReflectionTransformer implements CustomTransformer { const index = typeParameters.findIndex(v => getIdentifierName(v.name) === name); let container: Expression = this.f.createIdentifier('globalThis'); - if ((isFunctionDeclaration(node.parent) || isFunctionExpression(node.parent)) && node.parent.name) { + if (isArrowFunction(node.parent)) { + container = this.getArrowFunctionΩPropertyAccessIdentifier(node.parent); + } else if ((isFunctionDeclaration(node.parent) || isFunctionExpression(node.parent)) && node.parent.name) { container = node.parent.name; } else if (isMethodDeclaration(node.parent) && isIdentifier(node.parent.name)) { container = this.f.createPropertyAccessExpression(this.f.createIdentifier('this'), node.parent.name); @@ -791,7 +796,7 @@ export class ReflectionTransformer implements CustomTransformer { } else if (isMethodDeclaration(node) || isConstructorDeclaration(node)) { return this.injectResetΩ(node); } else if (isArrowFunction(node)) { - return this.decorateArrow(node); + return this.decorateArrowFunction(this.injectResetΩ(node)); } else if ((isNewExpression(node) || isCallExpression(node)) && node.typeArguments && node.typeArguments.length > 0) { if (isCallExpression(node)) { @@ -1113,7 +1118,32 @@ export class ReflectionTransformer implements CustomTransformer { return this.compilerOptions.module === ModuleKind.CommonJS ? 'cjs' : 'esm'; } - protected injectResetΩ(node: T): T { + protected getArrowFunctionΩPropertyAccessIdentifier(node: ArrowFunction): Identifier { + let { parent } = (node as any).original || node; + if (isVariableDeclaration(parent) && isIdentifier(parent.name)) { + return parent.name; + } else if (isPropertyAssignment(parent) && isIdentifier(parent.name)) { + const names: string[] = []; + while (parent) { + if (isObjectLiteralExpression(parent)) { + parent = parent.parent; + } else if (isVariableDeclaration(parent)) { + names.unshift(getIdentifierName(parent.name as Identifier)); + break; + } else if (isIdentifier(parent.name)) { + names.unshift(getIdentifierName(parent.name)); + parent = parent.parent; + } else { + throw new Error('Not implemented ' + parent.kind); + } + } + return this.f.createIdentifier(names.join('.')); + } else { + throw new Error('Unsupported parent ' + parent.kind); + } + } + + protected injectResetΩ(node: T): T { let hasReceiveType = false; for (const param of node.parameters) { if (param.type && getReceiveTypeParameter(param.type)) hasReceiveType = true; @@ -1121,7 +1151,9 @@ export class ReflectionTransformer implements CustomTransformer { if (!hasReceiveType) return node; let container: Expression = this.f.createIdentifier('globalThis'); - if ((isFunctionDeclaration(node) || isFunctionExpression(node)) && node.name) { + if (isArrowFunction(node)) { + container = this.getArrowFunctionΩPropertyAccessIdentifier(node); + } else if ((isFunctionDeclaration(node) || isFunctionExpression(node)) && node.name) { container = node.name; } else if (isMethodDeclaration(node) && isIdentifier(node.name)) { container = this.f.createPropertyAccessExpression(this.f.createIdentifier('this'), node.name); @@ -1137,9 +1169,11 @@ export class ReflectionTransformer implements CustomTransformer { this.f.createToken(ts.SyntaxKind.EqualsToken), this.f.createIdentifier('undefined') )); - const body = node.body ? this.f.updateBlock(node.body, [reset, ...node.body.statements]) : undefined; + const body = node.body ? this.f.updateBlock(node.body as Block, [reset, ...(node.body as Block).statements]) : undefined; - if (isFunctionDeclaration(node)) { + if (isArrowFunction(node)) { + return this.f.updateArrowFunction(node, node.modifiers, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, body as ConciseBody) as T; + } else if (isFunctionDeclaration(node)) { return this.f.updateFunctionDeclaration(node, node.modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, body) as T; } else if (isFunctionExpression(node)) { @@ -2617,10 +2651,10 @@ export class ReflectionTransformer implements CustomTransformer { } /** - * const fn = () => { } - * => const fn = Object.assign(() => {}, {__type: 34}) + * const fn = () => {} + * => const fn = __assignType(() => {}, [34]) */ - protected decorateArrow(expression: ArrowFunction) { + protected decorateArrowFunction(expression: ArrowFunction) { const encodedType = this.getTypeOfType(expression); if (!encodedType) return expression; diff --git a/packages/type/tests/compiler.spec.ts b/packages/type/tests/compiler.spec.ts index 0e1da0274..9113d266c 100644 --- a/packages/type/tests/compiler.spec.ts +++ b/packages/type/tests/compiler.spec.ts @@ -1246,7 +1246,7 @@ test('fn default argument', () => { expect(type.kind).toEqual(ReflectionKind.string); }); -test('ReceiveType', () => { +test('ReceiveType standard function', () => { const code = ` function cast(type: ReceiveType) { return reflect(type); @@ -1255,10 +1255,52 @@ test('ReceiveType', () => { const js = transpile(code); console.log('js', js); + expect(js).toContain('type = cast.Ω?.[0]'); + expect(js).toContain('cast.Ω = undefined'); + expect(js).toContain('cast.Ω = ['); + + const type = transpileAndReturn(code); + expect(type).toEqual({ kind: ReflectionKind.string }); +}); + +test('ReceiveType arrow function', () => { + const code = ` + const cast = (type: ReceiveType) => { + return reflect(type); + } + return cast();`; + + const js = transpile(code); + console.log('js', js); + expect(js).toContain('type = cast.Ω?.[0]'); + expect(js).toContain('cast.Ω = undefined'); + expect(js).toContain('cast.Ω = ['); + const type = transpileAndReturn(code); expect(type).toEqual({ kind: ReflectionKind.string }); }); +test('ReceiveType object property assignment arrow function', () => { + const code = ` + const obj = { + cast: (type: ReceiveType) => { + return reflect(type); + }, + }; + return obj.cast(); + `; + + const js = transpile(code); + console.log('js', js); + expect(js).toContain('type = obj.cast.Ω?.[0]'); + expect(js).toContain('obj.cast.Ω = undefined'); + expect(js).toContain('obj.cast.Ω = ['); + + const type = transpileAndReturn(code); + expect(type).toEqual({ kind: ReflectionKind.string }); +}); + + test('generic static', () => { const code = ` interface Request {