ast-transpiler
Version:
README.md
1,165 lines (995 loc) • 79.3 kB
text/typescript
import { BaseTranspiler } from "./baseTranspiler.js";
import ts, { BinaryExpression, CallExpression, TypeChecker } from 'typescript';
const SyntaxKind = ts.SyntaxKind;
const parserConfig = {
'ELSEIF_TOKEN': 'else if',
'OBJECT_OPENING': 'map[string]interface{} {',
'ARRAY_OPENING_TOKEN': '[]interface{}{',
'ARRAY_CLOSING_TOKEN': '}',
'PROPERTY_ASSIGNMENT_TOKEN': ':',
'VAR_TOKEN': 'object', // object
'METHOD_TOKEN': 'func',
'PROPERTY_ASSIGNMENT_OPEN': '',
'PROPERTY_ASSIGNMENT_CLOSE': '',
'SUPER_TOKEN': 'base',
'SUPER_CALL_TOKEN': 'base',
'FALSY_WRAPPER_OPEN': 'IsTrue(',
'FALSY_WRAPPER_CLOSE': ')',
'COMPARISON_WRAPPER_OPEN' : "IsEqual(",
'COMPARISON_WRAPPER_CLOSE' : ")",
'UKNOWN_PROP_WRAPPER_OPEN': 'this.call(',
'UNKOWN_PROP_WRAPPER_CLOSE': ')',
'UKNOWN_PROP_ASYNC_WRAPPER_OPEN': 'this.callAsync(',
'UNKOWN_PROP_ASYNC_WRAPPER_CLOSE': ')',
'DYNAMIC_CALL_OPEN': 'callDynamically(',
'EQUALS_EQUALS_WRAPPER_OPEN': 'IsEqual(',
'EQUALS_EQUALS_WRAPPER_CLOSE': ')',
'DIFFERENT_WRAPPER_OPEN': '!IsEqual(',
'DIFFERENT_WRAPPER_CLOSE': ')',
'GREATER_THAN_WRAPPER_OPEN': 'IsGreaterThan(',
'GREATER_THAN_WRAPPER_CLOSE': ')',
'GREATER_THAN_EQUALS_WRAPPER_OPEN': 'IsGreaterThanOrEqual(',
'GREATER_THAN_EQUALS_WRAPPER_CLOSE': ')',
'LESS_THAN_WRAPPER_OPEN': 'IsLessThan(',
'LESS_THAN_WRAPPER_CLOSE': ')',
'LESS_THAN_EQUALS_WRAPPER_OPEN': 'IsLessThanOrEqual(',
'LESS_THAN_EQUALS_WRAPPER_CLOSE': ')',
'PLUS_WRAPPER_OPEN':'Add(',
'PLUS_WRAPPER_CLOSE':')',
'MINUS_WRAPPER_OPEN':'Subtract(',
'MINUS_WRAPPER_CLOSE':')',
'ARRAY_LENGTH_WRAPPER_OPEN': 'GetArrayLength(',
'ARRAY_LENGTH_WRAPPER_CLOSE': ')',
'DIVIDE_WRAPPER_OPEN': 'Divide(',
'DIVIDE_WRAPPER_CLOSE': ')',
'MULTIPLY_WRAPPER_OPEN': 'Multiply(',
'MULTIPLY_WRAPPER_CLOSE': ')',
'INDEXOF_WRAPPER_OPEN': 'GetIndexOf(',
'INDEXOF_WRAPPER_CLOSE': ')',
'MOD_WRAPPER_OPEN': 'Mod(',
'MOD_WRAPPER_CLOSE': ')',
'FUNCTION_TOKEN': 'func',
'DEFAULT_RETURN_TYPE': 'interface{}',
'BLOCK_OPENING_TOKEN': '{',
'DEFAULT_PARAMETER_TYPE': 'interface{}',
'LINE_TERMINATOR': '',
'CONDITION_OPENING':'',
'CONDITION_CLOSE':'',
'AWAIT_TOKEN': '',
'NULL_TOKEN': 'nil',
'UNDEFINED_TOKEN': 'nil',
'WHILE_TOKEN': 'for',
'ELEMENT_ACCESS_WRAPPER_OPEN': 'GetValue(',
'ELEMENT_ACCESS_WRAPPER_CLOSE': ')',
};
export class GoTranspiler extends BaseTranspiler {
binaryExpressionsWrappers;
wrapThisCalls: boolean;
wrapCallMethods: string[] = [];
className: string;
classNameMap: { [key: string]: string };
DEFAULT_RETURN_TYPE = 'interface{}';
constructor(config = {}) {
config['parser'] = Object.assign ({}, parserConfig, config['parser'] ?? {});
super(config);
this.requiresParameterType = true;
this.requiresReturnType = true;
this.asyncTranspiling = false;
this.supportsFalsyOrTruthyValues = false;
this.requiresCallExpressionCast = true;
this.wrapThisCalls = false;
this.id = "Go";
this.className = "undefined";
this.classNameMap = config['classNameMap'] ?? {};
this.initConfig();
// user overrides
this.applyUserOverrides(config);
this.wrapThisCalls = config['wrapThisCalls'] ?? false;
this.wrapCallMethods = config['wrapCallMethods'] ?? [];
}
initConfig() {
this.LeftPropertyAccessReplacements = {
// 'this': '$this',
};
this.RightPropertyAccessReplacements = {
'push': 'Add', // list method
'indexOf': 'IndexOf', // list method
'toUpperCase': 'ToUpper',
'toLowerCase': 'ToLower',
'toString': 'ToString',
};
this.FullPropertyAccessReplacements = {
'JSON.parse': 'parseJson', // custom helper method
'console.log': 'fmt.Println',
'Number.MAX_SAFE_INTEGER': 'Int32.MaxValue',
'Math.min': 'Math.Min',
'Math.max': 'Math.Max',
'Math.log': 'Math.Log',
'Math.abs': 'Math.Abs',
// 'Math.ceil': 'Math.Ceiling', // need cast
// 'Math.round': 'Math.Round', // need to cast
'Math.floor': 'Math.Floor',
'Math.pow': 'Math.Pow',
// 'Promise.all': 'Task.WhenAll',
};
this.CallExpressionReplacements = {
// "parseInt": "parseINt",
// "parseFloat": "float.Parse",
};
this.ReservedKeywordsReplacements = {
// 'string': 'str',
// 'params': 'parameters',
'type': 'typeVar',
// 'internal': 'intern',
// 'event': 'eventVar',
// 'fixed': 'fixedVar',
};
this.binaryExpressionsWrappers = {
[ts.SyntaxKind.EqualsEqualsToken]: [this.EQUALS_EQUALS_WRAPPER_OPEN, this.EQUALS_EQUALS_WRAPPER_CLOSE],
[ts.SyntaxKind.EqualsEqualsEqualsToken]: [this.EQUALS_EQUALS_WRAPPER_OPEN, this.EQUALS_EQUALS_WRAPPER_CLOSE],
[ts.SyntaxKind.ExclamationEqualsToken]: [this.DIFFERENT_WRAPPER_OPEN, this.DIFFERENT_WRAPPER_CLOSE],
[ts.SyntaxKind.ExclamationEqualsEqualsToken]: [this.DIFFERENT_WRAPPER_OPEN, this.DIFFERENT_WRAPPER_CLOSE],
[ts.SyntaxKind.GreaterThanToken]: [this.GREATER_THAN_WRAPPER_OPEN, this.GREATER_THAN_WRAPPER_CLOSE],
[ts.SyntaxKind.GreaterThanEqualsToken]: [this.GREATER_THAN_EQUALS_WRAPPER_OPEN, this.GREATER_THAN_EQUALS_WRAPPER_CLOSE],
[ts.SyntaxKind.LessThanToken]: [this.LESS_THAN_WRAPPER_OPEN, this.LESS_THAN_WRAPPER_CLOSE],
[ts.SyntaxKind.LessThanEqualsToken]: [this.LESS_THAN_EQUALS_WRAPPER_OPEN, this.LESS_THAN_EQUALS_WRAPPER_CLOSE],
[ts.SyntaxKind.PlusToken]: [this.PLUS_WRAPPER_OPEN, this.PLUS_WRAPPER_CLOSE],
[ts.SyntaxKind.MinusToken]: [this.MINUS_WRAPPER_OPEN, this.MINUS_WRAPPER_CLOSE],
[ts.SyntaxKind.AsteriskToken]: [this.MULTIPLY_WRAPPER_OPEN, this.MULTIPLY_WRAPPER_CLOSE],
[ts.SyntaxKind.PercentToken]: [this.MOD_WRAPPER_OPEN, this.MOD_WRAPPER_CLOSE],
[ts.SyntaxKind.SlashToken]: [this.DIVIDE_WRAPPER_OPEN, this.DIVIDE_WRAPPER_CLOSE],
};
}
// getBlockOpen(identation){
// return this.getIden(identation) + this.BLOCK_OPENING_TOKEN;
// }
printSuperCallInsideConstructor(node, identation) {
return ""; // csharp does not need super call inside constructor
}
printStringLiteral(node) {
const token = this.STRING_QUOTE_TOKEN;
let text = node.text;
if (text in this.StringLiteralReplacements) {
return this.StringLiteralReplacements[text];
}
text = text.replaceAll("'", "\\\\" + "'");
text = text.replaceAll("\"", "\\" + "\"");
text = text.replaceAll("\n", "\\n");
return token + text + token;
}
transformFunctionNameIfNeeded(name): string {
return this.capitalize(name);
}
printPropertyDeclaration(node, identation) {
// let modifiers = this.printModifiers(node);
// modifiers = modifiers ? modifiers + " " : modifiers;
const name = this.capitalize(this.printNode(node.name, 0));
let type = 'interface{}';
if (node.type === undefined) {
type = 'interface{}';
} else if (node.type.kind === SyntaxKind.StringKeyword) {
type = 'string';
} else if (node.type.kind === SyntaxKind.NumberKeyword) {
type = 'int';
} else if (node.type.kind === SyntaxKind.BooleanKeyword || (ts as any).isBooleanLiteral(node)) {
type = 'bool';
} else if (node.type.kind === SyntaxKind.ArrayType) {
type = '[]interface{}';
}
if (node.initializer) {
// we have to save the value and initialize it later
let initializer = this.printNode(node.initializer, 0);
// quick fix
initializer = initializer.replaceAll('"', '');
return this.getIden(identation) + name + ' ' + type + ' ' + `\`default:"${initializer}"\`` + this.LINE_TERMINATOR;
}
return this.getIden(identation) + name + ' ' + type + this.LINE_TERMINATOR;
}
printStruct(node, indentation) {
// check if we have heritage
let heritageName = '';
if (node?.heritageClauses?.length > 0) {
const heritage = node.heritageClauses[0];
const heritageType = heritage.types[0];
let heritageEscapedText = heritageType.expression.escapedText;
if (this.classNameMap[heritageEscapedText]) {
heritageEscapedText = this.classNameMap[heritageEscapedText];
}
heritageName = this.getIden(indentation+1) + heritageEscapedText + '\n';
}
const propDeclarations = node.members.filter(member => member.kind === SyntaxKind.PropertyDeclaration);
return `type ${this.className} struct {\n${heritageName}${propDeclarations.map(member => this.printNode(member, indentation+1)).join("\n")}\n}`;
}
printNewStructMethod(node){
return `
func New${this.capitalize(this.className)}() *${(this.className)} {
p := &${this.className}{}
setDefaults(p)
return p
}\n`;
// TO remove `return copies lock value: github.com/ccxt/ccxt/go/v4.bitvavoWs contains github.com/ccxt/ccxt/go/v4.bitvavo contains github.com/ccxt/ccxt/go/v4.Exchange contains sync.Mutex`
// change the return value to
//
// return `
// func New${this.capitalize(className)}() *${(className)} {
// p := ${className}{}
// setDefaults(&p)
// return &p
// }\n`;
//
}
printClass(node, identation) {
this.className = node.name.escapedText;
if (this.classNameMap[this.className]) {
this.className = this.classNameMap[this.className];
}
const struct = this.printStruct(node, identation);
const newMethod = this.printNewStructMethod(node);
const methods = node.members.filter(member => member.kind === SyntaxKind.MethodDeclaration);
const classMethods = methods.map(method => this.printMethodDeclaration(method, identation)).join("\n");
// const classDefinition = this.printClassDefinition(node, identation);
// const classBody = this.printClassBody(node, identation);
// const classClosing = this.getBlockClose(identation);
// return classDefinition + classBody + classClosing;
return struct + "\n" + newMethod + "\n" + classMethods;
}
printPropertyAccessModifiers (node) {
return "";
}
printSpreadElement(node, identation) {
const expression = this.printNode(node.expression, 0);
return this.getIden(identation) + expression + this.SPREAD_TOKEN;
}
printMethodDeclaration(node, identation) {
let methodDef = this.printMethodDefinition(node, identation);
const isAsync = this.isAsyncFunction(node);
const funcBody = this.printFunctionBody(node, identation, isAsync);
methodDef += funcBody;
return methodDef;
}
printFunctionDeclaration(node, identation) {
if (ts.isArrowFunction(node)) {
const parameters = node.parameters.map(param => this.printParameter(param)).join(", ");
const body = this.printNode(node.body);
return `(${parameters}) => ${body}`;
}
const isAsync = this.isAsyncFunction(node);
let functionDef = this.printFunctionDefinition(node, identation);
const funcBody = this.printFunctionBody(node, identation, isAsync);
functionDef += funcBody;
return this.printNodeCommentsIfAny(node, identation, functionDef);
}
printMethodDefinition(node, identation) {
let name = node.name.escapedText;
name = this.transformMethodNameIfNeeded(name);
let returnType = this.printFunctionType(node);
const parsedArgs = this.printMethodParameters(node);
returnType = returnType ? returnType + " " : returnType;
const methodToken = this.METHOD_TOKEN ? this.METHOD_TOKEN + " " : "";
// const methodDef = this.getIden(identation) + returnType + methodToken + name
// + "(" + parsedArgs + ")";
const structReceiver = `(${this.THIS_TOKEN} *${this.className})`;
const methodDef = this.getIden(identation) + methodToken + " " + structReceiver + " " + name + "(" + parsedArgs + ") " + returnType;
return this.printNodeCommentsIfAny(node, identation, methodDef);
}
printFunctionDefinition(node, identation) {
let name = node.name.escapedText;
name = this.transformMethodNameIfNeeded(name);
let returnType = this.printFunctionType(node);
const parsedArgs = this.printMethodParameters(node);
returnType = returnType ? returnType + " " : returnType;
const methodToken = this.METHOD_TOKEN ? this.METHOD_TOKEN + " " : "";
// const methodDef = this.getIden(identation) + returnType + methodToken + name
// + "(" + parsedArgs + ")";
const methodDef = this.getIden(identation) + methodToken + name + "(" + parsedArgs + ") " + returnType;
return this.printNodeCommentsIfAny(node, identation, methodDef);
}
printMethodParameters(node) {
const params = node.parameters.map(param => this.printParameter(param));
const hasOptionalParameter = params.some(p => p === 'optional');
if (!hasOptionalParameter) {
return params.join(", ");
}
const paramsWithOptional = params.filter(param => param !== 'optional');
paramsWithOptional.push('optionalArgs ...interface{}');
return paramsWithOptional.join(", ");
}
printParameter(node, defaultValue = true) {
const name = this.printNode(node.name, 0);
const initializer = node.initializer;
const type = this.printParameterType(node);
if (defaultValue) {
if (initializer) {
return 'optional'; // will be handled later
}
// not supported we have to find an alternative for go like defining multiple methods with different parameters
// if (initializer) {
// const customDefaultValue = this.printCustomDefaultValueIfNeeded(initializer);
// const defaultValue = customDefaultValue ? customDefaultValue : this.printNode(initializer, 0);
// return type + name + this.SPACE_DEFAULT_PARAM + "=" + this.SPACE_DEFAULT_PARAM + defaultValue;
// }
return name + ' ' + type;
}
return name + ' ' + type;
}
printParameterType(node) {
const typeText = this.getType(node);
// // if (typeText === this.BOOLEAN_KEYWORD) {
// // return typeText;
// // }
//tmp default to interface
return 'interface{}';
if (typeText === this.STRING_KEYWORD) {
return 'string';
}
if (typeText === this.NUMBER_KEYWORD) {
return 'float64';
}
if (typeText === this.BOOLEAN_KEYWORD) {
return 'bool';
}
return this.DEFAULT_PARAMETER_TYPE;
if (typeText === undefined || typeText === this.STRING_KEYWORD) {
// throw new FunctionReturnTypeError("Parameter type is not supported or undefined");
this.warn(node, node.getText(), "Parameter type not found, will default to: " + this.DEFAULT_PARAMETER_TYPE);
return this.DEFAULT_PARAMETER_TYPE;
}
return typeText;
}
printFunctionType(node){
const typeText = this.getFunctionType(node);
if (typeText === 'void') {
// // If the function is async (returns a Promise in TS) but declared void, emit a typed channel
// if (this.isAsyncFunction(node)) {
// // Ensure element type is present; some edge cases yield '<- chan' only
// const elementType = this.DEFAULT_RETURN_TYPE || 'interface{}';
// return `<- chan ${elementType}`;
// }
return "";
}
if (typeText === undefined || (typeText !== this.VOID_KEYWORD && typeText !== this.PROMISE_TYPE_KEYWORD)) {
// throw new FunctionReturnTypeError("Function return type is not supported");
let res = "";
if (this.isAsyncFunction(node)) {
res = `<- chan ${this.DEFAULT_RETURN_TYPE}`;
} else {
res = this.DEFAULT_RETURN_TYPE;
}
this.warn(node, node.name.getText(), "Function return type not found, will default to: " + res);
return res;
}
if (typeText === this.PROMISE_TYPE_KEYWORD) {
return `<- chan interface{}`;
}
// move any trailing array brackets "[]" to directly precede the element type
if (typeText && typeText.endsWith('[]')) {
const core = typeText.substring(0, typeText.length - 2); // drop []
const lastBracketPos = core.lastIndexOf(']');
if (lastBracketPos !== -1) {
// insert [] right after the last ']'
return core.substring(0, lastBracketPos + 1) + '[]' + core.substring(lastBracketPos + 1);
}
}
return typeText;
}
printVariableDeclarationList(node,identation) {
const declaration = node.declarations[0];
// const varToken = this.VAR_TOKEN ? this.VAR_TOKEN + " ": "";
// const name = declaration.name.escapedText;
if (declaration?.name.kind === ts.SyntaxKind.ArrayBindingPattern) {
const arrayBindingPattern = declaration.name;
const arrayBindingPatternElements = arrayBindingPattern.elements;
const parsedArrayBindingElements = arrayBindingPatternElements.map((e) => this.printNode(e.name, 0));
const syntheticName = parsedArrayBindingElements.join("") + "Variable";
let arrayBindingStatement = `${this.getIden(identation)}${syntheticName} := ${this.printNode(declaration.initializer, 0)};\n`;
parsedArrayBindingElements.forEach((e, index) => {
// const type = this.getType(node);
// const parsedType = this.getTypeFromRawType(type);
const statement = this.getIden(identation) + `${e} := GetValue(${syntheticName},${index})`;
if (index < parsedArrayBindingElements.length - 1) {
arrayBindingStatement += statement + ";\n";
} else {
// printStatement adds the last ;
arrayBindingStatement += statement;
}
});
return arrayBindingStatement;
}
if (declaration?.initializer?.kind=== ts.SyntaxKind.AwaitExpression) {
const parsedName = this.printNode(declaration.name, 0);
const parsedInitializer = this.printNode(declaration.initializer, 0);
return `
${this.getIden(identation)}${parsedName}:= ${parsedInitializer}
${this.getIden(identation)}PanicOnError(${parsedName})`;
}
const isNew = declaration.initializer && (declaration.initializer.kind === ts.SyntaxKind.NewExpression);
const parsedValue = (declaration.initializer) ? this.printNode(declaration.initializer, identation) : this.NULL_TOKEN;
if (parsedValue === this.UNDEFINED_TOKEN) {
return this.getIden(identation) + "var " + this.printNode(declaration.name) + " interface{} = " + parsedValue;
}
if (node?.parent?.kind === ts.SyntaxKind.FirstStatement) {
if (isNew) {
return this.getIden(identation) + this.printNode(declaration.name) + " := " + parsedValue;
}
const varName = this.printNode(declaration.name);
const stm = this.getIden(identation) + "var " + varName + " interface{} = " + parsedValue;
if (parsedValue.startsWith("<-this.callInternal(")) {
return `
${stm}
${this.getIden(identation)}PanicOnError(${varName})`;
}
return stm;
}
return this.getIden(identation) + this.printNode(declaration.name) + " := " + parsedValue.trim();
}
// printObjectLiteralExpression(node, identation) {
// const objectCreation = 'make(map[string]interface{}) {';
// let formattedObjectBody = '{}';
// if (node.properties?.length > 0) {
// const objectBody = this.printObjectLiteralBody(node, identation);
// formattedObjectBody = objectBody ? "\n" + objectBody + "\n" + this.getIden(identation) : objectBody;
// }
// // return this.OBJECT_OPENING + formattedObjectBody + this.OBJECT_CLOSING;
// return objectCreation + formattedObjectBody;
// }
// printObjectLiteralBody(node, identation) {
// let objectName = node.parent?.name?.escapedText;
// if (objectName === undefined) {
// objectName = "object";
// }
// const body = node.properties.map((p) => `${this.getIden(identation)}${objectName}["${node.properties[0].name.text}"] = ${p.initializer.text}` ).join("\n");
// return body;
// }
printConstructorDeclaration (node, identation) {
const classNode = node.parent;
const className = this.printNode(classNode.name, 0);
const args = this.printMethodParameters(node);
const constructorBody = this.printFunctionBody(node, identation);
// find super call inside constructor and extract params
let superCallParams = '';
let hasSuperCall = false;
node.body?.statements.forEach(statement => {
if (ts.isExpressionStatement(statement)) {
const expression = statement.expression;
if (ts.isCallExpression(expression)) {
const expressionText = expression.expression.getText().trim();
if (expressionText === 'super') {
hasSuperCall = true;
superCallParams = expression.arguments.map((a) => {
return this.printNode(a, identation).trim();
}).join(", ");
}
}
}
});
if (hasSuperCall) {
return this.getIden(identation) + className +
`(${args}) : ${this.SUPER_CALL_TOKEN}(${superCallParams})` +
constructorBody;
}
return this.getIden(identation) +
className +
"(" + args + ")" +
constructorBody;
}
printThisElementAccesssIfNeeded(node, identation) {
// convert this[method] into this.call(method) or this.callAsync(method)
// const isAsync = node?.parent?.kind === ts.SyntaxKind.AwaitExpression;
const isAsync = true; // setting to true for now, because there are some scenarios where we don't know
// if the call is async or not, so we need to assume it is async
// example Promise.all([this.unknownPropAsync()])
const elementAccess = node.expression;
if (elementAccess?.kind === ts.SyntaxKind.ElementAccessExpression) {
if (elementAccess?.expression?.kind === ts.SyntaxKind.ThisKeyword) {
let parsedArg = node.arguments?.length > 0 ? this.printNode(node.arguments[0], identation).trimStart() : "";
const propName = this.printNode(elementAccess.argumentExpression, 0);
const wrapperOpen = isAsync ? this.UKNOWN_PROP_ASYNC_WRAPPER_OPEN : this.UKNOWN_PROP_WRAPPER_OPEN;
const wrapperClose = isAsync ? this.UNKOWN_PROP_ASYNC_WRAPPER_CLOSE : this.UNKOWN_PROP_WRAPPER_CLOSE;
parsedArg = parsedArg ? ", " + parsedArg : "";
return wrapperOpen + propName + parsedArg + wrapperClose;
}
}
return;
}
printDynamicCall(node, identation) {
// const isAsync = true; // setting to true for now, because there are some scenarios where we don't know
const elementAccess = node.expression;
if (elementAccess?.kind === ts.SyntaxKind.ElementAccessExpression) {
const parsedArg = node.arguments?.length > 0 ? node.arguments.map(n => this.printNode(n, identation).trimStart()).join(", ") : "";
// const target = this.printNode(elementAccess.expression, 0);
const propName = this.printNode(elementAccess.argumentExpression, 0);
const argsArray = `${parsedArg}`;
const open = this.DYNAMIC_CALL_OPEN;
const statement = `${open}${propName}, ${argsArray})`;
// statement = isAsync ? `((Task<object>)${statement})` : statement;
return statement;
}
return undefined;
}
printElementAccessExpressionExceptionIfAny(node) {
// Fix malformed Split(...) element access where the index arg is mistakenly placed
// inside the Split call. We force the correct pattern: GetValue(Split(str, sep), idx)
const tsKind = ts.SyntaxKind;
if (node.expression.kind === tsKind.CallExpression) {
const callExp = node.expression;
const calleeText = callExp.expression.getText();
if (calleeText.endsWith('.split') || calleeText.toLowerCase().includes('split')) {
// print Split call normally (should already close with ))
let splitCall = this.printNode(callExp, 0).trim();
if (!splitCall.endsWith(')')) {
splitCall += ')';
}
const idxArg = this.printNode(node.argumentExpression, 0);
return `GetValue(${splitCall}, ${idxArg})`;
}
}
// default: no exception
return undefined;
}
printWrappedUnknownThisProperty(node) {
const type = global.checker.getResolvedSignature(node);
if (type?.declaration === undefined) {
let parsedArguments = node.arguments?.map((a) => this.printNode(a, 0)).join(", ");
parsedArguments = parsedArguments ? parsedArguments : "";
const propName = node.expression?.name.escapedText;
// const isAsyncDecl = true;
// const isAsyncDecl = node?.parent?.kind === ts.SyntaxKind.AwaitExpression;
// const isAsyncDecl = false;
// const open = isAsyncDecl ? this.UKNOWN_PROP_ASYNC_WRAPPER_OPEN : this.UKNOWN_PROP_WRAPPER_OPEN;
// const close = this.UNKOWN_PROP_WRAPPER_CLOSE;
// return `${open}"${propName}"${parsedArguments}${close}`;
const argsArray = `${parsedArguments}`;
const open = this.DYNAMIC_CALL_OPEN;
const statement = `${open}"${propName}", ${argsArray})`;
return statement;
}
return undefined;
}
transformMethodNameIfNeeded(name: string): string {
const res = this.unCamelCaseIfNeeded(name);
return this.capitalize(res);
}
transformCallExpressionName(name: string) {
return this.capitalize(name);
}
transformPropertyAccessExpressionName(name: string) {
return this.capitalize(name);
}
printOutOfOrderCallExpressionIfAny(node, identation) {
if (node.expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
const args = node.arguments;
if (node.expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
const methodName = this.printNode(node.expression.name, 0);
if (this.wrapThisCalls || (this.wrapCallMethods.includes(methodName))) {
let argsParsed = "";
if (args.length > 0) {
argsParsed = args.map((a) => this.printNode(a, 0)).join(", ");
return `<-this.callInternal("${methodName}", ${argsParsed})`;
}
return `<-this.callInternal("${methodName}")`;
}
}
const expressionText = node.expression.getText().trim();
if (args.length === 1) {
const parsedArg = this.printNode(args[0], 0);
switch (expressionText) {
// case "JSON.parse":
// return `json_decode(${parsedArg}, $as_associative_array = true)`;
case "Math.abs":
return `mathAbs(${parsedArg})`;
}
} else if (args.length === 2)
{
const parsedArg1 = this.printNode(args[0], 0);
const parsedArg2 = this.printNode(args[1], 0);
switch (expressionText) {
case "Math.min":
return `mathMin(${parsedArg1}, ${parsedArg2})`;
case "Math.max":
return `mathMax(${parsedArg1}, ${parsedArg2})`;
case "Math.pow":
return `MathPow(${parsedArg1}, ${parsedArg2})`;
}
}
const leftSide = node.expression?.expression;
const leftSideText = leftSide ? this.printNode(leftSide, 0) : undefined;
// wrap unknown property this.X calls
if (leftSideText === this.THIS_TOKEN || leftSide.getFullText().indexOf("(this as any)") > -1) { // double check this
const res = this.printWrappedUnknownThisProperty(node);
if (res) {
return res;
}
}
}
// // replace this[method]() calls
// const thisElementAccess = this.printThisElementAccesssIfNeeded(node, identation);
// if (thisElementAccess) {
// return thisElementAccess;
// }
// handle dynamic calls, this[method](A) or exchange[b] (c) using reflection
if (node.expression.kind === ts.SyntaxKind.ElementAccessExpression) {
return this.printDynamicCall(node, identation);
}
return undefined;
}
handleTypeOfInsideBinaryExpression(node, identation) {
const left = node.left;
const right = node.right.text;
const op = node.operatorToken.kind;
const expression = left.expression;
const isDifferentOperator = op === ts.SyntaxKind.ExclamationEqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken;
const notOperator = isDifferentOperator ? this.NOT_TOKEN : "";
const target = this.printNode(expression, 0);
switch (right) {
case "string":
return notOperator + `IsString(${target})`;
case "number":
return notOperator + `IsNumber(${target})`;
case "boolean":
return notOperator + `IsBool(${target})`;
case "object":
return notOperator + `IsObject(${target})`;
case "function":
return notOperator + `IsFunction(${target})`;
}
return undefined;
}
printCustomBinaryExpressionIfAny(node, identation) {
const left = node.left;
const right = node.right;
const op = node.operatorToken.kind;
// ---------------------------------------------------------------
// Array destructuring assignment: [a, b] = foo()
// Transforms into:
// __tmpX := foo()
// a = GetValue(__tmpX, 0)
// b = GetValue(__tmpX, 1)
// ---------------------------------------------------------------
if (op === ts.SyntaxKind.EqualsToken &&
left.kind === ts.SyntaxKind.ArrayLiteralExpression) {
// const elems = (left.elements as any[]);
// const returnRandName = "retRes" + this.getLineBasedSuffix(node);
// const rhs = this.printNode(right, 0);
// // build extraction lines
// const assignments = elems.map((el, idx) => {
// const leftName = this.printNode(el, 0);
// return `${leftName} = GetValue(${returnRandName}, ${idx})`;
// }).join(`\n${this.getIden(identation)}`);
// return `${returnRandName} := ${rhs}\n${this.getIden(identation)}${assignments}`;
//
const arrayBindingPatternElements = left.elements;
const parsedArrayBindingElements = arrayBindingPatternElements.map((e) => this.printNode(e, 0));
const syntheticName = parsedArrayBindingElements.join("") + "Variable";
let arrayBindingStatement = `${syntheticName} := ${this.printNode(right, 0)};\n`;
parsedArrayBindingElements.forEach((e, index) => {
const statement = this.getIden(identation) + `${e} = GetValue(${syntheticName},${index})`;
if (index < parsedArrayBindingElements.length - 1) {
arrayBindingStatement += statement + ";\n";
} else {
// printStatement adds the last ;
arrayBindingStatement += statement;
}
});
return arrayBindingStatement;
}
// ---------------------------------------------------------------
// Go-style setter for element-access assignments: a[b] = v
// ---------------------------------------------------------------
if (op === ts.SyntaxKind.EqualsToken &&
left.kind === ts.SyntaxKind.ElementAccessExpression) {
// Collect base container and all keys (inner-most key is last).
const keys: any[] = [];
let baseExpr: any = null;
let cur: any = left;
while (ts.isElementAccessExpression(cur)) {
keys.unshift(cur.argumentExpression); // prepend
const expr = cur.expression;
if (!ts.isElementAccessExpression(expr)) {
baseExpr = expr;
break;
}
cur = expr;
}
const containerStr = this.printNode(baseExpr, 0);
const keyStrs = keys.map(k => this.printNode(k, 0));
// Build GetValue(GetValue( ... )) chain for all but the last key.
let acc = containerStr;
for (let i = 0; i < keyStrs.length - 1; i++) {
acc = `${this.ELEMENT_ACCESS_WRAPPER_OPEN}${acc}, ${keyStrs[i]}${this.ELEMENT_ACCESS_WRAPPER_CLOSE}`;
}
const lastKey = keyStrs[keyStrs.length - 1];
const rhs = this.printNode(right, 0);
return `AddElementToObject(${acc}, ${lastKey}, ${rhs})`;
}
// ---------------------------------------------------------------
// Go-style setter for element-access compound assignments: a[b] += v
// ---------------------------------------------------------------
if (op === ts.SyntaxKind.PlusEqualsToken &&
left.kind === ts.SyntaxKind.ElementAccessExpression) {
// Collect base container and all keys (inner-most key is last).
const keys: any[] = [];
let baseExpr: any = null;
let cur: any = left;
while (ts.isElementAccessExpression(cur)) {
keys.unshift(cur.argumentExpression); // prepend
const expr = cur.expression;
if (!ts.isElementAccessExpression(expr)) {
baseExpr = expr;
break;
}
cur = expr;
}
const containerStr = this.printNode(baseExpr, 0);
const keyStrs = keys.map(k => this.printNode(k, 0));
// Build GetValue(GetValue( ... )) chain for all but the last key.
let acc = containerStr;
for (let i = 0; i < keyStrs.length - 1; i++) {
acc = `${this.ELEMENT_ACCESS_WRAPPER_OPEN}${acc}, ${keyStrs[i]}${this.ELEMENT_ACCESS_WRAPPER_CLOSE}`;
}
const lastKey = keyStrs[keyStrs.length - 1];
const rhs = this.printNode(right, 0);
// For +=, we need to get the current value, add to it, then set it back
const currentValue = `${this.ELEMENT_ACCESS_WRAPPER_OPEN}${acc}, ${lastKey}${this.ELEMENT_ACCESS_WRAPPER_CLOSE}`;
const result = `AddElementToObject(${acc}, ${lastKey}, Add(${currentValue}, ${rhs}))`;
return result;
}
if (left.kind === ts.SyntaxKind.TypeOfExpression) {
const typeOfExpression = this.handleTypeOfInsideBinaryExpression(node, identation);
if (typeOfExpression) {
return typeOfExpression;
}
}
if (op === ts.SyntaxKind.InKeyword) {
return `InOp(${this.printNode(right, 0)}, ${this.printNode(left, 0)})`;
}
const leftText = this.printNode(left, 0);
const rightText = this.printNode(right, 0);
if (op === ts.SyntaxKind.PlusEqualsToken) {
return `${leftText} = Add(${leftText}, ${rightText})`;
}
if (op === ts.SyntaxKind.MinusEqualsToken) {
return `${leftText} = Subtract(${leftText}, ${rightText})`;
}
if (op in this.binaryExpressionsWrappers) {
const wrapper = this.binaryExpressionsWrappers[op];
const open = wrapper[0];
const close = wrapper[1];
return `${open}${leftText}, ${rightText}${close}`;
}
// x = y
// cast y to x type when y is unknown
// if (op === ts.SyntaxKind.EqualsToken) {
// const leftType = global.checker.getTypeAtLocation(left);
// const rightType = global.checker.getTypeAtLocation(right);
// if (this.isAnyType(rightType.flags) && !this.isAnyType(leftType.flags)) {
// // const parsedType = this.getTypeFromRawType(leftType);
// return `${leftText} = ${rightText}`;
// }
// }
return undefined;
}
// castVariableAssignmentIfNeeded(left, right, identation) {
// const leftType = global.checker.getTypeAtLocation(left);
// const rightType = global.checker.getTypeAtLocation(right);
// const leftText = this.printNode(left, 0);
// const rightText = this.printNode(right, 0);
// if (this.isAnyType(rightType.flags) && !this.isAnyType(leftType.flags)) {
// const parsedType = this.getTypeFromRawType(leftType);
// return `${this.getIden(identation)}${leftText} = (${parsedType})${rightText}`;
// }
// return undefined;
// }
transformPropertyAcessExpressionIfNeeded(node) {
const expression = node.expression;
const leftSide = this.printNode(expression, 0);
const rightSide = node.name.escapedText;
let rawExpression = undefined;
switch(rightSide) {
case 'length':
const type = (global.checker as TypeChecker).getTypeAtLocation(expression); // eslint-disable-line
// this.warnIfAnyType(node, type.flags, leftSide, "length");
// rawExpression = this.isStringType(type.flags) ? `(string${leftSide}).Length` : `(${leftSide}.Cast<object>().ToList()).Count`;
rawExpression = this.isStringType(type.flags) ? `GetLength(${leftSide})` : `${this.ARRAY_LENGTH_WRAPPER_OPEN}${leftSide}${this.ARRAY_LENGTH_WRAPPER_CLOSE}`; // `(${leftSide}.Cast<object>()).ToList().Count`
break;
case 'push':
rawExpression = `((IList<object>)${leftSide}).Add`;
break;
// case 'push':
// rawExpression = `(List<object>${leftSide}).Add`s
// break;
}
return rawExpression;
}
printCustomDefaultValueIfNeeded(node) {
return undefined;
}
printFunctionBody(node, identation, wrapInChannel = false) {
// check if there is any default parameter to initialize
let functionBody: string;
const funcParams = node.parameters;
const initParams = [];
if (funcParams.length > 0) {
const body = node.body.statements;
const first = body.length > 0 ? body[0] : [];
const remaining = body.length > 0 ? body.slice(1): [];
let firstStatement = this.printNode(first, identation + 1);
const remainingString = remaining.map((statement) => this.printNode(statement, identation + 1)).join("\n");
let offSetIndex = 0;
funcParams.forEach((param, i) => {
const initializer = param.initializer;
if (initializer) {
const index = i + offSetIndex;
// index = index < 0 ? 0 : i - 1;
const paramName = this.printNode(param.name, 0);
initParams.push(`${paramName} := GetArg(optionalArgs, ${index}, ${this.printNode(initializer, 0)})`);
initParams.push(`_ = ${paramName}`);
} else {
offSetIndex--;
}
});
if (initParams.length > 0) {
const defaultInitializers = initParams.map( l => this.getIden(identation+1) + l ).join("\n") + "\n";
const bodyParts = firstStatement.split("\n");
const commentPart = bodyParts.filter(line => this.isComment(line));
const isComment = commentPart.length > 0;
if (isComment) {
const commentPartString = commentPart.map((c) => this.getIden(identation+1) + c.trim()).join("\n");
const firstStmNoComment = bodyParts.filter(line => !this.isComment(line)).join("\n");
firstStatement = commentPartString + "\n" + defaultInitializers + firstStmNoComment;
} else {
firstStatement = defaultInitializers + firstStatement;
}
}
const blockOpen = this.getBlockOpen(identation);
const blockClose = this.getBlockClose(identation);
firstStatement = remainingString.length > 0 ? firstStatement + "\n" : firstStatement;
if (!wrapInChannel) {
functionBody = blockOpen + firstStatement + remainingString + blockClose;
} else {
functionBody = firstStatement + remainingString;
}
} else {
if (!wrapInChannel) {
functionBody = super.printFunctionBody(node, identation);
} else {
functionBody = node.body.statements.map(statement => {
// if (statement.kind === ts.SyntaxKind.ReturnStatement) {
// if (statement?.expression) {
// return this.getIden(identation) + "ch <-" + this.printNode(statement.expression) + '\n' + this.getIden(identation) + "return " + this.printNode(statement.expression);
// }
// }
return this.printNode(statement, identation);
}).join("\n");
}
}
if (wrapInChannel) {
// return statement might be inside ifs or other complex statements so we still have to replace them manually :(
// functionBody = functionBody.replace(/(\s*)return\s+([^\n]+\n?)/g, '$1ch <- $2$1');
const functionBodySplit = functionBody.split("\n");
const bodyWithIndentationExtraAndNoReturn = functionBodySplit.map((line) => {
const trimmedLine = line.trim();
// should we do this inside printReturn statement?
// if(trimmedLine.startsWith("return") && trimmedLine !== "return") {
// const returnIndentation = line.indexOf("return")/4 + this.getIden(1);
// let channelReturn = this.getIden(returnIndentation) + "ch <-" + line.replace("return", "").trimStart();
// if (trimmedLine === "return nil") {
// channelReturn = this.getIden(returnIndentation) + "ch <- nil\n" + this.getIden(returnIndentation) + "return nil";
// // it's hard because we don't want to remove the treturns from the emulated try-catches we have that are also functions
// // with return statements
// }
// return channelReturn;
// }
return this.getIden(identation+2) + line;
}).join("\n");
let shouldAddLastReturn = true;
// const bodySplit = bodyWithIndentationExtraAndNoReturn.split("\n");
const bodySplit = functionBodySplit;
const lastLine = bodySplit[bodySplit.length - 1];
if (lastLine.trim().startsWith("return") || lastLine.trim().startsWith("panic")) {
shouldAddLastReturn = false;
}
// Check if the function body ends with a conditional that has returns in all branches
if (node.body && this.blockEndsWithConditionalReturn(node.body.statements)) {
shouldAddLastReturn = false;
}
const lastReturn = shouldAddLastReturn ? this.getIden(identation+2) + "return nil" : "";
// const hasCatchInside = bodySplit.indexOf("recover()") > -1;
// if ((1+1 == 2) || hasCatchInside) {
functionBody = `{
${this.getIden(identation + 1)}ch := make(chan ${this.DEFAULT_RETURN_TYPE})
${this.getIden(identation + 1)}go func() interface{} {
${this.getIden(identation + 2)}defer close(ch)
${this.getIden(identation + 2)}defer ReturnPanicError(ch)
${bodyWithIndentationExtraAndNoReturn}
${lastReturn}
${this.getIden(identation + 1)}}()
${this.getIden(identation + 1)}return ch
${this.getIden(identation)}}`;
// } else {
// functionBody = `{
// ${id1}ch := make(chan ${this.DEFAULT_RETURN_TYPE})
// ${id1}var panicError interface{} = nil
// ${id1}var wg sync.WaitGroup
// ${id1}wg.Add(1)
// ${id1}go func() interface{} {
// ${id2}defer wg.Done()
// ${id2}defer close(ch)
// ${id2}defer func() {
// ${id3}if r := recover(); r != nil {
// ${id4}panicError = r
// ${id4}return
// ${id3}}
// ${id2}}()
// ${bodySplit}
// ${lastReturn}
// ${id1}}()
// ${id1}wg.Wait()
// ${id1}if panicError != nil {
// ${id2}panic(panicError)
// ${id1}}
// ${id1}return ch
// ${id}}`;
// }
// to do fix this later
// we can't pass nil to the channel when we just want to
// return from the try catch, otherwise the channel will close with nil
// instead of the proper result
functionBody = functionBody.replaceAll(/(^\s*)ch\s<-\snil\s+return\snil(\s*\})/gm, "$1return nil$2");
}
return functionBody;
}
printAwaitExpression(node, identation) {
const expression = this.printNode(node.expression, identation);
if (expression.startsWith("<-")) {
return expression;
}
return `(<-${expression})`;
}
printInstanceOfExpression(node: BinaryExpression, identation: number): string {
const left = this.printNode (node.left);
const right = this.printNode (node.right);
return this.getIden(identation) + `IsInstance(${left}, ${right})`;
}
getRandomNameSuffix() {
return Math.floor(Math.random() * 1000000).toString();
}
getLineBasedSuffix(node): string {
const { line, character } = global.src.getLineAndCharacterOfPosition(node.getStart());
return `${line}${character}`;
}
printExpressionStatement(node, identation) {
if (node?.expression?.kind === ts.SyntaxKind.AsExpression) {
node = node.expression;
}
if (node.expression.kind !== ts.SyntaxKind.AwaitExpression) {
return super.printExpressionStatement(node, identation);
}
const exprStm = this.printNode(node.expression, identation);
// const { line, character } = global.src.getLineAndCharacterOfPosition(node.getStart());
// console.log(`line: ${line}, character: ${character}`);
const returnRandName = "retRes" + this.getLineBasedSuffix(node);
// const expStatement =this.getIden(identation) + exprStm + this.LINE_TERMINATOR;
const expStatement = `
${this.getIden(identation)}${returnRandName} := ${exprStm}
${this.getIden(identation)}PanicOnError(${returnRandName})`;
return this.printNodeCommentsIfAny(node, identation, expStatement);
}
isInsideAsyncFunction(returnStatementNode) {
let currentNode = returnStatementNode;
while (currentNode) {
// Check if the current node is a function or method
if (ts.isFunctionDeclaration(currentNode) ||
ts.isFunctionExpression(currentNode) ||
ts.isArrowFunction(currentNode) ||
ts.isMethodDeclaration(currentNode)) {
return currentNode.modifiers && currentNode.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword);
}
// Move up the tree to the parent node
currentNode = currentNode.parent;
}
// Return false if no async function or method is found
return false;
}
printReturnStatement(node, identation) {
const isAsyncFunction = this.isInsideAsyncFunction(node);
// if (node?.expression?.kind !== ts.S