Skip to content

Commit

Permalink
Merge pull request #47 from southworks/issues_45_46
Browse files Browse the repository at this point in the history
Infer type from methods and constants
  • Loading branch information
agustinseifert authored Oct 1, 2022
2 parents 3e1b03e + 70598e7 commit 67b5a5b
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 48 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
11 changes: 10 additions & 1 deletion src/shared/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -57,6 +60,12 @@ export class Function<K extends FunctionLikeDeclarationBase = FunctionDeclaratio

public parse(node: K): void {
super.parse(node);
const returnStatement = (node.body as Block)?.statements.find(s => 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!)) {
Expand Down
62 changes: 41 additions & 21 deletions src/shared/type-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,66 @@
*/

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 {
private static isGenericArray(node?: TypeNode): node is NodeWithTypeArguments {
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<TypeNode>)[0]);
let tokenType = typeNode ? typeNode : (node as InitializerNode).initializer;
if (this.isGenericArray(tokenType as TypeNode)) {
tokenType = ((tokenType as NodeWithTypeArguments).typeArguments as NodeArray<TypeNode>)[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: {
Expand All @@ -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)
}
}
}
5 changes: 3 additions & 2 deletions src/shared/types/typed-class-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export class TypedClassElement<K extends TypedDeclaration> 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;
}
}
13 changes: 11 additions & 2 deletions src/shared/variable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -15,6 +15,15 @@ export class Variable extends TypedClassElement<VariableDeclaration> 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);
}
}
}
}
9 changes: 3 additions & 6 deletions src/templating/csharp/csharp-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}";`;
}
Expand Down
16 changes: 14 additions & 2 deletions src/templating/go/go-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/templating/template-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(); },
Expand Down
32 changes: 31 additions & 1 deletion src/test/csharp/constants.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(" {");
Expand All @@ -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<number>(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());
});
});
Loading

0 comments on commit 67b5a5b

Please sign in to comment.