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
JavaScript
;
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