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
JavaScript
;
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