diff --git a/package-lock.json b/package-lock.json index 125c0d2..0f40f8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@southworks/codeverter", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@southworks/codeverter", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "dependencies": { "ejs": "3.1.8", diff --git a/package.json b/package.json index d31a115..70aba70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@southworks/codeverter", - "version": "2.0.0", + "version": "2.0.1", "description": "Convert TypeScript code into different languages", "main": "lib/lib.js", "bin": { diff --git a/src/shared/function.ts b/src/shared/function.ts index 615ffea..4de5d41 100644 --- a/src/shared/function.ts +++ b/src/shared/function.ts @@ -13,9 +13,12 @@ import { isBlock, isVariableStatement, ParameterDeclaration, - Statement + ReturnStatement, + Statement, + SyntaxKind } from "typescript"; import { addVaribles } from "./helpers/variable-helper"; +import { TypeMapper } from "./type-mapper"; import { ParametrizedSourceElement, TypedSourceElement, ValuedSourceElement } from "./types/source-element"; import { TypedClassElement } from "./types/typed-class-element"; @@ -57,6 +60,12 @@ export class Function s.kind == SyntaxKind.ReturnStatement) as ReturnStatement; + if (!node.type && returnStatement?.expression) { + const { knownType, type } = TypeMapper.getType(node, returnStatement.expression); + this.knownType = knownType; + this.type = type; + } this.content = this.trimBody(node.body?.getText(this.getSourceFile()) ?? ""); if (node.body && isBlock(node.body!)) { diff --git a/src/shared/type-mapper.ts b/src/shared/type-mapper.ts index 9637c48..973ecbe 100644 --- a/src/shared/type-mapper.ts +++ b/src/shared/type-mapper.ts @@ -7,22 +7,28 @@ */ import { + ArrayLiteralExpression, ArrayTypeNode, + Expression, Identifier, NodeArray, NodeWithTypeArguments, SyntaxKind, - Type, - TypeChecker, TypeNode, TypeReferenceNode } from "typescript"; import { TypedDeclaration } from "./types/typed-declaration"; -interface IntrinsicNamed extends Type { - intrinsicName: string; +interface InitializerExpression extends Expression { + readonly expression: Expression; } +interface InitializerNode extends TypedDeclaration { + initializer: InitializerExpression | TypeNode | ArrayLiteralExpression | TypedDeclaration; +} + +type TypeMapperResult = { knownType: KnownTypes, type: string | KnownTypes }; + export type KnownTypes = "number" | "string" | "boolean" | "date" | "reference" | "void" | "array"; export class TypeMapper { @@ -30,33 +36,37 @@ export class TypeMapper { return node != undefined && "typeArguments" in node!; } - public static get(node: TypedDeclaration, typeChecker: TypeChecker): string | KnownTypes { - let kind = this.toKnownType(typeChecker, node, node.type); + private static get(kind: KnownTypes, node?: TypedDeclaration, typeNode?: TypeNode | Expression): string | KnownTypes { switch (kind) { case "reference": { - return ((node.type as TypeReferenceNode).typeName as Identifier).escapedText! + return ((typeNode as TypeReferenceNode).typeName as Identifier).escapedText! } case "array": { - let typeKind; - if (this.isGenericArray(node.type)) { - typeKind = this.toKnownType(typeChecker, node, ((node.type as NodeWithTypeArguments).typeArguments as NodeArray)[0]); + let tokenType = typeNode ? typeNode : (node as InitializerNode).initializer; + if (this.isGenericArray(tokenType as TypeNode)) { + tokenType = ((tokenType as NodeWithTypeArguments).typeArguments as NodeArray)[0]; } else { - typeKind = this.toKnownType(typeChecker, node, ((node.type as ArrayTypeNode).elementType as TypeNode)); + tokenType = (tokenType as ArrayTypeNode)?.elementType ?? (tokenType as ArrayLiteralExpression).elements[0]; } - return typeKind; + return this.toKnownType(node, tokenType); } default: return kind; } } - public static toKnownType(typeChecker: TypeChecker, node: TypedDeclaration, typeNode?: TypeNode): KnownTypes { + private static toKnownType(node?: TypedDeclaration, typeNode?: TypeNode | Expression): KnownTypes { switch (typeNode?.kind) { + case SyntaxKind.NumericLiteral: case SyntaxKind.NumberKeyword: return "number"; + case SyntaxKind.StringLiteral: case SyntaxKind.StringKeyword: return "string"; + case SyntaxKind.FalseKeyword: + case SyntaxKind.TrueKeyword: case SyntaxKind.BooleanKeyword: return "boolean"; + case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ArrayType: return "array"; default: { @@ -69,15 +79,25 @@ export class TypeMapper { } return "reference"; } - - // try to infer the type by accessing the node - const type = typeChecker.getTypeAtLocation(node) as IntrinsicNamed; - return type.intrinsicName == "number" - ? "number" - : type.intrinsicName == "string" - ? "string" - : "void"; + else if (typeNode?.kind == SyntaxKind.Identifier) { + if ((typeNode as Identifier)?.escapedText == "Array") { + return "array"; + } + } + const initializer = (node as InitializerNode)?.initializer; + if (initializer) { + return this.toKnownType(initializer as TypedDeclaration, (initializer as InitializerExpression).expression ?? initializer); + } + return "void"; } } } + + public static getType(node?: TypedDeclaration, typeNode?: TypeNode | Expression): TypeMapperResult { + const kind = this.toKnownType(node, typeNode); + return { + knownType: kind, + type: this.get(kind, node, typeNode) + } + } } diff --git a/src/shared/types/typed-class-element.ts b/src/shared/types/typed-class-element.ts index 6b104a7..73edc99 100644 --- a/src/shared/types/typed-class-element.ts +++ b/src/shared/types/typed-class-element.ts @@ -17,7 +17,8 @@ export class TypedClassElement extends ClassElement< public parse(node: K): void { super.parse(node); - this.knownType = TypeMapper.toKnownType(this.getTypeChecker(), node, node.type); - this.type = TypeMapper.get(node, this.getTypeChecker()); + const { knownType, type } = TypeMapper.getType(node, node.type); + this.knownType = knownType; + this.type = type; } } diff --git a/src/shared/variable.ts b/src/shared/variable.ts index 4ca4e4e..49e1cfa 100644 --- a/src/shared/variable.ts +++ b/src/shared/variable.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://github.com/southworks/codeverter/blob/main/LICENSE */ -import { LiteralExpression, VariableDeclaration } from "typescript"; +import { VariableDeclaration } from "typescript"; import { ValuedSourceElement } from "./types/source-element"; import { TypedClassElement } from "./types/typed-class-element"; @@ -15,6 +15,15 @@ export class Variable extends TypedClassElement implements public parse(node: VariableDeclaration): void { super.parse(node); - this.value = (node.initializer as LiteralExpression)?.text; + this.value = node.initializer?.getText(); + if (this.value) { + this.value = this.value.trim(); + if (this.value.startsWith("'") || this.value.startsWith("\"")) { + this.value = this.value.substring(1); + } + if (this.value.endsWith("'") || this.value.endsWith("\"")) { + this.value = this.value.substring(0, this.value.length - 1); + } + } } } diff --git a/src/templating/csharp/csharp-helpers.ts b/src/templating/csharp/csharp-helpers.ts index ce20353..341335b 100644 --- a/src/templating/csharp/csharp-helpers.ts +++ b/src/templating/csharp/csharp-helpers.ts @@ -31,16 +31,13 @@ export function getCSharpHelpers(helpers: TemplateHelper & CSharpHelpers): CShar generateInitializeValue: (e: ValuedSourceElement, semicolon: boolean) => { if (e.value != undefined) { if (e.knownType == "array") { - let defaultValue = e.value; - if (e.value.match("new Array")) { - defaultValue = e.value.match(/\((.*?)\)/g)?.toString().replace("(", "").replace(")", "") ?? e.value; - } else if (e.value.includes("[") && e.value.includes("]")) { - defaultValue = e.value.replace("[", "").replace("]", ""); - } + let defaultValue = helpers.getArrayDefault(e.value); defaultValue = defaultValue === "" ? defaultValue : ` ${defaultValue} `; return ` = new ${helpers.mapType(e)} {${defaultValue}};`; } else if (e.knownType == "date") { return ` = new DateTime();`; + } else if (e.knownType == "void") { + return ` = null; //${e.value}`; } else if (e.knownType == "string") { return ` = "${e.value}";`; } diff --git a/src/templating/go/go-helpers.ts b/src/templating/go/go-helpers.ts index 0a7b97e..3c41f0d 100644 --- a/src/templating/go/go-helpers.ts +++ b/src/templating/go/go-helpers.ts @@ -18,6 +18,7 @@ import { import { TemplateHelper } from "../template-helpers"; export interface GoHelpers { + getArrayValue(v: ValuedSourceElement): string; fixName(e: VisibilitySourceElement): string; printVariable(v: ValuedSourceElement, global: boolean): string; printEnum(v: EnumSourceElement): string; @@ -32,16 +33,27 @@ export interface GoHelpers { export function getGoHelpers(helpers: TemplateHelper & GoHelpers): GoHelpers { return { + getArrayValue: (v: ValuedSourceElement) => { + let defaultValue = helpers.getArrayDefault(v.value!); + let type = helpers.mapType(v); + return `${type}{${defaultValue}}`; + }, printVariable: (v: ValuedSourceElement, global: boolean) => { const name = v.visibility == "public" && global ? helpers.capitalize(v.name) : helpers.toLowerCase(v.name); - const declarationPrefix = v.kind == "constant" ? "const" : "var"; + + // in go arrays cannot be constants + const declarationPrefix = (v.kind == "constant" && v.knownType != "array") ? "const" : "var"; const asignChar = (v.kind == "constant" || v.knownType != "void") ? "=" : ":="; let value = v.value; - if (v.knownType == "string" || (v.knownType == "void" && isNaN(+v.value!))) { + if (v.knownType == "string") { value = `"${v.value}"`; + } else if (v.knownType == "void" && v.value) { + value = `0 //${v.value}`; + } else if (v.knownType == "array") { + value = helpers.getArrayValue(v); } if (value == "") { return ""; // cannot define empty const/var in go diff --git a/src/templating/template-helpers.ts b/src/templating/template-helpers.ts index cd055bd..0272708 100644 --- a/src/templating/template-helpers.ts +++ b/src/templating/template-helpers.ts @@ -10,6 +10,7 @@ import { TypedSourceElement } from "../shared/types/source-element"; import { TemplateGenerator } from "./template-generator"; export interface TemplateHelper { + getArrayDefault(str: string): string; capitalize(str: string): string; camelize(str: string): string; toUpperCase(str: string): string; @@ -69,6 +70,15 @@ export class TemplateHelpers { public static build(generator: TemplateGenerator): TemplateHelper { const defaulHelpers = { capitalize: (str: string) => { return str.replace(/\w/, c => c.toUpperCase()); }, + getArrayDefault: (str: string) => { + let result = str; + if (str?.match("new Array")) { + result = str.match(/\((.*?)\)/g)?.toString().replace("(", "").replace(")", "") ?? str; + } else if (str?.includes("[") && str.includes("]")) { + result = str.replace("[", "").replace("]", ""); + } + return result; + }, camelize: (str: string) => { return str[0].toLowerCase() + str.substring(1) }, toUpperCase: (str: string) => { return str.toUpperCase(); }, toLowerCase: (str: string) => { return str.toLowerCase(); }, diff --git a/src/test/csharp/constants.spec.ts b/src/test/csharp/constants.spec.ts index 87681cf..6205861 100644 --- a/src/test/csharp/constants.spec.ts +++ b/src/test/csharp/constants.spec.ts @@ -13,7 +13,7 @@ describe("CSharp: constant", () => { printFile(compilationResult, new CSharpGenerator(), strWritter); const expected = new StringWritter(); - expected.write(`namespace Test`); + expected.write("namespace Test"); expected.write("{"); expected.write(" public static class Helper"); expected.write(" {"); @@ -24,4 +24,34 @@ describe("CSharp: constant", () => { expect(strWritter.getString()).toBe(expected.getString()); }); + + test("Constants infer types", () => { + const code = ` + const constant = {a: 'a'};\n + const constantStr = 'asdasdad';\n + const constantBool = false;\n + const constantArr: number[] = [1, 2];\n + const constantArr2 = new Array(1, 2);\n + const ConstantNum = 123;`; + let compilationResult = compileTypeScriptCode(code, "test.ts"); + + const strWritter = new StringWritter(); + printFile(compilationResult, new CSharpGenerator(), strWritter); + + const expected = new StringWritter(); + expected.write("namespace Test"); + expected.write("{"); + expected.write(" public static class Helper"); + expected.write(" {"); + expected.write(` public const var CONSTANT = null; //{a: 'a'}`); + expected.write(` public const string CONSTANTSTR = "asdasdad";`); + expected.write(` public const bool CONSTANTBOOL = false;`); + expected.write(` public const int[] CONSTANTARR = new int[] { 1, 2 };`); + expected.write(` public const int[] CONSTANTARR2 = new int[] { 1, 2 };`); + expected.write(` public const int CONSTANTNUM = 123;`); + expected.write(" }"); + expected.write("}\n"); + + expect(strWritter.getString()).toBe(expected.getString()); + }); }); diff --git a/src/test/csharp/method.spec.ts b/src/test/csharp/method.spec.ts index f75dcd7..f827296 100644 --- a/src/test/csharp/method.spec.ts +++ b/src/test/csharp/method.spec.ts @@ -19,10 +19,10 @@ describe("CSharp: method", () => { printFile(compilationResult, new CSharpGenerator(), strWritter); const expected = new StringWritter(); - expected.write(`namespace Test`); - expected.write(`{`); + expected.write("namespace Test"); + expected.write("{"); expected.write(" public class Test"); - expected.write(` {`); + expected.write(" {"); expected.write(" public string Method()"); expected.write(" {"); expected.write(" string asd = \"holi\";"); @@ -41,7 +41,7 @@ describe("CSharp: method", () => { test("global function", () => { const code = new StringWritter(); - code.write("export function foo: string {"); + code.write("export function foo(): string {"); code.write(" return \"\";"); code.write("}"); @@ -51,10 +51,10 @@ describe("CSharp: method", () => { printFile(compilationResult, new CSharpGenerator(), strWritter); const expected = new StringWritter(); - expected.write(`namespace Test`); - expected.write(`{`); + expected.write("namespace Test"); + expected.write("{"); expected.write(" public static class Helper"); - expected.write(` {`); + expected.write(" {"); expected.write(" public static string Foo()"); expected.write(" {"); expected.write(" // return \"\";"); @@ -66,4 +66,60 @@ describe("CSharp: method", () => { expect(strWritter.getString()).toBe(expected.getString()); }); + + test("global function infer", () => { + const code = new StringWritter(); + code.write("export function foo() {"); + code.write(" return \"\";"); + code.write("}"); + + let compilationResult = compileTypeScriptCode(code.getString(), "test.ts"); + + const strWritter = new StringWritter(); + printFile(compilationResult, new CSharpGenerator(), strWritter); + + const expected = new StringWritter(); + expected.write("namespace Test"); + expected.write("{"); + expected.write(" public static class Helper"); + expected.write(" {"); + expected.write(" public static string Foo()"); + expected.write(" {"); + expected.write(" // return \"\";"); + expected.write(" return \"\";"); + expected.write(" }"); + expected.write(" }"); + expected.write("}"); + expected.write(""); + + expect(strWritter.getString()).toBe(expected.getString()); + }); + + test("global function infer void", () => { + const code = new StringWritter(); + code.write("export function foo() {"); + code.write(" return;"); + code.write("}"); + + let compilationResult = compileTypeScriptCode(code.getString(), "test.ts"); + + const strWritter = new StringWritter(); + printFile(compilationResult, new CSharpGenerator(), strWritter); + + const expected = new StringWritter(); + expected.write("namespace Test"); + expected.write("{"); + expected.write(" public static class Helper"); + expected.write(" {"); + expected.write(" public static void Foo()"); + expected.write(" {"); + expected.write(" // return;"); + expected.write(" return;"); + expected.write(" }"); + expected.write(" }"); + expected.write("}"); + expected.write(""); + + expect(strWritter.getString()).toBe(expected.getString()); + }); }); diff --git a/src/test/go/constants.spec.ts b/src/test/go/constants.spec.ts index f726360..85aaae3 100644 --- a/src/test/go/constants.spec.ts +++ b/src/test/go/constants.spec.ts @@ -13,7 +13,7 @@ describe("GO: constant", () => { printFile(compilationResult, new GoGenerator(), strWritter); const expected = new StringWritter(); - expected.write(`package test`); + expected.write("package test"); expected.write(""); expected.write(`const Constant string = "test"`); expected.write("const NumberConstant int = 123"); @@ -21,4 +21,31 @@ describe("GO: constant", () => { expect(strWritter.getString()).toBe(expected.getString()); }); + + test("Constants infer types", () => { + const code = ` + const constant = {a: 'a'};\n + const constantStr = 'asdasdad';\n + const constantBool = false;\n + const constantArr: number[] = [1, 2];\n + const constantArr2 = new Array(1, 2);\n + const ConstantNum = 123;`; + let compilationResult = compileTypeScriptCode(code, "test.ts"); + + const strWritter = new StringWritter(); + printFile(compilationResult, new GoGenerator(), strWritter); + + const expected = new StringWritter(); + expected.write("package test"); + expected.write(""); + expected.write(`const Constant = 0 //{a: 'a'}`); + expected.write(`const ConstantStr string = "asdasdad"`); + expected.write("const ConstantBool bool = false"); + expected.write("var ConstantArr []int = []int{1, 2}"); + expected.write("var ConstantArr2 []int = []int{1, 2}"); + expected.write("const ConstantNum int = 123"); + expected.write(""); + + expect(strWritter.getString()).toBe(expected.getString()); + }); }); diff --git a/src/test/go/method.spec.ts b/src/test/go/method.spec.ts index 55a6851..f60ef0b 100644 --- a/src/test/go/method.spec.ts +++ b/src/test/go/method.spec.ts @@ -18,7 +18,7 @@ describe("GO: method", () => { printFile(compilationResult, new GoGenerator(), strWritter); const expected = new StringWritter(); - expected.write(`package test`); + expected.write("package test"); expected.write(""); expected.write("type Test struct {"); expected.write("}"); @@ -77,7 +77,30 @@ describe("GO: method", () => { printFile(compilationResult, new GoGenerator(), strWritter); const expected = new StringWritter(); - expected.write(`package test`); + expected.write("package test"); + expected.write(""); + expected.write("func Foo() string {"); + expected.write("\t// return \"\";"); + expected.write("\treturn \"\""); + expected.write("}"); + expected.write(""); + + expect(strWritter.getString()).toBe(expected.getString()); + }); + + test("global function infer", () => { + const code = new StringWritter(); + code.write("export function foo() {"); + code.write(" return \"\";"); + code.write("}"); + + let compilationResult = compileTypeScriptCode(code.getString(), "test.ts"); + + const strWritter = new StringWritter(); + printFile(compilationResult, new GoGenerator(), strWritter); + + const expected = new StringWritter(); + expected.write("package test"); expected.write(""); expected.write("func Foo() string {"); expected.write("\t// return \"\";"); @@ -87,4 +110,27 @@ describe("GO: method", () => { expect(strWritter.getString()).toBe(expected.getString()); }); + + test("global function infer void", () => { + const code = new StringWritter(); + code.write("export function foo() {"); + code.write(" return;"); + code.write("}"); + + let compilationResult = compileTypeScriptCode(code.getString(), "test.ts"); + + const strWritter = new StringWritter(); + printFile(compilationResult, new GoGenerator(), strWritter); + + const expected = new StringWritter(); + expected.write("package test"); + expected.write(""); + expected.write("func Foo() {"); + expected.write("\t// return;"); + expected.write("\treturn"); + expected.write("}"); + expected.write(""); + + expect(strWritter.getString()).toBe(expected.getString()); + }); });