Skip to content

Commit c9005c6

Browse files
authored
Merge pull request #70 from Perryvw/cli-rework
CLI Rework
2 parents 2034249 + 818995f commit c9005c6

18 files changed

+298
-296
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ coverage/
1414

1515
# IDEA IDEs
1616
.idea/
17+
18+
typescript_lualib.lua

README.md

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

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

23-
**Options**
24-
```
25-
tstl [options] [files...]
26-
27-
In addition to the options listed below you can also pass options for the
28-
typescript compiler (For a list of options use tsc -h).
29-
30-
NOTES:
31-
- The tsc options might have no effect.
32-
- Options in tsconfig.json are prioritized.
33-
34-
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]
41-
42-
Examples:
43-
tstl path/to/file.ts [...] Compile files
44-
tstl -p path/to/tsconfig.json Compile project
45-
```
46-
4723
**Example tsconfig.json**
4824
```
4925
{

src/CommandLineParser.ts

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

src/Compiler.ts

Lines changed: 7 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,10 @@
33
import * as ts from "typescript";
44
import * as fs from "fs";
55
import * as path from "path";
6-
import * as yargs from 'yargs'
7-
8-
// ES6 syntax broken
9-
import dedent = require("dedent")
106

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

1911
function compile(fileNames: string[], options: CompilerOptions): void {
2012
let program = ts.createProgram(fileNames, options);
@@ -39,14 +31,6 @@ function compile(fileNames: string[], options: CompilerOptions): void {
3931
process.exit(1);
4032
}
4133

42-
if (!options.rootDir) {
43-
options.rootDir = process.cwd();
44-
}
45-
46-
if (!options.outDir) {
47-
options.outDir = options.rootDir;
48-
}
49-
5034
program.getSourceFiles().forEach(sourceFile => {
5135
if (!sourceFile.isDeclarationFile) {
5236
try {
@@ -83,146 +67,13 @@ function compile(fileNames: string[], options: CompilerOptions): void {
8367
});
8468

8569
// Copy lualib to target dir
86-
// This isnt run in sync because copyFileSync wont report errors.
87-
fs.copyFile(path.resolve(__dirname, "../dist/lualib/typescript.lua"), path.join(options.outDir, "typescript_lualib.lua"), (err: NodeJS.ErrnoException) => {
88-
if (err) {
89-
console.log("ERROR: copying lualib to output.");
90-
process.exit(1);
91-
}
92-
else {
93-
process.exit(0);
94-
}
95-
});
96-
}
97-
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-
}
70+
fs.copyFileSync(path.resolve(__dirname, "../dist/lualib/typescript.lua"), path.join(options.outDir, "typescript_lualib.lua"));
12971
}
13072

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);
73+
export function execCommandLine(argv?: string[]) {
74+
argv = argv ? argv : process.argv.slice(2);
75+
let commandLine = parseCommandLine(argv);
76+
compile(commandLine.fileNames, commandLine.options)
22677
}
22778

228-
executeCommandLine(process.argv.slice(2));
79+
execCommandLine();

0 commit comments

Comments
 (0)