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

439 lines 19.3 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_ELEMENT_TYPE = "Roact.Element"; exports.ROACT_COMPONENT_TYPE = "Roact.Component"; exports.ROACT_PURE_COMPONENT_TYPE = "Roact.PureComponent"; exports.ROACT_DERIVED_CLASSES_ERROR = utility_1.suggest("Composition is preferred over inheritance with Roact components.\n" + "...\tsee https://reactjs.org/docs/composition-vs-inheritance.html for more info about composition over inheritance."); const CONSTRUCTOR_METHOD_NAME = "init"; const INHERITANCE_METHOD_NAME = "extend"; const RESERVED_METHOD_NAMES = [ CONSTRUCTOR_METHOD_NAME, "setState", "_update", "getElementTraceback", "_forceUpdate", "_mount", "_unmount", INHERITANCE_METHOD_NAME, ]; /** * A list of lowercase names that map to Roblox elements for JSX */ const INTRINSIC_MAPPINGS = { billboardgui: "BillboardGui", frame: "Frame", imagebutton: "ImageButton", imagelabel: "ImageLabel", screengui: "ScreenGui", scrollingframe: "ScrollingFrame", surfacegui: "SurfaceGui", textbox: "TextBox", textbutton: "TextButton", textlabel: "TextLabel", uigridlayout: "UIGridLayout", uilistlayout: "UIListLayout", uipagelayout: "UIPageLayout", uitablelayout: "UITableLayout", uipadding: "UIPadding", uiscale: "UIScale", uiaspectratioconstraint: "UIAspectRatioConstraint", uisizeconstraint: "UISizeConstraint", uitextsizeconstraint: "UITextSizeConstraint", viewportframe: "ViewportFrame", }; function isRoactElementType(type) { const allowed = [ROACT_ELEMENT_TYPE, `${ROACT_ELEMENT_TYPE}[]`]; const types = type.getUnionTypes(); if (types.length > 0) { for (const unionType of types) { const unionTypeName = unionType.getText(); if (allowed.indexOf(unionTypeName) === -1 && unionTypeName !== "undefined") { return false; } } } else { return allowed.indexOf(type.getText()) !== -1; } return true; } function getFullTypeList(type) { const symbol = type.getSymbol(); const typeArray = new Array(); if (symbol) { symbol.getDeclarations().forEach(declaration => { const declarationType = typeUtilities_1.getType(declaration); typeArray.push(declarationType.getText()); for (const baseType of declarationType.getBaseTypes()) { typeArray.push(...getFullTypeList(baseType)); } }); } return typeArray; } function inheritsFromRoact(type) { let isRoactClass = false; for (const name of getFullTypeList(type)) { if (name.startsWith(exports.ROACT_COMPONENT_TYPE) || name.startsWith(exports.ROACT_PURE_COMPONENT_TYPE)) { isRoactClass = true; break; } } return isRoactClass; } exports.inheritsFromRoact = inheritsFromRoact; function getRoactType(node) { const extendsExp = node.getExtends(); if (extendsExp) { const extendsText = utility_1.skipNodesDownwards(extendsExp.getExpression()).getText(); if (extendsText.startsWith(exports.ROACT_COMPONENT_TYPE)) { return exports.ROACT_COMPONENT_TYPE; } else if (extendsText.startsWith(exports.ROACT_PURE_COMPONENT_TYPE)) { return exports.ROACT_PURE_COMPONENT_TYPE; } } return undefined; } exports.getRoactType = getRoactType; function inheritsFromRoactComponent(node) { const extendsExp = node.getExtends(); if (extendsExp) { const symbol = extendsExp.getType().getSymbol(); if (symbol) { const valueDec = symbol.getValueDeclaration(); if (valueDec && (ts.TypeGuards.isClassDeclaration(valueDec) || ts.TypeGuards.isClassExpression(valueDec))) { if (getRoactType(node) !== undefined) { return true; } else { return inheritsFromRoactComponent(valueDec); } } } } return false; } exports.inheritsFromRoactComponent = inheritsFromRoactComponent; function checkRoactReserved(className, name, node) { if (RESERVED_METHOD_NAMES.indexOf(name) !== -1) { let userError = `Member ${utility_1.bold(name)} in component ${utility_1.bold(className)} is a reserved Roact method name.`; if (name === CONSTRUCTOR_METHOD_NAME) { userError += `\n ... Use the constructor ${utility_1.bold("constructor(props)")} instead of the method ${utility_1.bold("init(props)")}.`; } else if (name === INHERITANCE_METHOD_NAME) { userError += "\n" + exports.ROACT_DERIVED_CLASSES_ERROR; } throw new CompilerError_1.CompilerError(userError, node, CompilerError_1.CompilerErrorType.RoactNoReservedMethods); } } exports.checkRoactReserved = checkRoactReserved; function compileSymbolPropertyCallback(state, node) { const symbol = node.getSymbolOrThrow(); const name = symbol.getName(); const value = symbol.getValueDeclarationOrThrow(); if (ts.TypeGuards.isFunctionLikeDeclaration(value)) { if (ts.TypeGuards.isMethodDeclaration(value)) { throw new CompilerError_1.CompilerError("Do not use Method signatures directly as callbacks for Roact Event, Changed or Ref.\n" + utility_1.suggest(`Change the declaration of \`${name}(...) {...}\` to \`${name} = () => { ... }\`, ` + ` or use an arrow function: \`() => { this.${name}() }\``), node, CompilerError_1.CompilerErrorType.RoactInvalidCallExpression); } } return _1.compileExpression(state, node); } function generateRoactSymbolProperty(state, roactSymbol, node, attributeCollection, hasExtraAttributes = false) { const expr = node.getChildrenOfKind(ts.SyntaxKind.JsxExpression); for (const expression of expr) { const innerExpression = expression.getExpressionOrThrow(); if (ts.TypeGuards.isObjectLiteralExpression(innerExpression)) { const properties = innerExpression.getProperties(); for (const property of properties) { if (ts.TypeGuards.isPropertyAssignment(property) || ts.TypeGuards.isShorthandPropertyAssignment(property)) { const propName = property.getName(); const rhs = property.getInitializerOrThrow(); let value; if (ts.TypeGuards.isPropertyAccessExpression(rhs)) { value = compileSymbolPropertyCallback(state, rhs); } else { if (hasExtraAttributes) { state.pushIndent(); // fix indentation with extra props } value = _1.compileExpression(state, rhs); } if (hasExtraAttributes) { state.popIndent(); // fix indentation with extra props } attributeCollection.push(`[Roact.${roactSymbol}.${propName}] = ${value}`); } } } else if (roactSymbol === "Ref") { let value; if (ts.TypeGuards.isPropertyAccessExpression(innerExpression)) { const getAccessExpression = innerExpression.getExpression(); if (ts.TypeGuards.isThisExpression(getAccessExpression)) { value = compileSymbolPropertyCallback(state, innerExpression); } else { if (hasExtraAttributes) { state.pushIndent(); // fix indentation with extra props } value = _1.compileExpression(state, getAccessExpression); } } else { if (hasExtraAttributes) { state.pushIndent(); // fix indentation with extra props } value = _1.compileExpression(state, innerExpression); } if (hasExtraAttributes) { state.popIndent(); } attributeCollection.push(`[Roact.Ref] = ${value}`); } else { throw new CompilerError_1.CompilerError(`Roact symbol ${roactSymbol} does not support (${innerExpression.getKindName()})`, node, CompilerError_1.CompilerErrorType.RoactInvalidSymbol); } } } exports.generateRoactSymbolProperty = generateRoactSymbolProperty; function generateRoactElement(state, // name: string, nameNode, attributes, children) { let str = `Roact.createElement(`; const name = nameNode.getText(); const attributeCollection = new Array(); const extraAttributeCollections = new Array(); const extraChildrenCollection = new Array(); const childCollection = new Array(); let key; state.roactIndent++; if (name.match(/^[a-z]+$/)) { // if lowercase // Check if defined as a intrinsic mapping const rbxName = INTRINSIC_MAPPINGS[name]; if (rbxName) { str += `"${rbxName}"`; } else { throw new CompilerError_1.CompilerError(`"${utility_1.bold(name)}" is not a valid primitive type.\n` + utility_1.suggest("Your roblox-ts may be out of date."), nameNode, CompilerError_1.CompilerErrorType.RoactInvalidPrimitive); } } else { str += name; } if (attributes.length > 0) { state.pushIndent(); const extraAttributes = attributes.filter(attr => ts.TypeGuards.isJsxSpreadAttribute(attr)); for (const attributeLike of attributes) { if (ts.TypeGuards.isJsxSpreadAttribute(attributeLike)) { const expression = attributeLike.getExpression(); extraAttributeCollections.push(_1.compileExpression(state, expression)); } else { const attribute = attributeLike; const attributeName = attribute.getName(); const value = _1.compileExpression(state, attribute.getInitializerOrThrow()); if (attributeName === "Key") { // handle setting a key for this element key = value; } else if (attributeName === "Event") { // handle [Roact.Event] generateRoactSymbolProperty(state, "Event", attributeLike, attributeCollection, extraAttributes.length > 0); } else if (attributeName === "Change") { // handle [Roact.Change] generateRoactSymbolProperty(state, "Change", attributeLike, attributeCollection, extraAttributes.length > 0); } else if (attributeName === "Ref") { // handle [Roact.Ref] generateRoactSymbolProperty(state, "Ref", attributeLike, attributeCollection, extraAttributes.length > 0); } else { attributeCollection.push(`${attributeName} = ${value}`); } } } state.popIndent(); // use Object.assign if we have extra attributes if (extraAttributeCollections.length > 0) { str += ", \n"; state.pushIndent(); state.usesTSLibrary = true; str += state.indent + "TS.Roact_combine("; // If it has other attributes if (attributeCollection.length > 0) { str += "{\n"; state.pushIndent(); str += state.indent + attributeCollection.join(",\n" + state.indent); state.popIndent(); str += ` \n${state.indent}},\n${state.indent}`; } else { str += `{}, `; } str += extraAttributeCollections.join(",\n" + state.indent); str += ")\n"; state.popIndent(); } else { str += ", {\n"; state.pushIndent(); str += state.indent + attributeCollection.join(",\n" + state.indent); state.popIndent(); str += ` \n${state.indent}}`; } } else { str += ", {}"; } if (children.length > 0) { state.pushIndent(); for (const child of children) { if (ts.TypeGuards.isJsxElement(child) || ts.TypeGuards.isJsxSelfClosingElement(child)) { const value = _1.compileExpression(state, child); childCollection.push(`${state.indent}${value}`); } else if (ts.TypeGuards.isJsxText(child)) { // If the inner text isn't just indentation/spaces if (child.getText().match(/[^\s]/)) { throw new CompilerError_1.CompilerError("Roact does not support text!", child, CompilerError_1.CompilerErrorType.RoactJsxTextNotSupported); } } else if (ts.TypeGuards.isJsxExpression(child)) { const expression = child.getExpressionOrThrow(); if (ts.TypeGuards.isCallExpression(expression)) { // Must return Roact.Element :( const returnType = expression.getReturnType(); if (isRoactElementType(returnType)) { if (typeUtilities_1.isArrayType(returnType)) { // Roact.Element[] extraChildrenCollection.push(state.indent + _1.compileExpression(state, expression)); } else { // Roact.Element extraChildrenCollection.push(state.indent + `{ ${_1.compileExpression(state, expression)} }`); } } else { throw new CompilerError_1.CompilerError(`Function call in an expression must return Roact.Element or Roact.Element[]`, expression, CompilerError_1.CompilerErrorType.RoactInvalidCallExpression); } } else if (ts.TypeGuards.isIdentifier(expression)) { const definitionNodes = expression.getDefinitionNodes(); for (const definitionNode of definitionNodes) { const type = typeUtilities_1.getType(definitionNode); if (isRoactElementType(type)) { extraChildrenCollection.push(state.indent + _1.compileExpression(state, expression)); } else { throw new CompilerError_1.CompilerError(`Roact does not support identifiers that have the return type ` + type.getText(), expression, CompilerError_1.CompilerErrorType.RoactInvalidIdentifierExpression); } } } else if (ts.TypeGuards.isPropertyAccessExpression(expression) || ts.TypeGuards.isElementAccessExpression(expression)) { const propertyType = typeUtilities_1.getType(expression); if (isRoactElementType(propertyType)) { extraChildrenCollection.push(_1.compileExpression(state, expression)); } else { throw new CompilerError_1.CompilerError(`Roact does not support the property type ` + propertyType.getText(), expression, CompilerError_1.CompilerErrorType.RoactInvalidPropertyExpression); } } else { throw new CompilerError_1.CompilerError(`Roact does not support this type of expression ` + `{${expression.getText()}} (${expression.getKindName()})`, expression, CompilerError_1.CompilerErrorType.RoactInvalidExpression); } } } state.popIndent(); if (extraChildrenCollection.length > 0) { state.usesTSLibrary = true; str += `, TS.Roact_combine(`; if (childCollection.length > 0) { str += "{\n" + state.indent; str += childCollection.join(",\n") + `\n${state.indent}}, `; } str += "\n"; str += extraChildrenCollection.join(",\n") + `\n`; str += state.indent + ")"; str += ")"; } else { // state.pushIndent(); str += state.indent + ", {\n"; str += childCollection.join(",\n") + `\n${state.indent}})`; } } else { if (extraAttributeCollections.length > 0) { str += state.indent + ")"; } else { str += ")"; } } state.roactIndent--; if (key && state.roactIndent > 0) { return `[${key}] = ${str}`; } else { return str; } } exports.generateRoactElement = generateRoactElement; function compileJsxElement(state, node) { if (!state.hasRoactImport) { throw new CompilerError_1.CompilerError("Cannot use JSX without importing Roact first!\n" + utility_1.suggest('To fix this, put `import Roact from "@rbxts/roact"` at the top of this file.'), node, CompilerError_1.CompilerErrorType.RoactJsxWithoutImport); } const open = node.getOpeningElement(); const tagNameNode = open.getTagNameNode(); const children = node.getJsxChildren(); const isArrayExpressionParent = node.getParentIfKind(ts.ts.SyntaxKind.ArrayLiteralExpression); if (isArrayExpressionParent) { state.roactIndent++; } const element = generateRoactElement(state, tagNameNode, open.getAttributes(), children); if (isArrayExpressionParent) { state.roactIndent--; } return element; } exports.compileJsxElement = compileJsxElement; function compileJsxSelfClosingElement(state, node) { if (!state.hasRoactImport) { throw new CompilerError_1.CompilerError("Cannot use JSX without importing Roact first!\n" + utility_1.suggest('To fix this, put `import Roact from "@rbxts/roact"` at the top of this file.'), node, CompilerError_1.CompilerErrorType.RoactJsxWithoutImport); } const tagNameNode = node.getTagNameNode(); const isArrayExpressionParent = node.getParentIfKind(ts.ts.SyntaxKind.ArrayLiteralExpression); if (isArrayExpressionParent) { state.roactIndent++; } const element = generateRoactElement(state, tagNameNode, node.getAttributes(), []); if (isArrayExpressionParent) { state.roactIndent--; } return element; } exports.compileJsxSelfClosingElement = compileJsxSelfClosingElement; //# sourceMappingURL=roact.js.map