From ef3ba605adbbb280bb1bcf6bd612861df65484ff Mon Sep 17 00:00:00 2001 From: lolleko Date: Fri, 23 Mar 2018 11:19:21 +0100 Subject: [PATCH 1/4] Added TSLint and fixed linter issues --- package-lock.json | 152 ++++++++++- package.json | 7 +- src/CommandLineParser.ts | 101 +++---- src/Compiler.ts | 59 +++-- src/ForHelper.ts | 56 ++-- src/TSHelper.ts | 77 +++--- src/Transpiler.ts | 552 ++++++++++++++++++++++----------------- tslint.json | 30 +++ 8 files changed, 667 insertions(+), 367 deletions(-) create mode 100644 tslint.json diff --git a/package-lock.json b/package-lock.json index 661f524c2..90bcc3b04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,6 +104,17 @@ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", "dev": true }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -139,6 +150,12 @@ "concat-map": "0.0.1" } }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -210,6 +227,21 @@ "urlgrey": "0.4.4" } }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", @@ -219,6 +251,12 @@ "delayed-stream": "1.0.0" } }, + "commander": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -289,6 +327,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "diff": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", + "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", + "dev": true + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -317,6 +361,12 @@ "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", "dev": true }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, "events-to-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", @@ -467,6 +517,12 @@ "ansi-regex": "2.1.1" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", @@ -550,6 +606,12 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, "js-yaml": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", @@ -2381,6 +2443,12 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, "performance-now": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", @@ -2471,12 +2539,27 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -2640,6 +2723,72 @@ "punycode": "1.4.1" } }, + "tslib": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", + "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", + "dev": true + }, + "tslint": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", + "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "builtin-modules": "1.1.1", + "chalk": "2.3.1", + "commander": "2.14.1", + "diff": "3.4.0", + "glob": "7.1.2", + "js-yaml": "3.10.0", + "minimatch": "3.0.4", + "resolve": "1.5.0", + "semver": "5.5.0", + "tslib": "1.9.0", + "tsutils": "2.21.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "5.2.0" + } + }, + "supports-color": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "tsutils": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.21.1.tgz", + "integrity": "sha512-heMkdeQ9iUc90ynfiNo5Y+GXrEEGy86KMvnSTfHO+Q40AuNQ1lZGXcv58fuU9XTUxI0V7YIN9xPN+CO9b1Gn3w==", + "dev": true, + "requires": { + "tslib": "1.9.0" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2659,7 +2808,8 @@ "typescript": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", - "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==" + "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", + "dev": true }, "universalify": { "version": "0.1.1", diff --git a/package.json b/package.json index 3b23c816b..5884158d3 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "repository": "https://github.com/Perryvw/TypescriptToLua", "scripts": { "build": "tsc -p tsconfig.json", - "test": "tsc -p ./test && node ./test/runner.js", + "test": "tslint -p . && tsc -p ./test && node ./test/runner.js", "coverage": "nyc npm test && nyc report --reporter=text-lcov > coverage.lcov && codecov", "coverage-html": "nyc npm test && nyc report --reporter=html", "release-patch": "npm version patch", @@ -23,7 +23,6 @@ "dependencies": { "dedent": "^0.7.0", "fs-extra": "^5.0.0", - "typescript": "^2.7.2", "yargs": "^11.0.0" }, "devDependencies": { @@ -34,6 +33,8 @@ "codecov": "^3.0.0", "deep-equal": "^1.0.1", "lua.vm.js": "0.0.1", - "nyc": "^11.5.0" + "nyc": "^11.5.0", + "tslint": "^5.9.1", + "typescript": "^2.7.2" } } diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index bb40ebf28..7d8f4076f 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -1,10 +1,10 @@ -import * as ts from "typescript"; -import * as yargs from 'yargs'; import * as fs from "fs"; import * as path from "path"; +import * as ts from "typescript"; +import * as yargs from "yargs"; // ES6 syntax broken -const dedent = require("dedent"); +import dedent = require("dedent"); export interface CompilerOptions extends ts.CompilerOptions { addHeader?: boolean; @@ -21,45 +21,44 @@ export class CLIError extends Error { } const optionDeclarations: { [key: string]: yargs.Options } = { - 'luaTarget': { - alias: 'lt', - choices: ['JIT', '5.3'], - default: 'JIT', - describe: 'Specify Lua target version.', - type: 'string' - }, - 'addHeader': { - alias: 'ah', - describe: 'Specify if a header will be added to compiled files.', + addHeader: { + alias: "ah", default: false, - type: 'boolean' + describe: "Specify if a header will be added to compiled files.", + type: "boolean", }, - 'dontRequireLuaLib': { - describe: 'Dont require lua library that enables advanced Typescipt/JS functionality.', + dontRequireLuaLib: { default: false, - type: 'boolean' + describe: "Dont require lua library that enables advanced Typescipt/JS functionality.", + type: "boolean", + }, + luaTarget: { + alias: "lt", + choices: ["JIT", "5.3"], + default: "JIT", + describe: "Specify Lua target version.", + type: "string", }, }; - /** * Pares the supplied arguments. * The result will include arguments supplied via CLI and arguments from tsconfig. */ export function parseCommandLine(args: string[]): ParsedCommandLine { const parsedArgs = yargs - .usage(dedent(`Syntax: tstl [options] [files...] - - In addition to the options listed below you can also pass options for the typescript compiler (For a list of options use tsc -h). - Some tsc options might have no effect.`)) - .example('tstl path/to/file.ts [...]', 'Compile files') - .example('tstl -p path/to/tsconfig.json', 'Compile project') + .usage("Syntax: tstl [options] [files...]\n\n" + + "In addition to the options listed below you can also pass options" + + "for the typescript compiler (For a list of options use tsc -h).\n" + + "Some tsc options might have no effect.") + .example("tstl path/to/file.ts [...]", "Compile files") + .example("tstl -p path/to/tsconfig.json", "Compile project") .wrap(yargs.terminalWidth()) .options(optionDeclarations) .fail((msg, err) => { throw new CLIError(msg); }) - .parse(args) + .parse(args); let commandLine = ts.parseCommandLine(args); @@ -72,10 +71,15 @@ export function parseCommandLine(args: string[]): ParsedCommandLine { // Load config if (commandLine.options.project) { findConfigFile(commandLine); - let configPath = commandLine.options.project; - let configContents = fs.readFileSync(configPath).toString(); + const configPath = commandLine.options.project; + const configContents = fs.readFileSync(configPath).toString(); const configJson = ts.parseConfigFileTextToJson(configPath, configContents); - commandLine = ts.parseJsonConfigFileContent(configJson.config, ts.sys, path.dirname(configPath), commandLine.options); + commandLine = ts.parseJsonConfigFileContent( + configJson.config, + ts.sys, + path.dirname(configPath), + commandLine.options + ); } // Add TSTL options from tsconfig @@ -96,11 +100,13 @@ export function parseCommandLine(args: string[]): ParsedCommandLine { commandLine.options.outDir = commandLine.options.rootDir; } - return commandLine; + return commandLine as ParsedCommandLine; } -function addTSTLOptions(commandLine: ts.ParsedCommandLine, additionalArgs?: yargs.Arguments, forceOverride?: boolean) { - additionalArgs = additionalArgs ? additionalArgs : commandLine.raw +function addTSTLOptions(commandLine: ts.ParsedCommandLine, + additionalArgs?: yargs.Arguments, + forceOverride?: boolean) { + additionalArgs = additionalArgs ? additionalArgs : commandLine.raw; // Add compiler options that are ignored by TS parsers if (additionalArgs) { for (const arg in additionalArgs) { @@ -118,15 +124,17 @@ function runDiagnostics(commandLine: ts.ParsedCommandLine) { if (commandLine.errors.length !== 0) { // Generate a list of valid option names and aliases - let optionNames: string[] = []; - for (let key in optionDeclarations) { - optionNames.push(key); - let alias = optionDeclarations[key].alias; - if (alias) { - if (typeof alias === "string") { - optionNames.push(alias); - } else { - optionNames.push(...alias); + const optionNames: string[] = []; + for (const key in optionDeclarations) { + if (optionDeclarations[key]) { + optionNames.push(key); + const alias = optionDeclarations[key].alias; + if (alias) { + if (typeof alias === "string") { + optionNames.push(alias); + } else { + optionNames.push(...alias); + } } } } @@ -134,7 +142,7 @@ function runDiagnostics(commandLine: ts.ParsedCommandLine) { commandLine.errors.forEach((err) => { let ignore = false; // Ignore errors caused by tstl specific compiler options - if (err.code == tsInvalidCompilerOptionErrorCode) { + if (err.code === tsInvalidCompilerOptionErrorCode) { for (const optionName of optionNames) { if (err.messageText.toString().indexOf(optionName) !== -1) { ignore = true; @@ -153,12 +161,17 @@ function findConfigFile(commandLine: ts.ParsedCommandLine) { if (!commandLine.options.project) { return; } - let configPath = path.isAbsolute(commandLine.options.project) ? commandLine.options.project : path.join(process.cwd(), commandLine.options.project); + let configPath; + if (path.isAbsolute(commandLine.options.project)) { + configPath = commandLine.options.project; + } else { + path.join(process.cwd(), commandLine.options.project); + } if (fs.statSync(configPath).isDirectory()) { - configPath = path.join(configPath, 'tsconfig.json'); + configPath = path.join(configPath, "tsconfig.json"); } else if (fs.statSync(configPath).isFile() && path.extname(configPath) === ".ts") { // Search for tsconfig upwards in directory hierarchy starting from the file path - let dir = path.dirname(configPath).split(path.sep); + const dir = path.dirname(configPath).split(path.sep); for (let i = dir.length; i > 0; i--) { const searchPath = dir.slice(0, i).join("/") + path.sep + "tsconfig.json"; diff --git a/src/Compiler.ts b/src/Compiler.ts index 71e04230f..836ca41c4 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -1,52 +1,57 @@ #!/usr/bin/env node -import * as ts from "typescript"; import * as fs from "fs"; import * as path from "path"; +import * as ts from "typescript"; +import { CompilerOptions, parseCommandLine } from "./CommandLineParser"; import { LuaTranspiler, TranspileError } from "./Transpiler"; import { TSHelper as tsEx } from "./TSHelper"; -import { CompilerOptions, parseCommandLine } from "./CommandLineParser"; function compile(fileNames: string[], options: CompilerOptions): void { - let program = ts.createProgram(fileNames, options); - let checker = program.getTypeChecker(); + const program = ts.createProgram(fileNames, options); + const checker = program.getTypeChecker(); // Get all diagnostics, ignore unsupported extension - const diagnostics = ts.getPreEmitDiagnostics(program).filter(diag => diag.code != 6054); - diagnostics.forEach(diagnostic => { + const diagnostics = ts.getPreEmitDiagnostics(program).filter((diag) => diag.code !== 6054); + diagnostics.forEach((diagnostic) => { if (diagnostic.file) { - let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); - let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); - console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); - } - else { - console.log(`${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`); + const { line, character } = + diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); + const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); + console.log( + `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}` + ); + } else { + console.log( + `${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}` + ); } }); // If there are errors dont emit - if (diagnostics.filter(diag => diag.category == ts.DiagnosticCategory.Error).length > 0) { + if (diagnostics.filter((diag) => diag.category === ts.DiagnosticCategory.Error).length > 0) { console.log("Stopping compilation process because of errors."); process.exit(1); } - program.getSourceFiles().forEach(sourceFile => { + program.getSourceFiles().forEach((sourceFile) => { if (!sourceFile.isDeclarationFile) { try { - let rootDir = options.rootDir; + const rootDir = options.rootDir; // Transpile AST - let lua = LuaTranspiler.transpileSourceFile(sourceFile, checker, options); + const lua = LuaTranspiler.transpileSourceFile(sourceFile, checker, options); let outPath = sourceFile.fileName; if (options.outDir !== options.rootDir) { - const relativeSourcePath = path.resolve(sourceFile.fileName).replace(path.resolve(rootDir), ""); + const relativeSourcePath = path.resolve(sourceFile.fileName) + .replace(path.resolve(rootDir), ""); outPath = path.join(options.outDir, relativeSourcePath); } // change extension - const fileNameLua = path.basename(outPath, path.extname(outPath)) + '.lua'; + const fileNameLua = path.basename(outPath, path.extname(outPath)) + ".lua"; outPath = path.join(path.dirname(outPath), fileNameLua); // Write output @@ -55,10 +60,11 @@ function compile(fileNames: string[], options: CompilerOptions): void { if (exception.node) { const pos = ts.getLineAndCharacterOfPosition(sourceFile, exception.node.pos); // Graciously handle transpilation errors - console.error("Encountered error parsing file: " + exception.message); - console.error(sourceFile.fileName + " line: " + (1 + pos.line) + " column: " + pos.character); - console.error(exception.stack); - process.exit(1); + throw new Error( + "Encountered error parsing file: " + exception.message + "\n" + + sourceFile.fileName + + " line: " + (1 + pos.line) + " column: " + pos.character + "\n" + + exception.stack); } else { throw exception; } @@ -67,13 +73,16 @@ function compile(fileNames: string[], options: CompilerOptions): void { }); // Copy lualib to target dir - fs.copyFileSync(path.resolve(__dirname, "../dist/lualib/typescript.lua"), path.join(options.outDir, "typescript_lualib.lua")); + fs.copyFileSync( + path.resolve(__dirname, "../dist/lualib/typescript.lua"), + path.join(options.outDir, "typescript_lualib.lua") + ); } export function execCommandLine(argv?: string[]) { argv = argv ? argv : process.argv.slice(2); - let commandLine = parseCommandLine(argv); - compile(commandLine.fileNames, commandLine.options) + const commandLine = parseCommandLine(argv); + compile(commandLine.fileNames, commandLine.options); } execCommandLine(); diff --git a/src/ForHelper.ts b/src/ForHelper.ts index 345c66028..45056306b 100644 --- a/src/ForHelper.ts +++ b/src/ForHelper.ts @@ -1,12 +1,12 @@ import * as ts from "typescript"; -import {TSHelper as tsEx} from "./TSHelper"; import {LuaTranspiler, TranspileError} from "./Transpiler"; +import {TSHelper as tsEx} from "./TSHelper"; export class ForHelper { // Get the ending value of a numeric for loop - static GetForEnd(condition: ts.Expression, transpiler: LuaTranspiler): string { + public static GetForEnd(condition: ts.Expression, transpiler: LuaTranspiler): string { if (ts.isBinaryExpression(condition)) { if (ts.isIdentifier(condition.left)) { // Account for lua 1 indexing @@ -19,7 +19,11 @@ export class ForHelper { case ts.SyntaxKind.GreaterThanToken: return transpiler.transpileExpression(condition.right) + "+1"; default: - throw new TranspileError("Unsupported for-loop condition operator: " + tsEx.enumName(condition.operatorToken, ts.SyntaxKind), condition); + throw new TranspileError( + "Unsupported for-loop condition operator: " + + tsEx.enumName(condition.operatorToken, ts.SyntaxKind), + condition + ); } } else { // Account for lua 1 indexing @@ -32,41 +36,63 @@ export class ForHelper { case ts.SyntaxKind.GreaterThanToken: return transpiler.transpileExpression(condition.left) + "-1"; default: - throw new TranspileError("Unsupported for-loop condition operator: " + tsEx.enumName(condition.operatorToken, ts.SyntaxKind), condition); + throw new TranspileError( + "Unsupported for-loop condition operator: " + + tsEx.enumName(condition.operatorToken, ts.SyntaxKind), + condition + ); } } } else { - throw new TranspileError("Unsupported for-loop condition type: " + tsEx.enumName(condition.kind, ts.SyntaxKind), condition); + throw new TranspileError( + "Unsupported for-loop condition type: " + + tsEx.enumName(condition.kind, ts.SyntaxKind), + condition + ); } } // Get increment step for numeric for loop - static GetForStep(incrementor: ts.Expression, transpiler: LuaTranspiler): string { + public static GetForStep(incrementor: ts.Expression, transpiler: LuaTranspiler): string { switch (incrementor.kind) { case ts.SyntaxKind.PostfixUnaryExpression: case ts.SyntaxKind.PrefixUnaryExpression: - switch ((incrementor).operator) { + const operator = + (incrementor as ts.PostfixUnaryExpression|ts.PrefixUnaryExpression).operator; + switch (operator) { case ts.SyntaxKind.PlusPlusToken: return "1"; case ts.SyntaxKind.MinusMinusToken: return "-1"; default: - throw new TranspileError("Unsupported for-loop increment step: " + tsEx.enumName(incrementor.kind, ts.SyntaxKind), incrementor); + throw new TranspileError( + "Unsupported for-loop increment step: " + + tsEx.enumName(incrementor.kind, ts.SyntaxKind), + incrementor + ); } case ts.SyntaxKind.BinaryExpression: - let value = ts.isIdentifier((incrementor).left) ? - transpiler.transpileExpression((incrementor).right) : - transpiler.transpileExpression((incrementor).left); - switch((incrementor).operatorToken.kind) { + const value = ts.isIdentifier((incrementor as ts.BinaryExpression).left) ? + transpiler.transpileExpression((incrementor as ts.BinaryExpression).right) : + transpiler.transpileExpression((incrementor as ts.BinaryExpression).left); + switch ((incrementor as ts.BinaryExpression).operatorToken.kind) { case ts.SyntaxKind.PlusEqualsToken: return value; case ts.SyntaxKind.MinusEqualsToken: return "-" + value; default: - throw new TranspileError("Unsupported for-loop increment step: " + tsEx.enumName(incrementor.kind, ts.SyntaxKind), incrementor); + throw new TranspileError( + "Unsupported for-loop increment step: " + + tsEx.enumName(incrementor.kind, ts.SyntaxKind), + incrementor + ); } default: - throw new TranspileError("Unsupported for-loop increment step: " + tsEx.enumName(incrementor.kind, ts.SyntaxKind), incrementor); + throw new TranspileError( + "Unsupported for-loop increment step: " + + tsEx.enumName(incrementor.kind, ts.SyntaxKind), + incrementor + ); } } -} \ No newline at end of file +} diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 81102a627..166931a89 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -2,23 +2,23 @@ import * as ts from "typescript"; export class TSHelper { // Get all children of a node, required until microsoft fixes node.getChildren() - static getChildren(node: ts.Node): ts.Node[] { + public static getChildren(node: ts.Node): ts.Node[] { const children: ts.Node[] = []; - node.forEachChild(child => { + node.forEachChild((child) => { children.push(child); }); return children; } // Get children filtered by function and cast to predefined type - static getChildrenOfType(node: ts.Node, typeFilter: (node: ts.Node) => boolean): T[] { - return this.getChildren(node).filter(typeFilter); + public static getChildrenOfType(node: ts.Node, typeFilter: (node: ts.Node) => boolean): T[] { + return this.getChildren(node).filter(typeFilter) as any as T[]; } // Reverse lookup of enum key by value - static enumName(needle, haystack) { - for (var name in haystack) { - if (haystack[name] == needle) { + public static enumName(needle, haystack) { + for (const name in haystack) { + if (haystack[name] === needle) { return name; } } @@ -26,106 +26,107 @@ export class TSHelper { } // Breaks down a mask into all flag names. - static enumNames(mask, haystack) { - let result = [mask]; - for (var name in haystack) { - if ((mask & haystack[name]) != 0 && mask >= haystack[name]) { + public static enumNames(mask, haystack) { + const result = [mask]; + for (const name in haystack) { + if ((mask & haystack[name]) !== 0 && mask >= haystack[name]) { result.push(name); } } return result; } - static containsStatement(statements: ts.NodeArray, kind: ts.SyntaxKind): boolean { - return statements.some(statement => statement.kind === kind); + public static containsStatement(statements: ts.NodeArray, kind: ts.SyntaxKind): boolean { + return statements.some((statement) => statement.kind === kind); } - static isFileModule(sourceFile: ts.SourceFile) { + public static isFileModule(sourceFile: ts.SourceFile) { if (sourceFile) { // Vanilla ts flags files as external module if they have an import or // export statement, we only check for export statements - return sourceFile.statements.some(statement => + return sourceFile.statements.some((statement) => (ts.getCombinedModifierFlags(statement) & ts.ModifierFlags.Export) !== 0 || statement.kind === ts.SyntaxKind.ExportAssignment - || statement.kind === ts.SyntaxKind.ExportDeclaration) + || statement.kind === ts.SyntaxKind.ExportDeclaration); } return false; } - static isStringType(type: ts.Type): boolean { + public static isStringType(type: ts.Type): boolean { return (type.flags & ts.TypeFlags.String) !== 0 || (type.flags & ts.TypeFlags.StringLike) !== 0 - || (type.flags & ts.TypeFlags.StringLiteral) !== 0 + || (type.flags & ts.TypeFlags.StringLiteral) !== 0; } - static isArrayType(type: ts.Type): boolean { + public static isArrayType(type: ts.Type): boolean { return (type.flags & ts.TypeFlags.Object) !== 0 - && (type).symbol - && (type).symbol.escapedName == "Array"; + && (type as ts.ObjectType).symbol + && (type as ts.ObjectType).symbol.escapedName === "Array"; } - static isTupleType(type: ts.Type): boolean { + public static isTupleType(type: ts.Type): boolean { return (type.flags & ts.TypeFlags.Object) !== 0 - && (type).typeArguments !== undefined; + && (type as ts.TypeReference).typeArguments !== undefined; } - static isCompileMembersOnlyEnum(type: ts.Type, checker: ts.TypeChecker): boolean { + public static isCompileMembersOnlyEnum(type: ts.Type, checker: ts.TypeChecker): boolean { return type.symbol && ((type.symbol.flags & ts.SymbolFlags.Enum) !== 0) && type.symbol.getDocumentationComment(checker)[0] !== undefined && this.hasCustomDecorator(type, checker, "!CompileMembersOnly"); } - static isPureAbstractClass(type: ts.Type, checker: ts.TypeChecker): boolean { + public static isPureAbstractClass(type: ts.Type, checker: ts.TypeChecker): boolean { return type.symbol && ((type.symbol.flags & ts.SymbolFlags.Class) !== 0) && this.hasCustomDecorator(type, checker, "!PureAbstract"); } - static isExtensionClass(type: ts.Type, checker: ts.TypeChecker): boolean { + public static isExtensionClass(type: ts.Type, checker: ts.TypeChecker): boolean { return type.symbol - && ((type.symbol.flags & ts.SymbolFlags.Class) != 0) + && ((type.symbol.flags & ts.SymbolFlags.Class) !== 0) && this.hasCustomDecorator(type, checker, "!Extension"); } - static isPhantom(type: ts.Type, checker: ts.TypeChecker): boolean { + public static isPhantom(type: ts.Type, checker: ts.TypeChecker): boolean { return type.symbol && ((type.symbol.flags & ts.SymbolFlags.Namespace) !== 0) && this.hasCustomDecorator(type, checker, "!Phantom"); } - static isTupleReturnFunction(type: ts.Type, checker: ts.TypeChecker): boolean { + public static isTupleReturnFunction(type: ts.Type, checker: ts.TypeChecker): boolean { return type.symbol && ((type.symbol.flags & ts.SymbolFlags.Function) !== 0) && this.hasCustomDecorator(type, checker, "!TupleReturn"); } - static hasCustomDecorator(type: ts.Type, checker: ts.TypeChecker, decorator: string): boolean { + public static hasCustomDecorator(type: ts.Type, checker: ts.TypeChecker, decorator: string): boolean { if (type.symbol) { - var comment = type.symbol.getDocumentationComment(checker); - var decorators = comment.filter(_ => _.kind == "text").map(_ => _.text.trim()).filter(_ => _[0] == "!"); + const comment = type.symbol.getDocumentationComment(checker); + const decorators = + comment.filter((_) => _.kind === "text").map((_) => _.text.trim()).filter((_) => _[0] === "!"); return decorators.indexOf(decorator) > -1; } return false; } // Depth-First-Search up the inheritance tree for the name of the symbol containing the member - static findMemberHolder(type: ts.Type, memberName: ts.__String, typeChecker: ts.TypeChecker): string { + public static findMemberHolder(type: ts.Type, memberName: ts.__String, typeChecker: ts.TypeChecker): string { if (type.symbol.members.has(memberName) || (type.symbol.exports && type.symbol.exports.has(memberName))) { while (this.isExtensionClass(type, typeChecker)) { - type = typeChecker.getBaseTypes(type)[0]; + type = typeChecker.getBaseTypes(type as ts.InterfaceType)[0]; } return type.symbol.name; } else { - for (let parent of typeChecker.getBaseTypes(type)) { - var parentMember = this.findMemberHolder(parent, memberName, typeChecker); - if (parentMember) return parentMember; + for (const parent of typeChecker.getBaseTypes(type as ts.InterfaceType)) { + const parentMember = this.findMemberHolder(parent, memberName, typeChecker); + if (parentMember) { return parentMember; } } } } // Search up until finding a node satisfying the callback - static findFirstNodeAbove(node: ts.Node, callback: (n: ts.Node) => n is T): T { + public static findFirstNodeAbove(node: ts.Node, callback: (n: ts.Node) => n is T): T { let current = node; while (current.parent) { if (callback(current.parent)) { diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 966223e38..b72009f88 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -1,13 +1,13 @@ import * as ts from "typescript"; -import { TSHelper as tsEx } from "./TSHelper"; -import { ForHelper } from "./ForHelper"; import { CompilerOptions } from "./CommandLineParser"; +import { ForHelper } from "./ForHelper"; +import { TSHelper as tsEx } from "./TSHelper"; import * as path from "path"; export class TranspileError extends Error { - node: ts.Node; + public node: ts.Node; constructor(message: string, node: ts.Node) { super(message); this.node = node; @@ -16,21 +16,23 @@ export class TranspileError extends Error { export enum Target { Lua53 = "5.3", - LuaJIT = "JIT" + LuaJIT = "JIT", } export class LuaTranspiler { public static AvailableLuaTargets = [Target.LuaJIT, Target.Lua53]; // Transpile a source file - static transpileSourceFile(node: ts.SourceFile, checker: ts.TypeChecker, options: CompilerOptions): string { - let transpiler = new LuaTranspiler(checker, options, node); - - const header = options.addHeader ? "--=======================================================================================\n" - + "-- Generated by TypescriptToLua transpiler https://github.com/Perryvw/TypescriptToLua \n" - + "-- Date: " + new Date().toDateString() + "\n" - + "--=======================================================================================\n" - : ""; + public static transpileSourceFile(node: ts.SourceFile, + checker: ts.TypeChecker, + options: CompilerOptions): string { + const transpiler = new LuaTranspiler(checker, options, node); + + let header = ""; + if (options.addHeader) { + header = "-- Generated by TypescriptToLua transpiler " + + "https://github.com/Perryvw/TypescriptToLua \n"; + } let result = header; if (!options.dontRequireLuaLib) { // require helper functions @@ -42,20 +44,20 @@ export class LuaTranspiler { } result += transpiler.transpileBlock(node); if (transpiler.isModule) { - result += "return exports\n" + result += "return exports\n"; } return result; } - indent: string; - checker: ts.TypeChecker; - options: ts.CompilerOptions; - genVarCounter: number; - transpilingSwitch: number; - namespace: string[]; - importCount: number; - isModule: boolean; - sourceFile: ts.SourceFile; + public indent: string; + public checker: ts.TypeChecker; + public options: ts.CompilerOptions; + public genVarCounter: number; + public transpilingSwitch: number; + public namespace: string[]; + public importCount: number; + public isModule: boolean; + public sourceFile: ts.SourceFile; constructor(checker: ts.TypeChecker, options: ts.CompilerOptions, sourceFile: ts.SourceFile) { this.indent = ""; @@ -69,26 +71,29 @@ export class LuaTranspiler { this.isModule = tsEx.isFileModule(sourceFile); } - pushIndent(): void { + public pushIndent(): void { this.indent = this.indent + " "; } - popIndent(): void { + public popIndent(): void { this.indent = this.indent.slice(4); } - definitionName(name: string | ts.__String): string { - return this.namespace.concat(name).join("."); + public definitionName(name: string | ts.__String): string { + return this.namespace.concat(name as string).join("."); } - accessPrefix(node?: ts.Node): string { + public accessPrefix(node?: ts.Node): string { return node && this.isModule ? - "local " : "" + "local " : ""; } - makeExport(name: string | ts.__String, node: ts.Node, dummy?: boolean): string { + public makeExport(name: string | ts.__String, node: ts.Node, dummy?: boolean): string { let result: string = ""; - if (node && node.modifiers && (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export)) { + if (node && + node.modifiers && + (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) + ) { if (dummy) { result = this.indent + `exports.${this.definitionName(name)} = {}\n`; } else { @@ -105,26 +110,31 @@ export class LuaTranspiler { return result; } - getImportPath(relativePath: string) { + public getImportPath(relativePath: string) { // Calculate absolute path to import - let absolutePathToImport = path.resolve(path.dirname(this.sourceFile.fileName), relativePath); + const absolutePathToImport = + path.resolve(path.dirname(this.sourceFile.fileName), relativePath); if (this.options.rootDir) { - // Calculate path realtive to project root and replace path.sep with dots (lua doesn't know paths) - return `"${absolutePathToImport.replace(this.options.rootDir, "").replace(new RegExp("\\\\|\/", "g"), ".").slice(1)}"`; + // Calculate path realtive to project root + // and replace path.sep with dots (lua doesn't know paths) + const relativePathToRoot = absolutePathToImport.replace(this.options.rootDir, "") + .replace(new RegExp("\\\\|\/", "g"), ".") + .slice(1); + return `"${relativePathToRoot}"`; } return `"${relativePath.replace(new RegExp("\\\\|\/", "g"), ".")}"`; } // Transpile a block - transpileBlock(node: ts.Node): string { + public transpileBlock(node: ts.Node): string { let result = ""; if (ts.isBlock(node)) { - node.statements.forEach(statement => { + node.statements.forEach((statement) => { result += this.transpileNode(statement); }); } else { - node.forEachChild(child => { + node.forEachChild((child) => { result += this.transpileNode(child); }); } @@ -133,49 +143,55 @@ export class LuaTranspiler { } // Transpile a node of unknown kind. - transpileNode(node: ts.Node): string { - //Ignore declarations - if (tsEx.getChildrenOfType(node, child => child.kind == ts.SyntaxKind.DeclareKeyword).length > 0) return ""; + public transpileNode(node: ts.Node): string { + // Ignore declarations + if (tsEx.getChildrenOfType(node, (child) => + child.kind === ts.SyntaxKind.DeclareKeyword).length > 0 + ) { + return ""; + } switch (node.kind) { case ts.SyntaxKind.ImportDeclaration: - return this.transpileImport(node); + return this.transpileImport(node as ts.ImportDeclaration); case ts.SyntaxKind.ClassDeclaration: - return this.transpileClass(node); + return this.transpileClass(node as ts.ClassDeclaration); case ts.SyntaxKind.ModuleDeclaration: - return this.transpileNamespace(node); + return this.transpileNamespace(node as ts.ModuleDeclaration); case ts.SyntaxKind.ModuleBlock: - return this.transpileBlock(node); + return this.transpileBlock(node as ts.Block); case ts.SyntaxKind.EnumDeclaration: - return this.transpileEnum(node); + return this.transpileEnum(node as ts.EnumDeclaration); case ts.SyntaxKind.FunctionDeclaration: - return this.transpileFunctionDeclaration(node); + return this.transpileFunctionDeclaration(node as ts.FunctionDeclaration); case ts.SyntaxKind.VariableStatement: - return this.indent + this.transpileVariableStatement(node) + "\n"; + return this.indent + + this.transpileVariableStatement(node as ts.VariableStatement) + "\n"; case ts.SyntaxKind.ExpressionStatement: - return this.indent + this.transpileExpression(tsEx.getChildren(node)[0]) + "\n"; + return this.indent + + this.transpileExpression(tsEx.getChildren(node)[0] as ts.Expression) + "\n"; case ts.SyntaxKind.ReturnStatement: - return this.indent + this.transpileReturn(node) + "\n"; + return this.indent + this.transpileReturn(node as ts.ReturnStatement) + "\n"; case ts.SyntaxKind.IfStatement: - return this.transpileIf(node); + return this.transpileIf(node as ts.IfStatement); case ts.SyntaxKind.WhileStatement: - return this.transpileWhile(node); + return this.transpileWhile(node as ts.WhileStatement); case ts.SyntaxKind.DoStatement: - return this.transpileDoStatement(node); + return this.transpileDoStatement(node as ts.DoStatement); case ts.SyntaxKind.ForStatement: - return this.transpileFor(node); + return this.transpileFor(node as ts.ForStatement); case ts.SyntaxKind.ForOfStatement: - return this.transpileForOf(node); + return this.transpileForOf(node as ts.ForOfStatement); case ts.SyntaxKind.ForInStatement: - return this.transpileForIn(node); + return this.transpileForIn(node as ts.ForInStatement); case ts.SyntaxKind.SwitchStatement: - return this.transpileSwitch(node); + return this.transpileSwitch(node as ts.SwitchStatement); case ts.SyntaxKind.BreakStatement: return this.transpileBreak(); case ts.SyntaxKind.TryStatement: - return this.transpileTry(node); + return this.transpileTry(node as ts.TryStatement); case ts.SyntaxKind.ThrowStatement: - return this.transpileThrow(node); + return this.transpileThrow(node as ts.ThrowStatement); case ts.SyntaxKind.ContinueStatement: // Disallow continue throw new TranspileError("Continue is not supported in Lua", node); @@ -189,43 +205,55 @@ export class LuaTranspiler { } } - transpileImport(node: ts.ImportDeclaration): string { + public transpileImport(node: ts.ImportDeclaration): string { const importPath = this.transpileExpression(node.moduleSpecifier); - let importPathWithoutQuotes = importPath.replace(new RegExp("\"", "g"), ""); + const importPathWithoutQuotes = importPath.replace(new RegExp("\"", "g"), ""); if (!node.importClause || !node.importClause.namedBindings) { - throw new TranspileError("Default Imports are not supported, please use named imports instead!", node); + throw new TranspileError( + "Default Imports are not supported, please use named imports instead!", + node + ); } const imports = node.importClause.namedBindings; if (ts.isNamedImports(imports)) { const fileImportTable = path.basename(importPathWithoutQuotes) + this.importCount; - let result = `local ${fileImportTable} = require(${this.getImportPath(importPathWithoutQuotes)})\n`; + const resolvedImportPath = this.getImportPath(importPathWithoutQuotes); + let result = `local ${fileImportTable} = require(${resolvedImportPath})\n`; this.importCount++; - imports.elements.forEach(element => { + imports.elements.forEach((element) => { + const nameText = element.name.escapedText; if (element.propertyName) { - result += `local ${element.name.escapedText} = ${fileImportTable}.${element.propertyName.escapedText}\n`; + result += + `local ${nameText} = ${fileImportTable}.${element.propertyName.escapedText}\n`; } else { - result += `local ${element.name.escapedText} = ${fileImportTable}.${element.name.escapedText}\n`; + result += + `local ${nameText} = ${fileImportTable}.${element.name.escapedText}\n`; } }); return result; } else if (ts.isNamespaceImport(imports)) { - return `local ${imports.name.escapedText} = require(${this.getImportPath(importPathWithoutQuotes)})\n`; + const resolvedImportPath = this.getImportPath(importPathWithoutQuotes); + return `local ${imports.name.escapedText} = require(${resolvedImportPath})\n`; } else { throw new TranspileError("Unsupported import type.", node); } } - transpileNamespace(node: ts.ModuleDeclaration): string { + public transpileNamespace(node: ts.ModuleDeclaration): string { // If phantom namespace just transpile the body as normal if (tsEx.isPhantom(this.checker.getTypeAtLocation(node), this.checker) && node.body) { return this.transpileNode(node.body); } const defName = this.definitionName(node.name.text); - let result = this.indent + this.accessPrefix(node) + `${node.name.text} = ${node.name.text} or {}\n`; + let result = + this.indent + + this.accessPrefix(node) + + `${node.name.text} = ${node.name.text} or {}\n`; + if (this.namespace.length > 0) { result += this.indent + `${defName} = ${node.name.text} or {}\n`; } @@ -243,7 +271,7 @@ export class LuaTranspiler { return result; } - transpileEnum(node: ts.EnumDeclaration): string { + public transpileEnum(node: ts.EnumDeclaration): string { let val = 0; let result = ""; @@ -256,20 +284,22 @@ export class LuaTranspiler { result += this.makeExport(name, node); } - node.members.forEach(member => { + node.members.forEach((member) => { if (member.initializer) { if (ts.isNumericLiteral(member.initializer)) { - val = parseInt(member.initializer.text); + val = parseInt(member.initializer.text, 10); } else { throw new TranspileError("Only numeric initializers allowed for enums.", node); } } if (membersOnly) { - const defName = this.definitionName((member.name).escapedText); + const defName = this.definitionName((member.name as ts.Identifier).escapedText); result += this.indent + `${defName}=${val}\n`; } else { - const defName = this.definitionName(`${node.name.escapedText}.${(member.name).escapedText}`); + const defName = this.definitionName( + `${node.name.escapedText}.${(member.name as ts.Identifier).escapedText}` + ); result += this.indent + `${defName}=${val}\n`; } @@ -278,15 +308,15 @@ export class LuaTranspiler { return result; } - transpileBreak(): string { + public transpileBreak(): string { if (this.transpilingSwitch > 0) { - return ''; + return ""; } else { return this.indent + "break\n"; } } - transpileIf(node: ts.IfStatement): string { + public transpileIf(node: ts.IfStatement): string { const condition = this.transpileExpression(node.expression); let result = this.indent + `if ${condition} then\n`; @@ -304,7 +334,7 @@ export class LuaTranspiler { return result + this.indent + "end\n"; } - transpileWhile(node: ts.WhileStatement): string { + public transpileWhile(node: ts.WhileStatement): string { const condition = this.transpileExpression(node.expression); let result = this.indent + `while ${condition} do\n`; @@ -314,7 +344,7 @@ export class LuaTranspiler { return result + this.indent + "end\n"; } - transpileDoStatement(node: ts.DoStatement): string { + public transpileDoStatement(node: ts.DoStatement): string { let result = this.indent + `repeat\n`; this.pushIndent(); @@ -327,15 +357,15 @@ export class LuaTranspiler { return result; } - transpileFor(node: ts.ForStatement): string { + public transpileFor(node: ts.ForStatement): string { // Get iterator variable - const variable = (node.initializer).declarations[0]; - const identifier = variable.name; + const variable = (node.initializer as ts.VariableDeclarationList).declarations[0]; + const identifier = variable.name as ts.Identifier; // Populate three components of lua numeric for loop: - let start = this.transpileExpression(variable.initializer); - let end = ForHelper.GetForEnd(node.condition, this); - let step = ForHelper.GetForStep(node.incrementor, this); + const start = this.transpileExpression(variable.initializer); + const end = ForHelper.GetForEnd(node.condition, this); + const step = ForHelper.GetForStep(node.incrementor, this); // Add header let result = this.indent + `for ${identifier.escapedText}=${start},${end},${step} do\n`; @@ -348,10 +378,10 @@ export class LuaTranspiler { return result + this.indent + "end\n"; } - transpileForOf(node: ts.ForOfStatement): string { + public transpileForOf(node: ts.ForOfStatement): string { // Get variable identifier - const variable = (node.initializer).declarations[0]; - const identifier = variable.name; + const variable = (node.initializer as ts.VariableDeclarationList).declarations[0]; + const identifier = variable.name as ts.Identifier; // Transpile expression const expression = this.transpileExpression(node.expression); @@ -371,10 +401,10 @@ export class LuaTranspiler { return result + this.indent + "end\n"; } - transpileForIn(node: ts.ForInStatement): string { + public transpileForIn(node: ts.ForInStatement): string { // Get variable identifier - const variable = (node.initializer).declarations[0]; - const identifier = variable.name; + const variable = (node.initializer as ts.VariableDeclarationList).declarations[0] as ts.VariableDeclaration; + const identifier = variable.name as ts.Identifier; // Transpile expression const expression = this.transpileExpression(node.expression); @@ -394,7 +424,7 @@ export class LuaTranspiler { return result + this.indent + "end\n"; } - transpileStatement(node: ts.Statement): string { + public transpileStatement(node: ts.Statement): string { if (ts.isBlock(node)) { return this.transpileBlock(node); } else { @@ -402,13 +432,13 @@ export class LuaTranspiler { } } - transpileSwitch(node: ts.SwitchStatement): string { + public transpileSwitch(node: ts.SwitchStatement): string { const expression = this.transpileExpression(node.expression, true); const clauses = node.caseBlock.clauses; let result = this.indent + "-------Switch statement start-------\n"; - let jumpTableName = "____switch" + this.genVarCounter; + const jumpTableName = "____switch" + this.genVarCounter; this.genVarCounter++; result += this.indent + `local ${jumpTableName} = {}\n`; @@ -417,7 +447,8 @@ export class LuaTranspiler { clauses.forEach((clause, index) => { if (ts.isCaseClause(clause)) { result += this.indent + `-- case:\n`; - result += this.indent + `${jumpTableName}[${this.transpileExpression(clause.expression, true)}] = function()\n`; + result += this.indent + + `${jumpTableName}[${this.transpileExpression(clause.expression, true)}] = function()\n`; } if (ts.isDefaultClause(clause)) { result += this.indent + `-- default:\n`; @@ -426,7 +457,7 @@ export class LuaTranspiler { this.pushIndent(); this.transpilingSwitch++; - clause.statements.forEach(statement => { + clause.statements.forEach((statement) => { result += this.transpileNode(statement); }); this.transpilingSwitch--; @@ -444,7 +475,8 @@ export class LuaTranspiler { if (i !== index && nextClause) { if (ts.isCaseClause(nextClause)) { - result += this.indent + `${jumpTableName}[${this.transpileExpression(nextClause.expression, true)}]()\n`; + result += this.indent + + `${jumpTableName}[${this.transpileExpression(nextClause.expression, true)}]()\n`; } else { result += this.indent + `${jumpTableName}["____default${this.genVarCounter}"]()\n`; } @@ -457,16 +489,20 @@ export class LuaTranspiler { result += this.indent + `end\n`; }); - result += this.indent + `if ${jumpTableName}[${expression}] then ${jumpTableName}[${expression}]()\n`; - result += this.indent + `elseif ${jumpTableName}["____default${this.genVarCounter}"] then ${jumpTableName}["____default${this.genVarCounter}"]() end\n`; - result += this.indent + "--------Switch statement end--------\n"; - - //Increment counter for next switch statement + result += this.indent + + `if ${jumpTableName}[${expression}] then ${jumpTableName}[${expression}]()\n`; + result += this.indent + + `elseif ${jumpTableName}["____default${this.genVarCounter}"] ` + + `then ${jumpTableName}["____default${this.genVarCounter}"]() end\n`; + result += this.indent + + "--------Switch statement end--------\n"; + + // Increment counter for next switch statement this.genVarCounter += clauses.length; return result; } - transpileTry(node: ts.TryStatement): string { + public transpileTry(node: ts.TryStatement): string { let tryFunc = "function()\n"; this.pushIndent(); tryFunc += this.transpileBlock(node.tryBlock); @@ -474,7 +510,7 @@ export class LuaTranspiler { tryFunc += "end"; let catchFunc = "function(e)\nend"; if (node.catchClause && node.catchClause.variableDeclaration) { - let variableName = (node.catchClause.variableDeclaration.name).escapedText; + const variableName = (node.catchClause.variableDeclaration.name as ts.Identifier).escapedText; catchFunc = this.indent + `function(${variableName})\n`; this.pushIndent(); catchFunc += this.transpileBlock(node.catchClause.block); @@ -488,54 +524,58 @@ export class LuaTranspiler { return result; } - transpileThrow(node: ts.ThrowStatement): string { + public transpileThrow(node: ts.ThrowStatement): string { if (ts.isStringLiteral(node.expression)) { return `error("${node.expression.text}")`; } else { - throw new TranspileError("Unsupported throw expression, only string literals are supported", node.expression) + throw new TranspileError( + "Unsupported throw expression, only string literals are supported", + node.expression + ); } } - transpileReturn(node: ts.ReturnStatement): string { + public transpileReturn(node: ts.ReturnStatement): string { if (node.expression) { - // If parent function is a TupleReturn function and return expression is an array literal, leave out brackets. - var declaration = tsEx.findFirstNodeAbove(node, ts.isFunctionDeclaration); + // If parent function is a TupleReturn function + // and return expression is an array literal, leave out brackets. + const declaration = tsEx.findFirstNodeAbove(node, ts.isFunctionDeclaration); if (declaration && tsEx.isTupleReturnFunction(this.checker.getTypeAtLocation(declaration), this.checker) && ts.isArrayLiteralExpression(node.expression)) { - return "return " + node.expression.elements.map(elem => this.transpileExpression(elem)).join(","); + return "return " + node.expression.elements.map((elem) => this.transpileExpression(elem)).join(","); } // Otherwise just do a normal return return "return " + this.transpileExpression(node.expression); } else { - return "return" + return "return"; } } - transpileExpression(node: ts.Node, brackets?: boolean): string { + public transpileExpression(node: ts.Node, brackets?: boolean): string { switch (node.kind) { case ts.SyntaxKind.BinaryExpression: // Add brackets to preserve ordering - return this.transpileBinaryExpression(node, brackets); + return this.transpileBinaryExpression(node as ts.BinaryExpression, brackets); case ts.SyntaxKind.ConditionalExpression: // Add brackets to preserve ordering - return this.transpileConditionalExpression(node, brackets); + return this.transpileConditionalExpression(node as ts.ConditionalExpression, brackets); case ts.SyntaxKind.CallExpression: - return this.transpileCallExpression(node); + return this.transpileCallExpression(node as ts.CallExpression); case ts.SyntaxKind.PropertyAccessExpression: - return this.transpilePropertyAccessExpression(node); + return this.transpilePropertyAccessExpression(node as ts.PropertyAccessExpression); case ts.SyntaxKind.ElementAccessExpression: - return this.transpileElementAccessExpression(node); + return this.transpileElementAccessExpression(node as ts.ElementAccessExpression); case ts.SyntaxKind.Identifier: // For identifiers simply return their name - return (node).text; + return (node as ts.Identifier).text; case ts.SyntaxKind.StringLiteral: - const text = (node).text; + const text = (node as ts.StringLiteral).text; return `"${text}"`; case ts.SyntaxKind.TemplateExpression: - return this.transpileTemplateExpression(node); + return this.transpileTemplateExpression(node as ts.TemplateExpression); case ts.SyntaxKind.NumericLiteral: - return (node).text; + return (node as ts.NumericLiteral).text; case ts.SyntaxKind.TrueKeyword: return "true"; case ts.SyntaxKind.FalseKeyword: @@ -545,38 +585,41 @@ export class LuaTranspiler { case ts.SyntaxKind.ThisKeyword: return "self"; case ts.SyntaxKind.PostfixUnaryExpression: - return this.transpilePostfixUnaryExpression(node); + return this.transpilePostfixUnaryExpression(node as ts.PostfixUnaryExpression); case ts.SyntaxKind.PrefixUnaryExpression: - return this.transpilePrefixUnaryExpression(node); + return this.transpilePrefixUnaryExpression(node as ts.PrefixUnaryExpression); case ts.SyntaxKind.ArrayLiteralExpression: - return this.transpileArrayLiteral(node); + return this.transpileArrayLiteral(node as ts.ArrayLiteralExpression); case ts.SyntaxKind.ObjectLiteralExpression: - return this.transpileObjectLiteral(node); + return this.transpileObjectLiteral(node as ts.ObjectLiteralExpression); case ts.SyntaxKind.DeleteExpression: - return this.transpileExpression((node).expression) + "=nil"; + return this.transpileExpression((node as ts.DeleteExpression).expression) + "=nil"; case ts.SyntaxKind.FunctionExpression: case ts.SyntaxKind.ArrowFunction: - return this.transpileArrowFunction(node); + return this.transpileArrowFunction(node as ts.ArrowFunction); case ts.SyntaxKind.NewExpression: - return this.transpileNewExpression(node); + return this.transpileNewExpression(node as ts.NewExpression); case ts.SyntaxKind.ComputedPropertyName: - return "[" + this.transpileExpression((node).expression) + "]"; + return "[" + this.transpileExpression((node as ts.ComputedPropertyName).expression) + "]"; case ts.SyntaxKind.ParenthesizedExpression: - return "(" + this.transpileExpression((node).expression) + ")"; + return "(" + this.transpileExpression((node as ts.ParenthesizedExpression).expression) + ")"; case ts.SyntaxKind.SuperKeyword: return "self.__base"; case ts.SyntaxKind.TypeAssertionExpression: // Simply ignore the type assertion - return this.transpileExpression((node).expression); + return this.transpileExpression((node as ts.TypeAssertion).expression); case ts.SyntaxKind.AsExpression: // Also ignore as casts - return this.transpileExpression((node).expression); + return this.transpileExpression((node as ts.AsExpression).expression); default: - throw new TranspileError("Unsupported expression kind: " + tsEx.enumName(node.kind, ts.SyntaxKind), node); + throw new TranspileError( + "Unsupported expression kind: " + tsEx.enumName(node.kind, ts.SyntaxKind), + node + ); } } - transpileBinaryExpression(node: ts.BinaryExpression, brackets?: boolean): string { + public transpileBinaryExpression(node: ts.BinaryExpression, brackets?: boolean): string { // Transpile operands const lhs = this.transpileExpression(node.left, true); const rhs = this.transpileExpression(node.right, true); @@ -723,7 +766,10 @@ export class LuaTranspiler { result = `${rhs}[${lhs}]~=nil`; break; default: - throw new TranspileError("Unsupported binary operator kind: " + ts.tokenToString(node.operatorToken.kind), node); + throw new TranspileError( + "Unsupported binary operator kind: " + ts.tokenToString(node.operatorToken.kind), + node + ); } } @@ -735,9 +781,9 @@ export class LuaTranspiler { } } - transpileTemplateExpression(node: ts.TemplateExpression) { - let parts = [`"${node.head.text}"`]; - node.templateSpans.forEach(span => { + public transpileTemplateExpression(node: ts.TemplateExpression) { + const parts = [`"${node.head.text}"`]; + node.templateSpans.forEach((span) => { const expr = this.transpileExpression(span.expression, true); if (ts.isTemplateTail(span.literal)) { parts.push(`tostring(${expr}).."${span.literal.text}"`); @@ -748,15 +794,15 @@ export class LuaTranspiler { return parts.join(".."); } - transpileConditionalExpression(node: ts.ConditionalExpression, brackets?: boolean): string { - let condition = this.transpileExpression(node.condition); - let val1 = this.transpileExpression(node.whenTrue); - let val2 = this.transpileExpression(node.whenFalse); + public transpileConditionalExpression(node: ts.ConditionalExpression, brackets?: boolean): string { + const condition = this.transpileExpression(node.condition); + const val1 = this.transpileExpression(node.whenTrue); + const val2 = this.transpileExpression(node.whenFalse); return `TS_ITE(${condition},function() return ${val1} end,function() return ${val2} end)`; } - transpilePostfixUnaryExpression(node: ts.PostfixUnaryExpression): string { + public transpilePostfixUnaryExpression(node: ts.PostfixUnaryExpression): string { const operand = this.transpileExpression(node.operand, true); switch (node.operator) { case ts.SyntaxKind.PlusPlusToken: @@ -768,7 +814,7 @@ export class LuaTranspiler { } } - transpilePrefixUnaryExpression(node: ts.PrefixUnaryExpression): string { + public transpilePrefixUnaryExpression(node: ts.PrefixUnaryExpression): string { const operand = this.transpileExpression(node.operand, true); switch (node.operator) { case ts.SyntaxKind.PlusPlusToken: @@ -784,25 +830,27 @@ export class LuaTranspiler { } } - transpileNewExpression(node: ts.NewExpression): string { + public transpileNewExpression(node: ts.NewExpression): string { const name = this.transpileExpression(node.expression); const params = this.transpileArguments(node.arguments, ts.createTrue()); return `${name}.new(${params})`; } - transpileCallExpression(node: ts.CallExpression): string { + public transpileCallExpression(node: ts.CallExpression): string { // Check for calls on primitives to override + let params; + let callPath; if (ts.isPropertyAccessExpression(node.expression)) { const expType = this.checker.getTypeAtLocation(node.expression.expression); - if (expType.symbol && expType.symbol.escapedName == "Math") { - const params = this.transpileArguments(node.arguments); + if (expType.symbol && expType.symbol.escapedName === "Math") { + params = this.transpileArguments(node.arguments); return this.transpileMathExpression(node.expression.name) + `(${params})`; } if (this.transpileExpression((node.expression as ts.PropertyAccessExpression).expression) === "String") { - const params = this.transpileArguments(node.arguments); + params = this.transpileArguments(node.arguments); return this.transpileStringExpression(node.expression.name) + `(${params})`; } @@ -818,44 +866,46 @@ export class LuaTranspiler { if (expType.symbol && (expType.symbol.flags & ts.SymbolFlags.Namespace)) { // Don't replace . with : for namespaces - const callPath = this.transpileExpression(node.expression); - const params = this.transpileArguments(node.arguments); + callPath = this.transpileExpression(node.expression); + params = this.transpileArguments(node.arguments); return `${callPath}(${params})`; } else { // Replace last . with : here - const callPath = `${this.transpileExpression(node.expression.expression)}:${node.expression.name.escapedText}`; - const params = this.transpileArguments(node.arguments); + callPath = + `${this.transpileExpression(node.expression.expression)}:${node.expression.name.escapedText}`; + params = this.transpileArguments(node.arguments); return `${callPath}(${params})`; } } // Handle super calls properly - if (node.expression.kind == ts.SyntaxKind.SuperKeyword) { - let callPath = this.transpileExpression(node.expression); - const params = this.transpileArguments(node.arguments, ts.createNode(ts.SyntaxKind.ThisKeyword)); + if (node.expression.kind === ts.SyntaxKind.SuperKeyword) { + callPath = this.transpileExpression(node.expression); + params = + this.transpileArguments(node.arguments, ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression); return `self.__base.constructor(${params})`; } - let callPath = this.transpileExpression(node.expression); - const params = this.transpileArguments(node.arguments); + callPath = this.transpileExpression(node.expression); + params = this.transpileArguments(node.arguments); return `${callPath}(${params})`; } - transpileStringCallExpression(node: ts.CallExpression): string { - const expression = node.expression; + public transpileStringCallExpression(node: ts.CallExpression): string { + const expression = node.expression as ts.PropertyAccessExpression; const params = this.transpileArguments(node.arguments); const caller = this.transpileExpression(expression.expression); switch (expression.name.escapedText) { case "replace": return `string.gsub(${caller},${params})`; case "indexOf": - if (node.arguments.length == 1) { + if (node.arguments.length === 1) { return `(string.find(${caller},${params},1,true) or 0)-1`; } else { return `(string.find(${caller},${params}+1,true) or 0)-1`; } case "substring": - if (node.arguments.length == 1) { + if (node.arguments.length === 1) { return `string.sub(${caller},${params}+1)`; } else { const arg1 = this.transpileExpression(node.arguments[0]); @@ -876,25 +926,28 @@ export class LuaTranspiler { } // Transpile a String._ property - transpileStringExpression(identifier: ts.Identifier): string { + public transpileStringExpression(identifier: ts.Identifier): string { const translation = { fromCharCode: "string.char", - fromCodePoint: "utf8.char" + fromCodePoint: "utf8.char", }; - if (identifier.escapedText === "fromCodePoint" && this.options.luaTarget !== Target.Lua53) { - throw new TranspileError(`Unsupported string property ${identifier.escapedText} is only supported for lua 5.3.`, identifier); + if (identifier.escapedText as string === "fromCodePoint" && this.options.luaTarget !== Target.Lua53) { + throw new TranspileError( + `Unsupported string property ${identifier.escapedText} is only supported for lua 5.3.`, + identifier + ); } - if (translation[identifier.escapedText]) { - return `${translation[identifier.escapedText]}`; + if (translation[identifier.escapedText as string]) { + return `${translation[identifier.escapedText as string]}`; } else { throw new TranspileError(`Unsupported string property ${identifier.escapedText}.`, identifier); } } - transpileArrayCallExpression(node: ts.CallExpression): string { - const expression = node.expression; + public transpileArrayCallExpression(node: ts.CallExpression): string { + const expression = node.expression as ts.PropertyAccessExpression; const params = this.transpileArguments(node.arguments); const caller = this.transpileExpression(expression.expression); switch (expression.name.escapedText) { @@ -928,7 +981,7 @@ export class LuaTranspiler { } } - transpileArguments(params: ts.NodeArray, context?: ts.Expression): string { + public transpileArguments(params: ts.NodeArray, context?: ts.Expression): string { const parameters: string[] = []; // Add context as first param if present @@ -936,14 +989,14 @@ export class LuaTranspiler { parameters.push(this.transpileExpression(context)); } - params.forEach(param => { + params.forEach((param) => { parameters.push(this.transpileExpression(param)); }); return parameters.join(","); } - transpilePropertyAccessExpression(node: ts.PropertyAccessExpression): string { + public transpilePropertyAccessExpression(node: ts.PropertyAccessExpression): string { const property = node.name.text; // Check for primitive types to override @@ -953,8 +1006,9 @@ export class LuaTranspiler { case ts.TypeFlags.StringLiteral: return this.transpileStringProperty(node); case ts.TypeFlags.Object: - if (tsEx.isArrayType(type)) + if (tsEx.isArrayType(type)) { return this.transpileArrayProperty(node); + } } // Do not output path for member only enums @@ -963,17 +1017,18 @@ export class LuaTranspiler { } // Catch math expressions - if (ts.isIdentifier(node.expression) && node.expression.escapedText == "Math") { + if (ts.isIdentifier(node.expression) && node.expression.escapedText === "Math") { return this.transpileMathExpression(node.name); } - let path = this.transpileExpression(node.expression); - return `${path}.${property}`; + const callPath = this.transpileExpression(node.expression); + return `${callPath}.${property}`; } // Transpile a Math._ property - transpileMathExpression(identifier: ts.Identifier): string { + public transpileMathExpression(identifier: ts.Identifier): string { const translation = { + PI: "pi", abs: "abs", acos: "acos", asin: "asin", @@ -985,24 +1040,23 @@ export class LuaTranspiler { log: "log", max: "max", min: "min", - PI: "pi", pow: "pow", random: "random", round: "round", sin: "sin", sqrt: "sqrt", - tan: "tan" + tan: "tan", }; - if (translation[identifier.escapedText]) { - return `math.${translation[identifier.escapedText]}`; + if (translation[identifier.escapedText as string]) { + return `math.${translation[identifier.escapedText as string]}`; } else { throw new TranspileError(`Unsupported math property ${identifier.escapedText}.`, identifier); } } // Transpile access of string properties, only supported properties are allowed - transpileStringProperty(node: ts.PropertyAccessExpression): string { + public transpileStringProperty(node: ts.PropertyAccessExpression): string { const property = node.name; switch (property.escapedText) { case "length": @@ -1013,7 +1067,7 @@ export class LuaTranspiler { } // Transpile access of array properties, only supported properties are allowed - transpileArrayProperty(node: ts.PropertyAccessExpression): string { + public transpileArrayProperty(node: ts.PropertyAccessExpression): string { const property = node.name; switch (property.escapedText) { case "length": @@ -1023,7 +1077,7 @@ export class LuaTranspiler { } } - transpileElementAccessExpression(node: ts.ElementAccessExpression): string { + public transpileElementAccessExpression(node: ts.ElementAccessExpression): string { const element = this.transpileExpression(node.expression); const index = this.transpileExpression(node.argumentExpression); @@ -1038,18 +1092,18 @@ export class LuaTranspiler { } // Transpile a variable statement - transpileVariableStatement(node: ts.VariableStatement): string { + public transpileVariableStatement(node: ts.VariableStatement): string { let result = ""; - node.declarationList.declarations.forEach(declaration => { - result += this.transpileVariableDeclaration(declaration); - result += this.makeExport((declaration.name).escapedText, node); + node.declarationList.declarations.forEach((declaration) => { + result += this.transpileVariableDeclaration(declaration as ts.VariableDeclaration); + result += this.makeExport((declaration.name as ts.Identifier).escapedText, node); }); return result; } - transpileVariableDeclaration(node: ts.VariableDeclaration): string { + public transpileVariableDeclaration(node: ts.VariableDeclaration): string { if (ts.isIdentifier(node.name)) { // Find variable identifier const identifier = node.name; @@ -1064,25 +1118,30 @@ export class LuaTranspiler { const value = this.transpileExpression(node.initializer); // Disallow ellipsis destruction - if (node.name.elements.some(elem => !ts.isBindingElement(elem) || elem.dotDotDotToken !== undefined)) { + if (node.name.elements.some((elem) => !ts.isBindingElement(elem) || elem.dotDotDotToken !== undefined)) { throw new TranspileError(`Ellipsis destruction is not allowed.`, node); } - const vars = node.name.elements.map(element => ((element).name).escapedText).join(","); + const vars = node.name.elements.map( + (element) => ((element as ts.BindingElement).name as ts.Identifier).escapedText).join(","); // Don't unpack TupleReturn decorated functions if (ts.isCallExpression(node.initializer) - && tsEx.isTupleReturnFunction(this.checker.getTypeAtLocation(node.initializer.expression), this.checker)) { + && tsEx.isTupleReturnFunction(this.checker.getTypeAtLocation(node.initializer.expression), this.checker) + ) { return `local ${vars}=${value}`; } else { return `local ${vars}=unpack(${value})`; } } else { - throw new TranspileError("Unsupported variable declaration type " + tsEx.enumName(node.name.kind, ts.SyntaxKind), node); + throw new TranspileError( + "Unsupported variable declaration type " + tsEx.enumName(node.name.kind, ts.SyntaxKind), + node + ); } } - transpileFunctionDeclaration(node: ts.FunctionDeclaration): string { + public transpileFunctionDeclaration(node: ts.FunctionDeclaration): string { let result = ""; const identifier = node.name; const methodName = identifier.escapedText; @@ -1090,9 +1149,9 @@ export class LuaTranspiler { const body = node.body; // Build parameter string - let paramNames: string[] = []; - parameters.forEach(param => { - paramNames.push((param.name).escapedText); + const paramNames: string[] = []; + parameters.forEach((param) => { + paramNames.push((param.name as ts.Identifier).escapedText as string); }); // Build function header @@ -1110,21 +1169,21 @@ export class LuaTranspiler { return result; } - transpileMethodDeclaration(node: ts.MethodDeclaration, path: string): string { + public transpileMethodDeclaration(node: ts.MethodDeclaration, callPath: string): string { let result = ""; - const identifier = node.name; + const identifier = node.name as ts.Identifier; const methodName = identifier.escapedText; const parameters = node.parameters; const body = node.body; // Build parameter string - let paramNames: string[] = ["self"]; - parameters.forEach(param => { - paramNames.push((param.name).escapedText); + const paramNames: string[] = ["self"]; + parameters.forEach((param) => { + paramNames.push((param.name as ts.Identifier).escapedText as string); }); // Build function header - result += this.indent + `function ${path}${methodName}(${paramNames.join(",")})\n`; + result += this.indent + `function ${callPath}${methodName}(${paramNames.join(",")})\n`; this.pushIndent(); result += this.transpileBlock(body); @@ -1137,12 +1196,12 @@ export class LuaTranspiler { } // Transpile a class declaration - transpileClass(node: ts.ClassDeclaration): string { + public transpileClass(node: ts.ClassDeclaration): string { // Find extends class, ignore implements let extendsType: ts.ExpressionWithTypeArguments | undefined; let noClassOr = false; - if (node.heritageClauses) node.heritageClauses.forEach(clause => { - if (clause.token == ts.SyntaxKind.ExtendsKeyword) { + if (node.heritageClauses) { node.heritageClauses.forEach((clause) => { + if (clause.token === ts.SyntaxKind.ExtendsKeyword) { const superType = this.checker.getTypeAtLocation(clause.types[0]); // Ignore purely abstract types (decorated with /** @PureAbstract */) if (!tsEx.isPureAbstractClass(superType, this.checker)) { @@ -1151,12 +1210,13 @@ export class LuaTranspiler { noClassOr = tsEx.hasCustomDecorator(superType, this.checker, "!NoClassOr"); } }); + } if (!node.name) { - throw new TranspileError("Unexpected Error: Node has no Name", node) + throw new TranspileError("Unexpected Error: Node has no Name", node); } - let className = node.name.escapedText; + let className = node.name.escapedText as string; let result = ""; // Skip header if this is an extension class @@ -1168,18 +1228,19 @@ export class LuaTranspiler { result += this.indent + this.accessPrefix(node) + `${className} = ${classOr}{}\n`; result += this.makeExport(className, node); } else { - const baseName = (extendsType.expression).escapedText; + const baseName = (extendsType.expression as ts.Identifier).escapedText; result += this.indent + this.accessPrefix(node) + `${className} = ${classOr}${baseName}.new()\n`; result += this.makeExport(className, node); } result += this.indent + `${className}.__index = ${className}\n`; if (extendsType) { - const baseName = (extendsType.expression).escapedText; + const baseName = (extendsType.expression as ts.Identifier).escapedText; result += this.indent + `${className}.__base = ${baseName}\n`; } result += this.indent + `function ${className}.new(construct, ...)\n`; result += this.indent + ` local instance = setmetatable({}, ${className})\n`; - result += this.indent + ` if construct and ${className}.constructor then ${className}.constructor(instance, ...) end\n`; + result += this.indent + ` if construct and ${className}.constructor then ` + + `${className}.constructor(instance, ...) end\n`; result += this.indent + ` return instance\n`; result += this.indent + `end\n`; } else { @@ -1187,23 +1248,23 @@ export class LuaTranspiler { result += this.makeExport(className, node, true); // Overwrite the original className with the class we are overriding for extensions if (extendsType) { - className = (extendsType.expression).escapedText; + className = (extendsType.expression as ts.Identifier).escapedText as string; } } // Get all properties with value const properties = node.members.filter(ts.isPropertyDeclaration) - .filter(_ => _.initializer); + .filter((_) => _.initializer); // Divide properties into static and non-static - const isStatic = _ => _.modifiers && _.modifiers.some(_ => _.kind == ts.SyntaxKind.StaticKeyword); + const isStatic = (_) => _.modifiers && _.modifiers.some((__) => __.kind === ts.SyntaxKind.StaticKeyword); const staticFields = properties.filter(isStatic); - const instanceFields = properties.filter(_ => !isStatic(_)); + const instanceFields = properties.filter((_) => !isStatic(_)); // Add static declarations for (const field of staticFields) { - const fieldName = (field.name).escapedText; - let value = this.transpileExpression(field.initializer); + const fieldName = (field.name as ts.Identifier).escapedText; + const value = this.transpileExpression(field.initializer); result += this.indent + `${className}.${fieldName} = ${value}\n`; } @@ -1216,27 +1277,36 @@ export class LuaTranspiler { // No constructor, make one to set all instance fields if there are any if (instanceFields.length > 0) { // Create empty constructor and add instance fields - result += this.transpileConstructor(ts.createConstructor([], [], [], ts.createBlock([], true)), className, instanceFields); + result += this.transpileConstructor(ts.createConstructor([], + [], + [], + ts.createBlock([], true)), + className, + instanceFields); } } // Transpile methods - node.members.filter(ts.isMethodDeclaration).forEach(method => { + node.members.filter(ts.isMethodDeclaration).forEach((method) => { result += this.transpileMethodDeclaration(method, `${className}.`); }); return result; } - transpileConstructor(node: ts.ConstructorDeclaration, className: string, instanceFields: ts.PropertyDeclaration[]): string { + public transpileConstructor(node: ts.ConstructorDeclaration, + className: string, + instanceFields: ts.PropertyDeclaration[]): string { const extraInstanceFields = []; - let parameters = ["self"]; - node.parameters.forEach(param => { + const parameters = ["self"]; + node.parameters.forEach((param) => { // If param has decorators, add extra instance field - if (param.modifiers != undefined) extraInstanceFields.push((param.name).escapedText); + if (param.modifiers !== undefined) { + extraInstanceFields.push((param.name as ts.Identifier).escapedText as string); + } // Add to parameter list - parameters.push((param.name).escapedText); + parameters.push((param.name as ts.Identifier).escapedText as string); }); let result = this.indent + `function ${className}.constructor(${parameters.join(",")})\n`; @@ -1248,10 +1318,10 @@ export class LuaTranspiler { for (const f of instanceFields) { // Get identifier - const fieldIdentifier = f.name; + const fieldIdentifier = f.name as ts.Identifier; const fieldName = fieldIdentifier.escapedText; - let value = this.transpileExpression(f.initializer); + const value = this.transpileExpression(f.initializer); result += this.indent + ` self.${fieldName} = ${value}\n`; } @@ -1264,20 +1334,20 @@ export class LuaTranspiler { return result + this.indent + "end\n"; } - transpileArrayLiteral(node: ts.ArrayLiteralExpression): string { - let values: string[] = []; + public transpileArrayLiteral(node: ts.ArrayLiteralExpression): string { + const values: string[] = []; - node.elements.forEach(child => { + node.elements.forEach((child) => { values.push(this.transpileExpression(child)); }); return "{" + values.join(",") + "}"; } - transpileObjectLiteral(node: ts.ObjectLiteralExpression): string { - let properties: string[] = []; + public transpileObjectLiteral(node: ts.ObjectLiteralExpression): string { + const properties: string[] = []; // Add all property assignments - node.properties.forEach(assignment => { + node.properties.forEach((assignment) => { const [key, value] = tsEx.getChildren(assignment); if (ts.isIdentifier(key)) { properties.push(`${key.escapedText}=` + this.transpileExpression(value)); @@ -1285,7 +1355,7 @@ export class LuaTranspiler { const index = this.transpileExpression(key); properties.push(`${index}=` + this.transpileExpression(value)); } else { - const index = this.transpileExpression(key); + const index = this.transpileExpression(key as ts.Expression); properties.push(`[${index}]=` + this.transpileExpression(value)); } }); @@ -1293,11 +1363,11 @@ export class LuaTranspiler { return "{" + properties.join(",") + "}"; } - transpileFunctionExpression(node: ts.FunctionExpression): string { + public transpileFunctionExpression(node: ts.FunctionExpression): string { // Build parameter string - let paramNames: string[] = []; - node.parameters.forEach(param => { - paramNames.push((param.name).escapedText); + const paramNames: string[] = []; + node.parameters.forEach((param) => { + paramNames.push((param.name as ts.Identifier).escapedText as string); }); let result = `function(${paramNames.join(",")})\n`; @@ -1307,11 +1377,11 @@ export class LuaTranspiler { return result + this.indent + "end\n"; } - transpileArrowFunction(node: ts.ArrowFunction): string { + public transpileArrowFunction(node: ts.ArrowFunction): string { // Build parameter string - let paramNames: string[] = []; - node.parameters.forEach(param => { - paramNames.push((param.name).escapedText); + const paramNames: string[] = []; + node.parameters.forEach((param) => { + paramNames.push((param.name as ts.Identifier).escapedText as string); }); if (ts.isBlock(node.body)) { diff --git a/tslint.json b/tslint.json new file mode 100644 index 000000000..a22a0cce5 --- /dev/null +++ b/tslint.json @@ -0,0 +1,30 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "align": [true, "parameters", "statements", "arguments", "members", "elements"], + "class-name": true, + "no-bitwise": false, + "indent": [true, "spaces", 4], + "max-classes-per-file": 1, + "max-line-length": [true, 120], + "no-console": [true, "error"], + "trailing-comma": [ + true, + { + "multiline": { + "objects": "always", + "arrays": "always", + "functions": "never", + "typeLiterals": "always" + }, + "esSpecCompliant": true + } + ], + "interface-name": false + }, + "rulesDirectory": [] +} From f8b164866a9817834a5eccd866ae639d2a366d99 Mon Sep 17 00:00:00 2001 From: lolleko Date: Fri, 23 Mar 2018 17:26:31 +0100 Subject: [PATCH 2/4] Fixed Error not beeing reported correctly --- src/Compiler.ts | 6 ++++-- tslint.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Compiler.ts b/src/Compiler.ts index 836ca41c4..654e4ab28 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -60,11 +60,13 @@ function compile(fileNames: string[], options: CompilerOptions): void { if (exception.node) { const pos = ts.getLineAndCharacterOfPosition(sourceFile, exception.node.pos); // Graciously handle transpilation errors - throw new Error( + console.error( "Encountered error parsing file: " + exception.message + "\n" + sourceFile.fileName + " line: " + (1 + pos.line) + " column: " + pos.character + "\n" + - exception.stack); + exception.stack + ); + process.exit(1); } else { throw exception; } diff --git a/tslint.json b/tslint.json index a22a0cce5..9e4711030 100644 --- a/tslint.json +++ b/tslint.json @@ -11,7 +11,7 @@ "indent": [true, "spaces", 4], "max-classes-per-file": 1, "max-line-length": [true, 120], - "no-console": [true, "error"], + "no-console": [false], "trailing-comma": [ true, { From f9924a59c02e5ba98efc926ecc299a0d0d268975 Mon Sep 17 00:00:00 2001 From: lolleko Date: Sat, 24 Mar 2018 14:46:31 +0100 Subject: [PATCH 3/4] Added version to header Removed radix HEader is now enabled by default --- src/CommandLineParser.ts | 2 +- src/Transpiler.ts | 10 +++++++--- tslint.json | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 7d8f4076f..7ce478960 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -23,7 +23,7 @@ export class CLIError extends Error { const optionDeclarations: { [key: string]: yargs.Options } = { addHeader: { alias: "ah", - default: false, + default: true, describe: "Specify if a header will be added to compiled files.", type: "boolean", }, diff --git a/src/Transpiler.ts b/src/Transpiler.ts index b72009f88..277bbe5dd 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -6,6 +6,10 @@ import { TSHelper as tsEx } from "./TSHelper"; import * as path from "path"; +/* tslint:disable */ +const packageJSON = require("../package.json"); +/* tslint:enable */ + export class TranspileError extends Error { public node: ts.Node; constructor(message: string, node: ts.Node) { @@ -30,8 +34,8 @@ export class LuaTranspiler { let header = ""; if (options.addHeader) { - header = "-- Generated by TypescriptToLua transpiler " + - "https://github.com/Perryvw/TypescriptToLua \n"; + header = "-- Generated by TypescriptToLua v" + packageJSON.version + "\n" + + "-- https://github.com/Perryvw/TypescriptToLua\n"; } let result = header; if (!options.dontRequireLuaLib) { @@ -287,7 +291,7 @@ export class LuaTranspiler { node.members.forEach((member) => { if (member.initializer) { if (ts.isNumericLiteral(member.initializer)) { - val = parseInt(member.initializer.text, 10); + val = parseInt(member.initializer.text); } else { throw new TranspileError("Only numeric initializers allowed for enums.", node); } diff --git a/tslint.json b/tslint.json index 9e4711030..a8b9bce9f 100644 --- a/tslint.json +++ b/tslint.json @@ -24,7 +24,8 @@ "esSpecCompliant": true } ], - "interface-name": false + "interface-name": false, + "radix": false }, "rulesDirectory": [] } From 2560bf0a9f1c6fc3ce4c7bff1f75b18454d66cf6 Mon Sep 17 00:00:00 2001 From: lolleko Date: Sat, 24 Mar 2018 14:51:28 +0100 Subject: [PATCH 4/4] Fixed test --- test/unit/cli.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/cli.spec.ts b/test/unit/cli.spec.ts index 94c806505..47ca20195 100644 --- a/test/unit/cli.spec.ts +++ b/test/unit/cli.spec.ts @@ -7,7 +7,7 @@ export class CLITests { @Test("defaultOption") @TestCase("luaTarget", "JIT") - @TestCase("addHeader", false) + @TestCase("addHeader", true) @TestCase("dontRequireLuaLib", false) @TestCase("rootDir", process.cwd()) @TestCase("outDir", process.cwd())