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

376 lines 16.2 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 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