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

378 lines 16.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; 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 path_1 = __importDefault(require("path")); const ts = __importStar(require("ts-morph")); const _1 = require("."); const CompilerError_1 = require("../errors/CompilerError"); const Project_1 = require("../Project"); const RojoProject_1 = require("../RojoProject"); const typeUtilities_1 = require("../typeUtilities"); const utility_1 = require("../utility"); function isDefinitionALet(def) { const parent = utility_1.skipNodesUpwards(def.getNode().getParent()); if (parent && ts.TypeGuards.isVariableDeclaration(parent)) { const grandparent = utility_1.skipNodesUpwards(parent.getParent()); return (ts.TypeGuards.isVariableDeclarationList(grandparent) && grandparent.getDeclarationKind() === ts.VariableDeclarationKind.Let); } return false; } function shouldLocalizeImport(namedImport) { for (const definition of namedImport.getDefinitions()) { if (isDefinitionALet(definition)) { return false; } } return true; } function getRojoUnavailableError(node) { return new CompilerError_1.CompilerError(`Failed to load Rojo configuration! Cannot compile ${node.getKindName()}`, node, CompilerError_1.CompilerErrorType.BadRojo); } function getRelativeImportPath(state, sourceFile, moduleFile) { let sourcePath = path_1.default.normalize(sourceFile.getFilePath()); let modulePath = path_1.default.normalize(moduleFile.getFilePath()); const sourceBaseName = utility_1.stripExtensions(sourceFile.getBaseName()); const sourceInit = sourceBaseName === "index" || sourceBaseName === "init"; if (sourceInit) { sourcePath = path_1.default.resolve(sourcePath, ".."); } const moduleBaseName = utility_1.stripExtensions(moduleFile.getBaseName()); const moduleInit = moduleBaseName === "index" || moduleBaseName === "init"; if (moduleInit) { modulePath = path_1.default.resolve(modulePath, ".."); } const relative = path_1.default.relative(sourcePath, modulePath).split(path_1.default.sep); let start = "script"; while (relative[0] === "..") { relative.shift(); start += ".Parent"; } if (!moduleInit) { relative[relative.length - 1] = utility_1.stripExtensions(relative[relative.length - 1]); } return `TS.import(${start}, ${relative.map(v => `"${v}"`).join(", ")})`; } function getRelativeImportPathRojo(state, sourceFile, moduleFile, node) { const rbxFrom = state.rojoProject.getRbxFromFile(utility_1.transformPathToLua(state.rootDirPath, state.outDirPath, sourceFile.getFilePath())).path; const rbxTo = state.rojoProject.getRbxFromFile(utility_1.transformPathToLua(state.rootDirPath, state.outDirPath, moduleFile.getFilePath())).path; if (!rbxFrom) { throw getRojoUnavailableError(node); } if (!rbxTo) { throw getRojoUnavailableError(node); } const relative = RojoProject_1.RojoProject.relative(rbxFrom, rbxTo); let start = "script"; while (relative[0] === "..") { relative.shift(); start += ".Parent"; } return `TS.import(${start}, ${relative.map(v => `"${v}"`).join(", ")})`; } const moduleCache = new Map(); function getModuleImportPath(state, moduleFile) { const modulesDir = state.modulesDir; let parts = modulesDir .getRelativePathTo(moduleFile) .split("/") .filter(part => part !== "."); const scope = parts.shift(); if (scope !== "@rbxts") { throw new CompilerError_1.CompilerError("Imported packages must have the @rbxts scope!", moduleFile, CompilerError_1.CompilerErrorType.BadPackageScope); } const moduleName = parts.shift(); let mainPath; if (moduleCache.has(moduleName)) { mainPath = moduleCache.get(moduleName); } else { const pkgJson = require(path_1.default.join(modulesDir.getPath(), scope, moduleName, "package.json")); mainPath = pkgJson.main; moduleCache.set(moduleName, mainPath); } parts = mainPath.split(/[\\/]/g); const last = utility_1.stripExtensions(parts.pop()); if (last !== "init") { parts.push(last); } parts = parts.filter(part => part !== ".").map(part => (utility_1.isValidLuaIdentifier(part) ? "." + part : `["${part}"]`)); state.usesTSLibrary = true; const params = `TS.getModule("${moduleName}")` + parts.join(""); return `TS.import(${params})`; } function getAbsoluteImportPathRojo(state, moduleFile, node) { if (!state.rojoProject) { throw getRojoUnavailableError(node); } const filePath = moduleFile.getFilePath(); const rbx = state.rojoProject.getRbxFromFile(utility_1.transformPathToLua(state.rootDirPath, state.outDirPath, filePath)); if (!rbx.path || rbx.path.length === 0) { throw new CompilerError_1.CompilerError(`Could not find Rojo data for ${filePath}`, node, CompilerError_1.CompilerErrorType.BadRojo); } const rbxPath = [...rbx.path]; let service = rbxPath.shift(); if (typeUtilities_1.isRbxService(service)) { service = `game:GetService("${service}")`; } else { throw new CompilerError_1.CompilerError(`"${service}" is not a valid Roblox Service!`, node, CompilerError_1.CompilerErrorType.InvalidService); } return `TS.import(${service}, ${rbxPath.map(v => `"${v}"`).join(", ")})`; } function getImportPath(state, sourceFile, moduleFile, node) { if (state.modulesDir && state.modulesDir.isAncestorOf(moduleFile)) { return getModuleImportPath(state, moduleFile); } if (state.projectInfo.type === Project_1.ProjectType.Game) { const fileRelation = state.rojoProject.getFileRelation(utility_1.transformPathToLua(state.rootDirPath, state.outDirPath, sourceFile.getFilePath()), utility_1.transformPathToLua(state.rootDirPath, state.outDirPath, moduleFile.getFilePath())); if (fileRelation === RojoProject_1.FileRelation.OutToOut) { return getAbsoluteImportPathRojo(state, moduleFile, node); } else if (fileRelation === RojoProject_1.FileRelation.OutToIn) { throw new CompilerError_1.CompilerError("Attempted to import a file inside of an isolated container from outside!", node, CompilerError_1.CompilerErrorType.IsolatedContainer); } else if (fileRelation === RojoProject_1.FileRelation.InToOut) { return getAbsoluteImportPathRojo(state, moduleFile, node); } else { return getRelativeImportPathRojo(state, sourceFile, moduleFile, node); } } else { if (state.rojoProject) { return getRelativeImportPathRojo(state, sourceFile, moduleFile, node); } else { return getRelativeImportPath(state, sourceFile, moduleFile); } } } function compileImportDeclaration(state, node) { const defaultImport = node.getDefaultImport(); const namespaceImport = node.getNamespaceImport(); const namedImports = node.getNamedImports(); const isRoact = (defaultImport && defaultImport.getText() === "Roact") || (namespaceImport && namespaceImport.getText() === "Roact"); if (isRoact) { state.hasRoactImport = true; } const isSideEffect = !defaultImport && !namespaceImport && namedImports.length === 0; if (!isRoact && !isSideEffect && (!namespaceImport || typeUtilities_1.isUsedAsType(namespaceImport)) && (!defaultImport || typeUtilities_1.isUsedAsType(defaultImport)) && namedImports.every(namedImport => typeUtilities_1.isUsedAsType(namedImport.getNameNode()))) { return ""; } const moduleFile = node.getModuleSpecifierSourceFile(); if (!moduleFile) { const specifier = node.getModuleSpecifier(); const text = specifier ? specifier.getText : "unknown"; throw new CompilerError_1.CompilerError(`Could not find file for '${text}'. Did you forget to "npm install"?`, node, CompilerError_1.CompilerErrorType.MissingModuleFile); } const luaPath = getImportPath(state, node.getSourceFile(), moduleFile, node); let result = ""; if (isSideEffect) { state.usesTSLibrary = true; return `${luaPath};\n`; } const lhs = new Array(); const rhs = new Array(); const unlocalizedImports = new Array(); if (defaultImport && (isRoact || !typeUtilities_1.isUsedAsType(defaultImport))) { const definitions = defaultImport.getDefinitions(); const exportAssignments = definitions.length > 0 && definitions[0] .getNode() .getSourceFile() .getExportAssignments(); const defaultImportExp = _1.compileExpression(state, defaultImport); if (exportAssignments && exportAssignments.length === 1 && exportAssignments[0].isExportEquals()) { state.usesTSLibrary = true; // If the defaultImport is importing an `export = ` statement, return `local ${defaultImportExp} = ${luaPath};\n`; } lhs.push(defaultImportExp); rhs.push(`._default`); unlocalizedImports.push(""); } if (namespaceImport && (isRoact || !typeUtilities_1.isUsedAsType(namespaceImport))) { lhs.push(_1.compileExpression(state, namespaceImport)); rhs.push(""); unlocalizedImports.push(""); } let rhsPrefix; let hasVarNames = false; namedImports .filter(namedImport => !typeUtilities_1.isUsedAsType(namedImport.getNameNode())) .forEach(namedImport => { const aliasNode = namedImport.getAliasNode(); const name = namedImport.getName(); const alias = aliasNode ? aliasNode.getText() : name; const shouldLocalize = shouldLocalizeImport(namedImport.getNameNode()); // keep these here no matter what, so that exports can take from initial state. _1.checkReserved(alias, node, true); lhs.push(alias); rhs.push(`.${name}`); if (shouldLocalize) { unlocalizedImports.push(""); } else { hasVarNames = true; unlocalizedImports.push(alias); } }); if (rhs.length === 1 && !hasVarNames) { rhsPrefix = luaPath; } else { rhsPrefix = state.getNewId(); result += `local ${rhsPrefix} = ${luaPath};\n`; } for (let i = 0; i < unlocalizedImports.length; i++) { const alias = unlocalizedImports[i]; if (alias !== "") { state.variableAliases.set(alias, rhsPrefix + rhs[i]); } } if (hasVarNames || lhs.length > 0) { const lhsStr = lhs.join(", "); const rhsStr = rhs.map(v => rhsPrefix + v).join(", "); result += `local ${lhsStr} = ${rhsStr};\n`; } state.usesTSLibrary = true; return result; } exports.compileImportDeclaration = compileImportDeclaration; function compileImportEqualsDeclaration(state, node) { const nameNode = node.getNameNode(); const name = node.getName(); const isRoact = name === "Roact"; if (isRoact) { state.hasRoactImport = true; } if (!isRoact && typeUtilities_1.isUsedAsType(nameNode)) { return ""; } const moduleFile = node.getExternalModuleReferenceSourceFile(); if (!moduleFile) { const text = node.getModuleReference().getText(); throw new CompilerError_1.CompilerError(`Could not find file for '${text}'. Did you forget to "npm install"?`, node, CompilerError_1.CompilerErrorType.MissingModuleFile); } const luaPath = getImportPath(state, node.getSourceFile(), moduleFile, node); state.usesTSLibrary = true; return state.indent + `local ${name} = ${luaPath};\n`; } exports.compileImportEqualsDeclaration = compileImportEqualsDeclaration; function compileExportDeclaration(state, node) { let luaPath = ""; if (node.hasModuleSpecifier()) { const moduleFile = node.getModuleSpecifierSourceFile(); if (!moduleFile) { const specifier = node.getModuleSpecifier(); const text = specifier ? specifier.getText : "unknown"; throw new CompilerError_1.CompilerError(`Could not find file for '${text}'. Did you forget to "npm install"?`, node, CompilerError_1.CompilerErrorType.MissingModuleFile); } luaPath = getImportPath(state, node.getSourceFile(), moduleFile, node); } const ancestor = node.getFirstAncestorByKind(ts.SyntaxKind.ModuleDeclaration) || node.getFirstAncestorByKind(ts.SyntaxKind.SourceFile); if (!ancestor) { throw new CompilerError_1.CompilerError("Could not find export ancestor!", node, CompilerError_1.CompilerErrorType.BadAncestor, true); } const lhs = new Array(); const rhs = new Array(); if (node.isNamespaceExport()) { state.usesTSLibrary = true; let ancestorName; if (ts.TypeGuards.isNamespaceDeclaration(ancestor)) { ancestorName = ancestor.getName(); } else { state.isModule = true; ancestorName = "_exports"; } return state.indent + `TS.exportNamespace(${luaPath}, ${ancestorName});\n`; } else { const namedExports = node.getNamedExports().filter(namedExport => !typeUtilities_1.isUsedAsType(namedExport.getNameNode())); if (namedExports.length === 0) { return ""; } let ancestorName; if (ts.TypeGuards.isNamespaceDeclaration(ancestor)) { ancestorName = ancestor.getName(); } else { state.isModule = true; ancestorName = "_exports"; } namedExports.forEach(namedExport => { const aliasNode = namedExport.getAliasNode(); let name = namedExport.getNameNode().getText(); if (name === "default") { name = "_default"; } const alias = aliasNode ? aliasNode.getText() : name; _1.checkReserved(alias, node); lhs.push(alias); if (luaPath !== "") { rhs.push(`.${name}`); } else { rhs.push(state.getAlias(name)); } }); let result = ""; let rhsPrefix = ""; const lhsPrefix = ancestorName + "."; if (luaPath !== "") { if (rhs.length <= 1) { rhsPrefix = `${luaPath}`; } else { rhsPrefix = state.getNewId(); result += state.indent + `local ${rhsPrefix} = ${luaPath};\n`; } } const lhsStr = lhs.map(v => lhsPrefix + v).join(", "); const rhsStr = rhs.map(v => rhsPrefix + v).join(", "); result += `${lhsStr} = ${rhsStr};\n`; state.usesTSLibrary = true; return result; } } exports.compileExportDeclaration = compileExportDeclaration; function compileExportAssignment(state, node) { const exp = utility_1.skipNodesDownwards(node.getExpression()); if (node.isExportEquals() && (!ts.TypeGuards.isIdentifier(exp) || !typeUtilities_1.isUsedAsType(exp))) { state.isModule = true; state.enterPrecedingStatementContext(); const expStr = _1.compileExpression(state, exp); return state.exitPrecedingStatementContextAndJoin() + `_exports = ${expStr};\n`; } else { const symbol = node.getSymbol(); if (symbol) { if (symbol.getName() === "default") { state.isModule = true; state.enterPrecedingStatementContext(); const expStr = _1.compileExpression(state, exp); return state.exitPrecedingStatementContextAndJoin() + "_exports._default = " + expStr + ";\n"; } } } return ""; } exports.compileExportAssignment = compileExportAssignment; //# sourceMappingURL=module.js.map