UNPKG

ast-transpiler

Version:
1,165 lines (995 loc) 79.3 kB
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