11#!/usr/bin/env node
22
33import * 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
7677function 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 ) ) ;
0 commit comments