UNPKG

typescript-to-lua

Version:

A generic TypeScript to Lua transpiler. Write your code in TypeScript and publish Lua!

268 lines 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createNaN = void 0; exports.unwrapVisitorResult = unwrapVisitorResult; exports.createSelfIdentifier = createSelfIdentifier; exports.createExportsIdentifier = createExportsIdentifier; exports.addToNumericExpression = addToNumericExpression; exports.getNumberLiteralValue = getNumberLiteralValue; exports.createUnpackCall = createUnpackCall; exports.isUnpackCall = isUnpackCall; exports.wrapInTable = wrapInTable; exports.wrapInToStringForConcat = wrapInToStringForConcat; exports.createHoistableVariableDeclarationStatement = createHoistableVariableDeclarationStatement; exports.createLocalOrExportedOrGlobalDeclaration = createLocalOrExportedOrGlobalDeclaration; const ts = require("typescript"); const CompilerOptions_1 = require("../../CompilerOptions"); const lua = require("../../LuaAST"); const utils_1 = require("../../utils"); const export_1 = require("./export"); const scope_1 = require("./scope"); const lualib_1 = require("./lualib"); const LuaLib_1 = require("../../LuaLib"); function unwrapVisitorResult(result) { return result === undefined ? [] : (0, utils_1.castArray)(result); } function createSelfIdentifier(tsOriginal) { return lua.createIdentifier("self", tsOriginal, undefined, "this"); } function createExportsIdentifier() { return lua.createIdentifier("____exports"); } function addToNumericExpression(expression, change) { if (change === 0) return expression; const literalValue = getNumberLiteralValue(expression); if (literalValue !== undefined) { const newNode = lua.createNumericLiteral(literalValue + change); lua.setNodePosition(newNode, expression); return newNode; } if (lua.isBinaryExpression(expression)) { if (lua.isNumericLiteral(expression.right) && ((expression.operator === lua.SyntaxKind.SubtractionOperator && expression.right.value === change) || (expression.operator === lua.SyntaxKind.AdditionOperator && expression.right.value === -change))) { return expression.left; } } return change > 0 ? lua.createBinaryExpression(expression, lua.createNumericLiteral(change), lua.SyntaxKind.AdditionOperator) : lua.createBinaryExpression(expression, lua.createNumericLiteral(-change), lua.SyntaxKind.SubtractionOperator); } function getNumberLiteralValue(expression) { if (!expression) return undefined; if (lua.isNumericLiteral(expression)) return expression.value; if (lua.isUnaryExpression(expression) && expression.operator === lua.SyntaxKind.NegationOperator && lua.isNumericLiteral(expression.operand)) { return -expression.operand.value; } return undefined; } function createUnpackCall(context, expression, tsOriginal) { if (context.luaTarget === CompilerOptions_1.LuaTarget.Universal) { return (0, lualib_1.transformLuaLibFunction)(context, LuaLib_1.LuaLibFeature.Unpack, tsOriginal, expression); } const unpack = context.luaTarget === CompilerOptions_1.LuaTarget.Lua50 || context.luaTarget === CompilerOptions_1.LuaTarget.Lua51 || context.luaTarget === CompilerOptions_1.LuaTarget.LuaJIT ? lua.createIdentifier("unpack") : lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("unpack")); return lua.setNodeFlags(lua.createCallExpression(unpack, [expression], tsOriginal), lua.NodeFlags.TableUnpackCall); } function isUnpackCall(node) { return lua.isCallExpression(node) && (node.flags & lua.NodeFlags.TableUnpackCall) !== 0; } function wrapInTable(...expressions) { const fields = expressions.map(e => lua.createTableFieldExpression(e)); return lua.createTableExpression(fields); } function wrapInToStringForConcat(expression) { if (lua.isStringLiteral(expression) || lua.isNumericLiteral(expression) || (lua.isBinaryExpression(expression) && expression.operator === lua.SyntaxKind.ConcatOperator)) { return expression; } return lua.createCallExpression(lua.createIdentifier("tostring"), [expression]); } function createHoistableVariableDeclarationStatement(context, identifier, initializer, tsOriginal) { const declaration = lua.createVariableDeclarationStatement(identifier, initializer, tsOriginal); if (identifier.symbolId !== undefined) { const scope = (0, scope_1.peekScope)(context); (0, utils_1.assert)(scope.type !== scope_1.ScopeType.Switch); (0, scope_1.addScopeVariableDeclaration)(scope, declaration); } return declaration; } function hasMultipleReferences(scope, identifiers) { const scopeSymbols = scope.referencedSymbols; if (!scopeSymbols) { return false; } const referenceLists = (0, utils_1.castArray)(identifiers).map(i => i.symbolId && scopeSymbols.get(i.symbolId)); return referenceLists.some(symbolRefs => symbolRefs && symbolRefs.length > 1); } function createLocalOrExportedOrGlobalDeclaration(context, lhs, rhs, tsOriginal, overrideExportScope) { let declaration; let assignment; const noImplicitGlobalVariables = context.options.noImplicitGlobalVariables === true; const isFunctionDeclaration = tsOriginal !== undefined && ts.isFunctionDeclaration(tsOriginal); const identifiers = (0, utils_1.castArray)(lhs); if (identifiers.length === 0) { return []; } const exportScope = overrideExportScope !== null && overrideExportScope !== void 0 ? overrideExportScope : (0, export_1.getIdentifierExportScope)(context, identifiers[0]); if (exportScope) { // exported if (!rhs) { return []; } else { assignment = lua.createAssignmentStatement(identifiers.map(identifier => (0, export_1.createExportedIdentifier)(context, identifier, exportScope)), rhs, tsOriginal); } } else { const scope = (0, scope_1.peekScope)(context); const isTopLevelVariable = scope.type === scope_1.ScopeType.File; if (context.isModule || !isTopLevelVariable || noImplicitGlobalVariables) { const isLuaFunctionExpression = rhs && !Array.isArray(rhs) && lua.isFunctionExpression(rhs); const isSafeRecursiveFunctionDeclaration = isFunctionDeclaration && isLuaFunctionExpression; if (!isSafeRecursiveFunctionDeclaration && hasMultipleReferences(scope, lhs)) { // Split declaration and assignment of identifiers that reference themselves in their declaration. // Put declaration above preceding statements in case the identifier is referenced in those. const precedingDeclaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal); context.prependPrecedingStatements(precedingDeclaration); if (rhs) { assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal); } // Remember local variable declarations for hoisting later (0, scope_1.addScopeVariableDeclaration)(scope, precedingDeclaration); } else { declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal); if (!isFunctionDeclaration) { // Remember local variable declarations for hoisting later (0, scope_1.addScopeVariableDeclaration)(scope, declaration); } } } else if (rhs) { // global assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal); } else { return []; } } if (isFunctionDeclaration) { // Remember function definitions for hoisting later const functionSymbolId = lhs.symbolId; const scope = (0, scope_1.peekScope)(context); if (functionSymbolId && scope.functionDefinitions) { const definitions = scope.functionDefinitions.get(functionSymbolId); if (definitions) { definitions.definition = declaration !== null && declaration !== void 0 ? declaration : assignment; } } } setJSDocComments(context, tsOriginal, declaration, assignment); if (declaration && assignment) { return [declaration, assignment]; } else if (declaration) { return [declaration]; } else if (assignment) { return [assignment]; } else { return []; } } /** * Apply JSDoc comments to the newly-created Lua statement, if present. * https://stackoverflow.com/questions/47429792/is-it-possible-to-get-comments-as-nodes-in-the-ast-using-the-typescript-compiler */ function setJSDocComments(context, tsOriginal, declaration, assignment) { // Respect the vanilla TypeScript option of "removeComments": // https://www.typescriptlang.org/tsconfig#removeComments if (context.options.removeComments) { return; } const docCommentArray = getJSDocCommentFromTSNode(context, tsOriginal); if (docCommentArray === undefined) { return; } if (declaration && assignment) { declaration.leadingComments = docCommentArray; } else if (declaration) { declaration.leadingComments = docCommentArray; } else if (assignment) { assignment.leadingComments = docCommentArray; } } function getJSDocCommentFromTSNode(context, tsOriginal) { if (tsOriginal === undefined) { return undefined; } // The "name" property is only on a subset of node types; we want to be permissive and get the // comments from as many nodes as possible. const node = tsOriginal; if (node.name === undefined) { return undefined; } const symbol = context.checker.getSymbolAtLocation(node.name); if (symbol === undefined) { return undefined; } // The TypeScript compiler separates JSDoc comments into the "documentation comment" and the // "tags". The former is conventionally at the top of the comment, and the bottom is // conventionally at the bottom. We need to get both from the TypeScript API and then combine // them into one block of text. const docCommentArray = symbol.getDocumentationComment(context.checker); const docCommentText = ts.displayPartsToString(docCommentArray).trim(); const jsDocTagInfoArray = symbol.getJsDocTags(context.checker); const jsDocTagsTextLines = jsDocTagInfoArray.map(jsDocTagInfo => { let text = "@" + jsDocTagInfo.name; if (jsDocTagInfo.text !== undefined) { const tagDescriptionTextArray = jsDocTagInfo.text .filter(symbolDisplayPart => symbolDisplayPart.text.trim() !== "") .map(symbolDisplayPart => symbolDisplayPart.text.trim()); const tagDescriptionText = tagDescriptionTextArray.join(" "); text += " " + tagDescriptionText; } return text; }); const jsDocTagsText = jsDocTagsTextLines.join("\n"); const combined = (docCommentText + "\n\n" + jsDocTagsText).trim(); if (combined === "") { return undefined; } // By default, TSTL will display comments immediately next to the "--" characters. We can make // the comments look better if we separate them by a space (similar to what Prettier does in // JavaScript/TypeScript). const linesWithoutSpace = combined.split("\n"); const lines = linesWithoutSpace.map(line => ` ${line}`); // We want to JSDoc comments to map on to LDoc comments: // https://stevedonovan.github.io/ldoc/manual/doc.md.html // LDoc comments require that the first line starts with three hyphens. // Thus, need to add a hyphen to the first line. if (lines.length > 0) { const firstLine = lines[0]; if (firstLine.startsWith(" @")) { lines.unshift("-"); } else { lines.shift(); lines.unshift("-" + firstLine); } return lines; } } const createNaN = (tsOriginal) => lua.createBinaryExpression(lua.createNumericLiteral(0), lua.createNumericLiteral(0), lua.SyntaxKind.DivisionOperator, tsOriginal); exports.createNaN = createNaN; //# sourceMappingURL=lua-ast.js.map