Something went wrong. Try again.
A native-speed rewrite of the roblox-ts compiler in golang
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221import path from "path";
import { D_EXT, DTS_EXT, INDEX_NAME, INIT_NAME, LUA_EXT, LUAU_EXT, TRANSFORMED_EXT, TS_EXT, TSX_EXT,} from "./constants";import { assert } from "./util/assert";
class PathInfo { private constructor( public dirName: string, public fileName: string, public exts: Array<string>, ) {}
public static from(filePath: string) { const dirName = path.dirname(filePath); const parts = filePath.slice(dirName.length + path.sep.length).split("."); const fileName = parts.shift(); const exts = parts.map(v => "." + v); assert(fileName !== undefined); return new PathInfo(dirName, fileName, exts); }
public extsPeek(depth = 0): string | undefined { return this.exts[this.exts.length - (depth + 1)]; }
public join(): string { return path.join(this.dirName, [this.fileName, ...this.exts].join("")); }}
export class PathTranslator { constructor( public readonly rootDir: string, public readonly outDir: string, public readonly buildInfoOutputPath: string | undefined, public readonly declaration: boolean, public readonly useLuauExtension = false, ) {}
private getLuauExt() { return this.useLuauExtension ? LUAU_EXT : LUA_EXT; }
private makeRelativeFactory(from = this.rootDir, to = this.outDir) { return (pathInfo: PathInfo) => path.join(to, path.relative(from, pathInfo.join())); }
/** * Maps an input path to an output path * - `.ts(x)` && !`.d.ts(x)` -> `.lua(u)` * - `index` -> `init` * - `src/*` -> `out/*` */ public getOutputPath(filePath: string) { const makeRelative = this.makeRelativeFactory(); const pathInfo = PathInfo.from(filePath);
if ((pathInfo.extsPeek() === TS_EXT || pathInfo.extsPeek() === TSX_EXT) && pathInfo.extsPeek(1) !== D_EXT) { pathInfo.exts.pop(); // pop .ts(x)
// index -> init if (pathInfo.fileName === INDEX_NAME) { pathInfo.fileName = INIT_NAME; }
pathInfo.exts.push(this.getLuauExt()); }
return makeRelative(pathInfo); }
/** * Maps an input path to an output .d.ts path * - `.ts(x)` && !`.d.ts(x)` -> `.d.ts` * - `src/*` -> `out/*` */ public getOutputDeclarationPath(filePath: string) { const makeRelative = this.makeRelativeFactory(); const pathInfo = PathInfo.from(filePath);
if ((pathInfo.extsPeek() === TS_EXT || pathInfo.extsPeek() === TSX_EXT) && pathInfo.extsPeek(1) !== D_EXT) { pathInfo.exts.pop(); // pop .ts(x) pathInfo.exts.push(DTS_EXT); }
return makeRelative(pathInfo); }
/** * Maps an input path to an output .transformed.ts(x) path * - `.ts(x)` -> `.transformed.ts(x)` * - `src/*` -> `out/*` */ public getOutputTransformedPath(filePath: string) { const makeRelative = this.makeRelativeFactory(); const pathInfo = PathInfo.from(filePath);
if (pathInfo.extsPeek(1) === D_EXT) { // Transformers currently never get a chance to transform .d.ts files // But case is covered anyways pathInfo.exts.splice(pathInfo.exts.length - 2, 0, TRANSFORMED_EXT); } else { // splice with deleteCount 0 = insert at index, shift up further elements pathInfo.exts.splice(pathInfo.exts.length - 1, 0, TRANSFORMED_EXT); }
return makeRelative(pathInfo); }
/** * Maps an output path to possible import paths * - `.lua(u)` -> `.ts(x)` * - `init` -> `index` * - `out/*` -> `src/*` */ public getInputPaths(filePath: string) { const makeRelative = this.makeRelativeFactory(this.outDir, this.rootDir); const possiblePaths = new Array<string>(); const pathInfo = PathInfo.from(filePath);
// index.*.lua(u) cannot come from a .ts file if (pathInfo.extsPeek() === this.getLuauExt() && pathInfo.fileName !== INDEX_NAME) { pathInfo.exts.pop(); // pop .lua(u)
// ts pathInfo.exts.push(TS_EXT); possiblePaths.push(makeRelative(pathInfo)); pathInfo.exts.pop();
// tsx pathInfo.exts.push(TSX_EXT); possiblePaths.push(makeRelative(pathInfo)); pathInfo.exts.pop();
// init -> index if (pathInfo.fileName === INIT_NAME) { const originalFileName = pathInfo.fileName; pathInfo.fileName = INDEX_NAME;
// index.*.ts pathInfo.exts.push(TS_EXT); possiblePaths.push(makeRelative(pathInfo)); pathInfo.exts.pop();
// index.*.tsx pathInfo.exts.push(TSX_EXT); possiblePaths.push(makeRelative(pathInfo)); pathInfo.exts.pop();
pathInfo.fileName = originalFileName; }
pathInfo.exts.push(this.getLuauExt()); }
if (this.declaration) { if ((pathInfo.extsPeek() === TS_EXT || pathInfo.extsPeek() === TSX_EXT) && pathInfo.extsPeek(1) === D_EXT) { const tsExt = pathInfo.exts.pop(); // pop .ts(x) assert(tsExt); pathInfo.exts.pop(); // pop .d
// .ts pathInfo.exts.push(TS_EXT); possiblePaths.push(makeRelative(pathInfo)); pathInfo.exts.pop();
// .tsx pathInfo.exts.push(TSX_EXT); possiblePaths.push(makeRelative(pathInfo)); pathInfo.exts.pop();
pathInfo.exts.push(D_EXT); pathInfo.exts.push(tsExt); } }
possiblePaths.push(makeRelative(pathInfo)); return possiblePaths; }
/** * Maps a src path to an import path. * Import paths are passed to RojoResolver and used virtually to resolve RbxPaths. * Because of this, the import path may not actually exist. * - `.d.ts(x)` -> `.ts(x)` -> `.lua(u)` * - `index` -> `init` */ public getImportPath(filePath: string, isNodeModule = false) { const makeRelative = this.makeRelativeFactory(); const pathInfo = PathInfo.from(filePath);
if (pathInfo.extsPeek() === TS_EXT || pathInfo.extsPeek() === TSX_EXT) { pathInfo.exts.pop(); // pop .ts(x) if (pathInfo.extsPeek() === D_EXT) { pathInfo.exts.pop(); // pop .d }
// index -> init if (pathInfo.fileName === INDEX_NAME) { pathInfo.fileName = INIT_NAME; }
pathInfo.exts.push(this.getLuauExt()); // push .lua(u) }
// inside of node_modules, we assume compiled file is sibling of filePath // outside, we check relative to outDir return isNodeModule ? pathInfo.join() : makeRelative(pathInfo); }}