Skip to content

Commit 34b0301

Browse files
authored
Reworked CLI (#37)
* Reworked CLI * Updated project path handling & Readme * Update Readme * Fixed transpiler throwing on the wrong String call
1 parent 681780c commit 34b0301

File tree

5 files changed

+174
-57
lines changed

5 files changed

+174
-57
lines changed

README.md

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
11
# TypescriptToLua
2-
Typescript to lua transpiler.
3-
4-
[![Build Status](https://travis-ci.org/Perryvw/TypescriptToLua.svg?branch=master)](https://travis-ci.org/Perryvw/TypescriptToLua)
5-
[![Coverage](https://codecov.io/gh/perryvw/typescripttolua/branch/master/graph/badge.svg)](https://codecov.io/gh/perryvw/typescripttolua)
2+
[![Build Status](https://travis-ci.org/Perryvw/TypescriptToLua.svg?branch=master)](https://travis-ci.org/Perryvw/TypescriptToLua) [![Coverage](https://codecov.io/gh/perryvw/typescripttolua/branch/master/graph/badge.svg)](https://codecov.io/gh/perryvw/typescripttolua)
63

4+
Typescript to lua transpiler.
75

86
## Usage Guide
97

8+
**Install**
9+
1010
`npm install -g typescript-to-lua`
1111

12-
`tstl path/to-my-file.ts`
12+
**Compile Files**
13+
14+
`tstl path/to-my-file.ts path/to-my-other-file.ts`
15+
16+
**Compile Projects**
17+
18+
`tstl -p path/to-my/tsconfig.json`
19+
20+
**Options**
21+
```
22+
Syntax: tstl [options] [files...]
23+
24+
In addtion to the options listed below you can also pass options for the
25+
typescript compiler (For a list of options use tsc -h). NOTE: The tsc options
26+
might have no effect.
27+
28+
Options:
29+
--version Show version number
30+
--luaTarget, -l Specify Lua target version: 'JIT' (Default), '5.1', '5.2',
31+
'5.3'
32+
--project, -p Compile the project given the path to its configuration file,
33+
or to a folder with a 'tsconfig.json'
34+
--help Show this message
35+
```
1336

1437
**Optionally:**
1538
Add the lualib files from dist/ to your project. This helper library unlocks additional typescript functions:
@@ -18,11 +41,6 @@ Add the lualib files from dist/ to your project. This helper library unlocks add
1841
- Includes lua `Map<S,T>` and `Set<T>` implementations
1942
Add `require("typescript")` in your code code if you want to use the lualib functionality.
2043

21-
### Transpiling a TypeScript project to Lua
22-
The compiler will automatically try to find a typescript configuration file `tsconfig.json` in the files. If found it will transpile all TypeScript files in subdirectories of the project.
23-
24-
**To prevent accidental compilation to Lua, you are required to add a `"target": "lua"` entry in your tsconfig compilerOptions.**
25-
2644
## Sublime Text integration
2745
This compiler works great in combination with the [Sublime Text Typescript plugin](https://github.com/Microsoft/TypeScript-Sublime-Plugin) (available through the package manager as `TypeScript`).
2846

@@ -33,7 +51,7 @@ To add the option to build with the Lua transpiler instead of the regular typesc
3351

3452
```
3553
{
36-
"cmd": ["tstl", "$file"]
54+
"cmd": ["tstl", "-p", "$file"]
3755
}
3856
```
3957
Save this in your Sublime settings as a `TypeScriptToLua.sublime-build`. You can now select the TypeScriptToLua build system in `Tools > Build System` to build using the normal hotkey (`ctrl+B`), or if you have multiple TypeScript projects open, you can choose your compiler before building by pressing `ctrl+shift+B`.

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"tstl": "./dist/Compiler.js"
1919
},
2020
"dependencies": {
21+
"minimist": "^1.2.0",
2122
"typescript": "^2.7.2"
2223
},
2324
"devDependencies": {

src/Compiler.ts

Lines changed: 129 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
#!/usr/bin/env node
22

33
import * as ts from "typescript";
4-
import {readFileSync,writeFileSync} from "fs";
4+
import * as fs from "fs";
5+
import * as path from "path";
56

6-
import {LuaTranspiler, TranspileError} from "./Transpiler";
7-
import {TSHelper as tsEx} from "./TSHelper";
7+
// ES6 syntax broken
8+
import minimist = require("minimist")
9+
import dedent = require("dedent")
810

9-
function compile(fileNames: string[], options: ts.CompilerOptions, projectRoot: string): void {
10-
// Verify target
11-
if ((<string><any>options.target) != "lua") {
12-
console.error("Wrong compilation target! Add \"target\": \"lua\" to your tsconfig.json!");
13-
process.exit(1);
14-
}
11+
import { LuaTranspiler, TranspileError } from "./Transpiler";
12+
import { TSHelper as tsEx } from "./TSHelper";
1513

14+
function compile(fileNames: string[], options: ts.CompilerOptions): void {
1615
let program = ts.createProgram(fileNames, options);
1716
let checker = program.getTypeChecker();
1817

1918
// Get all diagnostics, ignore unsupported extension
2019
const diagnostics = ts.getPreEmitDiagnostics(program).filter(diag => diag.code != 6054);
2120
diagnostics.forEach(diagnostic => {
22-
if (diagnostic.file) {
21+
if (diagnostic.file) {
2322
let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
2423
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
2524
console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
@@ -37,22 +36,24 @@ function compile(fileNames: string[], options: ts.CompilerOptions, projectRoot:
3736

3837
program.getSourceFiles().forEach(sourceFile => {
3938
if (!sourceFile.isDeclarationFile) {
40-
// Print AST for debugging
41-
//printAST(sourceFile, 0);
42-
4339
try {
40+
let rootDir = options.rootDir;
41+
if (!rootDir) {
42+
rootDir = process.cwd();
43+
}
44+
4445
// Transpile AST
45-
const addHeader = options.noHeader === true ? false : true;
46-
let lua = LuaTranspiler.transpileSourceFile(sourceFile, checker, addHeader);
47-
let outPath = sourceFile.fileName.substring(0, sourceFile.fileName.lastIndexOf(".")) + ".lua";
46+
let lua = LuaTranspiler.transpileSourceFile(sourceFile, checker, options);
4847

48+
let outPath = sourceFile.fileName;
4949
if (options.outDir) {
50-
var extension = options.outDir;
51-
if (extension[extension.length - 1] != "/") extension = extension + "/";
52-
outPath = outPath.replace(projectRoot + "/", projectRoot + "/" + extension);
53-
console.log(outPath);
50+
outPath = path.join(options.outDir, sourceFile.fileName.replace(rootDir, ""));
5451
}
5552

53+
// change extension
54+
var fileNameLua = path.basename(outPath, path.extname(outPath)) + '.lua';
55+
outPath = path.join(path.dirname(outPath), fileNameLua);
56+
5657
// Write output
5758
ts.sys.writeFile(outPath, lua);
5859
} catch (exception) {
@@ -75,32 +76,118 @@ function compile(fileNames: string[], options: ts.CompilerOptions, projectRoot:
7576

7677
function printAST(node: ts.Node, indent: number) {
7778
let indentStr = "";
78-
for (let i=0;i<indent;i++) indentStr += " ";
79+
for (let i = 0; i < indent; i++) indentStr += " ";
7980

8081
console.log(indentStr + tsEx.enumName(node.kind, ts.SyntaxKind));
8182
node.forEachChild(child => printAST(child, indent + 1));
8283
}
8384

84-
// Try to find tsconfig.json
85-
const filename = process.argv[2].split("\\").join("/");
86-
const filepath = filename.substring(0, filename.lastIndexOf("/"));
87-
let configPath = ts.findConfigFile(filepath, ts.sys.fileExists);
88-
89-
if (configPath) {
90-
configPath = configPath.split("\\").join("/");
91-
const projectRoot = configPath.substring(0, configPath.lastIndexOf("/"));
92-
93-
// Find all files
94-
let files = ts.sys.readDirectory(projectRoot, [".ts"]);
95-
96-
// Read config
97-
let configFile = ts.readConfigFile(configPath, ts.sys.readFile);
98-
if (configFile.error) {
99-
console.error("Error occured:");
100-
console.error(configFile.error);
101-
} else {
102-
compile(files, configFile.config.compilerOptions, projectRoot);
85+
// Removes the option and value form argv
86+
function removeInvalidOptionsFromArgv(commandLine: ReadonlyArray<string>, invalidOptions: ReadonlyArray<string>): ReadonlyArray<string> {
87+
let result = commandLine.slice()
88+
for (let opt of invalidOptions) {
89+
let index = result.indexOf('--' + opt);
90+
if (index === -1) {
91+
index = result.indexOf('-' + opt)
92+
}
93+
if (index !== -1) {
94+
result.splice(index, 2);
95+
}
10396
}
104-
} else {
105-
console.error("Could not find tsconfig.json, place one in your project root!");
97+
return result;
10698
}
99+
100+
// Polyfill for report diagnostics
101+
function logError(commandLine: ts.ParsedCommandLine) {
102+
if (commandLine.errors.length !== 0) {
103+
commandLine.errors.forEach((err) => {
104+
console.log(err.messageText);
105+
});
106+
process.exit(1);
107+
}
108+
}
109+
110+
function executeCommandLine(args: ReadonlyArray<string>) {
111+
// Right now luaTarget, version and help are the only cli options, if more are added we should
112+
// add a more advanced custom parser
113+
var argv = minimist(args, {
114+
string: ['l'],
115+
alias: { h: 'help', v: 'version', l: 'luaTarget' },
116+
default: { luaTarget: 'JIT' },
117+
'--': true,
118+
stopEarly: true,
119+
});
120+
121+
let tstlVersion
122+
try {
123+
tstlVersion = require('../package').version;
124+
} catch (e) {
125+
tstlVersion = "0.0.0";
126+
}
127+
128+
var helpString = dedent(`Version: ${tstlVersion}
129+
Syntax: tstl [options] [files...]
130+
131+
In addtion to the options listed below you can also pass options for the
132+
typescript compiler (For a list of options use tsc -h). NOTE: The tsc options
133+
might have no effect.
134+
135+
Options:
136+
--version Show version number
137+
--luaTarget, -l Specify Lua target version: 'JIT' (Default), '5.1', '5.2',
138+
'5.3'
139+
--project, -p Compile the project given the path to its configuration file,
140+
or to a folder with a 'tsconfig.json'
141+
--help Show this message
142+
`);
143+
if (argv.help) {
144+
console.log(helpString);
145+
process.exit(0);
146+
}
147+
if (argv.version) {
148+
console.log("Version: " + require('../package').version);
149+
}
150+
let validLuaTargets = ['JIT', '5.3', '5.2', '5.1'];
151+
if (!validLuaTargets.some(val => val === argv.luaTarget)) {
152+
console.error(`Invalid lua target valid targets are: ${validLuaTargets.toString()}`);
153+
}
154+
155+
// Remove tstl options otherwise ts will emit an error
156+
let sanitizedArgs = removeInvalidOptionsFromArgv(args, ['l', 'luaTarget'])
157+
158+
let commandLine = ts.parseCommandLine(sanitizedArgs);
159+
160+
logError(commandLine);
161+
162+
commandLine.options.luaTarget = argv.luaTarget;
163+
let configPath;
164+
if (commandLine.options.project) {
165+
configPath = path.isAbsolute(commandLine.options.project) ? commandLine.options.project : path.join(process.cwd(), commandLine.options.project);
166+
if (fs.statSync(configPath).isDirectory()) {
167+
configPath = path.join(configPath, 'tsconfig.json');
168+
} else if (fs.statSync(configPath).isFile() && path.extname(configPath) === ".ts") {
169+
// if supplied project points to a .ts fiel we try to find the config
170+
configPath = ts.findConfigFile(configPath, ts.sys.fileExists);
171+
}
172+
commandLine.options.project = configPath;
173+
let configContents = fs.readFileSync(configPath).toString();
174+
const configJson = ts.parseConfigFileTextToJson(configPath, configContents);
175+
commandLine = ts.parseJsonConfigFileContent(configJson.config, ts.sys, path.dirname(configPath), commandLine.options);
176+
177+
// Append lua target to options array since its ignored by TS parsers
178+
// option supplied on CLI is prioritized
179+
if (argv.luaTarget === "JIT" && commandLine.raw.luaTarget !== argv.luaTarget) {
180+
commandLine.options.luaTarget = commandLine.raw.luaTarget;
181+
}
182+
}
183+
184+
if (configPath && !commandLine.options.rootDir) {
185+
commandLine.options.rootDir = path.dirname(configPath);
186+
}
187+
188+
logError(commandLine);
189+
190+
compile(commandLine.fileNames, commandLine.options);
191+
}
192+
193+
executeCommandLine(process.argv.slice(2));

src/Transpiler.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ export class TranspileError extends Error {
1515

1616
export class LuaTranspiler {
1717
// Transpile a source file
18-
static transpileSourceFile(node: ts.SourceFile, checker: ts.TypeChecker, addHeader: boolean): string {
19-
let transpiler = new LuaTranspiler(checker);
20-
let header = addHeader ? "--=======================================================================================\n"
18+
static transpileSourceFile(node: ts.SourceFile, checker: ts.TypeChecker, options: ts.CompilerOptions): string {
19+
let transpiler = new LuaTranspiler(checker, options);
20+
let header = options.addHeader ? "--=======================================================================================\n"
2121
+ "-- Generated by TypescriptToLua transpiler https://github.com/Perryvw/TypescriptToLua \n"
2222
+ "-- Date: " + new Date().toDateString() + "\n"
2323
+ "--=======================================================================================\n"
@@ -37,15 +37,17 @@ export class LuaTranspiler {
3737

3838
indent: string;
3939
checker: ts.TypeChecker;
40+
options: ts.CompilerOptions
4041
genVarCounter: number;
4142
transpilingSwitch: boolean;
4243
namespace: string[];
4344
importCount: number;
4445
isModule: boolean;
4546

46-
constructor(checker: ts.TypeChecker) {
47+
constructor(checker: ts.TypeChecker, options: ts.CompilerOptions) {
4748
this.indent = "";
4849
this.checker = checker;
50+
this.options = options;
4951
this.genVarCounter = 0;
5052
this.transpilingSwitch = false;
5153
this.namespace = [];
@@ -703,6 +705,10 @@ export class LuaTranspiler {
703705
// TODO at check if compiler options is LUA 5.3
704706
// should throw an exception if codepoint is used sub 5.3
705707

708+
if (<string>identifier.escapedText === "fromCodePoint" && this.options.luaTarget !== '5.3') {
709+
throw new TranspileError(`Unsupported string property ${identifier.escapedText} is only supported for lua 5.3.`, identifier);
710+
}
711+
706712
if (translation[<string>identifier.escapedText]) {
707713
return `${translation[<string>identifier.escapedText]}`;
708714
} else {

0 commit comments

Comments
 (0)