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
342 lines • 16.6 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");
function compileParamDefault(state, exp, name) {
const initializer = utility_1.skipNodesDownwards(exp);
state.enterPrecedingStatementContext();
state.declarationContext.set(initializer, {
isIdentifier: ts.TypeGuards.isIdentifier(initializer) && !_1.isIdentifierDefinedInExportLet(initializer),
set: name,
});
const expStr = _1.compileExpression(state, initializer);
const context = state.exitPrecedingStatementContext();
state.pushIndent();
const declaration = state.declarationContext.delete(initializer) ? `${name} = ${expStr};` : "";
let newline;
let indentation;
let tab;
let contextLines;
if (context.length > (declaration ? 0 : 1)) {
newline = "\n";
indentation = state.indent;
tab = "\t";
contextLines = utility_1.joinIndentedLines(context, 2);
}
else {
newline = " ";
indentation = "";
tab = "";
contextLines = utility_1.joinIndentedLines(context, 0).replace(/\n/g, " ");
}
state.popIndent();
return "if ".concat(name, " == nil then", newline, contextLines, indentation, declaration ? tab + `${declaration}` + newline + indentation : "", "end;");
}
function getParameterData(state, paramNames, initializers, node, defaults) {
for (const param of node.getParameters()) {
const child = param.getFirstChild(exp => ts.TypeGuards.isIdentifier(exp) ||
ts.TypeGuards.isArrayBindingPattern(exp) ||
ts.TypeGuards.isObjectBindingPattern(exp));
/* istanbul ignore next */
if (child === undefined) {
throw new CompilerError_1.CompilerError("Child missing from parameter!", param, CompilerError_1.CompilerErrorType.ParameterChildMissing, true);
}
let name;
if (ts.TypeGuards.isIdentifier(child)) {
if (param.getName() === "this") {
continue;
}
name = _1.compileExpression(state, child);
}
else {
name = state.getNewId();
}
if (param.isRestParameter()) {
paramNames.push("...");
initializers.push(`local ${name} = { ... };`);
}
else {
paramNames.push(name);
}
if (param.hasInitializer()) {
(defaults ? defaults : initializers).push(compileParamDefault(state, param.getInitializer(), name));
}
if (param.hasScopeKeyword() || param.isReadonly()) {
const classDec = node.getParent();
if (ts.TypeGuards.isClassDeclaration(classDec) || ts.TypeGuards.isClassExpression(classDec)) {
_1.checkPropertyCollision(classDec, param);
}
initializers.push(`self.${name} = ${name};`);
}
if (ts.TypeGuards.isArrayBindingPattern(child) || ts.TypeGuards.isObjectBindingPattern(child)) {
const names = new Array();
const values = new Array();
const preStatements = new Array();
const postStatements = new Array();
getBindingData(state, names, values, preStatements, postStatements, child, name);
preStatements.forEach(statement => initializers.push(statement));
concatNamesAndValues(state, names, values, true, declaration => initializers.push(declaration), false);
postStatements.forEach(statement => initializers.push(statement));
}
}
}
exports.getParameterData = getParameterData;
function arrayAccessor(state, t, key) {
return `${t}[${key}]`;
}
function objectAccessor(state, t, node, getAccessor, nameNode = node, aliasNode = node) {
let name;
if (ts.TypeGuards.isShorthandPropertyAssignment(nameNode)) {
nameNode = nameNode.getNameNode();
}
const rhs = node
.getFirstAncestorOrThrow(ancestor => ts.TypeGuards.isObjectLiteralExpression(ancestor) || ts.TypeGuards.isObjectBindingPattern(ancestor))
.getParentOrThrow()
.getLastChildOrThrow(() => true);
if (ts.TypeGuards.isIdentifier(nameNode)) {
name = _1.compileExpression(state, nameNode);
}
else if (ts.TypeGuards.isComputedPropertyName(nameNode)) {
const exp = utility_1.skipNodesDownwards(nameNode.getExpression());
name = _1.getComputedPropertyAccess(state, exp, rhs);
return `${t}[${name}]`;
}
else if (ts.TypeGuards.isNumericLiteral(nameNode) || ts.TypeGuards.isStringLiteral(nameNode)) {
name = _1.compileExpression(state, nameNode);
return `${t}[${name}]`;
}
else {
throw new CompilerError_1.CompilerError(`Cannot index an object with type ${nameNode.getKindName()}.`, nameNode, CompilerError_1.CompilerErrorType.BadExpression, true);
}
if (getAccessor) {
const type = typeUtilities_1.getType(aliasNode);
if (typeUtilities_1.isArrayMethodType(type) || typeUtilities_1.isMapMethodType(type) || typeUtilities_1.isSetMethodType(type) || typeUtilities_1.isStringMethodType(type)) {
throw new CompilerError_1.CompilerError(`Cannot index method ${name} (a roblox-ts internal)`, aliasNode, CompilerError_1.CompilerErrorType.BadDestructuringType);
}
}
// We need this because length is built-in to the TS compiler, even if we removed it from our types
if (typeUtilities_1.getCompilerDirectiveWithLaxConstraint(typeUtilities_1.getType(rhs), "array" /* Array */, r => r.isTuple()) &&
name === "length") {
throw new CompilerError_1.CompilerError(`Cannot access the \`length\` property of a tuple! Instead use \`${rhs.getText()}.size()\``, node, CompilerError_1.CompilerErrorType.TupleLength);
}
return `${t}.${name}`;
}
function stringAccessor(state, t, key) {
return `${t}:sub(${key}, ${key})`;
}
function setAccessor(state, t, key, preStatements, idStack) {
const id = state.getNewId();
const lastId = idStack[idStack.length - 1];
if (lastId !== undefined) {
preStatements.push(`local ${id} = next(${t}, ${lastId})`);
}
else {
preStatements.push(`local ${id} = next(${t})`);
}
idStack.push(id);
return id;
}
function mapAccessor(state, t, key, preStatements, idStack, isHole = false) {
const keyId = state.getNewId();
const lastId = idStack[idStack.length - 1];
let valueId;
let valueIdStr = "";
if (!isHole) {
valueId = state.getNewId();
valueIdStr = `, ${valueId}`;
}
if (lastId !== undefined) {
preStatements.push(`local ${keyId}${valueIdStr} = next(${t}, ${lastId})`);
}
else {
preStatements.push(`local ${keyId}${valueIdStr} = next(${t})`);
}
idStack.push(keyId);
return `{ ${keyId}${valueIdStr} }`;
}
function iterAccessor(state, t, key, preStatements, idStack, isHole = false) {
if (isHole) {
preStatements.push(`${t}.next()`);
return "";
}
else {
const id = state.getNewId();
preStatements.push(`local ${id} = ${t}.next();`);
return `${id}.value`;
}
}
function iterableFunctionAccessor(state, t, key, preStatements, idStack, isHole = false) {
if (isHole) {
preStatements.push(`${t}()`);
return "";
}
else {
return `${t}()`;
}
}
function getAccessorForBindingPatternType(bindingPattern) {
const bindingPatternType = typeUtilities_1.getType(bindingPattern);
if (typeUtilities_1.isArrayType(bindingPatternType)) {
return arrayAccessor;
}
else if (typeUtilities_1.isStringType(bindingPatternType)) {
return stringAccessor;
}
else if (typeUtilities_1.isSetType(bindingPatternType)) {
return setAccessor;
}
else if (typeUtilities_1.isMapType(bindingPatternType)) {
return mapAccessor;
}
else if (typeUtilities_1.isIterableFunction(bindingPatternType)) {
return iterableFunctionAccessor;
}
else if (typeUtilities_1.isIterableIterator(bindingPatternType, bindingPattern) ||
typeUtilities_1.isObjectType(bindingPatternType) ||
ts.TypeGuards.isThisExpression(bindingPattern) ||
ts.TypeGuards.isSuperExpression(bindingPattern)) {
return iterAccessor;
}
else {
if (bindingPattern.getKind() === ts.SyntaxKind.ObjectBindingPattern) {
return null;
}
else {
throw new CompilerError_1.CompilerError(`Cannot destructure an object of type ${bindingPatternType.getText()}`, bindingPattern, CompilerError_1.CompilerErrorType.BadDestructuringType);
}
}
}
exports.getAccessorForBindingPatternType = getAccessorForBindingPatternType;
function concatNamesAndValues(state, names, values, isLocal, func, includeSpacing = true, includeSemicolon = true) {
if (values.length > 0) {
names[0] = names[0] || "_";
func(`${includeSpacing ? state.indent : ""}${isLocal ? "local " : ""}${names.join(", ")} = ${values.join(", ")}${includeSemicolon ? ";" : ""}${includeSpacing ? "\n" : ""}`);
}
}
exports.concatNamesAndValues = concatNamesAndValues;
function getBindingData(state, names, values, preStatements, postStatements, bindingPattern, parentId, getAccessor = getAccessorForBindingPatternType(bindingPattern)) {
const idStack = new Array();
const strKeys = bindingPattern.getKind() === ts.SyntaxKind.ObjectBindingPattern;
let childIndex = 1;
for (const item of bindingPattern.getFirstChildByKindOrThrow(ts.SyntaxKind.SyntaxList).getChildren()) {
if (ts.TypeGuards.isBindingElement(item)) {
const [child, op, pattern] = item.getChildren();
if (child.getKind() === ts.SyntaxKind.DotDotDotToken) {
throw new CompilerError_1.CompilerError("Operator ... is not supported for destructuring!", child, CompilerError_1.CompilerErrorType.SpreadDestructuring);
}
if (pattern &&
(ts.TypeGuards.isArrayBindingPattern(pattern) || ts.TypeGuards.isObjectBindingPattern(pattern))) {
const childId = state.getNewId();
const accessor = strKeys
? objectAccessor(state, parentId, child, getAccessor)
: getAccessor(state, parentId, childIndex, preStatements, idStack);
preStatements.push(`local ${childId} = ${accessor};`);
getBindingData(state, names, values, preStatements, postStatements, pattern, childId);
}
else if (ts.TypeGuards.isArrayBindingPattern(child)) {
const childId = state.getNewId();
const accessor = strKeys
? objectAccessor(state, parentId, child, getAccessor)
: getAccessor(state, parentId, childIndex, preStatements, idStack);
preStatements.push(`local ${childId} = ${accessor};`);
getBindingData(state, names, values, preStatements, postStatements, child, childId);
}
else if (ts.TypeGuards.isIdentifier(child)) {
const idNode = pattern && ts.TypeGuards.isIdentifier(pattern) ? pattern : child;
const id = _1.compileIdentifier(state, idNode, true);
names.push(id);
if (op && op.getKind() === ts.SyntaxKind.EqualsToken) {
postStatements.push(compileParamDefault(state, pattern, id));
}
const accessor = strKeys
? objectAccessor(state, parentId, child, getAccessor, child, idNode)
: getAccessor(state, parentId, childIndex, preStatements, idStack);
values.push(accessor);
}
else if (ts.TypeGuards.isObjectBindingPattern(child)) {
const childId = state.getNewId();
const accessor = strKeys
? objectAccessor(state, parentId, child, getAccessor)
: getAccessor(state, parentId, childIndex, preStatements, idStack);
preStatements.push(`local ${childId} = ${accessor};`);
getBindingData(state, names, values, preStatements, postStatements, child, childId);
}
else if (ts.TypeGuards.isComputedPropertyName(child)) {
const expStr = _1.getComputedPropertyAccess(state, utility_1.skipNodesDownwards(child.getExpression()), bindingPattern);
const accessor = `${parentId}[${expStr}]`;
const childId = _1.compileExpression(state, pattern);
preStatements.push(`local ${childId} = ${accessor};`);
}
else if (child.getKind() !== ts.SyntaxKind.CommaToken && !ts.TypeGuards.isOmittedExpression(child)) {
throw new CompilerError_1.CompilerError(`Unexpected ${child.getKindName()} in getBindingData.`, child, CompilerError_1.CompilerErrorType.UnexpectedBindingPattern, true);
}
}
else if (ts.TypeGuards.isIdentifier(item)) {
const id = _1.compileExpression(state, item);
names.push(id);
values.push(getAccessor(state, parentId, childIndex, preStatements, idStack));
}
else if (ts.TypeGuards.isPropertyAccessExpression(item)) {
const id = _1.compileExpression(state, item);
names.push(id);
values.push(getAccessor(state, parentId, childIndex, preStatements, idStack));
}
else if (ts.TypeGuards.isArrayLiteralExpression(item)) {
const childId = state.getNewId();
preStatements.push(`local ${childId} = ${getAccessor(state, parentId, childIndex, preStatements, idStack)};`);
getBindingData(state, names, values, preStatements, postStatements, item, childId);
}
else if (item.getKind() === ts.SyntaxKind.CommaToken) {
childIndex--;
}
else if (ts.TypeGuards.isObjectLiteralExpression(item)) {
const childId = state.getNewId();
preStatements.push(`local ${childId} = ${getAccessor(state, parentId, childIndex, preStatements, idStack)};`);
getBindingData(state, names, values, preStatements, postStatements, item, childId);
}
else if (ts.TypeGuards.isShorthandPropertyAssignment(item)) {
preStatements.push(`${item.getName()} = ${objectAccessor(state, parentId, item, getAccessor)};`);
}
else if (ts.TypeGuards.isPropertyAssignment(item)) {
let alias;
const nameNode = item.getNameNode();
if (item.hasInitializer()) {
const initializer = utility_1.skipNodesDownwards(item.getInitializer());
if (ts.TypeGuards.isIdentifier(initializer) ||
ts.TypeGuards.isPropertyAccessExpression(initializer) ||
ts.TypeGuards.isElementAccessExpression(initializer)) {
alias = _1.compileExpression(state, initializer);
preStatements.push(`${alias} = ${objectAccessor(state, parentId, item, getAccessor, nameNode)};`);
}
else {
alias = state.getNewId();
preStatements.push(`${alias} = ${objectAccessor(state, parentId, item, getAccessor, nameNode)};`);
getBindingData(state, names, values, preStatements, postStatements, initializer, alias);
}
}
else {
alias = item.getName();
preStatements.push(`${alias} = ${objectAccessor(state, parentId, item, getAccessor, nameNode)};`);
}
}
else if (ts.TypeGuards.isOmittedExpression(item)) {
getAccessor(state, parentId, childIndex, preStatements, idStack, true);
}
else {
throw new CompilerError_1.CompilerError(`Unexpected ${item.getKindName()} in getBindingData.`, item, CompilerError_1.CompilerErrorType.UnexpectedBindingPattern, true);
}
childIndex++;
}
}
exports.getBindingData = getBindingData;
//# sourceMappingURL=binding.js.map