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
376 lines • 16.2 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 LUA_RESERVED_METAMETHODS = [
"__index",
"__newindex",
"__add",
"__sub",
"__mul",
"__div",
"__mod",
"__pow",
"__unm",
"__eq",
"__lt",
"__le",
"__call",
"__concat",
"__tostring",
"__len",
"__metatable",
"__mode",
];
function nonGetterOrSetter(prop) {
if (ts.TypeGuards.isGetAccessorDeclaration(prop) || ts.TypeGuards.isSetAccessorDeclaration(prop)) {
throw new CompilerError_1.CompilerError("Getters and Setters are disallowed! See https://github.com/roblox-ts/roblox-ts/issues/457", prop, CompilerError_1.CompilerErrorType.GettersSettersDisallowed);
}
return prop;
}
function compileClassProperty(state, prop, name, precedingStatementContext) {
const propNameNode = prop.getNameNode();
if (propNameNode) {
let propStr;
if (ts.TypeGuards.isIdentifier(propNameNode)) {
const propName = propNameNode.getText();
propStr = "." + propName;
_1.checkMethodReserved(propName, prop);
}
else if (ts.TypeGuards.isStringLiteral(propNameNode)) {
const expStr = _1.compileExpression(state, propNameNode);
_1.checkMethodReserved(propNameNode.getLiteralText(), prop);
propStr = `[${expStr}]`;
}
else if (ts.TypeGuards.isNumericLiteral(propNameNode)) {
const expStr = _1.compileExpression(state, propNameNode);
propStr = `[${expStr}]`;
}
else if (ts.TypeGuards.isComputedPropertyName(propNameNode)) {
// ComputedPropertyName
const computedExp = propNameNode.getExpression();
if (ts.TypeGuards.isStringLiteral(computedExp)) {
_1.checkMethodReserved(computedExp.getLiteralText(), prop);
}
const computedExpStr = _1.compileExpression(state, computedExp);
propStr = `[${computedExpStr}]`;
}
else {
throw new CompilerError_1.CompilerError(`Unexpected prop type ${prop.getKindName()} in compileClass`, prop, CompilerError_1.CompilerErrorType.UnexpectedPropType, true);
}
if (ts.TypeGuards.isInitializerExpressionableNode(prop) && prop.hasInitializer()) {
state.enterPrecedingStatementContext(precedingStatementContext);
const initializer = utility_1.skipNodesDownwards(prop.getInitializer());
state.declarationContext.set(initializer, {
isIdentifier: false,
set: `${name}${propStr}`,
});
const expStr = _1.compileExpression(state, initializer);
state.exitPrecedingStatementContext();
if (state.declarationContext.delete(initializer)) {
precedingStatementContext.push(state.indent, name, propStr, " = ", expStr, ";\n");
}
}
else {
precedingStatementContext.push(state.indent, name, propStr, " = nil;\n");
}
}
}
function getClassMethod(classDec, methodName, getter = (c, name) => c.getMethod(name)) {
const method = getter(classDec, methodName);
if (method) {
return method;
}
const baseClass = classDec.getBaseClass();
if (baseClass) {
const baseMethod = getClassMethod(baseClass, methodName, getter);
if (baseMethod) {
return baseMethod;
}
}
else {
const extendsClass = classDec.getExtends();
if (extendsClass) {
const exp = extendsClass.getExpression();
if (exp && ts.TypeGuards.isClassExpression(exp)) {
const baseMethod = getClassMethod(exp, methodName, getter);
if (baseMethod) {
return baseMethod;
}
}
}
}
return undefined;
}
function getClassStaticMethod(classDec, methodName) {
return getClassMethod(classDec, methodName, (c, n) => c.getStaticMethod(n));
}
function getClassInstanceMethod(classDec, methodName) {
return getClassMethod(classDec, methodName, (c, n) => c.getInstanceMethod(n));
}
function checkMethodCollision(node, method) {
const methodName = method.getName();
if (method.isStatic()) {
if (getClassInstanceMethod(node, methodName)) {
throw new CompilerError_1.CompilerError(`An instance method already exists with the name ${methodName}`, node, CompilerError_1.CompilerErrorType.MethodCollision);
}
}
else {
if (getClassStaticMethod(node, methodName)) {
throw new CompilerError_1.CompilerError(`A static method already exists with the name ${methodName}`, node, CompilerError_1.CompilerErrorType.MethodCollision);
}
}
}
function getClassProperty(classDec, propName, getter = (c, n) => c.getProperty(n)) {
const property = getter(classDec, propName);
if (property) {
return property;
}
const baseClass = classDec.getBaseClass();
if (baseClass) {
const baseProp = getClassProperty(baseClass, propName, getter);
if (baseProp) {
return baseProp;
}
}
else {
const extendsClass = classDec.getExtends();
if (extendsClass) {
const exp = extendsClass.getExpression();
if (exp && ts.TypeGuards.isClassExpression(exp)) {
const baseProp = getClassProperty(exp, propName, getter);
if (baseProp) {
return baseProp;
}
}
}
}
return undefined;
}
function getClassStaticProperty(classDec, propName) {
return getClassProperty(classDec, propName, (c, n) => c.getStaticProperty(n));
}
function getClassInstanceProperty(classDec, propName) {
return getClassProperty(classDec, propName, (c, n) => c.getInstanceProperty(n));
}
function checkPropertyCollision(node, prop) {
const propName = prop.getName();
if (!ts.TypeGuards.isParameterDeclaration(prop) && prop.isStatic()) {
if (getClassInstanceProperty(node, propName)) {
throw new CompilerError_1.CompilerError(`An instance property already exists with the name ${propName}`, node, CompilerError_1.CompilerErrorType.PropertyCollision);
}
}
else {
if (getClassStaticProperty(node, propName)) {
throw new CompilerError_1.CompilerError(`A static property already exists with the name ${propName}`, node, CompilerError_1.CompilerErrorType.PropertyCollision);
}
}
}
exports.checkPropertyCollision = checkPropertyCollision;
function checkDecorators(node) {
if (node.getDecorators().length > 1) {
throw new CompilerError_1.CompilerError("Decorators are not yet implemented!", node, CompilerError_1.CompilerErrorType.Decorator);
}
}
// TODO: remove
function getConstructor(node) {
for (const constructor of node.getConstructors()) {
return constructor;
}
}
function checkDefaultIterator(extendsArray, prop) {
if (extendsArray && prop.getName() === "[Symbol.iterator]") {
// This check is sufficient because TS only considers something as having an iterator when it is
// literally `Symbol.iterator`. At present, writing Symbol or Symbol.iterator to another variable
// is not considered valid by TS
throw new CompilerError_1.CompilerError(`Cannot declare [Symbol.iterator] on class which extends from Array<T>`, prop, CompilerError_1.CompilerErrorType.DefaultIteratorOnArrayExtension);
}
}
function validateMethod(node, method, extendsArray, isRoact) {
if (isRoact) {
_1.checkRoactReserved(node.getName() || "", method.getName(), node);
}
checkDecorators(method);
checkMethodCollision(node, method);
checkDefaultIterator(extendsArray, method);
const nameNode = method.getNameNode();
if (ts.TypeGuards.isComputedPropertyName(nameNode)) {
let isSymbolPropAccess = false;
const exp = utility_1.skipNodesDownwards(nameNode.getExpression());
if (ts.TypeGuards.isPropertyAccessExpression(exp)) {
const subExp = utility_1.skipNodesDownwards(exp.getExpression());
if (ts.TypeGuards.isIdentifier(subExp) && subExp.getText() === "Symbol") {
isSymbolPropAccess = true;
}
}
if (!isSymbolPropAccess) {
throw new CompilerError_1.CompilerError("Cannot make a class with computed method names!", method, CompilerError_1.CompilerErrorType.ClassWithComputedMethodNames);
}
}
}
function compileClassInitializer(state, node, results, name) {
const prefix = ts.TypeGuards.isClassExpression(node) && node.getNameNode() ? "local " : "";
if (node.getExtends()) {
results.push(state.indent + `${prefix}${name} = setmetatable({}, { __index = super });\n`);
}
else {
results.push(state.indent + `${prefix}${name} = {};\n`);
}
results.push(state.indent + `${name}.__index = ${name};\n`);
}
function compileRoactClassInitializer(state, node, results, name, roactType) {
const prefix = ts.TypeGuards.isClassExpression(node) && node.getNameNode() ? "local " : "";
results.push(state.indent + `${prefix}${name} = ${roactType}:extend("${name}");\n`);
}
function compileClass(state, node) {
const name = node.getName() || state.getNewId();
const nameNode = node.getNameNode();
let expAlias;
checkDecorators(node);
if (nameNode) {
_1.checkReserved(name, nameNode, true);
}
if (ts.TypeGuards.isClassDeclaration(node)) {
state.pushExport(name, node);
}
// Roact
const roactType = _1.getRoactType(node);
const isRoact = roactType !== undefined;
if (!isRoact && _1.inheritsFromRoactComponent(node)) {
throw new CompilerError_1.CompilerError(`Cannot inherit ${utility_1.bold(node.getExtendsOrThrow().getText())}, must inherit ${utility_1.bold("Roact.Component")}\n` +
_1.ROACT_DERIVED_CLASSES_ERROR, node, CompilerError_1.CompilerErrorType.RoactSubClassesNotSupported);
}
let hasSuper = false;
const results = new Array();
const isExpression = ts.TypeGuards.isClassExpression(node);
if (isExpression) {
results.push(state.indent + `local ${nameNode ? (expAlias = state.getNewId()) : name};\n`);
}
else {
if (nameNode && typeUtilities_1.shouldHoist(node, nameNode)) {
state.pushHoistStack(name);
}
else {
results.push(state.indent + `local ${name};\n`);
}
}
results.push(state.indent + `do\n`);
state.pushIndent();
let extendsArray = false;
const extendExp = node.getExtends();
if (!isRoact && extendExp) {
const extendExpExp = utility_1.skipNodesDownwards(extendExp.getExpression());
extendsArray = typeUtilities_1.superExpressionClassInheritsFromArray(extendExpExp);
hasSuper = !typeUtilities_1.superExpressionClassInheritsFromArray(extendExpExp, false);
if (typeUtilities_1.superExpressionClassInheritsFromSetOrMap(extendExpExp)) {
throw new CompilerError_1.CompilerError("Cannot create a class which inherits from Map or Set!", extendExpExp, CompilerError_1.CompilerErrorType.BadClassExtends);
}
state.enterPrecedingStatementContext(results);
if (hasSuper) {
results.push(state.indent +
`local super = ${_1.compileExpression(state, utility_1.skipNodesDownwards(extendExp.getExpression()))};\n`);
}
state.exitPrecedingStatementContext();
}
if (!isRoact) {
compileClassInitializer(state, node, results, name);
}
else {
compileRoactClassInitializer(state, node, results, name, roactType);
}
if (!isRoact && !node.isAbstract()) {
results.push(state.indent + `function ${name}.new(...)\n`, state.indent + `\tlocal self = setmetatable({}, ${name});\n`, state.indent + `\tself:constructor(...);\n`, state.indent + `\treturn self;\n`, state.indent + `end;\n`);
}
const extraInitializers = new Array();
state.pushIndent();
for (let prop of node.getInstanceProperties()) {
checkDecorators(prop);
checkPropertyCollision(node, prop);
checkDefaultIterator(extendsArray, prop);
prop = nonGetterOrSetter(prop);
if (isRoact) {
_1.checkRoactReserved(name, prop.getName(), node);
}
if (prop.getParent() === node) {
compileClassProperty(state, prop, "self", extraInitializers);
}
}
state.popIndent();
results.push(_1.compileConstructorDeclaration(state, node, name, getConstructor(node), extraInitializers, hasSuper, isRoact));
for (const method of node.getStaticMethods()) {
if (method.getBody() !== undefined) {
const methodName = method.getName();
if (methodName === "new" || LUA_RESERVED_METAMETHODS.includes(methodName)) {
throw new CompilerError_1.CompilerError(`Cannot make a static method with name "${methodName}"!`, method, CompilerError_1.CompilerErrorType.BadStaticMethod);
}
validateMethod(node, method, extendsArray, isRoact);
state.enterPrecedingStatementContext(results);
results.push(_1.compileMethodDeclaration(state, method, name + "."));
state.exitPrecedingStatementContext();
}
}
for (const method of node.getInstanceMethods()) {
if (method.getBody() !== undefined) {
validateMethod(node, method, extendsArray, isRoact);
state.enterPrecedingStatementContext(results);
results.push(_1.compileMethodDeclaration(state, method, name + ":"));
state.exitPrecedingStatementContext();
}
}
for (const metamethod of LUA_RESERVED_METAMETHODS) {
if (getClassMethod(node, metamethod)) {
throw new CompilerError_1.CompilerError(`Cannot use Lua metamethod as identifier '${metamethod}' for a class`, node, CompilerError_1.CompilerErrorType.UndefinableMetamethod);
}
}
for (const prop of node.getStaticProperties()) {
checkDecorators(prop);
checkPropertyCollision(node, prop);
checkDefaultIterator(extendsArray, prop);
if (isRoact) {
_1.checkRoactReserved(name, prop.getName(), node);
}
compileClassProperty(state, nonGetterOrSetter(prop), name, results);
}
if (getClassMethod(node, "toString")) {
results.push(state.indent + `function ${name}:__tostring() return self:toString(); end;\n`);
results.push(state.indent + `function ${name}.__concat(a, b) return tostring(a) .. tostring(b) end;\n`);
}
if (isExpression) {
if (nameNode) {
results.push(state.indent + `${expAlias} = ${name};\n`);
}
state.popIndent();
results.push(state.indent + `end;\n`);
state.pushPrecedingStatements(node, ...results);
// Do not classify this as isPushed here.
return expAlias || name;
}
else {
state.popIndent();
results.push(state.indent + `end;\n`);
}
return results.join("");
}
function compileClassDeclaration(state, node) {
return compileClass(state, node);
}
exports.compileClassDeclaration = compileClassDeclaration;
function compileClassExpression(state, node) {
return compileClass(state, node);
}
exports.compileClassExpression = compileClassExpression;
function compileSuperExpression(state, node) {
return typeUtilities_1.isArrayType(typeUtilities_1.getType(node)) ? "self" : "super";
}
exports.compileSuperExpression = compileSuperExpression;
//# sourceMappingURL=class.js.map