From a1825a1a17a0937f214248e5a680d5e706903e91 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 7 Oct 2019 11:08:21 +1000 Subject: [PATCH 01/38] Implement bundle transformation --- src/LuaTransformer.ts | 53 ++++++++++--- src/TSTransformers.ts | 16 ++-- src/Transpile.ts | 22 ++++-- test/unit/outFile.spec.ts | 154 ++++++++++++++++++++++++++++++++++++++ test/util.ts | 6 +- 5 files changed, 228 insertions(+), 23 deletions(-) create mode 100644 test/unit/outFile.spec.ts diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index fcb8c4cd0..428a4994f 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -104,21 +104,43 @@ export class LuaTransformer { protected currentSourceFile!: ts.SourceFile; protected isModule!: boolean; + protected isWithinBundle!: boolean; protected resolver!: EmitResolver; /** @internal */ - public transform(sourceFile: ts.SourceFile): [tstl.Block, Set] { + public transform(node: ts.Bundle | ts.SourceFile): [tstl.Block, Set] { this.setupState(); - this.currentSourceFile = sourceFile; - this.isModule = tsHelper.isFileModule(sourceFile); + if (ts.isSourceFile(node)) { + this.currentSourceFile = node; + this.isModule = tsHelper.isFileModule(node); - // Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers. - // It's required because otherwise `getEmitResolver` won't use cached diagnostics, produced in `emitWorker` - // and would try to re-analyze the file, which would fail because of replaced nodes. - const originalSourceFile = ts.getParseTreeNode(sourceFile, ts.isSourceFile) || sourceFile; - this.resolver = this.checker.getEmitResolver(originalSourceFile); + // Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers. + // It's required because otherwise `getEmitResolver` won't use cached diagnostics, produced in `emitWorker` + // and would try to re-analyze the file, which would fail because of replaced nodes. + const originalSourceFile = ts.getParseTreeNode(node, ts.isSourceFile) || node; + this.resolver = this.checker.getEmitResolver(originalSourceFile); - return [this.transformSourceFile(sourceFile), this.luaLibFeatureSet]; + return [this.transformSourceFile(node), this.luaLibFeatureSet]; + } else { + return [this.transformBundle(node), this.luaLibFeatureSet]; + } + } + + public transformBundle(bundle: ts.Bundle): tstl.Block { + this.isWithinBundle = true; + const combinedStatements = bundle.sourceFiles.reduce( + (statements: tstl.Statement[], sourceFile: ts.SourceFile) => { + this.currentSourceFile = sourceFile; + this.isModule = tsHelper.isFileModule(sourceFile); + const originalSourceFile = ts.getParseTreeNode(sourceFile, ts.isSourceFile) || sourceFile; + this.resolver = this.checker.getEmitResolver(originalSourceFile); + const transformResult = this.transformSourceFile(sourceFile); + return [...statements, ...transformResult.statements]; + }, + [] + ); + + return tstl.createBlock(combinedStatements, bundle); } public transformSourceFile(sourceFile: ts.SourceFile): tstl.Block { @@ -149,6 +171,19 @@ export class LuaTransformer { // return exports statements.push(tstl.createReturnStatement([this.createExportsIdentifier()])); + + if (this.isWithinBundle) { + const packagePreload = tstl.createTableIndexExpression( + tstl.createIdentifier("package"), + tstl.createStringLiteral("preload") + ); + const exportPath = tsHelper.getExportPath(sourceFile.fileName, this.options); + const packagePreloadDeclaration = tstl.createAssignmentStatement( + tstl.createTableIndexExpression(packagePreload, tstl.createStringLiteral(exportPath)), + tstl.createFunctionExpression(tstl.createBlock(statements, sourceFile)) + ); + return tstl.createBlock([packagePreloadDeclaration], sourceFile); + } } } diff --git a/src/TSTransformers.ts b/src/TSTransformers.ts index 8cc38356c..fa555f007 100644 --- a/src/TSTransformers.ts +++ b/src/TSTransformers.ts @@ -9,12 +9,18 @@ export function getCustomTransformers( program: ts.Program, diagnostics: ts.Diagnostic[], customTransformers: ts.CustomTransformers, - onSourceFile: (sourceFile: ts.SourceFile) => void + onRootNode: (node: ts.Bundle | ts.SourceFile) => void ): ts.CustomTransformers { - const luaTransformer: ts.TransformerFactory = () => sourceFile => { - onSourceFile(sourceFile); - return ts.createSourceFile(sourceFile.fileName, "", ts.ScriptTarget.ESNext); - }; + const luaTransformer: ts.CustomTransformerFactory = () => ({ + transformBundle: node => { + onRootNode(node); + return ts.createBundle([]); + }, + transformSourceFile: node => { + onRootNode(node); + return ts.createSourceFile(node.fileName, "", ts.ScriptTarget.ESNext); + }, + }); const transformersFromOptions = loadTransformersFromOptions(program, diagnostics); diff --git a/src/Transpile.ts b/src/Transpile.ts index 28a6282a9..4ee6e390d 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -78,26 +78,32 @@ export function transpile({ } } - const processSourceFile = (sourceFile: ts.SourceFile) => { + const processRootNode = (node: ts.Bundle | ts.SourceFile) => { + if (ts.isBundle(node) && !options.outFile) { + throw new Error("The option outFile must be specified when transforming a bundle."); + } + + const fileName = ts.isBundle(node) ? options.outFile! : node.fileName; + try { - const [luaAst, lualibFeatureSet] = transformer.transform(sourceFile); + const [luaAst, lualibFeatureSet] = transformer.transform(node); if (!options.noEmit && !options.emitDeclarationOnly) { - const [lua, sourceMap] = printer.print(luaAst, lualibFeatureSet, sourceFile.fileName); - updateTranspiledFile(sourceFile.fileName, { luaAst, lua, sourceMap }); + const [lua, sourceMap] = printer.print(luaAst, lualibFeatureSet, fileName); + updateTranspiledFile(fileName, { luaAst, lua, sourceMap }); } } catch (err) { if (!(err instanceof TranspileError)) throw err; diagnostics.push(diagnosticFactories.transpileError(err)); - updateTranspiledFile(sourceFile.fileName, { + updateTranspiledFile(fileName, { lua: `error(${JSON.stringify(err.message)})\n`, sourceMap: "", }); } }; - const transformers = getCustomTransformers(program, diagnostics, customTransformers, processSourceFile); + const transformers = getCustomTransformers(program, diagnostics, customTransformers, processRootNode); const writeFile: ts.WriteFileCallback = (fileName, data, _bom, _onError, sourceFiles = []) => { for (const sourceFile of sourceFiles) { @@ -123,7 +129,7 @@ export function transpile({ if (targetSourceFiles) { for (const file of targetSourceFiles) { if (isEmittableJsonFile(file)) { - processSourceFile(file); + processRootNode(file); } else { diagnostics.push(...program.emit(file, writeFile, undefined, false, transformers).diagnostics); } @@ -135,7 +141,7 @@ export function transpile({ program .getSourceFiles() .filter(isEmittableJsonFile) - .forEach(processSourceFile); + .forEach(processRootNode); } options.noEmit = oldNoEmit; diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts new file mode 100644 index 000000000..2b8784f0e --- /dev/null +++ b/test/unit/outFile.spec.ts @@ -0,0 +1,154 @@ +import * as util from "../util"; +import * as ts from "typescript"; + +const exportValueSource = ` + export const value = true; +`; + +const reexportValueSource = ` + export { value } from "./export"; +`; + +test.each<[string, Record]>([ + [ + "Import module -> main", + { + "main.ts": ` + import { value } from "./module"; + if (value !== true) { + throw "Failed to import x"; + } + `, + "module.ts": exportValueSource, + }, + ], + [ + "Import chain export -> reexport -> main", + { + "main.ts": ` + import { value } from "./reexport"; + if (value !== true) { + throw "Failed to import value"; + } + `, + "reexport.ts": reexportValueSource, + "export.ts": exportValueSource, + }, + ], + [ + "Import chain with a different order", + { + "main.ts": ` + import { value } from "./reexport"; + if (value !== true) { + throw "Failed to import value"; + } + `, + "export.ts": exportValueSource, + "reexport.ts": reexportValueSource, + }, + ], + [ + "Import diamond export -> reexport1 & reexport2 -> main", + { + "main.ts": ` + import { value as a } from "./reexport1"; + import { value as b } from "./reexport2"; + if (a !== true || b !== true) { + throw "Failed to import a or b"; + } + `, + "export.ts": exportValueSource, + "reexport1.ts": reexportValueSource, + "reexport2.ts": reexportValueSource, + }, + ], + [ + "Import diamond different order", + { + "reexport1.ts": reexportValueSource, + "reexport2.ts": reexportValueSource, + "export.ts": exportValueSource, + "main.ts": ` + import { value as a } from "./reexport1"; + import { value as b } from "./reexport2"; + if (a !== true || b !== true) { + throw "Failed to import a or b"; + } + `, + }, + ], + [ + "Modules in directories", + { + "main.ts": ` + import { value } from "./module/module"; + if (value !== true) { + throw "Failed to import value"; + } + `, + "module/module.ts": ` + export const value = true; + `, + }, + ], + [ + "Modules aren't ordered by name", + { + "main.ts": ` + import { value } from "./a"; + if (value !== true) { + throw "Failed to import value"; + } + `, + "a.ts": ` + export const value = true; + `, + }, + ], + [ + "Modules in directories", + { + "main/main.ts": ` + import { value } from "../module"; + if (value !== true) { + throw "Failed to import value"; + } + `, + "module.ts": ` + export const value = true; + `, + }, + ], + [ + "LuaLibs are usable", + { + "module.ts": ` + export const array = [1, 2]; + array.push(3); + `, + "main.ts": ` + import { array } from "./module"; + if (array[2] !== 3) { + throw "Array's third item is not three"; + } + `, + }, + ], +])("outFile tests (%s)", (_, files) => { + const testBuilder = util.testBundle` + ${files["main.ts"]} + ` + .setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD }) + .setMainFileName("main.ts"); + + const extraFiles = Object.keys(files) + .map(file => ({ fileName: file, code: files[file] })) + .filter(file => file.fileName !== "main.ts"); + + extraFiles.forEach(extraFile => { + testBuilder.addExtraFile(extraFile.fileName, extraFile.code); + }); + + testBuilder.expectNoExecutionError(); +}); diff --git a/test/util.ts b/test/util.ts index 8abc0dbc6..2cba52e5b 100644 --- a/test/util.ts +++ b/test/util.ts @@ -223,7 +223,8 @@ export abstract class TestBuilder { @memoize public getMainLuaFileResult(): ExecutableTranspiledFile { const { transpiledFiles } = this.getLuaResult(); - const mainFile = transpiledFiles.find(x => x.fileName === this.mainFileName); + const mainFileName = this.options.outFile ? this.options.outFile : this.mainFileName; + const mainFile = transpiledFiles.find(x => x.fileName === mainFileName); expect(mainFile).toMatchObject({ lua: expect.any(String), sourceMap: expect.any(String) }); return mainFile as ExecutableTranspiledFile; } @@ -418,6 +419,8 @@ class AccessorTestBuilder extends TestBuilder { } } +class BundleTestBuilder extends AccessorTestBuilder {} + class ModuleTestBuilder extends AccessorTestBuilder { public setReturnExport(name: string): this { expect(this.hasProgram).toBe(false); @@ -462,6 +465,7 @@ const createTestBuilderFactory = ( return new builder(tsCode); }; +export const testBundle = createTestBuilderFactory(BundleTestBuilder, false); export const testModule = createTestBuilderFactory(ModuleTestBuilder, false); export const testModuleTemplate = createTestBuilderFactory(ModuleTestBuilder, true); export const testFunction = createTestBuilderFactory(FunctionTestBuilder, false); From eb05fcb7a1b137dea93172ef743abadf0a840a89 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 7 Oct 2019 11:12:14 +1000 Subject: [PATCH 02/38] Remove unneeded changes --- .github/workflows/ci.yml | 42 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 20 +++++++++++++++++ .prettierrc.js | 2 +- .travis.yml | 17 -------------- README.md | 7 +++--- appveyor.yml | 31 -------------------------- test/unit/outFile.spec.ts | 4 +--- 7 files changed, 67 insertions(+), 56 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..645444dc9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: [push, pull_request] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + - run: npm install --global npm@6 + - run: npm ci + - run: npm run lint + env: + CI: true + + test: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js 8.5.0 + uses: actions/setup-node@v1 + with: + node-version: 8.5.0 + - run: npm install --global npm@6 + - run: npm ci + - run: npm run build + - run: npx jest --coverage + env: + CI: true + - if: matrix.os == 'ubuntu-latest' + continue-on-error: true + uses: codecov/codecov-action@v1.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..b344325aa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +name: Release + +on: + push: + tags: "*" + +jobs: + release: + name: Release + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + - run: npm install --global npm@6 + - run: npm ci + - run: npm run build + - run: npm publish --dry-run + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} diff --git a/.prettierrc.js b/.prettierrc.js index 87974397e..e9fb0b291 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -6,5 +6,5 @@ module.exports = { tabWidth: 4, trailingComma: "es5", endOfLine: isCI ? "lf" : "auto", - overrides: [{ files: ["**/*.md", "**/*.yml", "**/.*.yml"], options: { tabWidth: 2 } }], + overrides: [{ files: ["**/*.md", "**/*.yml"], options: { tabWidth: 2 } }], }; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e75d82b40..000000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: node_js -node_js: "8.5.0" - -script: - - npm run build - - npm test -- --coverage -after_success: npx codecov - -deploy: - provider: npm - email: lorenz.junglas@student.kit.edu - skip_cleanup: true - api_key: - secure: zXFXK1GohutgqKXQ101uKcJKcf0wgCuVPFMY44Xl/JwXeLsA1CJ4aqUdoXh2ramcI+9wTVj7RR3N3/7W+2DeDCfUlj+gxw26pTV3EUnU2B1TXEz01Ar07WPHmhHJSS5tgyo9kPUqm/YBFkJuDtfx0Kp3sL6IRYDddvGv/2L1X8r3I14PxgppkU9T4FGINdClOEwfwelmxx3kjxpnTsUHZ+ztx7o+blteSf/r+pT1RDaIap80JaTy6vHwJKECRSvtpyTKEYeVmBIaA3yXqRhfMdlp8bU20t2DXoDGXWPk0GXRGXkVHE6yj64gPe/eOOesAxzv0v/vr5w4poemJ+SGwP3RSbrbVtiusD4FC6+hFBH7hiNUhxww5euvbAfNOTq2XyxtjcNmKB5/O675xGihK1gBgrsPdJ4enwkhQNrUuQBHm5wIKGiaH7t7q+T8W9JAnk3FGGuSZPg9b7AnFDQ3graxZK9mtOxi0GvE7DHinH6qErd4noGjS/KSy1fnDkCEkeplOQmvxb4w7wLMR5pePFc77NBXiR3RJDKh4QKskcvXPx58OWNffTkS2QwYPYhraNNKbUfGFBNEA4KPNmw1jYlDE/1BhOebmONpZQMP/CdcCWL+CKyTdi1/289Ak4B0iSZZN+Yx+AONzPlcvBVi+bXeXtqoIt2zMFKkr/0YFQg= - on: - tags: true - repo: TypeScriptToLua/TypeScriptToLua diff --git a/README.md b/README.md index 5fff008ac..b1438a222 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,9 @@

TypeScriptToLua

- Build status - Build status - Coverage - Chat with us! + Continuous Integration status + Coverage + Chat with us!

diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 5e5b29138..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Test against the latest version of this Node.js version -environment: - nodejs_version: "8.5.0" - -# Do not build feature branch with open Pull Requests -skip_branch_with_pr: true - -# Cache dependencies -cache: - - node_modules - -# Install scripts. (runs after repo cloning) -install: - # Get the latest stable version of Node.js or io.js - - ps: Install-Product node $env:nodejs_version - # Upgrade npm - - npm install --global npm@6 - # Install modules - - npm ci - -# Post-install test scripts. -test_script: - # Output useful info for debugging. - - node --version - - npm --version - # Run tests - - npm run build - - npm test - -# Don't actually build. -build: off diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 2b8784f0e..2384c3d08 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -138,9 +138,7 @@ test.each<[string, Record]>([ ])("outFile tests (%s)", (_, files) => { const testBuilder = util.testBundle` ${files["main.ts"]} - ` - .setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD }) - .setMainFileName("main.ts"); + `.setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD }); const extraFiles = Object.keys(files) .map(file => ({ fileName: file, code: files[file] })) From 299fda87fc951027bb552e5a99e5ed567de57386 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 7 Oct 2019 13:06:04 +1000 Subject: [PATCH 03/38] Add option luaModuleSystem --- src/CommandLineParser.ts | 8 ++++++- src/CompilerOptions.ts | 6 ++++++ src/LuaLib.ts | 1 + src/LuaTransformer.ts | 9 ++++++-- src/lualib/LuaRequire.ts | 16 ++++++++++++++ test/unit/luaModuleSystem.spec.ts | 36 +++++++++++++++++++++++++++++++ test/unit/outFile.spec.ts | 4 +++- test/util.ts | 18 +++++++++++++++- 8 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 src/lualib/LuaRequire.ts create mode 100644 test/unit/luaModuleSystem.spec.ts diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index d657e0127..31be73325 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -1,6 +1,6 @@ import * as path from "path"; import * as ts from "typescript"; -import { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; +import { CompilerOptions, LuaLibImportKind, LuaTarget, LuaModuleSystemKind } from "./CompilerOptions"; import * as diagnosticFactories from "./diagnostics"; export interface ParsedCommandLine extends ts.ParsedCommandLine { @@ -31,6 +31,12 @@ const optionDeclarations: CommandLineOption[] = [ type: "enum", choices: Object.values(LuaLibImportKind), }, + { + name: "luaModuleSystem", + description: "Specify the way modules are resolved on the target Lua environment.", + type: "enum", + choices: Object.values(LuaModuleSystemKind), + }, { name: "luaTarget", aliases: ["lt"], diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index f7c5999c5..66c40b547 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -20,6 +20,7 @@ export interface TransformerImport { export type CompilerOptions = OmitIndexSignature & { noImplicitSelf?: boolean; noHeader?: boolean; + luaModuleSystem?: LuaModuleSystemKind; luaTarget?: LuaTarget; luaLibImport?: LuaLibImportKind; noHoisting?: boolean; @@ -28,6 +29,11 @@ export type CompilerOptions = OmitIndexSignature & { [option: string]: ts.CompilerOptions[string] | Array; }; +export enum LuaModuleSystemKind { + Require = "require", + None = "none", +} + export enum LuaLibImportKind { None = "none", Always = "always", diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 9d15f9fb4..697df5619 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -33,6 +33,7 @@ export enum LuaLibFeature { InstanceOf = "InstanceOf", InstanceOfObject = "InstanceOfObject", Iterator = "Iterator", + LuaRequire = "LuaRequire", Map = "Map", NewIndex = "NewIndex", Number = "Number", diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 1cb59c7a9..f9734cf38 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -1,6 +1,6 @@ import * as path from "path"; import * as ts from "typescript"; -import { CompilerOptions, LuaTarget } from "./CompilerOptions"; +import { CompilerOptions, LuaTarget, LuaModuleSystemKind } from "./CompilerOptions"; import { DecoratorKind } from "./Decorator"; import * as tstl from "./LuaAST"; import { LuaLibFeature } from "./LuaLib"; @@ -546,7 +546,12 @@ export class LuaTransformer { ) : moduleSpecifier.text; const modulePath = tstl.createStringLiteral(modulePathString); - return tstl.createCallExpression(tstl.createIdentifier("require"), [modulePath], moduleSpecifier); + const requireCallString = + this.options.luaModuleSystem === LuaModuleSystemKind.None ? "__TS__LuaRequire" : "require"; + if (this.options.luaModuleSystem === LuaModuleSystemKind.None) { + this.importLuaLibFeature(LuaLibFeature.LuaRequire); + } + return tstl.createCallExpression(tstl.createIdentifier(requireCallString), [modulePath], moduleSpecifier); } protected validateClassElement(element: ts.ClassElement): void { diff --git a/src/lualib/LuaRequire.ts b/src/lualib/LuaRequire.ts new file mode 100644 index 000000000..31a610408 --- /dev/null +++ b/src/lualib/LuaRequire.ts @@ -0,0 +1,16 @@ +globalThis.package = {}; +globalThis.package.preload = {}; +globalThis.package.loaded = {}; + +function __TS__LuaRequire(this: void, moduleName: string): any { + if (!globalThis.package.loaded[moduleName]) { + const module: (this: void, module: string) => any = globalThis.package.preload[moduleName]; + if (module) { + globalThis.package.loaded[moduleName] = module(moduleName); + } else { + // tslint:disable-next-line: no-string-throw + throw `module '${moduleName}' not found:`; + } + } + return globalThis.package.loaded[moduleName]; +} diff --git a/test/unit/luaModuleSystem.spec.ts b/test/unit/luaModuleSystem.spec.ts new file mode 100644 index 000000000..ace8a2aab --- /dev/null +++ b/test/unit/luaModuleSystem.spec.ts @@ -0,0 +1,36 @@ +import * as util from "../util"; +import * as ts from "typescript"; +import { LuaModuleSystemKind } from "../../src"; + +test.each<[string, Record]>([ + [ + "Import module -> main", + { + "main.ts": ` + import { value } from "./module"; + if (value !== true) { + throw "Failed to import value"; + } + `, + "module.ts": ` + export const value = true; + `, + }, + ], +])("luaModuleSystem with outFile (%s)", (_, files) => { + const testBuilder = util.testBundle` + ${files["main.ts"]} + ` + .setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD, luaModuleSystem: LuaModuleSystemKind.None }) + .setModuleSystem("none"); + + const extraFiles = Object.keys(files) + .map(file => ({ fileName: file, code: files[file] })) + .filter(file => file.fileName !== "main.ts"); + + extraFiles.forEach(extraFile => { + testBuilder.addExtraFile(extraFile.fileName, extraFile.code); + }); + + testBuilder.expectNoExecutionError(); +}); diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 2384c3d08..2d56d46df 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -138,7 +138,9 @@ test.each<[string, Record]>([ ])("outFile tests (%s)", (_, files) => { const testBuilder = util.testBundle` ${files["main.ts"]} - `.setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD }); + ` + .setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD }) + .setModuleSystem("require"); const extraFiles = Object.keys(files) .map(file => ({ fileName: file, code: files[file] })) diff --git a/test/util.ts b/test/util.ts index 2cba52e5b..7c6a0a2e6 100644 --- a/test/util.ts +++ b/test/util.ts @@ -419,7 +419,23 @@ class AccessorTestBuilder extends TestBuilder { } } -class BundleTestBuilder extends AccessorTestBuilder {} +class BundleTestBuilder extends AccessorTestBuilder { + protected moduleSystemKind: "none" | "require" = "require"; + public setModuleSystem(kind: "none" | "require"): this { + this.moduleSystemKind = kind; + return this; + } + public getLuaCodeWithWrapper(): string { + if (this.moduleSystemKind === "require") { + return super.getLuaCodeWithWrapper(); + } + + const noRequire = "_G.require = nil"; + const noPackage = "_G.package = nil"; + const luaCode = super.getLuaCodeWithWrapper(); + return `${noRequire}\n${noPackage}\n${luaCode}`; + } +} class ModuleTestBuilder extends AccessorTestBuilder { public setReturnExport(name: string): this { From e9837642385336f6767c091fe60305b7ccbd6c40 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 7 Oct 2019 13:08:21 +1000 Subject: [PATCH 04/38] Update luaModule system test name --- test/unit/luaModuleSystem.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/luaModuleSystem.spec.ts b/test/unit/luaModuleSystem.spec.ts index ace8a2aab..0e3be3a63 100644 --- a/test/unit/luaModuleSystem.spec.ts +++ b/test/unit/luaModuleSystem.spec.ts @@ -4,7 +4,7 @@ import { LuaModuleSystemKind } from "../../src"; test.each<[string, Record]>([ [ - "Import module -> main", + "Import module without package or require", { "main.ts": ` import { value } from "./module"; From c6447c43ddf765bf42caec3c329a14f857035d48 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 7 Oct 2019 13:13:31 +1000 Subject: [PATCH 05/38] Improve formatting of luaModuleSystem test --- test/unit/luaModuleSystem.spec.ts | 38 ++++++++----------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/test/unit/luaModuleSystem.spec.ts b/test/unit/luaModuleSystem.spec.ts index 0e3be3a63..3d42c8e90 100644 --- a/test/unit/luaModuleSystem.spec.ts +++ b/test/unit/luaModuleSystem.spec.ts @@ -2,35 +2,15 @@ import * as util from "../util"; import * as ts from "typescript"; import { LuaModuleSystemKind } from "../../src"; -test.each<[string, Record]>([ - [ - "Import module without package or require", - { - "main.ts": ` - import { value } from "./module"; - if (value !== true) { - throw "Failed to import value"; - } - `, - "module.ts": ` - export const value = true; - `, - }, - ], -])("luaModuleSystem with outFile (%s)", (_, files) => { - const testBuilder = util.testBundle` - ${files["main.ts"]} +test("luaModuleSystem with outFile prevents re-creates require and package", () => { + util.testBundle` + import { value } from "./module"; + if (value !== true) { + throw "Failed to import value"; + } ` .setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD, luaModuleSystem: LuaModuleSystemKind.None }) - .setModuleSystem("none"); - - const extraFiles = Object.keys(files) - .map(file => ({ fileName: file, code: files[file] })) - .filter(file => file.fileName !== "main.ts"); - - extraFiles.forEach(extraFile => { - testBuilder.addExtraFile(extraFile.fileName, extraFile.code); - }); - - testBuilder.expectNoExecutionError(); + .setModuleSystem("none") + .addExtraFile("module.ts", "export const value = true;") + .expectNoExecutionError(); }); From c50ad149bf86577be743d88ba6f196ae253bd5a5 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 11 Oct 2019 16:21:46 +1000 Subject: [PATCH 06/38] Remove luaModuleSystem --- src/CommandLineParser.ts | 8 +------- src/CompilerOptions.ts | 6 ------ src/LuaTransformer.ts | 7 +++---- test/unit/luaModuleSystem.spec.ts | 16 ---------------- test/unit/outFile.spec.ts | 4 +--- test/util.ts | 9 --------- 6 files changed, 5 insertions(+), 45 deletions(-) delete mode 100644 test/unit/luaModuleSystem.spec.ts diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 31be73325..d657e0127 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -1,6 +1,6 @@ import * as path from "path"; import * as ts from "typescript"; -import { CompilerOptions, LuaLibImportKind, LuaTarget, LuaModuleSystemKind } from "./CompilerOptions"; +import { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; import * as diagnosticFactories from "./diagnostics"; export interface ParsedCommandLine extends ts.ParsedCommandLine { @@ -31,12 +31,6 @@ const optionDeclarations: CommandLineOption[] = [ type: "enum", choices: Object.values(LuaLibImportKind), }, - { - name: "luaModuleSystem", - description: "Specify the way modules are resolved on the target Lua environment.", - type: "enum", - choices: Object.values(LuaModuleSystemKind), - }, { name: "luaTarget", aliases: ["lt"], diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index 66c40b547..f7c5999c5 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -20,7 +20,6 @@ export interface TransformerImport { export type CompilerOptions = OmitIndexSignature & { noImplicitSelf?: boolean; noHeader?: boolean; - luaModuleSystem?: LuaModuleSystemKind; luaTarget?: LuaTarget; luaLibImport?: LuaLibImportKind; noHoisting?: boolean; @@ -29,11 +28,6 @@ export type CompilerOptions = OmitIndexSignature & { [option: string]: ts.CompilerOptions[string] | Array; }; -export enum LuaModuleSystemKind { - Require = "require", - None = "none", -} - export enum LuaLibImportKind { None = "none", Always = "always", diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index f9734cf38..0f971a17d 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -1,6 +1,6 @@ import * as path from "path"; import * as ts from "typescript"; -import { CompilerOptions, LuaTarget, LuaModuleSystemKind } from "./CompilerOptions"; +import { CompilerOptions, LuaTarget } from "./CompilerOptions"; import { DecoratorKind } from "./Decorator"; import * as tstl from "./LuaAST"; import { LuaLibFeature } from "./LuaLib"; @@ -546,9 +546,8 @@ export class LuaTransformer { ) : moduleSpecifier.text; const modulePath = tstl.createStringLiteral(modulePathString); - const requireCallString = - this.options.luaModuleSystem === LuaModuleSystemKind.None ? "__TS__LuaRequire" : "require"; - if (this.options.luaModuleSystem === LuaModuleSystemKind.None) { + const requireCallString = this.options.outFile ? "__TS__LuaRequire" : "require"; + if (this.options.outFile) { this.importLuaLibFeature(LuaLibFeature.LuaRequire); } return tstl.createCallExpression(tstl.createIdentifier(requireCallString), [modulePath], moduleSpecifier); diff --git a/test/unit/luaModuleSystem.spec.ts b/test/unit/luaModuleSystem.spec.ts deleted file mode 100644 index 3d42c8e90..000000000 --- a/test/unit/luaModuleSystem.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as util from "../util"; -import * as ts from "typescript"; -import { LuaModuleSystemKind } from "../../src"; - -test("luaModuleSystem with outFile prevents re-creates require and package", () => { - util.testBundle` - import { value } from "./module"; - if (value !== true) { - throw "Failed to import value"; - } - ` - .setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD, luaModuleSystem: LuaModuleSystemKind.None }) - .setModuleSystem("none") - .addExtraFile("module.ts", "export const value = true;") - .expectNoExecutionError(); -}); diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 2d56d46df..2384c3d08 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -138,9 +138,7 @@ test.each<[string, Record]>([ ])("outFile tests (%s)", (_, files) => { const testBuilder = util.testBundle` ${files["main.ts"]} - ` - .setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD }) - .setModuleSystem("require"); + `.setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD }); const extraFiles = Object.keys(files) .map(file => ({ fileName: file, code: files[file] })) diff --git a/test/util.ts b/test/util.ts index 7c6a0a2e6..f358042aa 100644 --- a/test/util.ts +++ b/test/util.ts @@ -420,16 +420,7 @@ class AccessorTestBuilder extends TestBuilder { } class BundleTestBuilder extends AccessorTestBuilder { - protected moduleSystemKind: "none" | "require" = "require"; - public setModuleSystem(kind: "none" | "require"): this { - this.moduleSystemKind = kind; - return this; - } public getLuaCodeWithWrapper(): string { - if (this.moduleSystemKind === "require") { - return super.getLuaCodeWithWrapper(); - } - const noRequire = "_G.require = nil"; const noPackage = "_G.package = nil"; const luaCode = super.getLuaCodeWithWrapper(); From 635ba3bc90a45a2b907418f16d0bab889fb45ba9 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 11 Oct 2019 17:03:52 +1000 Subject: [PATCH 07/38] Add string array luaEntry option --- src/CommandLineParser.ts | 37 ++++++++++++++++++++++++++++++++++++- src/CompilerOptions.ts | 1 + src/LuaTransformer.ts | 36 +++++++++++++++++++++++------------- src/diagnostics.ts | 5 +++++ src/lualib/LuaRequire.ts | 12 +++++------- 5 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index d657e0127..55a46b6be 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -22,9 +22,18 @@ interface CommandLineOptionOfBoolean extends CommandLineOptionBase { type: "boolean"; } -type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean; +interface CommandLineOptionOfStringArray extends CommandLineOptionBase { + type: "string[]"; +} + +type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean | CommandLineOptionOfStringArray; const optionDeclarations: CommandLineOption[] = [ + { + name: "luaEntry", + description: "Specifies a series of entry points to execute in the resulting output file.", + type: "string[]", + }, { name: "luaLibImport", description: "Specifies how js standard features missing in lua are imported.", @@ -185,6 +194,14 @@ function readCommandLineArgument(option: CommandLineOption, value: any): Command }; } + if (option.type === 'string[]') { + return { + error: diagnosticFactories.optionCanOnlyBeSpecifiedInTsconfigJsonFile(option.name), + value: undefined, + increment: 0 + }; + } + return { ...readValue(option, value), increment: 1 }; } @@ -227,6 +244,24 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult { return { value: enumValue }; } + + case "string[]": { + if (Array.isArray(value)) { + if (value.some(str => typeof str !== "string")) { + return { + value: undefined, + error: diagnosticFactories.invalidStringArrayForOption(option.name), + }; + } + + return { value }; + } + + return { + value: undefined, + error: diagnosticFactories.invalidStringArrayForOption(option.name), + }; + } } } diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index f7c5999c5..c22b22175 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -20,6 +20,7 @@ export interface TransformerImport { export type CompilerOptions = OmitIndexSignature & { noImplicitSelf?: boolean; noHeader?: boolean; + luaEntry?: string[]; luaTarget?: LuaTarget; luaLibImport?: LuaLibImportKind; noHoisting?: boolean; diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 0f971a17d..288cfe878 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -140,6 +140,16 @@ export class LuaTransformer { [] ); + if (this.options.luaEntry) { + const requireCalls = this.options.luaEntry.map(entry => { + const formattedEntryName = tsHelper.formatPathToLuaPath(entry.replace(/.ts$/, "")); + return tstl.createExpressionStatement( + this.createModuleRequire(ts.createStringLiteral(formattedEntryName), false) + ); + }); + combinedStatements.push(...requireCalls); + } + return tstl.createBlock(combinedStatements, bundle); } @@ -171,22 +181,22 @@ export class LuaTransformer { // return exports statements.push(tstl.createReturnStatement([this.createExportsIdentifier()])); - - if (this.isWithinBundle) { - const packagePreload = tstl.createTableIndexExpression( - tstl.createIdentifier("package"), - tstl.createStringLiteral("preload") - ); - const exportPath = tsHelper.getExportPath(sourceFile.fileName, this.options); - const packagePreloadDeclaration = tstl.createAssignmentStatement( - tstl.createTableIndexExpression(packagePreload, tstl.createStringLiteral(exportPath)), - tstl.createFunctionExpression(tstl.createBlock(statements, sourceFile)) - ); - return tstl.createBlock([packagePreloadDeclaration], sourceFile); - } } } + if (this.isWithinBundle) { + const packagePreload = tstl.createTableIndexExpression( + tstl.createIdentifier("tstlpackage"), + tstl.createStringLiteral("preload") + ); + const exportPath = tsHelper.getExportPath(sourceFile.fileName, this.options); + const packagePreloadDeclaration = tstl.createAssignmentStatement( + tstl.createTableIndexExpression(packagePreload, tstl.createStringLiteral(exportPath)), + tstl.createFunctionExpression(tstl.createBlock(statements, sourceFile)) + ); + return tstl.createBlock([packagePreloadDeclaration], sourceFile); + } + return tstl.createBlock(statements, sourceFile); } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 2f052d272..a9d9ce2a2 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -130,3 +130,8 @@ export const optionBuildMustBeFirstCommandLineArgument = createCommandLineError( 6369, () => "Option '--build' must be the first command line argument." ); + +export const invalidStringArrayForOption = createCommandLineError( + 6370, + (name: string) => `Option '${name}' received an invalid array of strings.` +); diff --git a/src/lualib/LuaRequire.ts b/src/lualib/LuaRequire.ts index 31a610408..4cfd30817 100644 --- a/src/lualib/LuaRequire.ts +++ b/src/lualib/LuaRequire.ts @@ -1,16 +1,14 @@ -globalThis.package = {}; -globalThis.package.preload = {}; -globalThis.package.loaded = {}; +const tstlpackage = { preload: {}, loaded: {} }; function __TS__LuaRequire(this: void, moduleName: string): any { - if (!globalThis.package.loaded[moduleName]) { - const module: (this: void, module: string) => any = globalThis.package.preload[moduleName]; + if (!tstlpackage.loaded[moduleName]) { + const module: (this: void, module: string) => any = tstlpackage.preload[moduleName]; if (module) { - globalThis.package.loaded[moduleName] = module(moduleName); + tstlpackage.loaded[moduleName] = module(moduleName); } else { // tslint:disable-next-line: no-string-throw throw `module '${moduleName}' not found:`; } } - return globalThis.package.loaded[moduleName]; + return tstlpackage.loaded[moduleName]; } From e777b88d643650b6550d65f859b7312a66318c06 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 13 Oct 2019 10:01:00 +1000 Subject: [PATCH 08/38] Allow luaEntry to be specified via command line --- src/CommandLineParser.ts | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 55a46b6be..3b3c129af 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -194,14 +194,6 @@ function readCommandLineArgument(option: CommandLineOption, value: any): Command }; } - if (option.type === 'string[]') { - return { - error: diagnosticFactories.optionCanOnlyBeSpecifiedInTsconfigJsonFile(option.name), - value: undefined, - increment: 0 - }; - } - return { ...readValue(option, value), increment: 1 }; } @@ -257,10 +249,14 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult { return { value }; } - return { - value: undefined, - error: diagnosticFactories.invalidStringArrayForOption(option.name), - }; + if (typeof value !== "string") { + return { + value: undefined, + error: diagnosticFactories.compilerOptionRequiresAValueOfType(option.name, "string"), + }; + } + + return { value: [value] }; } } } From 1606517e7fdbacbdf7a8700117215b608c6f595d Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 13 Oct 2019 10:05:51 +1000 Subject: [PATCH 09/38] Ensure outFile tests are run --- test/unit/outFile.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 2384c3d08..ce6c88b52 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -138,7 +138,7 @@ test.each<[string, Record]>([ ])("outFile tests (%s)", (_, files) => { const testBuilder = util.testBundle` ${files["main.ts"]} - `.setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD }); + `.setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD, luaEntry: ["main.ts"] }); const extraFiles = Object.keys(files) .map(file => ({ fileName: file, code: files[file] })) From 7b17745a096e10e5b5347df831658243c3c39efa Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 15 Oct 2019 19:03:05 +1000 Subject: [PATCH 10/38] Support cyclic imports in output bundles --- src/LuaTransformer.ts | 6 ++++-- src/lualib/LuaRequire.ts | 25 +++++++++++++++---------- test/unit/outFile.spec.ts | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 288cfe878..cd45bfb95 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -167,7 +167,7 @@ export class LuaTransformer { statements = this.performHoisting(this.transformStatements(sourceFile.statements)); this.popScope(); - if (this.isModule) { + if (this.isModule && !this.isWithinBundle) { // If export equals was not used. Create the exports table. // local exports = {} if (!this.visitedExportEquals) { @@ -192,7 +192,9 @@ export class LuaTransformer { const exportPath = tsHelper.getExportPath(sourceFile.fileName, this.options); const packagePreloadDeclaration = tstl.createAssignmentStatement( tstl.createTableIndexExpression(packagePreload, tstl.createStringLiteral(exportPath)), - tstl.createFunctionExpression(tstl.createBlock(statements, sourceFile)) + tstl.createFunctionExpression(tstl.createBlock(statements, sourceFile), [ + this.createExportsIdentifier(), + ]) ); return tstl.createBlock([packagePreloadDeclaration], sourceFile); } diff --git a/src/lualib/LuaRequire.ts b/src/lualib/LuaRequire.ts index 4cfd30817..8a0faf832 100644 --- a/src/lualib/LuaRequire.ts +++ b/src/lualib/LuaRequire.ts @@ -1,14 +1,19 @@ -const tstlpackage = { preload: {}, loaded: {} }; +const tstlpackage: { + preload: Record void>; + loaded: Record; +} = { preload: {}, loaded: {} }; function __TS__LuaRequire(this: void, moduleName: string): any { - if (!tstlpackage.loaded[moduleName]) { - const module: (this: void, module: string) => any = tstlpackage.preload[moduleName]; - if (module) { - tstlpackage.loaded[moduleName] = module(moduleName); - } else { - // tslint:disable-next-line: no-string-throw - throw `module '${moduleName}' not found:`; - } + if (tstlpackage.loaded[moduleName]) { + return tstlpackage.loaded[moduleName]; } - return tstlpackage.loaded[moduleName]; + const loadScript = tstlpackage.preload[moduleName]; + if (!loadScript) { + // tslint:disable-next-line: no-string-throw + throw `module '${moduleName}' not found`; + } + const moduleExports = {}; + tstlpackage.loaded[moduleName] = moduleExports; + loadScript(moduleExports); + return moduleExports; } diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index ce6c88b52..8acf8f138 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -150,3 +150,22 @@ test.each<[string, Record]>([ testBuilder.expectNoExecutionError(); }); + +test("outFile cyclic imports", () => { + util.testBundle` + export const a = true; + import { b } from "./b"; + if (b !== true) { + throw "Did not receive true from module b"; + } + ` + .addExtraFile( + "b.ts", + ` + import { a } from "./main"; + export const b = a; + ` + ) + .setOptions({ outFile: "main.lua", noHoisting: true, module: ts.ModuleKind.AMD, luaEntry: ["main.ts"] }) + .expectNoExecutionError(); +}); From 7c6ab1192270d47389bf2dd00daebef4d22d8a97 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 15 Oct 2019 19:52:26 +1000 Subject: [PATCH 11/38] Split up outFile tests --- test/unit/outFile.spec.ts | 267 +++++++++++++++----------------------- 1 file changed, 107 insertions(+), 160 deletions(-) diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 8acf8f138..91ba83bc8 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -1,171 +1,118 @@ import * as util from "../util"; import * as ts from "typescript"; +import { CompilerOptions } from "../../src/CompilerOptions"; -const exportValueSource = ` - export const value = true; -`; +const exportValueSource = "export const value = true;"; +const reexportValueSource = 'export { value } from "./export";'; +const validateValueSource = 'if (value !== true) { throw "Failed to import value" }'; -const reexportValueSource = ` - export { value } from "./export"; -`; +function outFileOptionsWithEntry(entry: string): CompilerOptions { + return { outFile: "main.lua", noHoisting: true, module: ts.ModuleKind.AMD, luaEntry: [entry] }; +} -test.each<[string, Record]>([ - [ - "Import module -> main", - { - "main.ts": ` - import { value } from "./module"; - if (value !== true) { - throw "Failed to import x"; - } - `, - "module.ts": exportValueSource, - }, - ], - [ - "Import chain export -> reexport -> main", - { - "main.ts": ` - import { value } from "./reexport"; - if (value !== true) { - throw "Failed to import value"; - } - `, - "reexport.ts": reexportValueSource, - "export.ts": exportValueSource, - }, - ], - [ - "Import chain with a different order", - { - "main.ts": ` - import { value } from "./reexport"; - if (value !== true) { - throw "Failed to import value"; - } - `, - "export.ts": exportValueSource, - "reexport.ts": reexportValueSource, - }, - ], - [ - "Import diamond export -> reexport1 & reexport2 -> main", - { - "main.ts": ` - import { value as a } from "./reexport1"; - import { value as b } from "./reexport2"; - if (a !== true || b !== true) { - throw "Failed to import a or b"; - } - `, - "export.ts": exportValueSource, - "reexport1.ts": reexportValueSource, - "reexport2.ts": reexportValueSource, - }, - ], - [ - "Import diamond different order", - { - "reexport1.ts": reexportValueSource, - "reexport2.ts": reexportValueSource, - "export.ts": exportValueSource, - "main.ts": ` - import { value as a } from "./reexport1"; - import { value as b } from "./reexport2"; - if (a !== true || b !== true) { - throw "Failed to import a or b"; - } - `, - }, - ], - [ - "Modules in directories", - { - "main.ts": ` - import { value } from "./module/module"; - if (value !== true) { - throw "Failed to import value"; - } - `, - "module/module.ts": ` - export const value = true; - `, - }, - ], - [ - "Modules aren't ordered by name", - { - "main.ts": ` - import { value } from "./a"; - if (value !== true) { - throw "Failed to import value"; - } - `, - "a.ts": ` - export const value = true; - `, - }, - ], - [ - "Modules in directories", - { - "main/main.ts": ` - import { value } from "../module"; - if (value !== true) { - throw "Failed to import value"; - } - `, - "module.ts": ` - export const value = true; - `, - }, - ], - [ - "LuaLibs are usable", - { - "module.ts": ` - export const array = [1, 2]; - array.push(3); - `, - "main.ts": ` - import { array } from "./module"; - if (array[2] !== 3) { - throw "Array's third item is not three"; - } - `, - }, - ], -])("outFile tests (%s)", (_, files) => { - const testBuilder = util.testBundle` - ${files["main.ts"]} - `.setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD, luaEntry: ["main.ts"] }); +describe("outFile", () => { + test("import module -> main", () => { + util.testBundle` + import { value } from "./module"; + ${validateValueSource} + ` + .addExtraFile("module.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); + }); - const extraFiles = Object.keys(files) - .map(file => ({ fileName: file, code: files[file] })) - .filter(file => file.fileName !== "main.ts"); + test("import chain export -> reexport -> main", () => { + util.testBundle` + import { value } from "./reexport"; + ${validateValueSource} + ` + .addExtraFile("reexport.ts", reexportValueSource) + .addExtraFile("export.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); + }); - extraFiles.forEach(extraFile => { - testBuilder.addExtraFile(extraFile.fileName, extraFile.code); + test("diamond imports/exports -> reexport1 & reexport2 -> main", () => { + util.testBundle` + import { value as a } from "./reexport1"; + import { value as b } from "./reexport2"; + if (a !== true || b !== true) { + throw "Failed to import a or b"; + } + ` + .addExtraFile("reexport1.ts", reexportValueSource) + .addExtraFile("reexport2.ts", reexportValueSource) + .addExtraFile("export.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); }); - testBuilder.expectNoExecutionError(); -}); + test("module in directory", () => { + util.testBundle` + import { value } from "./module/module"; + ${validateValueSource} + ` + .addExtraFile("module/module.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); + }); + + test("modules aren't ordered by name", () => { + util.testBundle` + import { value } from "./a"; + ${validateValueSource} + ` + .addExtraFile("a.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); + }); -test("outFile cyclic imports", () => { - util.testBundle` - export const a = true; - import { b } from "./b"; - if (b !== true) { - throw "Did not receive true from module b"; - } - ` - .addExtraFile( - "b.ts", - ` - import { a } from "./main"; - export const b = a; - ` - ) - .setOptions({ outFile: "main.lua", noHoisting: true, module: ts.ModuleKind.AMD, luaEntry: ["main.ts"] }) - .expectNoExecutionError(); + test("entry point in directory", () => { + util.testBundle`` + .addExtraFile( + "main/main.ts", + ` + import { value } from "../module"; + ${validateValueSource} + ` + ) + .addExtraFile("module.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main/main.ts")) + .expectNoExecutionError(); + }); + + test("LuaLibs", () => { + util.testBundle` + import { array } from "./module"; + if (array[2] !== 3) { + throw "Array's third item is not three"; + } + ` + .addExtraFile( + "module.ts", + ` + export const array = [1, 2]; + array.push(3); + ` + ) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); + }); + + test("cyclic imports", () => { + util.testBundle` + export const a = true; + import { value } from "./b"; + ${validateValueSource} + ` + .addExtraFile( + "b.ts", + ` + import { a } from "./main"; + export const value = a; + ` + ) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); + }); }); From d36356dbedfdcb8bec9837142cda6a3fa97c12a1 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 15 Oct 2019 20:35:48 +1000 Subject: [PATCH 12/38] Resolve luaEntry paths relative to tsconfig --- src/LuaTransformer.ts | 13 +++++++++---- src/TSTLErrors.ts | 3 +++ src/diagnostics.ts | 6 +++--- test/unit/outFile.spec.ts | 15 +++++++++++++++ 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index cd45bfb95..78d9c6dae 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -142,10 +142,15 @@ export class LuaTransformer { if (this.options.luaEntry) { const requireCalls = this.options.luaEntry.map(entry => { - const formattedEntryName = tsHelper.formatPathToLuaPath(entry.replace(/.ts$/, "")); - return tstl.createExpressionStatement( - this.createModuleRequire(ts.createStringLiteral(formattedEntryName), false) - ); + const sourceFile = this.program.getSourceFile(entry); + if (sourceFile) { + const formattedEntryName = tsHelper.getExportPath(sourceFile.fileName, this.options); + return tstl.createExpressionStatement( + this.createModuleRequire(ts.createStringLiteral(formattedEntryName), false) + ); + } else { + throw TSTLErrors.LuaEntryNotFound(entry); + } }); combinedStatements.push(...requireCalls); } diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index 89867e01d..73b61543b 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -60,6 +60,9 @@ export const InvalidThrowExpression = (node: ts.Node) => export const ForbiddenStaticClassPropertyName = (node: ts.Node, name: string) => new TranspileError(`Cannot use "${name}" as a static class property or method name.`, node); +export const LuaEntryNotFound = (entryName: string) => + new TranspileError(`Could not find luaEntry file '${entryName}'.`, ts.createNode(ts.SyntaxKind.Unknown)); + export const MissingClassName = (node: ts.Node) => new TranspileError(`Class declarations must have a name.`, node); export const MissingForOfVariables = (node: ts.Node) => diff --git a/src/diagnostics.ts b/src/diagnostics.ts index a9d9ce2a2..4a964cd83 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -2,9 +2,9 @@ import * as ts from "typescript"; import { TranspileError } from "./TranspileError"; export const transpileError = (error: TranspileError): ts.Diagnostic => ({ - file: error.node.getSourceFile(), - start: error.node.getStart(), - length: error.node.getWidth(), + file: error.node.kind === ts.SyntaxKind.Unknown ? undefined : error.node.getSourceFile(), + start: error.node.kind === ts.SyntaxKind.Unknown ? undefined : error.node.getStart(), + length: error.node.kind === ts.SyntaxKind.Unknown ? undefined : error.node.getWidth(), category: ts.DiagnosticCategory.Error, code: 0, source: "typescript-to-lua", diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 91ba83bc8..c770345f6 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -1,5 +1,6 @@ import * as util from "../util"; import * as ts from "typescript"; +import * as TSTLErrors from "../../src/TSTLErrors"; import { CompilerOptions } from "../../src/CompilerOptions"; const exportValueSource = "export const value = true;"; @@ -115,4 +116,18 @@ describe("outFile", () => { .setOptions(outFileOptionsWithEntry("main.ts")) .expectNoExecutionError(); }); + + test("luaEntry doesn't exist", () => { + util.testBundle`` + .setOptions(outFileOptionsWithEntry("entry.ts")) + .expectToHaveDiagnosticOfError(TSTLErrors.LuaEntryNotFound("entry.ts")); + }); + + test("luaEntry resolved from path specified in tsconfig", () => { + util.testBundle`` + .addExtraFile("src/main.ts", "") + .addExtraFile("src/module.ts", "") + .setOptions({ rootDir: "src", ...outFileOptionsWithEntry("src/main.ts") }) + .expectToHaveNoDiagnostics(); + }); }); From ba2cc1fec14d76ce54e9278877843aed751f21c1 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 15 Oct 2019 20:47:13 +1000 Subject: [PATCH 13/38] Reduce luaEntry option to a string naming a module to export --- src/CommandLineParser.ts | 23 ++++++----------------- src/CompilerOptions.ts | 2 +- src/LuaTransformer.ts | 21 +++++++++------------ src/diagnostics.ts | 5 ----- test/unit/outFile.spec.ts | 4 ++-- 5 files changed, 18 insertions(+), 37 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 3b3c129af..00fedf1c8 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -22,17 +22,17 @@ interface CommandLineOptionOfBoolean extends CommandLineOptionBase { type: "boolean"; } -interface CommandLineOptionOfStringArray extends CommandLineOptionBase { - type: "string[]"; +interface CommandLineOptionOfString extends CommandLineOptionBase { + type: "string"; } -type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean | CommandLineOptionOfStringArray; +type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean | CommandLineOptionOfString; const optionDeclarations: CommandLineOption[] = [ { name: "luaEntry", - description: "Specifies a series of entry points to execute in the resulting output file.", - type: "string[]", + description: "Specifies an entry point that will be executed in the resulting output file.", + type: "string", }, { name: "luaLibImport", @@ -237,18 +237,7 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult { return { value: enumValue }; } - case "string[]": { - if (Array.isArray(value)) { - if (value.some(str => typeof str !== "string")) { - return { - value: undefined, - error: diagnosticFactories.invalidStringArrayForOption(option.name), - }; - } - - return { value }; - } - + case "string": { if (typeof value !== "string") { return { value: undefined, diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index c22b22175..11b51ded5 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -20,7 +20,7 @@ export interface TransformerImport { export type CompilerOptions = OmitIndexSignature & { noImplicitSelf?: boolean; noHeader?: boolean; - luaEntry?: string[]; + luaEntry?: string; luaTarget?: LuaTarget; luaLibImport?: LuaLibImportKind; noHoisting?: boolean; diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 78d9c6dae..631bda9fb 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -141,18 +141,15 @@ export class LuaTransformer { ); if (this.options.luaEntry) { - const requireCalls = this.options.luaEntry.map(entry => { - const sourceFile = this.program.getSourceFile(entry); - if (sourceFile) { - const formattedEntryName = tsHelper.getExportPath(sourceFile.fileName, this.options); - return tstl.createExpressionStatement( - this.createModuleRequire(ts.createStringLiteral(formattedEntryName), false) - ); - } else { - throw TSTLErrors.LuaEntryNotFound(entry); - } - }); - combinedStatements.push(...requireCalls); + const sourceFile = this.program.getSourceFile(this.options.luaEntry); + if (sourceFile) { + const formattedEntryName = tsHelper.getExportPath(sourceFile.fileName, this.options); + const requireCall = this.createModuleRequire(ts.createStringLiteral(formattedEntryName), false); + const returnStatement = tstl.createReturnStatement([requireCall]); + combinedStatements.push(returnStatement); + } else { + throw TSTLErrors.LuaEntryNotFound(this.options.luaEntry); + } } return tstl.createBlock(combinedStatements, bundle); diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 4a964cd83..5b98ef5dc 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -130,8 +130,3 @@ export const optionBuildMustBeFirstCommandLineArgument = createCommandLineError( 6369, () => "Option '--build' must be the first command line argument." ); - -export const invalidStringArrayForOption = createCommandLineError( - 6370, - (name: string) => `Option '${name}' received an invalid array of strings.` -); diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index c770345f6..4ed12c5a8 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -7,8 +7,8 @@ const exportValueSource = "export const value = true;"; const reexportValueSource = 'export { value } from "./export";'; const validateValueSource = 'if (value !== true) { throw "Failed to import value" }'; -function outFileOptionsWithEntry(entry: string): CompilerOptions { - return { outFile: "main.lua", noHoisting: true, module: ts.ModuleKind.AMD, luaEntry: [entry] }; +function outFileOptionsWithEntry(luaEntry: string): CompilerOptions { + return { outFile: "main.lua", noHoisting: true, module: ts.ModuleKind.AMD, luaEntry }; } describe("outFile", () => { From 69ad1b06f15222e1563e52874372cf6b0dad6388 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 15 Oct 2019 20:56:08 +1000 Subject: [PATCH 14/38] Use bundle exports in outFile tests --- test/unit/outFile.spec.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 4ed12c5a8..bc6968fce 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -5,7 +5,7 @@ import { CompilerOptions } from "../../src/CompilerOptions"; const exportValueSource = "export const value = true;"; const reexportValueSource = 'export { value } from "./export";'; -const validateValueSource = 'if (value !== true) { throw "Failed to import value" }'; +const exportResultSource = "export const result = value;"; function outFileOptionsWithEntry(luaEntry: string): CompilerOptions { return { outFile: "main.lua", noHoisting: true, module: ts.ModuleKind.AMD, luaEntry }; @@ -15,22 +15,22 @@ describe("outFile", () => { test("import module -> main", () => { util.testBundle` import { value } from "./module"; - ${validateValueSource} + ${exportResultSource} ` .addExtraFile("module.ts", exportValueSource) .setOptions(outFileOptionsWithEntry("main.ts")) - .expectNoExecutionError(); + .expectToEqual({ result: true }); }); test("import chain export -> reexport -> main", () => { util.testBundle` import { value } from "./reexport"; - ${validateValueSource} + ${exportResultSource} ` .addExtraFile("reexport.ts", reexportValueSource) .addExtraFile("export.ts", exportValueSource) .setOptions(outFileOptionsWithEntry("main.ts")) - .expectNoExecutionError(); + .expectToEqual({ result: true }); }); test("diamond imports/exports -> reexport1 & reexport2 -> main", () => { @@ -51,7 +51,7 @@ describe("outFile", () => { test("module in directory", () => { util.testBundle` import { value } from "./module/module"; - ${validateValueSource} + ${exportValueSource} ` .addExtraFile("module/module.ts", exportValueSource) .setOptions(outFileOptionsWithEntry("main.ts")) @@ -61,11 +61,11 @@ describe("outFile", () => { test("modules aren't ordered by name", () => { util.testBundle` import { value } from "./a"; - ${validateValueSource} + ${exportResultSource} ` .addExtraFile("a.ts", exportValueSource) .setOptions(outFileOptionsWithEntry("main.ts")) - .expectNoExecutionError(); + .expectToEqual({ result: true }); }); test("entry point in directory", () => { @@ -74,12 +74,12 @@ describe("outFile", () => { "main/main.ts", ` import { value } from "../module"; - ${validateValueSource} + ${exportResultSource} ` ) .addExtraFile("module.ts", exportValueSource) .setOptions(outFileOptionsWithEntry("main/main.ts")) - .expectNoExecutionError(); + .expectToEqual({ result: true }); }); test("LuaLibs", () => { @@ -104,7 +104,7 @@ describe("outFile", () => { util.testBundle` export const a = true; import { value } from "./b"; - ${validateValueSource} + ${exportResultSource} ` .addExtraFile( "b.ts", @@ -114,7 +114,7 @@ describe("outFile", () => { ` ) .setOptions(outFileOptionsWithEntry("main.ts")) - .expectNoExecutionError(); + .expectToEqual({ a: true, result: true }); }); test("luaEntry doesn't exist", () => { From 4634d63bd79a18ed07b072e15742f6bf94c77718 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 15 Oct 2019 20:58:28 +1000 Subject: [PATCH 15/38] Remove string array parsing --- src/CommandLineParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 00fedf1c8..1f3cf645f 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -245,7 +245,7 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult { }; } - return { value: [value] }; + return { value }; } } } From b2ab7db223ea4c5f56b769ddc635788534956a3b Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 16 Oct 2019 18:52:44 +1000 Subject: [PATCH 16/38] Remove describe from outFile tests --- test/unit/outFile.spec.ts | 218 +++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 110 deletions(-) diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index bc6968fce..785e85621 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -11,123 +11,121 @@ function outFileOptionsWithEntry(luaEntry: string): CompilerOptions { return { outFile: "main.lua", noHoisting: true, module: ts.ModuleKind.AMD, luaEntry }; } -describe("outFile", () => { - test("import module -> main", () => { - util.testBundle` - import { value } from "./module"; - ${exportResultSource} - ` - .addExtraFile("module.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) - .expectToEqual({ result: true }); - }); +test("import module -> main", () => { + util.testBundle` + import { value } from "./module"; + ${exportResultSource} + ` + .addExtraFile("module.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectToEqual({ result: true }); +}); - test("import chain export -> reexport -> main", () => { - util.testBundle` - import { value } from "./reexport"; - ${exportResultSource} - ` - .addExtraFile("reexport.ts", reexportValueSource) - .addExtraFile("export.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) - .expectToEqual({ result: true }); - }); +test("import chain export -> reexport -> main", () => { + util.testBundle` + import { value } from "./reexport"; + ${exportResultSource} + ` + .addExtraFile("reexport.ts", reexportValueSource) + .addExtraFile("export.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectToEqual({ result: true }); +}); - test("diamond imports/exports -> reexport1 & reexport2 -> main", () => { - util.testBundle` - import { value as a } from "./reexport1"; - import { value as b } from "./reexport2"; - if (a !== true || b !== true) { - throw "Failed to import a or b"; - } - ` - .addExtraFile("reexport1.ts", reexportValueSource) - .addExtraFile("reexport2.ts", reexportValueSource) - .addExtraFile("export.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) - .expectNoExecutionError(); - }); +test("diamond imports/exports -> reexport1 & reexport2 -> main", () => { + util.testBundle` + import { value as a } from "./reexport1"; + import { value as b } from "./reexport2"; + if (a !== true || b !== true) { + throw "Failed to import a or b"; + } + ` + .addExtraFile("reexport1.ts", reexportValueSource) + .addExtraFile("reexport2.ts", reexportValueSource) + .addExtraFile("export.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); +}); - test("module in directory", () => { - util.testBundle` - import { value } from "./module/module"; - ${exportValueSource} - ` - .addExtraFile("module/module.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) - .expectNoExecutionError(); - }); +test("module in directory", () => { + util.testBundle` + import { value } from "./module/module"; + ${exportValueSource} + ` + .addExtraFile("module/module.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); +}); - test("modules aren't ordered by name", () => { - util.testBundle` - import { value } from "./a"; - ${exportResultSource} - ` - .addExtraFile("a.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) - .expectToEqual({ result: true }); - }); +test("modules aren't ordered by name", () => { + util.testBundle` + import { value } from "./a"; + ${exportResultSource} + ` + .addExtraFile("a.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectToEqual({ result: true }); +}); - test("entry point in directory", () => { - util.testBundle`` - .addExtraFile( - "main/main.ts", - ` - import { value } from "../module"; - ${exportResultSource} - ` - ) - .addExtraFile("module.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main/main.ts")) - .expectToEqual({ result: true }); - }); +test("entry point in directory", () => { + util.testBundle`` + .addExtraFile( + "main/main.ts", + ` + import { value } from "../module"; + ${exportResultSource} + ` + ) + .addExtraFile("module.ts", exportValueSource) + .setOptions(outFileOptionsWithEntry("main/main.ts")) + .expectToEqual({ result: true }); +}); - test("LuaLibs", () => { - util.testBundle` - import { array } from "./module"; - if (array[2] !== 3) { - throw "Array's third item is not three"; - } - ` - .addExtraFile( - "module.ts", - ` - export const array = [1, 2]; - array.push(3); - ` - ) - .setOptions(outFileOptionsWithEntry("main.ts")) - .expectNoExecutionError(); - }); +test("LuaLibs", () => { + util.testBundle` + import { array } from "./module"; + if (array[2] !== 3) { + throw "Array's third item is not three"; + } + ` + .addExtraFile( + "module.ts", + ` + export const array = [1, 2]; + array.push(3); + ` + ) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectNoExecutionError(); +}); - test("cyclic imports", () => { - util.testBundle` - export const a = true; - import { value } from "./b"; - ${exportResultSource} - ` - .addExtraFile( - "b.ts", - ` - import { a } from "./main"; - export const value = a; - ` - ) - .setOptions(outFileOptionsWithEntry("main.ts")) - .expectToEqual({ a: true, result: true }); - }); +test("cyclic imports", () => { + util.testBundle` + export const a = true; + import { value } from "./b"; + ${exportResultSource} + ` + .addExtraFile( + "b.ts", + ` + import { a } from "./main"; + export const value = a; + ` + ) + .setOptions(outFileOptionsWithEntry("main.ts")) + .expectToEqual({ a: true, result: true }); +}); - test("luaEntry doesn't exist", () => { - util.testBundle`` - .setOptions(outFileOptionsWithEntry("entry.ts")) - .expectToHaveDiagnosticOfError(TSTLErrors.LuaEntryNotFound("entry.ts")); - }); +test("luaEntry doesn't exist", () => { + util.testBundle`` + .setOptions(outFileOptionsWithEntry("entry.ts")) + .expectToHaveDiagnosticOfError(TSTLErrors.LuaEntryNotFound("entry.ts")); +}); - test("luaEntry resolved from path specified in tsconfig", () => { - util.testBundle`` - .addExtraFile("src/main.ts", "") - .addExtraFile("src/module.ts", "") - .setOptions({ rootDir: "src", ...outFileOptionsWithEntry("src/main.ts") }) - .expectToHaveNoDiagnostics(); - }); +test("luaEntry resolved from path specified in tsconfig", () => { + util.testBundle`` + .addExtraFile("src/main.ts", "") + .addExtraFile("src/module.ts", "") + .setOptions({ rootDir: "src", ...outFileOptionsWithEntry("src/main.ts") }) + .expectToHaveNoDiagnostics(); }); From b683a489d5ffec62df3c12013062f75b698f0a15 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 16 Oct 2019 22:10:53 +1000 Subject: [PATCH 17/38] Allow export equals in bundles --- src/LuaTransformer.ts | 28 +++++++++++++++------------- src/lualib/LuaRequire.ts | 6 +++--- test/unit/outFile.spec.ts | 8 ++++++-- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 631bda9fb..36d93f19e 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -131,6 +131,7 @@ export class LuaTransformer { const combinedStatements = bundle.sourceFiles.reduce( (statements: tstl.Statement[], sourceFile: ts.SourceFile) => { this.currentSourceFile = sourceFile; + this.visitedExportEquals = false; this.isModule = tsHelper.isFileModule(sourceFile); const originalSourceFile = ts.getParseTreeNode(sourceFile, ts.isSourceFile) || sourceFile; this.resolver = this.checker.getEmitResolver(originalSourceFile); @@ -169,16 +170,18 @@ export class LuaTransformer { statements = this.performHoisting(this.transformStatements(sourceFile.statements)); this.popScope(); - if (this.isModule && !this.isWithinBundle) { - // If export equals was not used. Create the exports table. - // local exports = {} - if (!this.visitedExportEquals) { - statements.unshift( - tstl.createVariableDeclarationStatement( - this.createExportsIdentifier(), - tstl.createTableExpression() - ) - ); + if (this.isModule) { + if (!this.isWithinBundle) { + // If export equals was not used. Create the exports table. + // local exports = {} + if (!this.visitedExportEquals) { + statements.unshift( + tstl.createVariableDeclarationStatement( + this.createExportsIdentifier(), + tstl.createTableExpression() + ) + ); + } } // return exports @@ -192,11 +195,10 @@ export class LuaTransformer { tstl.createStringLiteral("preload") ); const exportPath = tsHelper.getExportPath(sourceFile.fileName, this.options); + const moduleParameters = this.visitedExportEquals ? undefined : [this.createExportsIdentifier()]; const packagePreloadDeclaration = tstl.createAssignmentStatement( tstl.createTableIndexExpression(packagePreload, tstl.createStringLiteral(exportPath)), - tstl.createFunctionExpression(tstl.createBlock(statements, sourceFile), [ - this.createExportsIdentifier(), - ]) + tstl.createFunctionExpression(tstl.createBlock(statements, sourceFile), moduleParameters) ); return tstl.createBlock([packagePreloadDeclaration], sourceFile); } diff --git a/src/lualib/LuaRequire.ts b/src/lualib/LuaRequire.ts index 8a0faf832..63e770903 100644 --- a/src/lualib/LuaRequire.ts +++ b/src/lualib/LuaRequire.ts @@ -1,5 +1,5 @@ const tstlpackage: { - preload: Record void>; + preload: Record object>; loaded: Record; } = { preload: {}, loaded: {} }; @@ -14,6 +14,6 @@ function __TS__LuaRequire(this: void, moduleName: string): any { } const moduleExports = {}; tstlpackage.loaded[moduleName] = moduleExports; - loadScript(moduleExports); - return moduleExports; + tstlpackage.loaded[moduleName] = loadScript(moduleExports); + return tstlpackage.loaded[moduleName]; } diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 785e85621..fa68dc009 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -8,7 +8,7 @@ const reexportValueSource = 'export { value } from "./export";'; const exportResultSource = "export const result = value;"; function outFileOptionsWithEntry(luaEntry: string): CompilerOptions { - return { outFile: "main.lua", noHoisting: true, module: ts.ModuleKind.AMD, luaEntry }; + return { outFile: "main.lua", module: ts.ModuleKind.AMD, luaEntry }; } test("import module -> main", () => { @@ -112,7 +112,7 @@ test("cyclic imports", () => { export const value = a; ` ) - .setOptions(outFileOptionsWithEntry("main.ts")) + .setOptions({ noHoisting: true, ...outFileOptionsWithEntry("main.ts") }) .expectToEqual({ a: true, result: true }); }); @@ -129,3 +129,7 @@ test("luaEntry resolved from path specified in tsconfig", () => { .setOptions({ rootDir: "src", ...outFileOptionsWithEntry("src/main.ts") }) .expectToHaveNoDiagnostics(); }); + +test("export equals", () => { + util.testBundle`export = "result"`.setOptions(outFileOptionsWithEntry("main.ts")).expectToEqual("result"); +}); From cc71c2a3da02eedc14c088b98c79d39d3144d942 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 16 Oct 2019 22:16:35 +1000 Subject: [PATCH 18/38] Use seperate variables for bundle module system --- src/LuaTransformer.ts | 11 ++++------- src/lualib/LuaRequire.ts | 18 ++++++++---------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 36d93f19e..17d88e5d4 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -190,17 +190,14 @@ export class LuaTransformer { } if (this.isWithinBundle) { - const packagePreload = tstl.createTableIndexExpression( - tstl.createIdentifier("tstlpackage"), - tstl.createStringLiteral("preload") - ); + const moduleTableIdentifier = tstl.createIdentifier("__TS__MODULES"); const exportPath = tsHelper.getExportPath(sourceFile.fileName, this.options); const moduleParameters = this.visitedExportEquals ? undefined : [this.createExportsIdentifier()]; - const packagePreloadDeclaration = tstl.createAssignmentStatement( - tstl.createTableIndexExpression(packagePreload, tstl.createStringLiteral(exportPath)), + const moduleDeclaration = tstl.createAssignmentStatement( + tstl.createTableIndexExpression(moduleTableIdentifier, tstl.createStringLiteral(exportPath)), tstl.createFunctionExpression(tstl.createBlock(statements, sourceFile), moduleParameters) ); - return tstl.createBlock([packagePreloadDeclaration], sourceFile); + return tstl.createBlock([moduleDeclaration], sourceFile); } return tstl.createBlock(statements, sourceFile); diff --git a/src/lualib/LuaRequire.ts b/src/lualib/LuaRequire.ts index 63e770903..a96336bf1 100644 --- a/src/lualib/LuaRequire.ts +++ b/src/lualib/LuaRequire.ts @@ -1,19 +1,17 @@ -const tstlpackage: { - preload: Record object>; - loaded: Record; -} = { preload: {}, loaded: {} }; +const __TS__MODULES: Record any> = {}; +const __TS__MODULECACHE: Record = {}; function __TS__LuaRequire(this: void, moduleName: string): any { - if (tstlpackage.loaded[moduleName]) { - return tstlpackage.loaded[moduleName]; + if (__TS__MODULECACHE[moduleName]) { + return __TS__MODULECACHE[moduleName]; } - const loadScript = tstlpackage.preload[moduleName]; + const loadScript = __TS__MODULES[moduleName]; if (!loadScript) { // tslint:disable-next-line: no-string-throw throw `module '${moduleName}' not found`; } const moduleExports = {}; - tstlpackage.loaded[moduleName] = moduleExports; - tstlpackage.loaded[moduleName] = loadScript(moduleExports); - return tstlpackage.loaded[moduleName]; + __TS__MODULECACHE[moduleName] = moduleExports; + __TS__MODULECACHE[moduleName] = loadScript(moduleExports); + return __TS__MODULECACHE[moduleName]; } From 8972539393bcd1bfd960708ecaa74acf9441c6ea Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 16 Oct 2019 22:23:53 +1000 Subject: [PATCH 19/38] Add primative type command line option --- src/CommandLineParser.ts | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 1f3cf645f..e43126167 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -18,15 +18,11 @@ interface CommandLineOptionOfEnum extends CommandLineOptionBase { choices: string[]; } -interface CommandLineOptionOfBoolean extends CommandLineOptionBase { - type: "boolean"; +interface CommandLineOptionOfPrimitiveType extends CommandLineOptionBase { + type: "string" | "boolean"; } -interface CommandLineOptionOfString extends CommandLineOptionBase { - type: "string"; -} - -type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean | CommandLineOptionOfString; +type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfPrimitiveType; const optionDeclarations: CommandLineOption[] = [ { @@ -206,11 +202,12 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult { if (value === null) return { value }; switch (option.type) { + case "string": case "boolean": { - if (typeof value !== "boolean") { + if (typeof value !== option.type) { return { value: undefined, - error: diagnosticFactories.compilerOptionRequiresAValueOfType(option.name, "boolean"), + error: diagnosticFactories.compilerOptionRequiresAValueOfType(option.name, option.type), }; } @@ -236,17 +233,6 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult { return { value: enumValue }; } - - case "string": { - if (typeof value !== "string") { - return { - value: undefined, - error: diagnosticFactories.compilerOptionRequiresAValueOfType(option.name, "string"), - }; - } - - return { value }; - } } } From 650054862aeeaf23d8593fb2783e33583791a2b9 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 16 Oct 2019 22:40:29 +1000 Subject: [PATCH 20/38] Refactor to new transformSourceFileWithState method --- src/LuaTransformer.ts | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 17d88e5d4..de0fc3324 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -92,8 +92,6 @@ export class LuaTransformer { this.genVarCounter = 0; this.luaLibFeatureSet = new Set(); - this.visitedExportEquals = false; - this.scopeStack = []; this.classStack = []; @@ -111,16 +109,7 @@ export class LuaTransformer { public transform(node: ts.Bundle | ts.SourceFile): [tstl.Block, Set] { this.setupState(); if (ts.isSourceFile(node)) { - this.currentSourceFile = node; - this.isModule = tsHelper.isFileModule(node); - - // Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers. - // It's required because otherwise `getEmitResolver` won't use cached diagnostics, produced in `emitWorker` - // and would try to re-analyze the file, which would fail because of replaced nodes. - const originalSourceFile = ts.getParseTreeNode(node, ts.isSourceFile) || node; - this.resolver = this.checker.getEmitResolver(originalSourceFile); - - return [this.transformSourceFile(node), this.luaLibFeatureSet]; + return [this.transformSourceFileWithState(node), this.luaLibFeatureSet]; } else { return [this.transformBundle(node), this.luaLibFeatureSet]; } @@ -130,12 +119,7 @@ export class LuaTransformer { this.isWithinBundle = true; const combinedStatements = bundle.sourceFiles.reduce( (statements: tstl.Statement[], sourceFile: ts.SourceFile) => { - this.currentSourceFile = sourceFile; - this.visitedExportEquals = false; - this.isModule = tsHelper.isFileModule(sourceFile); - const originalSourceFile = ts.getParseTreeNode(sourceFile, ts.isSourceFile) || sourceFile; - this.resolver = this.checker.getEmitResolver(originalSourceFile); - const transformResult = this.transformSourceFile(sourceFile); + const transformResult = this.transformSourceFileWithState(sourceFile); return [...statements, ...transformResult.statements]; }, [] @@ -157,6 +141,7 @@ export class LuaTransformer { } public transformSourceFile(sourceFile: ts.SourceFile): tstl.Block { + this.visitedExportEquals = false; let statements: tstl.Statement[] = []; if (sourceFile.flags & ts.NodeFlags.JsonFile) { const statement = sourceFile.statements[0]; @@ -203,6 +188,19 @@ export class LuaTransformer { return tstl.createBlock(statements, sourceFile); } + private transformSourceFileWithState(node: ts.SourceFile): tstl.Block { + this.currentSourceFile = node; + this.isModule = tsHelper.isFileModule(node); + + // Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers. + // It's required because otherwise `getEmitResolver` won't use cached diagnostics, produced in `emitWorker` + // and would try to re-analyze the file, which would fail because of replaced nodes. + const originalSourceFile = ts.getParseTreeNode(node, ts.isSourceFile) || node; + this.resolver = this.checker.getEmitResolver(originalSourceFile); + + return this.transformSourceFile(node); + } + public transformStatement(node: ts.Statement): StatementVisitResult { // Ignore declarations if (node.modifiers && node.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword)) { From 69693f034bc405d2c4920ea2ba8ff2d2706b2110 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 16 Oct 2019 23:25:50 +1000 Subject: [PATCH 21/38] Generate and use a full luaEntry path if project is specified --- src/LuaTransformer.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index de0fc3324..e4824f406 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -126,7 +126,14 @@ export class LuaTransformer { ); if (this.options.luaEntry) { - const sourceFile = this.program.getSourceFile(this.options.luaEntry); + // If specified via CLI, the program's host should pick up a relative luaEntry path + let luaEntryPath = this.options.luaEntry; + if (this.options.project) { + // If specified via project. Figure out the full path to the file based off the project's path + const tsconfigDirectory = path.dirname(path.resolve(this.options.project)); + luaEntryPath = path.resolve(tsconfigDirectory, this.options.luaEntry); + } + const sourceFile = this.program.getSourceFile(luaEntryPath); if (sourceFile) { const formattedEntryName = tsHelper.getExportPath(sourceFile.fileName, this.options); const requireCall = this.createModuleRequire(ts.createStringLiteral(formattedEntryName), false); From d45395f72ce4f3c82c4ea0221084f9a9f330c256 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 17 Oct 2019 18:49:56 +1000 Subject: [PATCH 22/38] Use suggested config based resolution for luaEntrys in tsconfig --- src/LuaTransformer.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index e4824f406..57e0e51a8 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -130,8 +130,9 @@ export class LuaTransformer { let luaEntryPath = this.options.luaEntry; if (this.options.project) { // If specified via project. Figure out the full path to the file based off the project's path - const tsconfigDirectory = path.dirname(path.resolve(this.options.project)); - luaEntryPath = path.resolve(tsconfigDirectory, this.options.luaEntry); + const configFileName = this.options.configFilePath as string | undefined; + const basedir = configFileName ? path.dirname(configFileName) : process.cwd(); + luaEntryPath = path.resolve(basedir, this.options.luaEntry); } const sourceFile = this.program.getSourceFile(luaEntryPath); if (sourceFile) { From a9a94d3ef9dda4ab6728c896cb84f2b2e78a91be Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 18 Oct 2019 19:14:34 +1000 Subject: [PATCH 23/38] Check for configFilePath for luaEntry, not project --- src/LuaTransformer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 57e0e51a8..47f488f35 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -128,7 +128,7 @@ export class LuaTransformer { if (this.options.luaEntry) { // If specified via CLI, the program's host should pick up a relative luaEntry path let luaEntryPath = this.options.luaEntry; - if (this.options.project) { + if (this.options.configFilePath) { // If specified via project. Figure out the full path to the file based off the project's path const configFileName = this.options.configFilePath as string | undefined; const basedir = configFileName ? path.dirname(configFileName) : process.cwd(); From 127681933aab30bea9785058f052772c861b0a6e Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 18 Oct 2019 19:15:48 +1000 Subject: [PATCH 24/38] Make transformBundle private --- src/LuaTransformer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 47f488f35..1b4b32bdf 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -115,7 +115,7 @@ export class LuaTransformer { } } - public transformBundle(bundle: ts.Bundle): tstl.Block { + private transformBundle(bundle: ts.Bundle): tstl.Block { this.isWithinBundle = true; const combinedStatements = bundle.sourceFiles.reduce( (statements: tstl.Statement[], sourceFile: ts.SourceFile) => { From 16b1f042ca282318c8e0bc957169fc8a1b757e6a Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 18 Oct 2019 19:26:19 +1000 Subject: [PATCH 25/38] Add default options to BundleTestBuilder --- test/unit/outFile.spec.ts | 22 +++++----------------- test/util.ts | 7 +++++++ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index fa68dc009..c6f8d7195 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -1,23 +1,16 @@ import * as util from "../util"; -import * as ts from "typescript"; import * as TSTLErrors from "../../src/TSTLErrors"; -import { CompilerOptions } from "../../src/CompilerOptions"; const exportValueSource = "export const value = true;"; const reexportValueSource = 'export { value } from "./export";'; const exportResultSource = "export const result = value;"; -function outFileOptionsWithEntry(luaEntry: string): CompilerOptions { - return { outFile: "main.lua", module: ts.ModuleKind.AMD, luaEntry }; -} - test("import module -> main", () => { util.testBundle` import { value } from "./module"; ${exportResultSource} ` .addExtraFile("module.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) .expectToEqual({ result: true }); }); @@ -28,7 +21,6 @@ test("import chain export -> reexport -> main", () => { ` .addExtraFile("reexport.ts", reexportValueSource) .addExtraFile("export.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) .expectToEqual({ result: true }); }); @@ -43,7 +35,6 @@ test("diamond imports/exports -> reexport1 & reexport2 -> main", () => { .addExtraFile("reexport1.ts", reexportValueSource) .addExtraFile("reexport2.ts", reexportValueSource) .addExtraFile("export.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) .expectNoExecutionError(); }); @@ -53,7 +44,6 @@ test("module in directory", () => { ${exportValueSource} ` .addExtraFile("module/module.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) .expectNoExecutionError(); }); @@ -63,7 +53,6 @@ test("modules aren't ordered by name", () => { ${exportResultSource} ` .addExtraFile("a.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main.ts")) .expectToEqual({ result: true }); }); @@ -77,7 +66,7 @@ test("entry point in directory", () => { ` ) .addExtraFile("module.ts", exportValueSource) - .setOptions(outFileOptionsWithEntry("main/main.ts")) + .setOptions({ luaEntry: "main/main.ts" }) .expectToEqual({ result: true }); }); @@ -95,7 +84,6 @@ test("LuaLibs", () => { array.push(3); ` ) - .setOptions(outFileOptionsWithEntry("main.ts")) .expectNoExecutionError(); }); @@ -112,13 +100,13 @@ test("cyclic imports", () => { export const value = a; ` ) - .setOptions({ noHoisting: true, ...outFileOptionsWithEntry("main.ts") }) + .setOptions({ noHoisting: true }) .expectToEqual({ a: true, result: true }); }); test("luaEntry doesn't exist", () => { util.testBundle`` - .setOptions(outFileOptionsWithEntry("entry.ts")) + .setOptions({ luaEntry: "entry.ts" }) .expectToHaveDiagnosticOfError(TSTLErrors.LuaEntryNotFound("entry.ts")); }); @@ -126,10 +114,10 @@ test("luaEntry resolved from path specified in tsconfig", () => { util.testBundle`` .addExtraFile("src/main.ts", "") .addExtraFile("src/module.ts", "") - .setOptions({ rootDir: "src", ...outFileOptionsWithEntry("src/main.ts") }) + .setOptions({ rootDir: "src" }) .expectToHaveNoDiagnostics(); }); test("export equals", () => { - util.testBundle`export = "result"`.setOptions(outFileOptionsWithEntry("main.ts")).expectToEqual("result"); + util.testBundle`export = "result"`.expectToEqual("result"); }); diff --git a/test/util.ts b/test/util.ts index f358042aa..01443fda3 100644 --- a/test/util.ts +++ b/test/util.ts @@ -420,6 +420,13 @@ class AccessorTestBuilder extends TestBuilder { } class BundleTestBuilder extends AccessorTestBuilder { + protected mainFileName = "main.ts"; + + public constructor(_tsCode: string) { + super(_tsCode); + this.setOptions({ outFile: "main.lua", module: ts.ModuleKind.AMD, luaEntry: this.mainFileName }); + } + public getLuaCodeWithWrapper(): string { const noRequire = "_G.require = nil"; const noPackage = "_G.package = nil"; From ecc2450582d230e3621d021e499689058d198839 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 18 Oct 2019 19:30:52 +1000 Subject: [PATCH 26/38] Simplify LuaLib outFile test --- test/unit/outFile.spec.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index c6f8d7195..47997f38a 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -72,19 +72,9 @@ test("entry point in directory", () => { test("LuaLibs", () => { util.testBundle` - import { array } from "./module"; - if (array[2] !== 3) { - throw "Array's third item is not three"; - } - ` - .addExtraFile( - "module.ts", - ` - export const array = [1, 2]; - array.push(3); - ` - ) - .expectNoExecutionError(); + export const result = [1, 2]; + result.push(3); + `.expectToEqual({ result: [1, 2, 3] }); }); test("cyclic imports", () => { From 8ababd53a8da1549ebd73f0a8b0648735c5fa0f8 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 19:48:52 +1000 Subject: [PATCH 27/38] Update src/LuaTransformer.ts Co-Authored-By: ark120202 --- src/LuaTransformer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 1b4b32bdf..6d0cdefca 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -134,6 +134,7 @@ export class LuaTransformer { const basedir = configFileName ? path.dirname(configFileName) : process.cwd(); luaEntryPath = path.resolve(basedir, this.options.luaEntry); } + const sourceFile = this.program.getSourceFile(luaEntryPath); if (sourceFile) { const formattedEntryName = tsHelper.getExportPath(sourceFile.fileName, this.options); From 1479285a620e31b3573bf28a722d81076b49a214 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 19:49:05 +1000 Subject: [PATCH 28/38] Update src/LuaTransformer.ts Co-Authored-By: ark120202 --- src/LuaTransformer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 6d0cdefca..6f5320273 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -570,6 +570,7 @@ export class LuaTransformer { if (this.options.outFile) { this.importLuaLibFeature(LuaLibFeature.LuaRequire); } + return tstl.createCallExpression(tstl.createIdentifier(requireCallString), [modulePath], moduleSpecifier); } From cf2b791eb1e1ce61bc6663040207bfa356b866c0 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 19:45:12 +1000 Subject: [PATCH 29/38] Rename LuaRequire globals --- src/lualib/LuaRequire.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lualib/LuaRequire.ts b/src/lualib/LuaRequire.ts index a96336bf1..fc83eb45a 100644 --- a/src/lualib/LuaRequire.ts +++ b/src/lualib/LuaRequire.ts @@ -1,17 +1,17 @@ -const __TS__MODULES: Record any> = {}; -const __TS__MODULECACHE: Record = {}; +const ____modules: Record any> = {}; +const ____modulecache: Record = {}; function __TS__LuaRequire(this: void, moduleName: string): any { - if (__TS__MODULECACHE[moduleName]) { - return __TS__MODULECACHE[moduleName]; + if (____modulecache[moduleName]) { + return ____modulecache[moduleName]; } - const loadScript = __TS__MODULES[moduleName]; + const loadScript = ____modules[moduleName]; if (!loadScript) { // tslint:disable-next-line: no-string-throw throw `module '${moduleName}' not found`; } const moduleExports = {}; - __TS__MODULECACHE[moduleName] = moduleExports; - __TS__MODULECACHE[moduleName] = loadScript(moduleExports); - return __TS__MODULECACHE[moduleName]; + ____modulecache[moduleName] = moduleExports; + ____modulecache[moduleName] = loadScript(moduleExports); + return ____modulecache[moduleName]; } From e43f8414e219182283163fb14904af89ca73c71c Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 19:47:47 +1000 Subject: [PATCH 30/38] Refactor undefined configFilePath condition in transformer --- src/LuaTransformer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 6f5320273..1c5e6a102 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -130,8 +130,7 @@ export class LuaTransformer { let luaEntryPath = this.options.luaEntry; if (this.options.configFilePath) { // If specified via project. Figure out the full path to the file based off the project's path - const configFileName = this.options.configFilePath as string | undefined; - const basedir = configFileName ? path.dirname(configFileName) : process.cwd(); + const basedir = path.dirname(this.options.configFilePath as string); luaEntryPath = path.resolve(basedir, this.options.luaEntry); } From bda19b88e6247a469e12d39272350818275ee3db Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 19:48:25 +1000 Subject: [PATCH 31/38] Change requireCallString to requireFunctionName --- src/LuaTransformer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 1c5e6a102..73bb31e31 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -565,12 +565,12 @@ export class LuaTransformer { ) : moduleSpecifier.text; const modulePath = tstl.createStringLiteral(modulePathString); - const requireCallString = this.options.outFile ? "__TS__LuaRequire" : "require"; + const requireFunctionName = this.options.outFile ? "__TS__LuaRequire" : "require"; if (this.options.outFile) { this.importLuaLibFeature(LuaLibFeature.LuaRequire); } - return tstl.createCallExpression(tstl.createIdentifier(requireCallString), [modulePath], moduleSpecifier); + return tstl.createCallExpression(tstl.createIdentifier(requireFunctionName), [modulePath], moduleSpecifier); } protected validateClassElement(element: ts.ClassElement): void { From 0c700d4efea360d5e396a73cb6ad24dcc88f1683 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 20:05:46 +1000 Subject: [PATCH 32/38] Disable lint rule in LuaRequire and fix transformer --- src/LuaTransformer.ts | 2 +- src/lualib/LuaRequire.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 73bb31e31..1784ec73e 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -183,7 +183,7 @@ export class LuaTransformer { } if (this.isWithinBundle) { - const moduleTableIdentifier = tstl.createIdentifier("__TS__MODULES"); + const moduleTableIdentifier = tstl.createIdentifier("____modules"); const exportPath = tsHelper.getExportPath(sourceFile.fileName, this.options); const moduleParameters = this.visitedExportEquals ? undefined : [this.createExportsIdentifier()]; const moduleDeclaration = tstl.createAssignmentStatement( diff --git a/src/lualib/LuaRequire.ts b/src/lualib/LuaRequire.ts index fc83eb45a..6a23740b8 100644 --- a/src/lualib/LuaRequire.ts +++ b/src/lualib/LuaRequire.ts @@ -1,3 +1,4 @@ +// tslint:disable: variable-name const ____modules: Record any> = {}; const ____modulecache: Record = {}; From e1e6964ecde303ead2bae47602ae5c528d55d0d9 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 20:16:54 +1000 Subject: [PATCH 33/38] Avoid mutable luaEntryPath in LuaTransformer --- src/LuaTransformer.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 1784ec73e..3b707a9f5 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -126,14 +126,10 @@ export class LuaTransformer { ); if (this.options.luaEntry) { - // If specified via CLI, the program's host should pick up a relative luaEntry path - let luaEntryPath = this.options.luaEntry; - if (this.options.configFilePath) { - // If specified via project. Figure out the full path to the file based off the project's path - const basedir = path.dirname(this.options.configFilePath as string); - luaEntryPath = path.resolve(basedir, this.options.luaEntry); - } - + const basedir = this.options.configFilePath + ? path.dirname(this.options.configFilePath as string) + : undefined; + const luaEntryPath = basedir ? path.resolve(basedir, this.options.luaEntry) : this.options.luaEntry; const sourceFile = this.program.getSourceFile(luaEntryPath); if (sourceFile) { const formattedEntryName = tsHelper.getExportPath(sourceFile.fileName, this.options); From b95f068552e5b5bd55de51cc59799dabfbe35aa6 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 20:32:01 +1000 Subject: [PATCH 34/38] Use camelCasing in LuaRequire --- src/lualib/LuaRequire.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lualib/LuaRequire.ts b/src/lualib/LuaRequire.ts index 6a23740b8..086e41d90 100644 --- a/src/lualib/LuaRequire.ts +++ b/src/lualib/LuaRequire.ts @@ -1,10 +1,10 @@ // tslint:disable: variable-name const ____modules: Record any> = {}; -const ____modulecache: Record = {}; +const ____moduleCache: Record = {}; function __TS__LuaRequire(this: void, moduleName: string): any { - if (____modulecache[moduleName]) { - return ____modulecache[moduleName]; + if (____moduleCache[moduleName]) { + return ____moduleCache[moduleName]; } const loadScript = ____modules[moduleName]; if (!loadScript) { @@ -12,7 +12,7 @@ function __TS__LuaRequire(this: void, moduleName: string): any { throw `module '${moduleName}' not found`; } const moduleExports = {}; - ____modulecache[moduleName] = moduleExports; - ____modulecache[moduleName] = loadScript(moduleExports); - return ____modulecache[moduleName]; + ____moduleCache[moduleName] = moduleExports; + ____moduleCache[moduleName] = loadScript(moduleExports); + return ____moduleCache[moduleName]; } From 2d7e1ce4e5ba55c38c71b361583a9bf15ac599d7 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 21:02:30 +1000 Subject: [PATCH 35/38] Inline variables in outFile tests --- test/unit/outFile.spec.ts | 65 ++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 47997f38a..6ae60aa25 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -1,59 +1,48 @@ import * as util from "../util"; import * as TSTLErrors from "../../src/TSTLErrors"; -const exportValueSource = "export const value = true;"; -const reexportValueSource = 'export { value } from "./export";'; -const exportResultSource = "export const result = value;"; - test("import module -> main", () => { util.testBundle` - import { value } from "./module"; - ${exportResultSource} + export { value } from "./module"; ` - .addExtraFile("module.ts", exportValueSource) - .expectToEqual({ result: true }); + .addExtraFile("module.ts", "export const value = true") + .expectToEqual({ value: true }); }); test("import chain export -> reexport -> main", () => { util.testBundle` - import { value } from "./reexport"; - ${exportResultSource} + export { value } from "./reexport"; ` - .addExtraFile("reexport.ts", reexportValueSource) - .addExtraFile("export.ts", exportValueSource) - .expectToEqual({ result: true }); + .addExtraFile("reexport.ts", "export { value } from './export'") + .addExtraFile("export.ts", "export const value = true") + .expectToEqual({ value: true }); }); test("diamond imports/exports -> reexport1 & reexport2 -> main", () => { util.testBundle` - import { value as a } from "./reexport1"; - import { value as b } from "./reexport2"; - if (a !== true || b !== true) { - throw "Failed to import a or b"; - } + export { value as a } from "./reexport1"; + export { value as b } from "./reexport2"; ` - .addExtraFile("reexport1.ts", reexportValueSource) - .addExtraFile("reexport2.ts", reexportValueSource) - .addExtraFile("export.ts", exportValueSource) - .expectNoExecutionError(); + .addExtraFile("reexport1.ts", "export { value } from './export'") + .addExtraFile("reexport2.ts", "export { value } from './export'") + .addExtraFile("export.ts", "export const value = true") + .expectToEqual({ a: true, b: true }); }); test("module in directory", () => { util.testBundle` - import { value } from "./module/module"; - ${exportValueSource} + export { value } from "./module/module"; ` - .addExtraFile("module/module.ts", exportValueSource) - .expectNoExecutionError(); + .addExtraFile("module/module.ts", "export const value = true") + .expectToEqual({ value: true }); }); test("modules aren't ordered by name", () => { util.testBundle` - import { value } from "./a"; - ${exportResultSource} + export { value } from "./a"; ` - .addExtraFile("a.ts", exportValueSource) - .expectToEqual({ result: true }); + .addExtraFile("a.ts", "export const value = true") + .expectToEqual({ value: true }); }); test("entry point in directory", () => { @@ -61,13 +50,12 @@ test("entry point in directory", () => { .addExtraFile( "main/main.ts", ` - import { value } from "../module"; - ${exportResultSource} + export { value } from "../module"; ` ) - .addExtraFile("module.ts", exportValueSource) + .addExtraFile("module.ts", "export const value = true") .setOptions({ luaEntry: "main/main.ts" }) - .expectToEqual({ result: true }); + .expectToEqual({ value: true }); }); test("LuaLibs", () => { @@ -79,19 +67,20 @@ test("LuaLibs", () => { test("cyclic imports", () => { util.testBundle` + import * as b from "./b"; export const a = true; - import { value } from "./b"; - ${exportResultSource} + export const valueResult = b.value; + export const lazyValueResult = b.lazyValue(); ` .addExtraFile( "b.ts", ` import { a } from "./main"; export const value = a; + export const lazyValue = () => a; ` ) - .setOptions({ noHoisting: true }) - .expectToEqual({ a: true, result: true }); + .expectToEqual({ a: true, valueResult: true, lazyValueResult: true }); }); test("luaEntry doesn't exist", () => { From 360fdd2a4d55237a2573d2a7ad22085bb5404e05 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 21:20:15 +1000 Subject: [PATCH 36/38] Improve cyclic import outFile test --- test/unit/outFile.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/outFile.spec.ts b/test/unit/outFile.spec.ts index 6ae60aa25..e21d36299 100644 --- a/test/unit/outFile.spec.ts +++ b/test/unit/outFile.spec.ts @@ -75,12 +75,12 @@ test("cyclic imports", () => { .addExtraFile( "b.ts", ` - import { a } from "./main"; - export const value = a; - export const lazyValue = () => a; + import * as a from "./main"; + export const value = a.a; + export const lazyValue = () => a.a; ` ) - .expectToEqual({ a: true, valueResult: true, lazyValueResult: true }); + .expectToEqual({ a: true, lazyValueResult: true }); }); test("luaEntry doesn't exist", () => { From d40a619ebf7fd3238786638383f7933519f248cc Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Oct 2019 21:29:53 +1000 Subject: [PATCH 37/38] Allow optional TranspileError node parameter --- src/TSTLErrors.ts | 2 +- src/TranspileError.ts | 2 +- src/diagnostics.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index 73b61543b..5f138c61d 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -61,7 +61,7 @@ export const ForbiddenStaticClassPropertyName = (node: ts.Node, name: string) => new TranspileError(`Cannot use "${name}" as a static class property or method name.`, node); export const LuaEntryNotFound = (entryName: string) => - new TranspileError(`Could not find luaEntry file '${entryName}'.`, ts.createNode(ts.SyntaxKind.Unknown)); + new TranspileError(`Could not find luaEntry file '${entryName}'.`); export const MissingClassName = (node: ts.Node) => new TranspileError(`Class declarations must have a name.`, node); diff --git a/src/TranspileError.ts b/src/TranspileError.ts index baeded940..d0610b16f 100644 --- a/src/TranspileError.ts +++ b/src/TranspileError.ts @@ -2,7 +2,7 @@ import * as ts from "typescript"; export class TranspileError extends Error { public name = "TranspileError"; - constructor(message: string, public node: ts.Node) { + constructor(message: string, public node?: ts.Node) { super(message); } } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 5b98ef5dc..087c7ece7 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -2,9 +2,9 @@ import * as ts from "typescript"; import { TranspileError } from "./TranspileError"; export const transpileError = (error: TranspileError): ts.Diagnostic => ({ - file: error.node.kind === ts.SyntaxKind.Unknown ? undefined : error.node.getSourceFile(), - start: error.node.kind === ts.SyntaxKind.Unknown ? undefined : error.node.getStart(), - length: error.node.kind === ts.SyntaxKind.Unknown ? undefined : error.node.getWidth(), + file: error.node ? error.node.getSourceFile() : undefined, + start: error.node ? error.node.getStart() : undefined, + length: error.node ? error.node.getWidth() : undefined, category: ts.DiagnosticCategory.Error, code: 0, source: "typescript-to-lua", From 5d63ba8be1d906d9c0734fe042c868c8ff7d54a3 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 25 Oct 2019 16:30:00 +1000 Subject: [PATCH 38/38] Move transformSourceFileWithState above transformSourceFile --- src/LuaTransformer.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 3b707a9f5..fb1723223 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -144,6 +144,19 @@ export class LuaTransformer { return tstl.createBlock(combinedStatements, bundle); } + private transformSourceFileWithState(node: ts.SourceFile): tstl.Block { + this.currentSourceFile = node; + this.isModule = tsHelper.isFileModule(node); + + // Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers. + // It's required because otherwise `getEmitResolver` won't use cached diagnostics, produced in `emitWorker` + // and would try to re-analyze the file, which would fail because of replaced nodes. + const originalSourceFile = ts.getParseTreeNode(node, ts.isSourceFile) || node; + this.resolver = this.checker.getEmitResolver(originalSourceFile); + + return this.transformSourceFile(node); + } + public transformSourceFile(sourceFile: ts.SourceFile): tstl.Block { this.visitedExportEquals = false; let statements: tstl.Statement[] = []; @@ -192,19 +205,6 @@ export class LuaTransformer { return tstl.createBlock(statements, sourceFile); } - private transformSourceFileWithState(node: ts.SourceFile): tstl.Block { - this.currentSourceFile = node; - this.isModule = tsHelper.isFileModule(node); - - // Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers. - // It's required because otherwise `getEmitResolver` won't use cached diagnostics, produced in `emitWorker` - // and would try to re-analyze the file, which would fail because of replaced nodes. - const originalSourceFile = ts.getParseTreeNode(node, ts.isSourceFile) || node; - this.resolver = this.checker.getEmitResolver(originalSourceFile); - - return this.transformSourceFile(node); - } - public transformStatement(node: ts.Statement): StatementVisitResult { // Ignore declarations if (node.modifiers && node.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword)) {