Skip to content

Commit 6f8d154

Browse files
committed
Split compiling and CLI parsing
Cleaned up parser
1 parent 2034249 commit 6f8d154

File tree

4 files changed

+181
-152
lines changed

4 files changed

+181
-152
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ More detailed documentation and info on writing declarations can be found [on th
2020

2121
`tstl -p path/to/tsconfig.json`
2222

23-
**Options**
23+
**Usage**
2424
```
2525
tstl [options] [files...]
2626
@@ -32,12 +32,12 @@ NOTES:
3232
- Options in tsconfig.json are prioritized.
3333
3434
Options:
35-
--help Show help [boolean]
36-
--version Show version number [boolean]
37-
--lt, --luaTarget Specify Lua target version.
38-
[string] [choices: "JIT", "5.1", "5.2", "5.3"] [default: "JIT"]
39-
--ah, --addHeader Specify if a header will be added to compiled files.
40-
[boolean] [default: true]
35+
--lt, --luaTarget Specify Lua target version.
36+
[string] [choices: "JIT", "5.3"] [default: "JIT"]
37+
--ah, --addHeader Specify if a header will be added to compiled files.
38+
[boolean] [default: false]
39+
-h, --help Show help [boolean]
40+
-v, --version Show version number [boolean]
4141
4242
Examples:
4343
tstl path/to/file.ts [...] Compile files

src/CommandLineParser.ts

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import * as ts from "typescript";
2+
import * as yargs from 'yargs';
3+
import * as fs from "fs";
4+
import * as path from "path";
5+
6+
// ES6 syntax broken
7+
import dedent = require("dedent");
8+
9+
export interface CompilerOptions extends ts.CompilerOptions {
10+
addHeader?: boolean;
11+
luaTarget?: string;
12+
}
13+
14+
export interface ParsedCommandLine extends ts.ParsedCommandLine {
15+
options: CompilerOptions;
16+
}
17+
18+
const optionDeclarations: { [key: string]: yargs.Options } = {
19+
'lt': {
20+
alias: 'luaTarget',
21+
choices: ['JIT', '5.3'],
22+
default: 'JIT',
23+
describe: 'Specify Lua target version.',
24+
type: 'string'
25+
},
26+
'ah': {
27+
alias: 'addHeader',
28+
describe: 'Specify if a header will be added to compiled files.',
29+
default: false,
30+
type: 'boolean'
31+
},
32+
'h': {
33+
alias: 'help'
34+
},
35+
'v': {
36+
alias: 'version'
37+
}
38+
};
39+
40+
41+
/**
42+
* Pares the supplied arguments.
43+
* The result will include arguments supplied via CLI and arguments from tsconfig.
44+
*/
45+
export function parseCommandLine(args: ReadonlyArray<string>) : ParsedCommandLine {
46+
const argv = yargs
47+
.usage(dedent(`tstl [options] [files...]
48+
49+
In addition to the options listed below you can also pass options for the typescript compiler (For a list of options use tsc -h).
50+
51+
NOTES:
52+
- The tsc options might have no effect.
53+
- Options in tsconfig.json are prioritized.`))
54+
.example('tstl path/to/file.ts [...]', 'Compile files')
55+
.example('tstl -p path/to/tsconfig.json', 'Compile project')
56+
.options(optionDeclarations)
57+
.argv;
58+
59+
let commandLine = ts.parseCommandLine(args);
60+
61+
// Run diagnostics to check for invalid tsc/tstl options
62+
runDiagnostics(commandLine);
63+
64+
if (commandLine.options.project) {
65+
findConfigFile(commandLine);
66+
let configPath = commandLine.options.project;
67+
let configContents = fs.readFileSync(configPath).toString();
68+
const configJson = ts.parseConfigFileTextToJson(configPath, configContents);
69+
commandLine = ts.parseJsonConfigFileContent(configJson.config, ts.sys, path.dirname(configPath), commandLine.options);
70+
}
71+
72+
// Add compiler options that are ignored by TS parsers
73+
for (const arg in commandLine.raw) {
74+
// we dont have to check if the options is vlaid becuase we already validated at this point.
75+
if (getOptionNameMap()[arg]) {
76+
commandLine.options[arg] = commandLine.raw[arg];
77+
}
78+
}
79+
80+
if (commandLine.options.project && !commandLine.options.rootDir) {
81+
commandLine.options.rootDir = path.dirname(commandLine.options.project);
82+
}
83+
84+
if (!commandLine.options.rootDir) {
85+
commandLine.options.rootDir = process.cwd();
86+
}
87+
88+
if (!commandLine.options.outDir) {
89+
commandLine.options.outDir = commandLine.options.rootDir;
90+
}
91+
92+
// Run diagnostics again to check for errors in tsconfig
93+
runDiagnostics(commandLine);
94+
95+
return commandLine;
96+
}
97+
98+
interface OptionNameMap {
99+
[key: string]: boolean
100+
}
101+
102+
let optionNameMapCache: OptionNameMap;
103+
104+
/**
105+
* Gets a list of valid tstl options including aliases
106+
* That can be used for arguemtn checking.
107+
*/
108+
function getOptionNameMap(): OptionNameMap{
109+
if (optionNameMapCache) {
110+
return optionNameMapCache;
111+
}
112+
let optNameMap: OptionNameMap = {};
113+
for (let key in optionDeclarations) {
114+
optNameMap[key] = true
115+
let alias = optionDeclarations[key].alias;
116+
if (typeof alias === "string") {
117+
optNameMap[alias] = true;
118+
} else {
119+
alias.forEach((val) => optNameMap[val] = true);
120+
}
121+
}
122+
optionNameMapCache = optNameMap;
123+
return optNameMap;
124+
}
125+
126+
/** Check the current state of the ParsedCommandLine for errors */
127+
function runDiagnostics(commandLine: ts.ParsedCommandLine) {
128+
const tsInvalidCompilerOptionErrorCode = 5023;
129+
130+
if (commandLine.errors.length !== 0) {
131+
commandLine.errors.forEach((err) => {
132+
let ignore = false;
133+
// Ignore errors caused by tstl specific compiler options
134+
if (err.code == tsInvalidCompilerOptionErrorCode) {
135+
for (const key in getOptionNameMap()) {
136+
if (err.messageText.toString().indexOf(key) !== -1) {
137+
ignore = true;
138+
}
139+
}
140+
}
141+
142+
if (!ignore) {
143+
console.log(`error TS${err.code}: ${err.messageText}`);
144+
process.exit(1);
145+
}
146+
});
147+
}
148+
}
149+
150+
/** Find configFile, function form ts api seems to be broken? */
151+
function findConfigFile(commandLine: ts.ParsedCommandLine) {
152+
let configPath = path.isAbsolute(commandLine.options.project) ? commandLine.options.project : path.join(process.cwd(), commandLine.options.project);
153+
if (fs.statSync(configPath).isDirectory()) {
154+
configPath = path.join(configPath, 'tsconfig.json');
155+
} else if (fs.statSync(configPath).isFile() && path.extname(configPath) === ".ts") {
156+
// Search for tsconfig upwards in directory hierarchy starting from the file path
157+
let dir = path.dirname(configPath).split(path.sep);
158+
for (let i = dir.length; i > 0; i--) {
159+
const searchPath = dir.slice(0, i).join("/") + path.sep + "tsconfig.json";
160+
161+
// If tsconfig.json was found, stop searching
162+
if (ts.sys.fileExists(searchPath)) {
163+
configPath = searchPath;
164+
break;
165+
}
166+
}
167+
}
168+
commandLine.options.project = configPath;
169+
}

src/Compiler.ts

Lines changed: 3 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ import dedent = require("dedent")
1010

1111
import { LuaTranspiler, TranspileError } from "./Transpiler";
1212
import { TSHelper as tsEx } from "./TSHelper";
13-
14-
interface CompilerOptions extends ts.CompilerOptions {
15-
addHeader?: boolean;
16-
luaTarget?: string;
17-
}
13+
import { CompilerOptions, parseCommandLine } from "./CommandLineParser";
1814

1915
function compile(fileNames: string[], options: CompilerOptions): void {
2016
let program = ts.createProgram(fileNames, options);
@@ -39,14 +35,6 @@ function compile(fileNames: string[], options: CompilerOptions): void {
3935
process.exit(1);
4036
}
4137

42-
if (!options.rootDir) {
43-
options.rootDir = process.cwd();
44-
}
45-
46-
if (!options.outDir) {
47-
options.outDir = options.rootDir;
48-
}
49-
5038
program.getSourceFiles().forEach(sourceFile => {
5139
if (!sourceFile.isDeclarationFile) {
5240
try {
@@ -95,134 +83,5 @@ function compile(fileNames: string[], options: CompilerOptions): void {
9583
});
9684
}
9785

98-
function printAST(node: ts.Node, indent: number) {
99-
let indentStr = "";
100-
for (let i = 0; i < indent; i++) indentStr += " ";
101-
102-
console.log(indentStr + tsEx.enumName(node.kind, ts.SyntaxKind));
103-
node.forEachChild(child => printAST(child, indent + 1));
104-
}
105-
106-
// Polyfill for report diagnostics
107-
function logError(commandLine: ts.ParsedCommandLine, tstlOptionKeys: ReadonlyArray<string>) {
108-
const tsInvalidCompilerOptionErrorCode = 5023;
109-
let ignoredErrorCount = 0;
110-
111-
if (commandLine.errors.length !== 0) {
112-
commandLine.errors.forEach((err) => {
113-
// Ignore errors caused by tstl specific compiler options
114-
if (err.code == tsInvalidCompilerOptionErrorCode) {
115-
for (const key of tstlOptionKeys) {
116-
if (err.messageText.toString().indexOf(key) != -1) {
117-
ignoredErrorCount += 1;
118-
}
119-
}
120-
}
121-
else {
122-
console.log(err.messageText);
123-
}
124-
});
125-
if (commandLine.errors.length > ignoredErrorCount) {
126-
process.exit(1);
127-
}
128-
}
129-
}
130-
131-
function executeCommandLine(args: ReadonlyArray<string>) {
132-
const tstlOptions: {[key: string]: yargs.Options} = {
133-
'lt': {
134-
alias: 'luaTarget',
135-
choices: ['JIT', '5.1', '5.2', '5.3'],
136-
default: 'JIT',
137-
describe: 'Specify Lua target version.',
138-
type: 'string'
139-
},
140-
'ah': {
141-
alias: 'addHeader',
142-
describe: 'Specify if a header will be added to compiled files.',
143-
default: true,
144-
type: 'boolean'
145-
}
146-
};
147-
148-
const tstlOptionKeys = [];
149-
for (let key in tstlOptions) {
150-
let optionName = key;
151-
if (tstlOptions[key].alias) {
152-
optionName = tstlOptions[key].alias as string;
153-
}
154-
155-
tstlOptionKeys.push(optionName);
156-
}
157-
158-
const argv = yargs
159-
.usage(dedent(`tstl [options] [files...]
160-
161-
In addition to the options listed below you can also pass options for the typescript compiler (For a list of options use tsc -h).
162-
163-
NOTES:
164-
- The tsc options might have no effect.
165-
- Options in tsconfig.json are prioritized.`))
166-
.example('tstl path/to/file.ts [...]', 'Compile files')
167-
.example('tstl -p path/to/tsconfig.json', 'Compile project')
168-
.options(tstlOptions)
169-
.argv;
170-
171-
let commandLine = ts.parseCommandLine(args);
172-
173-
logError(commandLine, tstlOptionKeys);
174-
175-
// Add tstl CLI options
176-
for (const key of tstlOptionKeys) {
177-
commandLine.options[key] = argv[key];
178-
}
179-
180-
let configPath;
181-
if (commandLine.options.project) {
182-
configPath = path.isAbsolute(commandLine.options.project) ? commandLine.options.project : path.join(process.cwd(), commandLine.options.project);
183-
if (fs.statSync(configPath).isDirectory()) {
184-
configPath = path.join(configPath, 'tsconfig.json');
185-
} else if (fs.statSync(configPath).isFile() && path.extname(configPath) === ".ts") {
186-
// Search for tsconfig upwards in directory hierarchy starting from the file path
187-
let dir = path.dirname(configPath).split(path.sep);
188-
let found = false;
189-
for (let i = dir.length; i > 0; i--) {
190-
const searchPath = dir.slice(0, i).join("/") + path.sep + "tsconfig.json";
191-
192-
// If tsconfig.json was found, stop searching
193-
if (ts.sys.fileExists(searchPath)) {
194-
configPath = searchPath;
195-
found = true;
196-
break;
197-
}
198-
}
199-
200-
if (!found) {
201-
console.error("Tried to build project but could not find tsconfig.json!");
202-
process.exit(1);
203-
}
204-
}
205-
commandLine.options.project = configPath;
206-
let configContents = fs.readFileSync(configPath).toString();
207-
const configJson = ts.parseConfigFileTextToJson(configPath, configContents);
208-
commandLine = ts.parseJsonConfigFileContent(configJson.config, ts.sys, path.dirname(configPath), commandLine.options);
209-
210-
// Add compiler options that are ignored by TS parsers
211-
// Options supplied in tsconfig are prioritized to allow for CLI defaults
212-
for (const compilerOption in commandLine.raw.compilerOptions) {
213-
if (tstlOptionKeys.indexOf(compilerOption) != -1) {
214-
commandLine.options[compilerOption] = commandLine.raw.compilerOptions[compilerOption];
215-
}
216-
}
217-
}
218-
219-
if (configPath && !commandLine.options.rootDir) {
220-
commandLine.options.rootDir = path.dirname(configPath);
221-
}
222-
223-
logError(commandLine, tstlOptionKeys);
224-
225-
compile(commandLine.fileNames, commandLine.options);
226-
}
227-
228-
executeCommandLine(process.argv.slice(2));
86+
let commandLine = parseCommandLine(process.argv.slice(2));
87+
compile(commandLine.fileNames, commandLine.options)

src/Transpiler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as ts from "typescript";
22

33
import { TSHelper as tsEx } from "./TSHelper";
44
import { ForHelper } from "./ForHelper";
5+
import { CompilerOptions } from "./CommandLineParser";
56

67
import * as path from "path";
78

@@ -22,7 +23,7 @@ export class LuaTranspiler {
2223
public static AvailableLuaTargets = [Target.LuaJIT, Target.Lua53];
2324

2425
// Transpile a source file
25-
static transpileSourceFile(node: ts.SourceFile, checker: ts.TypeChecker, options: ts.CompilerOptions): string {
26+
static transpileSourceFile(node: ts.SourceFile, checker: ts.TypeChecker, options: CompilerOptions): string {
2627
let transpiler = new LuaTranspiler(checker, options, node);
2728

2829
const header = options.addHeader ? "--=======================================================================================\n"

0 commit comments

Comments
 (0)