UNPKG

roblox-ts

Version:

<div align="center"><img width=25% src="https://i.imgur.com/yCjHmng.png"></div> <h1 align="center"><a href="https://roblox-ts.github.io/">roblox-ts</a></h1> <div align="center">A TypeScript-to-Lua Compiler for Roblox</div> <br> <div align="center"> <a hr

858 lines 38.4 kB
"use strict"; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const ts = __importStar(require("ts-morph")); const _1 = require("."); const CompilerError_1 = require("../errors/CompilerError"); const typeUtilities_1 = require("../typeUtilities"); const utility_1 = require("../utility"); const roact_1 = require("./roact"); const STRING_MACRO_METHODS = ["format", "gmatch", "gsub", "lower", "rep", "reverse", "upper"]; function shouldWrapExpression(subExp, strict) { subExp = utility_1.skipNodesDownwards(subExp); return (!ts.TypeGuards.isIdentifier(subExp) && !ts.TypeGuards.isThisExpression(subExp) && !ts.TypeGuards.isSuperExpression(subExp) && !ts.TypeGuards.isElementAccessExpression(subExp) && (strict || (!ts.TypeGuards.isCallExpression(subExp) && !ts.TypeGuards.isPropertyAccessExpression(subExp) && !ts.TypeGuards.isStringLiteral(subExp) && !ts.TypeGuards.isNewExpression(subExp) && !ts.TypeGuards.isClassExpression(subExp) && !ts.TypeGuards.isNumericLiteral(subExp)))); } exports.shouldWrapExpression = shouldWrapExpression; function getLeftHandSideParent(subExp, climb = 3) { let exp = utility_1.skipNodesUpwards(subExp); for (let i = 0; i < climb; i++) { exp = utility_1.skipNodesUpwards(exp.getParent()); } return exp; } function getPropertyCallParentIsExpressionStatement(subExp) { return ts.TypeGuards.isExpressionStatement(getLeftHandSideParent(subExp)); } function wrapExpFunc(replacer) { return (state, params) => replacer(compileCallArgumentsAndSeparateAndJoinWrapped(state, params, true)[0]); } function compileCallArgumentsAndSeparateAndJoin(state, params) { const [accessPath, ...compiledArgs] = compileCallArguments(state, params); return [accessPath, compiledArgs.join(", ")]; } function compileCallArgumentsAndSeparateWrapped(state, params, strict = false, compile = _1.compileExpression) { const [accessPath, ...compiledArgs] = compileCallArguments(state, params, undefined, compile); // If we compile to a method call, we might need to wrap in parenthesis // We are going to wrap in parenthesis just to be safe, // unless it's a CallExpression, Identifier, ElementAccessExpression, or PropertyAccessExpression const [subExp] = params; let accessStr; if (!state.getCurrentPrecedingStatementContext(subExp).isPushed && !accessPath.match(/^\(*_\d+\)*$/) && shouldWrapExpression(subExp, strict)) { accessStr = `(${accessPath})`; } else { accessStr = accessPath; } return [accessStr, compiledArgs]; } function compileCallArgumentsAndSeparateAndJoinWrapped(state, params, strict = false) { const [accessStr, compiledArgs] = compileCallArgumentsAndSeparateWrapped(state, params, strict); return [accessStr, compiledArgs.join(", ")]; } function addOneToStringIndex(valueStr) { if (valueStr === "nil") { return "nil"; } if (valueStr.indexOf("e") === -1 && valueStr.indexOf("E") === -1) { const valueNumber = Number(valueStr); if (!Number.isNaN(valueNumber)) { return (valueNumber < 0 ? valueNumber : valueNumber + 1).toString(); } } return valueStr + " + 1"; } exports.addOneToStringIndex = addOneToStringIndex; function macroStringIndexFunction(methodName, incrementedArgs, decrementedArgs = []) { return (state, params) => { let i = -1; let wasIncrementing; const [accessPath, compiledArgs] = compileCallArgumentsAndSeparateWrapped(state, params, true, (subState, param) => { const previousParam = params[i++]; let incrementing; if (incrementedArgs.indexOf(i) !== -1) { incrementing = true; } else if (decrementedArgs.indexOf(i) !== -1) { incrementing = false; } if (previousParam && incrementing === wasIncrementing && ts.TypeGuards.isIdentifier(param) && ts.TypeGuards.isIdentifier(previousParam)) { const definitions = param.getDefinitions().map(def => def.getNode()); if (previousParam.getDefinitions().every((def, j) => definitions[j] === def.getNode())) { wasIncrementing = incrementing; return ""; } } wasIncrementing = incrementing; const expStr = _1.compileExpression(subState, param); if (incrementing === undefined) { return expStr; } if (expStr === "nil") { return "nil"; } if (expStr.indexOf("e") === -1 && expStr.indexOf("E") === -1) { const valueNumber = Number(expStr); if (!Number.isNaN(valueNumber)) { if (incrementing) { return (valueNumber >= 0 ? valueNumber + 1 : valueNumber).toString(); } else { return (valueNumber < 0 ? valueNumber - 1 : valueNumber).toString(); } } } const currentContext = state.getCurrentPrecedingStatementContext(param); const id = currentContext.isPushed ? expStr : state.pushPrecedingStatementToNewId(param, expStr); const isNullable = typeUtilities_1.isNullableType(typeUtilities_1.getType(param)); if (incrementing) { currentContext.push(state.indent + `if ${isNullable ? `${id} and ` : ""}${id} >= 0 then ${id} = ${id} + 1; end\n`); } else { currentContext.push(state.indent + `if ${isNullable ? `${id} and ` : ""}${id} < 0 then ${id} = ${id} - 1; end\n`); } return id; }); return `${accessPath}:${methodName}(${compiledArgs .map((arg, j, args) => (arg === "" ? args[j - 1] : arg)) .join(", ")})`; }; } const findMacro = macroStringIndexFunction("find", [2]); function padAmbiguous(state, params) { const [strParam, maxLengthParam, fillStringParam] = params; let str; let maxLength; let fillString; [str, maxLength, fillString] = compileCallArguments(state, params); if (!ts.TypeGuards.isStringLiteral(strParam) && (!ts.TypeGuards.isIdentifier(strParam) || _1.isIdentifierDefinedInExportLet(strParam))) { str = state.pushPrecedingStatementToNewId(strParam, str); } let fillStringLength; if (fillStringParam === undefined) { fillString = `(" ")`; fillStringLength = "1"; } else { if (typeUtilities_1.isNullableType(typeUtilities_1.getType(fillStringParam))) { fillString = `(${fillString} or " ")`; } else if (!fillString.match(/^\(*_\d+\)*$/) && shouldWrapExpression(fillStringParam, true)) { fillString = `(${fillString})`; } if (ts.TypeGuards.isStringLiteral(fillStringParam)) { fillStringLength = `${fillStringParam.getLiteralText().length}`; } else { fillStringLength = `#${fillString}`; } } let targetLength; let repititions; let rawRepititions; if (ts.TypeGuards.isStringLiteral(strParam)) { if (maxLengthParam && ts.TypeGuards.isNumericLiteral(maxLengthParam)) { const literalTargetLength = maxLengthParam.getLiteralValue() - strParam.getLiteralText().length; if (fillStringParam === undefined || ts.TypeGuards.isStringLiteral(fillStringParam)) { rawRepititions = literalTargetLength / (fillStringParam ? fillStringParam.getLiteralText().length : 1); repititions = `${Math.ceil(rawRepititions)}`; } targetLength = `${literalTargetLength}`; } else { targetLength = `${maxLength} - ${strParam.getLiteralText().length}`; if (fillStringLength !== "1") { targetLength = state.pushPrecedingStatementToNewId(maxLengthParam, `${targetLength}`); } } } else { targetLength = `${maxLength} - #${str}`; if (fillStringLength !== "1") { targetLength = state.pushPrecedingStatementToNewId(maxLengthParam, `${targetLength}`); } } const doNotTrim = (rawRepititions !== undefined && rawRepititions === Math.ceil(rawRepititions)) || fillStringLength === "1"; return [ `${fillString}:rep(${repititions || (fillStringLength === "1" ? targetLength : `math.ceil(${targetLength} / ${fillStringLength ? fillStringLength : 1})`)})${doNotTrim ? "" : `:sub(1, ${targetLength})`}`, str, ]; } const STRING_REPLACE_METHODS = new Map([ [ "size", (state, params) => { return _1.appendDeclarationIfMissing(state, getLeftHandSideParent(params[0]), `#${compileCallArgumentsAndSeparateAndJoinWrapped(state, params)[0]}`); }, ], ["trim", wrapExpFunc(accessPath => `${accessPath}:match("^%s*(.-)%s*$")`)], ["trimLeft", wrapExpFunc(accessPath => `${accessPath}:match("^%s*(.-)$")`)], ["trimRight", wrapExpFunc(accessPath => `${accessPath}:match("^(.-)%s*$")`)], [ "split", (state, params) => { const [str, args] = compileCallArgumentsAndSeparateAndJoinWrapped(state, params, true); return `string.split(${str}, ${args})`; }, ], ["slice", macroStringIndexFunction("sub", [1], [2])], ["sub", macroStringIndexFunction("sub", [1, 2])], ["byte", macroStringIndexFunction("byte", [1, 2])], [ "find", (state, params) => { state.usesTSLibrary = true; return `TS.string_find_wrap(${findMacro(state, params)})`; }, ], ["match", macroStringIndexFunction("match", [2])], [ "padStart", (state, params) => _1.appendDeclarationIfMissing(state, getLeftHandSideParent(params[0]), padAmbiguous(state, params).join(" .. ")), ], [ "padEnd", (state, params) => { const [a, b] = padAmbiguous(state, params); return _1.appendDeclarationIfMissing(state, getLeftHandSideParent(params[0]), [b, a].join(" .. ")); }, ], ]); STRING_REPLACE_METHODS.set("trimStart", STRING_REPLACE_METHODS.get("trimLeft")); STRING_REPLACE_METHODS.set("trimEnd", STRING_REPLACE_METHODS.get("trimRight")); const isMapOrSetOrArrayEmpty = (state, params) => _1.appendDeclarationIfMissing(state, getLeftHandSideParent(params[0]), `(next(${_1.compileExpression(state, params[0])}) == nil)`); const ARRAY_REPLACE_METHODS = new Map([ [ "size", (state, params) => { return _1.appendDeclarationIfMissing(state, getLeftHandSideParent(params[0]), `#${compileCallArgumentsAndSeparateAndJoinWrapped(state, params)[0]}`); }, ], [ "pop", (state, params) => { const [subExp] = params; const accessPath = _1.getReadableExpressionName(state, subExp); if (getPropertyCallParentIsExpressionStatement(subExp)) { return `${accessPath}[#${accessPath}] = nil`; } else { const node = getLeftHandSideParent(subExp, 2); let id; const len = state.pushPrecedingStatementToReuseableId(subExp, `#${accessPath}`); const place = `${accessPath}[${len}]`; const nullSet = state.indent + `${place} = nil; -- ${subExp.getText()}.pop\n`; id = state.pushToDeclarationOrNewId(node, place); const context = state.getCurrentPrecedingStatementContext(subExp); const { isPushed } = context; state.pushPrecedingStatements(subExp, nullSet); context.isPushed = isPushed; return id; } }, ], [ "unorderedRemove", (state, params) => { const [subExp] = params; const node = getLeftHandSideParent(subExp, 2); const accessPath = _1.getReadableExpressionName(state, subExp); let id; const len = state.pushPrecedingStatementToReuseableId(subExp, `#${accessPath}`); const lastPlace = `${accessPath}[${len}]`; const isStatement = getPropertyCallParentIsExpressionStatement(subExp); let removingIndex = _1.addOneToArrayIndex(compileCallArguments(state, params.slice(1))[0]); if (!isStatement && !typeUtilities_1.isConstantExpression(params[1], 0)) { removingIndex = state.pushPrecedingStatementToNewId(subExp, removingIndex); } const removingPlace = `${accessPath}[${removingIndex}]`; if (!isStatement) { id = state.pushToDeclarationOrNewId(node, removingPlace); } const context = state.getCurrentPrecedingStatementContext(subExp); const { isPushed } = context; state.pushPrecedingStatements(subExp, state.indent + `${removingPlace} = ${lastPlace}; -- ${subExp.getText()}.unorderedRemove\n`); const nullSet = state.indent + `${lastPlace} = nil`; if (!isStatement) { state.pushPrecedingStatements(subExp, nullSet + ";\n"); } context.isPushed = isPushed; return isStatement ? nullSet : id; }, ], ["shift", (state, params) => `table.remove(${_1.compileExpression(state, params[0])}, 1)`], [ "join", (state, params) => { const subType = typeUtilities_1.getType(params[0]); const arrayType = subType.getArrayElementType(); if ((arrayType ? arrayType.isUnion() ? arrayType.getUnionTypes() : [arrayType] : subType.getTupleElements()).every(validType => validType.isNumber() || validType.isString())) { const argStrs = compileCallArguments(state, params); return `table.concat(${argStrs[0]}, ${params[1] ? argStrs[1] : `","`})`; } }, ], [ "push", (state, params) => { const [subExp] = params; const isStatement = getPropertyCallParentIsExpressionStatement(subExp); const node = getLeftHandSideParent(subExp, 2); const { length: numParams } = params; if (params.some(param => ts.TypeGuards.isSpreadElement(param))) { state.usesTSLibrary = true; let arrayStr = _1.compileExpression(state, subExp); state.enterPrecedingStatementContext(); const listStr = _1.compileSpreadableListAndJoin(state, params.slice(1), false); const context = state.exitPrecedingStatementContext(); if (context.length > 0) { arrayStr = state.pushPrecedingStatementToNewId(subExp, arrayStr); state.pushPrecedingStatements(subExp, ...context); } return `TS.array_push_apply(${arrayStr}, ${listStr})`; } else { const accessPath = _1.getReadableExpressionName(state, subExp); if (numParams <= 2) { const firstParam = params[1]; let accessor = `#${accessPath}${firstParam ? " + 1" : ""}`; if (isStatement) { return firstParam ? `${accessPath}[${accessor}] = ${_1.compileExpression(state, firstParam)}` : `local _ = ${accessor}`; } else { accessor = state.pushToDeclarationOrNewId(node, accessor); if (firstParam) { state.pushPrecedingStatements(subExp, state.indent + `${accessPath}[${accessor}] = ${_1.compileExpression(state, firstParam)};\n`); } return accessor; } } else { state.usesTSLibrary = true; return `TS.array_push_stack(${accessPath}, ${compileList(state, params.slice(1)).join(", ")})`; } } }, ], [ "unshift", (state, params) => { const length = params.length; const [subExp] = params; if (params.some(param => ts.TypeGuards.isSpreadElement(param))) { return undefined; } let accessPath; let paramStr; [accessPath, paramStr] = compileCallArgumentsAndSeparateAndJoin(state, params); const isStatement = getPropertyCallParentIsExpressionStatement(subExp); if (length === 1) { const result = `#${accessPath}`; return isStatement ? `local _ = ${result}` : result; } else if (length === 2) { if (isStatement) { const expStr = `table.insert(${accessPath}, 1, ${paramStr})`; return expStr; } else { accessPath = _1.getReadableExpressionName(state, subExp, accessPath); state.pushPrecedingStatements(subExp, state.indent + `table.insert(${accessPath}, 1, ${paramStr});\n`); return `#${accessPath}`; } } }, ], [ "insert", (state, params) => { const [accessPath, indexParamStr, valueParamStr] = compileCallArguments(state, params); return `table.insert(${accessPath}, ${_1.addOneToArrayIndex(indexParamStr)}, ${valueParamStr})`; }, ], [ "remove", (state, params) => { const [accessPath, indexParamStr] = compileCallArguments(state, params); return `table.remove(${accessPath}, ${_1.addOneToArrayIndex(indexParamStr)})`; }, ], ["isEmpty", isMapOrSetOrArrayEmpty], ]); function getPropertyAccessExpressionRoot(root) { while (ts.TypeGuards.isCallExpression(root)) { const exp = root.getExpression(); if (!ts.TypeGuards.isPropertyAccessExpression(exp)) { break; } root = exp.getExpression(); } return root; } function setKeyOfMapOrSet(kind) { const func = (state, params) => { const [subExp] = params; const wasPushed = state.getCurrentPrecedingStatementContext(subExp).isPushed; const root = getPropertyAccessExpressionRoot(subExp); let accessStr; let key; let value; [accessStr, key, value] = compileCallArguments(state, params); const accessPath = _1.getReadableExpressionName(state, root, accessStr); if (kind === "map" && ts.TypeGuards.isSpreadElement(params[1])) { const newKey = state.getNewId(); value = state.getNewId(); state.pushPrecedingStatements(subExp, state.indent + `local ${newKey}, ${value} = ${key};\n`); key = newKey; } const expStr = `${accessPath}[${key}] = ${value || "true"}`; if (getPropertyCallParentIsExpressionStatement(subExp)) { return expStr; } else { const isPushed = wasPushed || ts.TypeGuards.isNewExpression(root) || (ts.TypeGuards.isIdentifier(root) && _1.isIdentifierDefinedInConst(root)); state.pushPrecedingStatements(subExp, state.indent + expStr + `;\n`); state.getCurrentPrecedingStatementContext(subExp).isPushed = isPushed; return accessPath; } }; return func; } const hasKeyOfMapOrSet = (state, params) => { const [accessPath, key] = compileCallArgumentsAndSeparateAndJoinWrapped(state, params); return _1.appendDeclarationIfMissing(state, getLeftHandSideParent(params[0]), `(${accessPath}[${key}] ~= nil)`); }; const deleteKeyOfMapOrSet = (state, params) => { const [accessPath, key] = compileCallArguments(state, params); const expStr = `${accessPath}[${key}] = nil`; const [subExp] = params; if (getPropertyCallParentIsExpressionStatement(subExp)) { return expStr; } else { const node = getLeftHandSideParent(subExp, 2); const id = state.pushToDeclarationOrNewId(node, `${accessPath}[${key}] ~= nil`); state.pushPrecedingStatements(subExp, state.indent + expStr + `;\n`); state.getCurrentPrecedingStatementContext(subExp).isPushed = true; return id; } }; const MAP_REPLACE_METHODS = new Map([ ["set", setKeyOfMapOrSet("map")], ["delete", deleteKeyOfMapOrSet], ["has", hasKeyOfMapOrSet], ["isEmpty", isMapOrSetOrArrayEmpty], [ "get", (state, params) => { const [accessPath, key] = compileCallArgumentsAndSeparateAndJoinWrapped(state, params); return _1.appendDeclarationIfMissing(state, getLeftHandSideParent(params[0]), `${accessPath}[${key}]`); }, ], ]); const SET_REPLACE_METHODS = new Map([ ["add", setKeyOfMapOrSet("set")], ["delete", deleteKeyOfMapOrSet], ["has", hasKeyOfMapOrSet], ["isEmpty", isMapOrSetOrArrayEmpty], ]); const OBJECT_REPLACE_METHODS = new Map().set("isEmpty", (state, params) => _1.appendDeclarationIfMissing(state, getLeftHandSideParent(params[0]), `(next(${compileCallArguments(state, params)[1]}) == nil)`)); const RBX_MATH_CLASSES = ["CFrame", "UDim", "UDim2", "Vector2", "Vector2int16", "Vector3", "Vector3int16"]; function makeGlobalExpressionMacro(compose) { return (state, params) => { const [subExp] = params; let [obj, type] = compileCallArguments(state, params); if (ts.TypeGuards.isSpreadElement(subExp)) { const id = state.getNewId(); type = state.getNewId(); state.pushPrecedingStatements(subExp, state.indent + `local ${id}, ${type} = ${obj};\n`); obj = id; } const compiledStr = compose(obj, type); return _1.appendDeclarationIfMissing(state, getLeftHandSideParent(subExp, 2), `(${compiledStr})`); }; } const GLOBAL_REPLACE_METHODS = new Map([ ["typeIs", makeGlobalExpressionMacro((obj, type) => `typeof(${obj}) == ${type}`)], ["classIs", makeGlobalExpressionMacro((obj, className) => `${obj}.ClassName == ${className}`)], ]); function compileList(state, args, compile = _1.compileExpression) { const argStrs = new Array(); let lastContextualIndex; const cached = new Array(); for (let i = 0; i < args.length; i++) { const arg = args[i]; if (!ts.TypeGuards.isSpreadElement(arg)) { _1.checkNonAny(arg); } state.enterPrecedingStatementContext(); argStrs[i] = compile(state, arg); const currentContext = state.exitPrecedingStatementContext(); if (currentContext.length > 0) { lastContextualIndex = i; cached[i] = currentContext; } } if (lastContextualIndex !== undefined) { for (let i = 0; i < lastContextualIndex; i++) { const arg = args[i]; const argStr = argStrs[i]; const cachedStrs = cached[i]; if (cachedStrs) { state.pushPrecedingStatements(arg, ...cachedStrs); } if (typeUtilities_1.shouldPushToPrecedingStatement(arg, argStr, cachedStrs || [])) { argStrs[i] = state.pushPrecedingStatementToReuseableId(arg, argStr, cached[i + 1]); } } state.pushPrecedingStatements(args[lastContextualIndex], ...cached[lastContextualIndex]); } return argStrs; } exports.compileList = compileList; function compileCallArguments(state, args, extraParameter, compile = _1.compileExpression) { let argStrs; if (_1.shouldCompileAsSpreadableList(args)) { argStrs = [`unpack(${_1.compileSpreadableListAndJoin(state, args, true, compile)})`]; } else { argStrs = compileList(state, args, compile); } if (extraParameter) { argStrs.unshift(extraParameter); } return argStrs; } exports.compileCallArguments = compileCallArguments; function compileCallArgumentsAndJoin(state, args, extraParameter) { return compileCallArguments(state, args, extraParameter).join(", "); } exports.compileCallArgumentsAndJoin = compileCallArgumentsAndJoin; function checkNonImportExpression(exp) { if (ts.TypeGuards.isImportExpression(exp)) { throw new CompilerError_1.CompilerError("Dynamic import expressions are not supported! Use 'require()' instead and assert the type.", exp, CompilerError_1.CompilerErrorType.NoDynamicImport); } return exp; } function compileCallExpression(state, node, doNotWrapTupleReturn = !typeUtilities_1.isTupleReturnTypeCall(node)) { const exp = utility_1.skipNodesDownwards(_1.checkNonAny(checkNonImportExpression(node.getExpression()))); let result; if (ts.TypeGuards.isPropertyAccessExpression(exp)) { result = compilePropertyCallExpression(state, node, exp); } else if (ts.TypeGuards.isElementAccessExpression(exp)) { result = compileElementAccessCallExpression(state, node, exp); } else { const params = node.getArguments().map(arg => utility_1.skipNodesDownwards(arg)); if (ts.TypeGuards.isSuperExpression(exp)) { if (typeUtilities_1.superExpressionClassInheritsFromArray(exp, false)) { if (params.length > 0) { throw new CompilerError_1.CompilerError("Cannot call super() with arguments when extending from Array", exp, CompilerError_1.CompilerErrorType.SuperArrayCall); } return ts.TypeGuards.isExpressionStatement(utility_1.skipNodesUpwards(node.getParent())) ? "" : "nil"; } else if (roact_1.inheritsFromRoact(exp.getType())) { return ""; } else { return `super.constructor(${compileCallArgumentsAndJoin(state, params, "self")})`; } } const isSubstitutableMethod = GLOBAL_REPLACE_METHODS.get(exp.getText()); if (isSubstitutableMethod) { const str = isSubstitutableMethod(state, params); if (str) { return str; } } if (ts.TypeGuards.isIdentifier(exp)) { for (const def of exp.getDefinitions()) { const definitionParent = utility_1.skipNodesUpwards(utility_1.skipNodesUpwards(def.getNode()).getParent()); if (definitionParent && ts.TypeGuards.isFunctionExpression(definitionParent) && _1.isFunctionExpressionMethod(definitionParent)) { const alternative = "this." + utility_1.skipNodesUpwards(definitionParent.getParent()).getName(); throw new CompilerError_1.CompilerError(`Cannot call local function expression \`${exp.getText()}\` (this is a foot-gun). Prefer \`${alternative}\``, exp, CompilerError_1.CompilerErrorType.BadFunctionExpressionMethodCall); } } } let callPath = _1.compileExpression(state, exp); if (ts.TypeGuards.isBinaryExpression(exp) || ts.TypeGuards.isArrowFunction(exp) || (ts.TypeGuards.isFunctionExpression(exp) && !exp.getNameNode())) { callPath = `(${callPath})`; } result = `${callPath}(${compileCallArgumentsAndJoin(state, params)})`; } if (!doNotWrapTupleReturn) { result = `{ ${result} }`; } return result; } exports.compileCallExpression = compileCallExpression; function compilePropertyMethod(state, property, params, className, replaceMethods) { const isSubstitutableMethod = replaceMethods.get(property); if (isSubstitutableMethod) { const str = isSubstitutableMethod(state, params); if (str) { return str; } } if (className === "Object") { params = params.slice(1); } state.usesTSLibrary = true; return `TS.${className}_${property}(${compileCallArgumentsAndJoin(state, params)})`; } function getPropertyAccessExpressionType(state, expression) { _1.checkApiAccess(state, expression.getNameNode()); const expType = typeUtilities_1.getType(expression); const property = expression.getName(); if (typeUtilities_1.isArrayMethodType(expType)) { return 0 /* Array */; } if (typeUtilities_1.isStringMethodType(expType)) { if (STRING_MACRO_METHODS.indexOf(property) !== -1) { return 1 /* BuiltInStringMethod */; } return 2 /* String */; } if (typeUtilities_1.isSetMethodType(expType)) { return 6 /* Set */; } if (typeUtilities_1.isMapMethodType(expType)) { return 5 /* Map */; } const subExp = utility_1.skipNodesDownwards(expression.getExpression()); const subExpType = typeUtilities_1.getType(subExp); const subExpTypeSym = subExpType.getSymbol(); if (subExpTypeSym && ts.TypeGuards.isPropertyAccessExpression(expression)) { const subExpTypeName = subExpTypeSym.getEscapedName(); // custom promises if (subExpTypeName === "Promise") { if (property === "then") { return 3 /* PromiseThen */; } } // for is a reserved word in Lua if (subExpTypeName === "SymbolConstructor") { if (property === "for") { return 4 /* SymbolFor */; } } if (subExpTypeName === "ObjectConstructor") { return 7 /* ObjectConstructor */; } // custom math if (RBX_MATH_CLASSES.indexOf(subExpTypeName) !== -1) { switch (property) { case "add": return 8 /* RbxMathAdd */; case "sub": return 9 /* RbxMathSub */; case "mul": return 10 /* RbxMathMul */; case "div": return 11 /* RbxMathDiv */; } } } return -1 /* None */; } exports.getPropertyAccessExpressionType = getPropertyAccessExpressionType; function getSymbolOrThrow(node, t) { const symbol = t.getSymbol(); if (symbol === undefined) { throw new CompilerError_1.CompilerError(`Attempt to call non-method \`${node.getText()}\``, node, CompilerError_1.CompilerErrorType.BadMethodCall); } return symbol; } function getMethodCallBacksInfo(node) { const type = typeUtilities_1.getType(node).getNonNullableType(); const allMethods = typeUtilities_1.typeConstraint(type, t => getSymbolOrThrow(node, t) .getDeclarations() .every(dec => { if (ts.TypeGuards.isParameteredNode(dec)) { const thisParam = dec.getParameter("this"); if (thisParam) { const structure = thisParam.getStructure(); if (structure.type === "void") { return false; } else { return true; } } } if (_1.isMethodDeclaration(dec) || ts.TypeGuards.isMethodSignature(dec)) { return true; } return false; })); const allCallbacks = typeUtilities_1.typeConstraint(type, t => getSymbolOrThrow(node, t) .getDeclarations() .every(dec => { if (ts.TypeGuards.isParameteredNode(dec)) { const thisParam = dec.getParameter("this"); if (thisParam) { const structure = thisParam.getStructure(); if (structure.type === "void") { return true; } else { return false; } } } if (ts.TypeGuards.isFunctionTypeNode(dec) || ts.TypeGuards.isPropertySignature(dec) || (ts.TypeGuards.isFunctionExpression(dec) && !_1.isFunctionExpressionMethod(dec)) || ts.TypeGuards.isArrowFunction(dec) || ts.TypeGuards.isFunctionDeclaration(dec)) { return true; } return false; })); return [allMethods, allCallbacks]; } function compileElementAccessCallExpression(state, node, expression) { const expExp = utility_1.skipNodesDownwards(expression.getExpression()); const accessor = ts.TypeGuards.isSuperExpression(expExp) ? "super" : _1.getReadableExpressionName(state, expExp); const accessedPath = _1.compileElementAccessDataTypeExpression(state, expression, accessor)(_1.compileElementAccessBracketExpression(state, expression)); const params = node.getArguments().map(arg => utility_1.skipNodesDownwards(arg)); const [allMethods, allCallbacks] = getMethodCallBacksInfo(expression); let paramsStr = compileCallArgumentsAndJoin(state, params); if (allMethods && !allCallbacks) { paramsStr = paramsStr ? `${accessor}, ` + paramsStr : accessor; } else if (!allMethods && allCallbacks) { if (ts.TypeGuards.isSuperExpression(expExp)) { throw new CompilerError_1.CompilerError(`\`${accessedPath}\` is not a real method! Prefer \`this${accessedPath.slice(5)}\` instead.`, expExp, CompilerError_1.CompilerErrorType.BadSuperCall); } } else { // mixed methods and callbacks throw new CompilerError_1.CompilerError("Attempted to call a function with mixed types! All definitions must either be a method or a callback.", node, CompilerError_1.CompilerErrorType.MixedMethodCall); } return `${accessedPath}(${paramsStr})`; } exports.compileElementAccessCallExpression = compileElementAccessCallExpression; function compilePropertyCallExpression(state, node, expression) { _1.checkApiAccess(state, expression.getNameNode()); const property = expression.getName(); const params = [ utility_1.skipNodesDownwards(expression.getExpression()), ...node.getArguments().map(arg => utility_1.skipNodesDownwards(arg)), ]; switch (getPropertyAccessExpressionType(state, expression)) { case 0 /* Array */: { return compilePropertyMethod(state, property, params, "array", ARRAY_REPLACE_METHODS); } case 2 /* String */: { return compilePropertyMethod(state, property, params, "string", STRING_REPLACE_METHODS); } case 5 /* Map */: { return compilePropertyMethod(state, property, params, "map", MAP_REPLACE_METHODS); } case 6 /* Set */: { return compilePropertyMethod(state, property, params, "set", SET_REPLACE_METHODS); } case 7 /* ObjectConstructor */: { return compilePropertyMethod(state, property, params, "Object", OBJECT_REPLACE_METHODS); } case 1 /* BuiltInStringMethod */: { const [accessPath, compiledArgs] = compileCallArgumentsAndSeparateAndJoinWrapped(state, params, true); return `${accessPath}:${property}(${compiledArgs})`; } case 3 /* PromiseThen */: { const [accessPath, compiledArgs] = compileCallArgumentsAndSeparateAndJoin(state, params); return `${accessPath}:andThen(${compiledArgs})`; } case 4 /* SymbolFor */: { const [accessPath, compiledArgs] = compileCallArgumentsAndSeparateAndJoin(state, params); return `${accessPath}.getFor(${compiledArgs})`; } case 8 /* RbxMathAdd */: { const argStrs = compileCallArguments(state, params); return _1.appendDeclarationIfMissing(state, utility_1.skipNodesUpwards(node.getParent()), `(${argStrs[0]} + ${argStrs[1]})`); } case 9 /* RbxMathSub */: { const argStrs = compileCallArguments(state, params); return _1.appendDeclarationIfMissing(state, utility_1.skipNodesUpwards(node.getParent()), `(${argStrs[0]} - ${argStrs[1]})`); } case 10 /* RbxMathMul */: { const argStrs = compileCallArguments(state, params); return _1.appendDeclarationIfMissing(state, utility_1.skipNodesUpwards(node.getParent()), `(${argStrs[0]} * ${argStrs[1]})`); } case 11 /* RbxMathDiv */: { const argStrs = compileCallArguments(state, params); return _1.appendDeclarationIfMissing(state, utility_1.skipNodesUpwards(node.getParent()), `(${argStrs[0]} / ${argStrs[1]})`); } } const [allMethods, allCallbacks] = getMethodCallBacksInfo(expression); let accessedPath; let paramsStr; [accessedPath, paramsStr] = compileCallArgumentsAndSeparateAndJoin(state, params); let sep; const [subExp] = params; if (allMethods && !allCallbacks) { if (ts.TypeGuards.isSuperExpression(subExp)) { accessedPath = "super"; paramsStr = paramsStr ? "self, " + paramsStr : "self"; sep = "."; } else { sep = ":"; } } else if (!allMethods && allCallbacks) { sep = "."; if (ts.TypeGuards.isSuperExpression(subExp)) { throw new CompilerError_1.CompilerError(`\`super.${property}\` is not a real method! Prefer \`this.${property}\` instead.`, subExp, CompilerError_1.CompilerErrorType.BadSuperCall); } } else { // mixed methods and callbacks throw new CompilerError_1.CompilerError("Attempted to call a function with mixed types! All definitions must either be a method or a callback.", node, CompilerError_1.CompilerErrorType.MixedMethodCall); } if (shouldWrapExpression(subExp, false)) { accessedPath = `(${accessedPath})`; } return `${accessedPath}${sep}${property}(${paramsStr})`; } exports.compilePropertyCallExpression = compilePropertyCallExpression; //# sourceMappingURL=call.js.map