From 9034551a24646ee4d4a9ffee88cb7ca2fbdbd621 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 17 Nov 2019 23:19:45 +0100 Subject: [PATCH 01/18] security audit fix --- package-lock.json | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index a69f58e8f..a33e2537d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2512,9 +2512,9 @@ "dev": true }, "handlebars": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.2.tgz", - "integrity": "sha512-cIv17+GhL8pHHnRJzGu2wwcthL5sb8uDKBHvZ2Dtu5s1YNt0ljbzKbamnc+gr69y7bzwQiBdr5+hOpRd5pnOdg==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.2.tgz", + "integrity": "sha512-29Zxv/cynYB7mkT1rVWQnV7mGX6v7H/miQ6dbEpYTKq5eJBN7PsRB+ViYJlcT6JINTSu4dVB9kOqEun78h6Exg==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -5227,16 +5227,23 @@ "integrity": "sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw==" }, "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", + "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==", "dev": true, "optional": true, "requires": { - "commander": "~2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" }, "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", From d612dd46cd6b2163c9e9d90c3aba03fb0a52bf50 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 17 Nov 2019 23:23:21 +0100 Subject: [PATCH 02/18] Alternative bundling approach --- src/CompilerOptions.ts | 22 ++++++++ src/LuaPrinter.ts | 8 ++- src/TSTLErrors.ts | 2 + src/Transpile.ts | 26 +++++++--- src/bundle.ts | 59 ++++++++++++++++++++++ test/unit/outfile.spec.ts | 103 ++++++++++++++++++++++++++++++++++++++ test/util.ts | 18 ++++++- 7 files changed, 227 insertions(+), 11 deletions(-) create mode 100644 src/bundle.ts create mode 100644 test/unit/outfile.spec.ts diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index f7c5999c5..1081c0bae 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -20,6 +20,8 @@ export interface TransformerImport { export type CompilerOptions = OmitIndexSignature & { noImplicitSelf?: boolean; noHeader?: boolean; + luaBundle?: string; + luaBundleEntry?: string; luaTarget?: LuaTarget; luaLibImport?: LuaLibImportKind; noHoisting?: boolean; @@ -41,3 +43,23 @@ export enum LuaTarget { Lua53 = "5.3", LuaJIT = "JIT", } + +export function validateOptions(options: CompilerOptions): ts.Diagnostic[] { + const diagnostics: ts.Diagnostic[] = []; + + if (options.luaBundle && (options.luaBundleEntry === "" || !options.luaBundleEntry)) { + diagnostics.push(configErrorDiagnostic(`'luaBundleEntry' is required when 'luaBundle' is enabled.`)); + } + + return diagnostics; +} + +const configErrorDiagnostic = (message: string): ts.Diagnostic => ({ + file: undefined, + start: undefined, + length: undefined, + category: ts.DiagnosticCategory.Error, + code: 0, + source: "typescript-to-lua", + messageText: message, +}); diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 48ea0e752..e9b24879b 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -43,7 +43,11 @@ export class LuaPrinter { public constructor(private options: CompilerOptions, private emitHost: EmitHost) {} - public print(block: tstl.Block, luaLibFeatures?: Set, sourceFile = ""): [string, string] { + public print( + block: tstl.Block, + luaLibFeatures?: Set, + sourceFile = "" + ): [string, string, SourceNode] { // Add traceback lualib if sourcemap traceback option is enabled if (this.options.sourceMapTraceback) { if (luaLibFeatures === undefined) { @@ -71,7 +75,7 @@ export class LuaPrinter { codeResult = codeResult.replace("{#SourceMapTraceback}", stackTraceOverride); } - return [codeResult, sourceMap.toString()]; + return [codeResult, sourceMap.toString(), rootSourceNode]; } private printInlineSourceMap(sourceMap: SourceMapGenerator): string { diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index c32318f45..15a08dd15 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -57,6 +57,8 @@ export const InvalidElementCall = (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 Error(`Could not find luaEntry file '${entryName}'.`); + 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/Transpile.ts b/src/Transpile.ts index 28a6282a9..b790b1e9a 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -1,11 +1,13 @@ import * as ts from "typescript"; -import { CompilerOptions } from "./CompilerOptions"; +import { CompilerOptions, validateOptions } from "./CompilerOptions"; import * as diagnosticFactories from "./diagnostics"; import { Block } from "./LuaAST"; import { LuaPrinter } from "./LuaPrinter"; import { LuaTransformer } from "./LuaTransformer"; import { TranspileError } from "./TranspileError"; import { getCustomTransformers } from "./TSTransformers"; +import { bundleTranspiledFiles } from "./bundle"; +import { SourceNode } from "source-map"; export interface TranspiledFile { fileName: string; @@ -16,6 +18,8 @@ export interface TranspiledFile { declarationMap?: string; } +export type TranspiledFileWithSourceNode = TranspiledFile & { sourceMapNode?: SourceNode }; + export interface TranspileResult { diagnostics: ts.Diagnostic[]; transpiledFiles: TranspiledFile[]; @@ -44,10 +48,10 @@ export function transpile({ }: TranspileOptions): TranspileResult { const options = program.getCompilerOptions() as CompilerOptions; - const diagnostics: ts.Diagnostic[] = []; - let transpiledFiles: TranspiledFile[] = []; + const diagnostics = validateOptions(options); + let transpiledFiles: TranspiledFileWithSourceNode[] = []; - const updateTranspiledFile = (fileName: string, update: Omit) => { + const updateTranspiledFile = (fileName: string, update: Omit) => { const file = transpiledFiles.find(f => f.fileName === fileName); if (file) { Object.assign(file, update); @@ -57,7 +61,11 @@ export function transpile({ }; if (options.noEmitOnError) { - const preEmitDiagnostics = [...program.getOptionsDiagnostics(), ...program.getGlobalDiagnostics()]; + const preEmitDiagnostics = [ + ...diagnostics, + ...program.getOptionsDiagnostics(), + ...program.getGlobalDiagnostics(), + ]; if (targetSourceFiles) { for (const sourceFile of targetSourceFiles) { @@ -82,8 +90,8 @@ export function transpile({ try { const [luaAst, lualibFeatureSet] = transformer.transform(sourceFile); if (!options.noEmit && !options.emitDeclarationOnly) { - const [lua, sourceMap] = printer.print(luaAst, lualibFeatureSet, sourceFile.fileName); - updateTranspiledFile(sourceFile.fileName, { luaAst, lua, sourceMap }); + const [lua, sourceMap, sourceNode] = printer.print(luaAst, lualibFeatureSet, sourceFile.fileName); + updateTranspiledFile(sourceFile.fileName, { luaAst, lua, sourceMap, sourceMapNode: sourceNode }); } } catch (err) { if (!(err instanceof TranspileError)) throw err; @@ -144,5 +152,9 @@ export function transpile({ transpiledFiles = []; } + if (options.luaBundle && options.luaBundleEntry) { + transpiledFiles = [bundleTranspiledFiles(options.luaBundleEntry, options.luaBundleEntry, transpiledFiles)]; + } + return { diagnostics, transpiledFiles }; } diff --git a/src/bundle.ts b/src/bundle.ts new file mode 100644 index 000000000..009f4c28a --- /dev/null +++ b/src/bundle.ts @@ -0,0 +1,59 @@ +import { TranspiledFileWithSourceNode } from "./Transpile"; +import { SourceNode } from "source-map"; + +const trimExtension = (fileName: string) => (fileName.endsWith(".ts") ? fileName.slice(0, -3) : fileName); +const formatPath = (fileName: string) => trimExtension(fileName).replace("/", "."); + +export function bundleTranspiledFiles( + bundleFile: string, + entryModule: string, + transpiledFiles: TranspiledFileWithSourceNode[] +): TranspiledFileWithSourceNode { + const moduleTable = moduleTableNode(transpiledFiles); + const requireOverride = `function require(file) return __Lua_BundleModules[file](); end\n`; + const entryPoint = `return require("${formatPath(entryModule)}")\n`; + + const bundleNode = joinSourceChunks([moduleTable, requireOverride, entryPoint]); + const { code, map } = bundleNode.toStringWithSourceMap(); + + return { + fileName: bundleFile, + lua: code, + sourceMap: map.toString(), + sourceMapNode: moduleTable, + declaration: mergeDeclarations(transpiledFiles), + declarationMap: mergeDeclarations(transpiledFiles), + }; +} + +function moduleSourceNode(transpiledFile: TranspiledFileWithSourceNode): SourceNode { + const tableEntryHead = `["${formatPath(transpiledFile.fileName)}"] = function() `; + const tableEntryTail = `end,\n`; + + if (transpiledFile.lua && transpiledFile.sourceMapNode) { + return joinSourceChunks([tableEntryHead, transpiledFile.sourceMapNode, tableEntryTail]); + } else { + return joinSourceChunks([tableEntryHead, tableEntryTail]); + } +} + +function moduleTableNode(transpiledFiles: TranspiledFileWithSourceNode[]): SourceNode { + const tableHead = `__Lua_BundleModules = {\n`; + const tableEnd = `}\n`; + const tableEntries = transpiledFiles.map(moduleSourceNode); + + return joinSourceChunks([tableHead, ...tableEntries, tableEnd]); +} + +function mergeDeclarations(transpiledFiles: TranspiledFileWithSourceNode[]): string { + return transpiledFiles + .map(f => f.declaration) + .filter(x => x !== undefined) + .join("\n"); +} + +type SourceChunk = string | SourceNode; +function joinSourceChunks(chunks: SourceChunk[]): SourceNode { + // tslint:disable-next-line:no-null-keyword + return new SourceNode(null, null, null, chunks); +} diff --git a/test/unit/outfile.spec.ts b/test/unit/outfile.spec.ts new file mode 100644 index 000000000..e1b158bfd --- /dev/null +++ b/test/unit/outfile.spec.ts @@ -0,0 +1,103 @@ +import * as util from "../util"; +import * as TSTLErrors from "../../src/TSTLErrors"; + +test("import module -> main", () => { + util.testBundle` + export { value } from "./module"; + ` + .addExtraFile("module.ts", "export const value = true") + .expectToEqual({ value: true }); +}); + +test("import chain export -> reexport -> main", () => { + util.testBundle` + export { value } from "./reexport"; + ` + .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` + export { value as a } from "./reexport1"; + export { value as b } from "./reexport2"; + ` + .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` + export { value } from "./module/module"; + ` + .addExtraFile("module/module.ts", "export const value = true") + .expectToEqual({ value: true }); +}); + +test("modules aren't ordered by name", () => { + util.testBundle` + export { value } from "./a"; + ` + .addExtraFile("a.ts", "export const value = true") + .expectToEqual({ value: true }); +}); + +test("entry point in directory", () => { + util.testBundle`` + .addExtraFile( + "main/main.ts", + ` + export { value } from "../module"; + ` + ) + .addExtraFile("module.ts", "export const value = true") + .setEntryPoint("main/main.ts") + .debug() + .expectToEqual({ value: true }); +}); + +test("LuaLibs", () => { + util.testBundle` + export const result = [1, 2]; + result.push(3); + `.expectToEqual({ result: [1, 2, 3] }); +}); + +test("cyclic imports", () => { + util.testBundle` + import * as b from "./b"; + export const a = true; + export const valueResult = b.value; + export const lazyValueResult = b.lazyValue(); + ` + .addExtraFile( + "b.ts", + ` + import * as a from "./main"; + export const value = a.a; + export const lazyValue = () => a.a; + ` + ) + .expectToEqual({ a: true, lazyValueResult: true }); +}); + +test("luaEntry doesn't exist", () => { + util.testBundle`` + .setOptions({ luaEntry: "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" }) + .expectToHaveNoDiagnostics(); +}); + +test("export equals", () => { + util.testBundle`export = "result"`.expectToEqual("result"); +}); diff --git a/test/util.ts b/test/util.ts index 33e48b16f..a7d917267 100644 --- a/test/util.ts +++ b/test/util.ts @@ -219,7 +219,9 @@ export abstract class TestBuilder { @memoize public getMainLuaFileResult(): ExecutableTranspiledFile { const { transpiledFiles } = this.getLuaResult(); - const mainFile = transpiledFiles.find(x => x.fileName === this.mainFileName); + const mainFile = this.options.luaBundle + ? transpiledFiles[0] + : transpiledFiles.find(x => x.fileName === this.mainFileName); expect(mainFile).toMatchObject({ lua: expect.any(String), sourceMap: expect.any(String) }); return mainFile as ExecutableTranspiledFile; } @@ -335,7 +337,7 @@ export abstract class TestBuilder { return this; } - public expectToHaveDiagnosticOfError(error: tstl.TranspileError): this { + public expectToHaveDiagnosticOfError(error: Error): this { this.expectToHaveDiagnostics(); expect(this.getLuaDiagnostics()).toHaveLength(1); const firstDiagnostic = this.getLuaDiagnostics()[0]; @@ -414,6 +416,17 @@ class AccessorTestBuilder extends TestBuilder { } } +class BundleTestBuilder extends AccessorTestBuilder { + public constructor(_tsCode: string) { + super(_tsCode); + this.setOptions({ luaBundle: "main.lua", luaBundleEntry: this.mainFileName }); + } + + public setEntryPoint(fileName: string): this { + return this.setOptions({ luaBundleEntry: fileName }); + } +} + class ModuleTestBuilder extends AccessorTestBuilder { public setReturnExport(name: string): this { expect(this.hasProgram).toBe(false); @@ -458,6 +471,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 478ff449596489b4e3273bd882f5ee72fa420ae0 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Mon, 18 Nov 2019 19:52:10 +0100 Subject: [PATCH 03/18] Changed default lualib inline to require, support for lualib require in bundling. Added diagnostics and more tests --- src/CompilerOptions.ts | 19 +++++ src/Emit.ts | 4 +- src/LuaPrinter.ts | 2 +- src/TSHelper.ts | 10 +-- src/TSTLErrors.ts | 2 - src/Transpile.ts | 9 ++- src/bundle.ts | 72 ++++++++++++------- src/diagnostics.ts | 10 +++ src/utils.ts | 13 ++++ test/cli/parse.spec.ts | 40 +++++------ test/cli/tsconfig.spec.ts | 6 +- test/legacy-utils.ts | 2 +- test/setup.ts | 6 +- test/transpile/directories.spec.ts | 2 +- test/transpile/outFile.spec.ts | 8 +-- .../functions/noImplicitSelfOption.spec.ts | 8 +-- .../functionExpressionTypeInference.spec.ts | 2 +- test/unit/modules/resolution.spec.ts | 2 +- test/unit/outfile.spec.ts | 49 +++++++++---- test/unit/printer/sourcemaps.spec.ts | 8 +-- test/unit/transformers/transformers.spec.ts | 2 +- test/util.ts | 39 +++++++--- 22 files changed, 208 insertions(+), 107 deletions(-) diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index 1081c0bae..4f4741fec 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -51,6 +51,15 @@ export function validateOptions(options: CompilerOptions): ts.Diagnostic[] { diagnostics.push(configErrorDiagnostic(`'luaBundleEntry' is required when 'luaBundle' is enabled.`)); } + if (options.luaBundle && options.luaLibImport === LuaLibImportKind.Inline) { + diagnostics.push( + configWarningDiagnostic( + `Using 'luaBundle' with 'luaLibImport: "inline"' might generate duplicate code. ` + + `It is recommended to use 'luaLibImport: "require"'` + ) + ); + } + return diagnostics; } @@ -63,3 +72,13 @@ const configErrorDiagnostic = (message: string): ts.Diagnostic => ({ source: "typescript-to-lua", messageText: message, }); + +const configWarningDiagnostic = (message: string): ts.Diagnostic => ({ + file: undefined, + start: undefined, + length: undefined, + category: ts.DiagnosticCategory.Warning, + code: 0, + source: "typescript-to-lua", + messageText: message, +}); diff --git a/src/Emit.ts b/src/Emit.ts index ecb1453f9..67ef4e276 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -2,9 +2,7 @@ import * as path from "path"; import * as ts from "typescript"; import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; import { EmitHost, TranspiledFile } from "./Transpile"; -import { normalizeSlashes } from "./utils"; - -const trimExt = (filePath: string) => filePath.slice(0, -path.extname(filePath).length); +import { normalizeSlashes, trimExt } from "./utils"; export interface OutputFile { name: string; diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index e9b24879b..4fec7e595 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -117,7 +117,7 @@ export class LuaPrinter { } if (luaLibFeatures) { - const luaLibImport = this.options.luaLibImport || LuaLibImportKind.Inline; + const luaLibImport = this.options.luaLibImport || LuaLibImportKind.Require; // Require lualib bundle if ( (luaLibImport === LuaLibImportKind.Require && luaLibFeatures.size > 0) || diff --git a/src/TSHelper.ts b/src/TSHelper.ts index c237ae188..4c0735310 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -5,6 +5,7 @@ import { Decorator, DecoratorKind } from "./Decorator"; import * as tstl from "./LuaAST"; import * as TSTLErrors from "./TSTLErrors"; import { EmitResolver } from "./LuaTransformer"; +import { formatPathToLuaPath } from "./utils"; export enum ContextType { None, @@ -1060,15 +1061,6 @@ export function getExportPath(fileName: string, options: ts.CompilerOptions): st return formatPathToLuaPath(absolutePath.replace(absoluteRootDirPath, "").slice(1)); } -export function formatPathToLuaPath(filePath: string): string { - filePath = filePath.replace(/\.json$/, ""); - if (process.platform === "win32") { - // Windows can use backslashes - filePath = filePath.replace(/\.\\/g, "").replace(/\\/g, "."); - } - return filePath.replace(/\.\//g, "").replace(/\//g, "."); -} - export function isBuiltinErrorTypeName(name: string): boolean { return builtinErrorTypeNames.has(name); } diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index 15a08dd15..c32318f45 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -57,8 +57,6 @@ export const InvalidElementCall = (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 Error(`Could not find luaEntry file '${entryName}'.`); - 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/Transpile.ts b/src/Transpile.ts index b790b1e9a..d44518b07 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -153,7 +153,14 @@ export function transpile({ } if (options.luaBundle && options.luaBundleEntry) { - transpiledFiles = [bundleTranspiledFiles(options.luaBundleEntry, options.luaBundleEntry, transpiledFiles)]; + const [bundleDiagnostics, bundle] = bundleTranspiledFiles( + options.luaBundleEntry, + options.luaBundleEntry, + transpiledFiles, + emitHost + ); + diagnostics.push(...bundleDiagnostics); + transpiledFiles = [bundle]; } return { diagnostics, transpiledFiles }; diff --git a/src/bundle.ts b/src/bundle.ts index 009f4c28a..52354d674 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -1,29 +1,57 @@ -import { TranspiledFileWithSourceNode } from "./Transpile"; +import * as path from "path"; +import { TranspiledFileWithSourceNode, EmitHost } from "./Transpile"; import { SourceNode } from "source-map"; +import { formatPathToLuaPath, trimExt } from "./utils"; +import { Diagnostic } from "typescript"; +import { couldNotFindBundleEntryPoint } from "./diagnostics"; -const trimExtension = (fileName: string) => (fileName.endsWith(".ts") ? fileName.slice(0, -3) : fileName); -const formatPath = (fileName: string) => trimExtension(fileName).replace("/", "."); +const formatPath = (path: string) => formatPathToLuaPath(trimExt(path)); export function bundleTranspiledFiles( bundleFile: string, entryModule: string, - transpiledFiles: TranspiledFileWithSourceNode[] -): TranspiledFileWithSourceNode { - const moduleTable = moduleTableNode(transpiledFiles); - const requireOverride = `function require(file) return __Lua_BundleModules[file](); end\n`; + transpiledFiles: TranspiledFileWithSourceNode[], + emitHost: EmitHost +): [Diagnostic[], TranspiledFileWithSourceNode] { + const diagnostics: Diagnostic[] = []; + + if (transpiledFiles.find(f => f.fileName === entryModule) === undefined) { + return [[couldNotFindBundleEntryPoint(entryModule)], { fileName: bundleFile }]; + } + + // For each file: [""] = function() end, + const moduleTableEntries: SourceChunk[] = transpiledFiles.map(moduleSourceNode); + + // If any of the modules contains a require for lualib_bundle, add it to the module table. + const lualibRequired = transpiledFiles.some(f => f.lua && f.lua.match(/require\("lualib_bundle"\)/)); + if (lualibRequired) { + const lualibBundle = emitHost.readFile(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua")); + moduleTableEntries.push(`["lualib_bundle"] = function() ${lualibBundle} end,\n`); + } + + // Create ____modules table containing all entries from moduleTableEntries + const moduleTable = createModuleTableNode(moduleTableEntries); + + // Override `require` to read from ____modules table. + const requireOverride = + `function require(file) if ____modules[file] then return ____modules[file]() ` + + `else error("Could not find module '"..file.."' to require.") end end\n`; const entryPoint = `return require("${formatPath(entryModule)}")\n`; const bundleNode = joinSourceChunks([moduleTable, requireOverride, entryPoint]); const { code, map } = bundleNode.toStringWithSourceMap(); - return { - fileName: bundleFile, - lua: code, - sourceMap: map.toString(), - sourceMapNode: moduleTable, - declaration: mergeDeclarations(transpiledFiles), - declarationMap: mergeDeclarations(transpiledFiles), - }; + return [ + diagnostics, + { + fileName: bundleFile, + lua: code, + sourceMap: map.toString(), + sourceMapNode: moduleTable, + declaration: undefined, + declarationMap: undefined, + }, + ]; } function moduleSourceNode(transpiledFile: TranspiledFileWithSourceNode): SourceNode { @@ -37,19 +65,11 @@ function moduleSourceNode(transpiledFile: TranspiledFileWithSourceNode): SourceN } } -function moduleTableNode(transpiledFiles: TranspiledFileWithSourceNode[]): SourceNode { - const tableHead = `__Lua_BundleModules = {\n`; +function createModuleTableNode(fileChunks: SourceChunk[]): SourceNode { + const tableHead = `____modules = {\n`; const tableEnd = `}\n`; - const tableEntries = transpiledFiles.map(moduleSourceNode); - - return joinSourceChunks([tableHead, ...tableEntries, tableEnd]); -} -function mergeDeclarations(transpiledFiles: TranspiledFileWithSourceNode[]): string { - return transpiledFiles - .map(f => f.declaration) - .filter(x => x !== undefined) - .join("\n"); + return joinSourceChunks([tableHead, ...fileChunks, tableEnd]); } type SourceChunk = string | SourceNode; diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 5e614a380..9382ca834 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -50,3 +50,13 @@ export const transformerShouldBeATsTransformerFactory = (transform: string): ts. source: "typescript-to-lua", messageText: `"${transform}" transformer should be a ts.TransformerFactory or an object with ts.TransformerFactory values`, }); + +export const couldNotFindBundleEntryPoint = (entryPoint: string): ts.Diagnostic => ({ + file: undefined, + start: undefined, + length: undefined, + category: ts.DiagnosticCategory.Error, + code: 0, + source: "typescript-to-lua", + messageText: `Could not find bundle entry point '${entryPoint}'. It should be a file in the project.`, +}); diff --git a/src/utils.ts b/src/utils.ts index 0fe3f68b6..eb68b6b1d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1 +1,14 @@ +import * as path from "path"; + export const normalizeSlashes = (filePath: string) => filePath.replace(/\\/g, "/"); + +export const trimExt = (filePath: string) => filePath.slice(0, -path.extname(filePath).length); + +export const formatPathToLuaPath = (filePath: string) => { + filePath = filePath.replace(/\.json$/, ""); + if (process.platform === "win32") { + // Windows can use backslashes + filePath = filePath.replace(/\.\\/g, "").replace(/\\/g, "."); + } + return filePath.replace(/\.\//g, "").replace(/\//g, "."); +}; diff --git a/test/cli/parse.spec.ts b/test/cli/parse.spec.ts index 0f4942d9c..9203a29e3 100644 --- a/test/cli/parse.spec.ts +++ b/test/cli/parse.spec.ts @@ -12,7 +12,7 @@ describe("command line", () => { const commandLine = "--project tsconfig.json --noHeader -t es3 -lt 5.3"; const result = tstl.parseCommandLine(commandLine.split(" ")); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options).toEqual({ project: "tsconfig.json", noHeader: true, @@ -24,13 +24,13 @@ describe("command line", () => { test("should error on unknown options", () => { const result = tstl.parseCommandLine(["--unknownOption"]); - expect(result.errors).toHaveDiagnostics(); + expect(result.errors).toHaveErrorDiagnostics(); }); test("should parse options case-insensitively", () => { const result = tstl.parseCommandLine(["--NoHeader"]); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.noHeader).toBe(true); }); @@ -38,7 +38,7 @@ describe("command line", () => { test("should parse enums", () => { const result = tstl.parseCommandLine(["--luaTarget", "5.1"]); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua51); }); @@ -46,7 +46,7 @@ describe("command line", () => { for (const value of ["jit", "JiT", "JIT"]) { const result = tstl.parseCommandLine(["--luaTarget", value]); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.LuaJIT); } }); @@ -54,7 +54,7 @@ describe("command line", () => { test("should error on invalid value", () => { const result = tstl.parseCommandLine(["--luaTarget", "invalid"]); - expect(result.errors).toHaveDiagnostics(); + expect(result.errors).toHaveErrorDiagnostics(); }); }); @@ -62,14 +62,14 @@ describe("command line", () => { test.each([true, false])("should parse booleans (%p)", value => { const result = tstl.parseCommandLine(["--noHeader", value.toString()]); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.noHeader).toBe(value); }); test("should be case-sensitive", () => { const result = tstl.parseCommandLine(["--noHeader", "FALSE"]); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.noHeader).toBe(true); expect(result.fileNames).toEqual(["FALSE"]); }); @@ -77,14 +77,14 @@ describe("command line", () => { test("should be parsed without a value", () => { const result = tstl.parseCommandLine(["--noHeader"]); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.noHeader).toBe(true); }); test("shouldn't parse following arguments as values", () => { const result = tstl.parseCommandLine(["--noHeader", "--noHoisting"]); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.noHeader).toBe(true); expect(result.options.noHoisting).toBe(true); }); @@ -92,7 +92,7 @@ describe("command line", () => { test("shouldn't parse following files as values", () => { const result = tstl.parseCommandLine(["--noHeader", "file.ts"]); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.noHeader).toBe(true); }); }); @@ -118,7 +118,7 @@ describe("command line", () => { ])("--%s %s", (optionName, value, expected) => { const result = tstl.parseCommandLine([`--${optionName}`, value]); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options).toEqual(expected); }); }); @@ -143,21 +143,21 @@ describe("tsconfig", () => { test("should allow unknown root-level options", () => { const result = parseConfigFileContent({ unknownOption: true }); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.unknownOption).toBeUndefined(); }); test("should error on unknown namespaced options", () => { const result = parseConfigFileContent({ tstl: { unknownOption: true } }); - expect(result.errors).toHaveDiagnostics(); + expect(result.errors).toHaveErrorDiagnostics(); expect(result.options.unknownOption).toBeUndefined(); }); test("should parse options case-sensitively", () => { const result = parseConfigFileContent({ tstl: { NoHeader: true } }); - expect(result.errors).toHaveDiagnostics(); + expect(result.errors).toHaveErrorDiagnostics(); expect(result.options.NoHeader).toBeUndefined(); expect(result.options.noHeader).toBeUndefined(); }); @@ -166,7 +166,7 @@ describe("tsconfig", () => { test("should parse enums", () => { const result = parseConfigFileContent({ tstl: { luaTarget: "5.1" } }); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua51); }); @@ -174,7 +174,7 @@ describe("tsconfig", () => { for (const value of ["jit", "JiT", "JIT"]) { const result = parseConfigFileContent({ tstl: { luaTarget: value } }); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.LuaJIT); } }); @@ -182,7 +182,7 @@ describe("tsconfig", () => { test("should error on invalid value", () => { const result = parseConfigFileContent({ tstl: { luaTarget: "invalid" } }); - expect(result.errors).toHaveDiagnostics(); + expect(result.errors).toHaveErrorDiagnostics(); }); }); @@ -190,14 +190,14 @@ describe("tsconfig", () => { test.each([true, false])("should parse booleans (%p)", value => { const result = parseConfigFileContent({ tstl: { noHeader: value } }); - expect(result.errors).not.toHaveDiagnostics(); + expect(result.errors).not.toHaveErrorDiagnostics(); expect(result.options.noHeader).toBe(value); }); test("shouldn't parse strings", () => { const result = parseConfigFileContent({ tstl: { noHeader: "true" } }); - expect(result.errors).toHaveDiagnostics(); + expect(result.errors).toHaveErrorDiagnostics(); expect(result.options.noHeader).toBeUndefined(); }); }); diff --git a/test/cli/tsconfig.spec.ts b/test/cli/tsconfig.spec.ts index b4fac5cbf..3d09eb8e3 100644 --- a/test/cli/tsconfig.spec.ts +++ b/test/cli/tsconfig.spec.ts @@ -79,15 +79,15 @@ describe("inferred", () => { describe("errors", () => { test("specified file does not exist", () => { - expect([locate("tsconfig.json")]).toHaveDiagnostics(); + expect([locate("tsconfig.json")]).toHaveErrorDiagnostics(); }); test("specified directory does not exist", () => { - expect([locate("project")]).toHaveDiagnostics(); + expect([locate("project")]).toHaveErrorDiagnostics(); }); test("cannot be mixed", async () => { await fs.outputFile("tsconfig.json", ""); - expect([locate("tsconfig.json", [""])]).toHaveDiagnostics(); + expect([locate("tsconfig.json", [""])]).toHaveErrorDiagnostics(); }); }); diff --git a/test/legacy-utils.ts b/test/legacy-utils.ts index 4b9815d17..18dde8b90 100644 --- a/test/legacy-utils.ts +++ b/test/legacy-utils.ts @@ -14,7 +14,7 @@ export function transpileString( expect(file.lua).toBeDefined(); const errors = diagnostics.filter(d => !ignoreDiagnostics || d.source === "typescript-to-lua"); - expect(errors).not.toHaveDiagnostics(); + expect(errors).not.toHaveErrorDiagnostics(); return file.lua!.trim(); } diff --git a/test/setup.ts b/test/setup.ts index dff900cb9..3f853efae 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -5,7 +5,7 @@ declare global { namespace jest { interface Matchers { toThrowExactError(error: Error): R; - toHaveDiagnostics(): R; + toHaveErrorDiagnostics(): R; } } } @@ -30,7 +30,7 @@ expect.extend({ return { pass: true, message: () => "" }; }, - toHaveDiagnostics(diagnostics: ts.Diagnostic[]): jest.CustomMatcherResult { + toHaveErrorDiagnostics(diagnostics: ts.Diagnostic[]): jest.CustomMatcherResult { expect(diagnostics).toBeInstanceOf(Array); // @ts-ignore const matcherHint = this.utils.matcherHint("toHaveDiagnostics", undefined, "", this); @@ -42,7 +42,7 @@ expect.extend({ }); return { - pass: diagnostics.length > 0, + pass: diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length > 0, message: () => { return ( matcherHint + diff --git a/test/transpile/directories.spec.ts b/test/transpile/directories.spec.ts index 458c14fec..18422dfe7 100644 --- a/test/transpile/directories.spec.ts +++ b/test/transpile/directories.spec.ts @@ -28,6 +28,6 @@ test.each([ ); const { diagnostics, emittedFiles } = buildVirtualProject(fileNames, options); - expect(diagnostics).not.toHaveDiagnostics(); + expect(diagnostics).not.toHaveErrorDiagnostics(); expect(emittedFiles).toMatchSnapshot(); }); diff --git a/test/transpile/outFile.spec.ts b/test/transpile/outFile.spec.ts index fa073490e..6a388bd0e 100644 --- a/test/transpile/outFile.spec.ts +++ b/test/transpile/outFile.spec.ts @@ -7,7 +7,7 @@ test("should support absolute outFile", () => { outFile: path.join(__dirname, "output.script"), }); - expect(diagnostics).not.toHaveDiagnostics(); + expect(diagnostics).not.toHaveErrorDiagnostics(); expect(emittedFiles).toEqual(["output.script"]); }); @@ -17,7 +17,7 @@ test("should support relative outFile", () => { outFile: "output.script", }); - expect(diagnostics).not.toHaveDiagnostics(); + expect(diagnostics).not.toHaveErrorDiagnostics(); expect(emittedFiles).toEqual(["output.script"]); }); @@ -27,7 +27,7 @@ test("should support outFile with declaration", () => { declaration: true, }); - expect(diagnostics).not.toHaveDiagnostics(); + expect(diagnostics).not.toHaveErrorDiagnostics(); expect(emittedFiles).toEqual(["output.d.ts", "output.script"]); }); @@ -39,6 +39,6 @@ test("should resolve outFile relative to base directory", () => { declaration: true, }); - expect(diagnostics).not.toHaveDiagnostics(); + expect(diagnostics).not.toHaveErrorDiagnostics(); expect(emittedFiles).toEqual(["output.d.ts", "output.script"]); }); diff --git a/test/unit/functions/noImplicitSelfOption.spec.ts b/test/unit/functions/noImplicitSelfOption.spec.ts index 906d80a65..5ecf2c39c 100644 --- a/test/unit/functions/noImplicitSelfOption.spec.ts +++ b/test/unit/functions/noImplicitSelfOption.spec.ts @@ -6,7 +6,7 @@ test("enables noSelfInFile behaviour for functions", () => { const test: (this: void) => void = fooBar; ` .setOptions({ noImplicitSelf: true }) - .expectToHaveNoDiagnostics(); + .expectToHaveNoErrorDiagnostics(); }); test("enables noSelfInFile behaviour for methods", () => { @@ -18,7 +18,7 @@ test("enables noSelfInFile behaviour for methods", () => { const test: (this: any) => void = fooBar.fooBar; ` .setOptions({ noImplicitSelf: true }) - .expectToHaveNoDiagnostics(); + .expectToHaveNoErrorDiagnostics(); }); test("generates declaration files with @noSelfInFile", () => { @@ -26,7 +26,7 @@ test("generates declaration files with @noSelfInFile", () => { export function bar() {} ` .setOptions({ declaration: true, noImplicitSelf: true }) - .expectToHaveNoDiagnostics(); + .expectToHaveNoErrorDiagnostics(); const declarationFile = builder.getLuaResult().transpiledFiles.find(f => f.declaration); if (!util.expectToBeDefined(declarationFile) || !util.expectToBeDefined(declarationFile.declaration)) return; @@ -36,5 +36,5 @@ test("generates declaration files with @noSelfInFile", () => { const test: (this: void) => void = bar; ` .addExtraFile("foo.d.ts", declarationFile.declaration) - .expectToHaveNoDiagnostics(); + .expectToHaveNoErrorDiagnostics(); }); diff --git a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts index e475f2216..00620a655 100644 --- a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts +++ b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts @@ -24,7 +24,7 @@ test("noSelfInFile works when first statement has other annotations", () => { function foo() {} const test: (this: void) => void = foo; - `.expectToHaveNoDiagnostics(); + `.expectToHaveNoErrorDiagnostics(); }); test.each(["(this: void, s: string) => string", "(this: any, s: string) => string", "(s: string) => string"])( diff --git a/test/unit/modules/resolution.spec.ts b/test/unit/modules/resolution.spec.ts index 6c1dbe105..1ed523bc3 100644 --- a/test/unit/modules/resolution.spec.ts +++ b/test/unit/modules/resolution.spec.ts @@ -82,7 +82,7 @@ test("doesn't resolve paths out of root dir", () => { .setMainFileName("src/main.ts") .setOptions({ rootDir: "./src" }) .disableSemanticCheck() - .expectToHaveDiagnostics(); + .expectToHaveErrorDiagnostics(); }); test.each([ diff --git a/test/unit/outfile.spec.ts b/test/unit/outfile.spec.ts index e1b158bfd..1643119f5 100644 --- a/test/unit/outfile.spec.ts +++ b/test/unit/outfile.spec.ts @@ -1,5 +1,17 @@ import * as util from "../util"; -import * as TSTLErrors from "../../src/TSTLErrors"; +import { couldNotFindBundleEntryPoint } from "../../src/diagnostics"; +import { LuaLibImportKind } from "../../src"; +import { DiagnosticCategory } from "typescript"; + +test("no entry point", () => { + util.testBundle`` + .setOptions({ luaBundleEntry: undefined }) + .expectToHaveDiagnostic( + d => + d.messageText === `'luaBundleEntry' is required when 'luaBundle' is enabled.` && + d.category === DiagnosticCategory.Error + ); +}); test("import module -> main", () => { util.testBundle` @@ -55,15 +67,32 @@ test("entry point in directory", () => { ) .addExtraFile("module.ts", "export const value = true") .setEntryPoint("main/main.ts") - .debug() .expectToEqual({ value: true }); }); -test("LuaLibs", () => { +test.each([LuaLibImportKind.Inline, LuaLibImportKind.Require])("LuaLibs", lualibOption => { + util.testBundle` + export const result = [1, 2]; + result.push(3); + ` + .setOptions({ luaLibImport: lualibOption }) + .expectToEqual({ result: [1, 2, 3] }); +}); + +test("LuaBundle and LuaLibImport.Inline generate warning", () => { util.testBundle` export const result = [1, 2]; result.push(3); - `.expectToEqual({ result: [1, 2, 3] }); + ` + .setOptions({ luaLibImport: LuaLibImportKind.Inline }) + .expectToHaveDiagnostic( + d => + d.category === DiagnosticCategory.Warning && + d.messageText === + `Using 'luaBundle' with 'luaLibImport: "inline"' might generate duplicate code. ` + + `It is recommended to use 'luaLibImport: "require"'` + ) + .expectToEqual({ result: [1, 2, 3] }); // Result should still be the same }); test("cyclic imports", () => { @@ -81,13 +110,11 @@ test("cyclic imports", () => { export const lazyValue = () => a.a; ` ) - .expectToEqual({ a: true, lazyValueResult: true }); + .expectExecutionError("stack overflow"); }); test("luaEntry doesn't exist", () => { - util.testBundle`` - .setOptions({ luaEntry: "entry.ts" }) - .expectToHaveDiagnosticOfError(TSTLErrors.LuaEntryNotFound("entry.ts")); + util.testBundle``.setEntryPoint("entry.ts").expectToHaveExactDiagnostic(couldNotFindBundleEntryPoint("entry.ts")); }); test("luaEntry resolved from path specified in tsconfig", () => { @@ -95,9 +122,5 @@ test("luaEntry resolved from path specified in tsconfig", () => { .addExtraFile("src/main.ts", "") .addExtraFile("src/module.ts", "") .setOptions({ rootDir: "src" }) - .expectToHaveNoDiagnostics(); -}); - -test("export equals", () => { - util.testBundle`export = "result"`.expectToEqual("result"); + .expectToHaveNoErrorDiagnostics(); }); diff --git a/test/unit/printer/sourcemaps.spec.ts b/test/unit/printer/sourcemaps.spec.ts index 4023dad19..b4f8b3128 100644 --- a/test/unit/printer/sourcemaps.spec.ts +++ b/test/unit/printer/sourcemaps.spec.ts @@ -152,7 +152,7 @@ test.each([ ])("Source map has correct mapping (%p)", async ({ code, assertPatterns }) => { const file = util .testModule(code) - .expectToHaveNoDiagnostics() + .expectToHaveNoErrorDiagnostics() .getMainLuaFileResult(); const consumer = await new SourceMapConsumer(file.sourceMap); @@ -169,7 +169,7 @@ test("Source map has correct sources", async () => { const file = util.testModule` const foo = "foo" ` - .expectToHaveNoDiagnostics() + .expectToHaveNoErrorDiagnostics() .getMainLuaFileResult(); const consumer = await new SourceMapConsumer(file.sourceMap); @@ -181,7 +181,7 @@ test("Source map has correct source root", async () => { const file = util.testModule` const foo = "foo" ` - .expectToHaveNoDiagnostics() + .expectToHaveNoErrorDiagnostics() .getMainLuaFileResult(); const sourceMap = JSON.parse(file.sourceMap); @@ -199,7 +199,7 @@ test.each([ ])("Source map has correct name mappings (%p)", async ({ code, name }) => { const file = util .testModule(code) - .expectToHaveNoDiagnostics() + .expectToHaveNoErrorDiagnostics() .getMainLuaFileResult(); const consumer = await new SourceMapConsumer(file.sourceMap); diff --git a/test/unit/transformers/transformers.spec.ts b/test/unit/transformers/transformers.spec.ts index db36c27dc..237950376 100644 --- a/test/unit/transformers/transformers.spec.ts +++ b/test/unit/transformers/transformers.spec.ts @@ -44,7 +44,7 @@ describe("resolution", () => { const transform = path.join(__dirname, "error.ts"); const options = optionsOfTransformer({ transform }); const { diagnostics } = util.transpileStringResult("", options); - expect(diagnostics).toHaveDiagnostics(); + expect(diagnostics).toHaveErrorDiagnostics(); }); }); diff --git a/test/util.ts b/test/util.ts index a7d917267..1f4d2ce78 100644 --- a/test/util.ts +++ b/test/util.ts @@ -129,6 +129,7 @@ export class ExecutionError extends Error { export type ExecutableTranspiledFile = tstl.TranspiledFile & { lua: string; sourceMap: string }; export type TapCallback = (builder: TestBuilder) => void; +export type DiagnosticMatcher = (diagnostic: ts.Diagnostic) => boolean; export abstract class TestBuilder { constructor(protected _tsCode: string) {} @@ -332,21 +333,41 @@ export abstract class TestBuilder { return this; } - public expectToHaveDiagnostics(): this { - expect(this.getLuaDiagnostics()).toHaveDiagnostics(); + public expectToHaveDiagnostic(matcher: DiagnosticMatcher): this { + expect(this.getLuaDiagnostics().find(matcher)).toBeDefined(); + return this; + } + + public expectToHaveExactDiagnostic(diagnostic: ts.Diagnostic): this { + expect(this.getLuaDiagnostics()).toContainEqual(diagnostic); + return this; + } + + public expectToHaveErrorDiagnostics(): this { + expect(this.getLuaDiagnostics()).toHaveErrorDiagnostics(); return this; } public expectToHaveDiagnosticOfError(error: Error): this { - this.expectToHaveDiagnostics(); + this.expectToHaveErrorDiagnostics(); expect(this.getLuaDiagnostics()).toHaveLength(1); const firstDiagnostic = this.getLuaDiagnostics()[0]; expect(firstDiagnostic).toMatchObject({ messageText: error.message }); return this; } - public expectToHaveNoDiagnostics(): this { - expect(this.getLuaDiagnostics()).not.toHaveDiagnostics(); + public expectToHaveNoErrorDiagnostics(): this { + expect(this.getLuaDiagnostics()).not.toHaveErrorDiagnostics(); + return this; + } + + public expectExecutionError(message: string): this { + const luaResult = this.getLuaExecutionResult(); + if (!(luaResult instanceof ExecutionError)) { + expect(luaResult).toBeInstanceOf(ExecutionError); + } else { + expect(luaResult.message).toContain(message); + } return this; } @@ -360,7 +381,7 @@ export abstract class TestBuilder { } public expectToMatchJsResult(allowErrors = false): this { - this.expectToHaveNoDiagnostics(); + this.expectToHaveNoErrorDiagnostics(); if (!allowErrors) this.expectNoExecutionError(); const luaResult = this.getLuaExecutionResult(); @@ -371,20 +392,20 @@ export abstract class TestBuilder { } public expectToEqual(expected: any): this { - this.expectToHaveNoDiagnostics(); + this.expectToHaveNoErrorDiagnostics(); const luaResult = this.getLuaExecutionResult(); expect(luaResult).toEqual(expected); return this; } public expectLuaToMatchSnapshot(): this { - this.expectToHaveNoDiagnostics(); + this.expectToHaveNoErrorDiagnostics(); expect(this.getMainLuaCodeChunk()).toMatchSnapshot(); return this; } public expectResultToMatchSnapshot(): this { - this.expectToHaveNoDiagnostics(); + this.expectToHaveNoErrorDiagnostics(); expect(this.getLuaExecutionResult()).toMatchSnapshot(); return this; } From 2cab0ef291adff65cf187fc32489bacc47a34e19 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Tue, 19 Nov 2019 21:48:40 +0100 Subject: [PATCH 04/18] Added failing test for entry point resolution --- src/Emit.ts | 18 +++----- src/Transpile.ts | 8 ++-- src/bundle.ts | 19 ++++---- src/cli/parse.ts | 27 +++++++++++- src/utils.ts | 6 +-- test/cli/parse.spec.ts | 32 ++++++++++++++ test/setup.ts | 2 +- test/transpile/bundle.spec.ts | 16 +++++++ test/transpile/bundle/index.ts | 3 ++ test/transpile/bundle/otherFile.ts | 3 ++ test/transpile/bundle/tsconfig.json | 15 +++++++ test/transpile/outFile.spec.ts | 44 ------------------- test/transpile/outFile/index.ts | 1 - test/unit/{outfile.spec.ts => bundle.spec.ts} | 0 test/util.ts | 40 +++++++++-------- 15 files changed, 141 insertions(+), 93 deletions(-) create mode 100644 test/transpile/bundle.spec.ts create mode 100644 test/transpile/bundle/index.ts create mode 100644 test/transpile/bundle/otherFile.ts create mode 100644 test/transpile/bundle/tsconfig.json delete mode 100644 test/transpile/outFile.spec.ts delete mode 100644 test/transpile/outFile/index.ts rename test/unit/{outfile.spec.ts => bundle.spec.ts} (100%) diff --git a/src/Emit.ts b/src/Emit.ts index 67ef4e276..87a12deb7 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -2,7 +2,7 @@ import * as path from "path"; import * as ts from "typescript"; import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; import { EmitHost, TranspiledFile } from "./Transpile"; -import { normalizeSlashes, trimExt } from "./utils"; +import { normalizeSlashes, trimExtension } from "./utils"; export interface OutputFile { name: string; @@ -15,7 +15,7 @@ export function emitTranspiledFiles( transpiledFiles: TranspiledFile[], emitHost: EmitHost = ts.sys ): OutputFile[] { - let { rootDir, outDir, outFile, luaLibImport } = options; + let { rootDir, outDir, luaLibImport } = options; const configFileName = options.configFilePath as string | undefined; // TODO: Use getCommonSourceDirectory @@ -31,14 +31,8 @@ export function emitTranspiledFiles( outPath = path.resolve(outDir, path.relative(rootDir, fileName)); } - // change extension or rename to outFile - if (outFile) { - outPath = path.isAbsolute(outFile) ? outFile : path.resolve(baseDir, outFile); - } else { - outPath = trimExt(outPath) + ".lua"; - } - - outPath = normalizeSlashes(outPath); + // change extension + outPath = normalizeSlashes(trimExtension(outPath) + ".lua"); if (lua !== undefined) { files.push({ name: outPath, text: lua }); @@ -49,11 +43,11 @@ export function emitTranspiledFiles( } if (declaration !== undefined) { - files.push({ name: trimExt(outPath) + ".d.ts", text: declaration }); + files.push({ name: trimExtension(outPath) + ".d.ts", text: declaration }); } if (declarationMap !== undefined) { - files.push({ name: trimExt(outPath) + ".d.ts.map", text: declarationMap }); + files.push({ name: trimExtension(outPath) + ".d.ts.map", text: declarationMap }); } } diff --git a/src/Transpile.ts b/src/Transpile.ts index d44518b07..d20abf8b3 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -16,10 +16,10 @@ export interface TranspiledFile { sourceMap?: string; declaration?: string; declarationMap?: string; + /** @internal */ + sourceMapNode?: SourceNode; } -export type TranspiledFileWithSourceNode = TranspiledFile & { sourceMapNode?: SourceNode }; - export interface TranspileResult { diagnostics: ts.Diagnostic[]; transpiledFiles: TranspiledFile[]; @@ -49,9 +49,9 @@ export function transpile({ const options = program.getCompilerOptions() as CompilerOptions; const diagnostics = validateOptions(options); - let transpiledFiles: TranspiledFileWithSourceNode[] = []; + let transpiledFiles: TranspiledFile[] = []; - const updateTranspiledFile = (fileName: string, update: Omit) => { + const updateTranspiledFile = (fileName: string, update: Omit) => { const file = transpiledFiles.find(f => f.fileName === fileName); if (file) { Object.assign(file, update); diff --git a/src/bundle.ts b/src/bundle.ts index 52354d674..b5c422336 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -1,21 +1,21 @@ import * as path from "path"; -import { TranspiledFileWithSourceNode, EmitHost } from "./Transpile"; +import { TranspiledFile, EmitHost } from "./Transpile"; import { SourceNode } from "source-map"; -import { formatPathToLuaPath, trimExt } from "./utils"; +import { formatPathToLuaPath, trimExtension } from "./utils"; import { Diagnostic } from "typescript"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; -const formatPath = (path: string) => formatPathToLuaPath(trimExt(path)); +const formatPath = (path: string) => formatPathToLuaPath(trimExtension(path)); export function bundleTranspiledFiles( bundleFile: string, entryModule: string, - transpiledFiles: TranspiledFileWithSourceNode[], + transpiledFiles: TranspiledFile[], emitHost: EmitHost -): [Diagnostic[], TranspiledFileWithSourceNode] { +): [Diagnostic[], TranspiledFile] { const diagnostics: Diagnostic[] = []; - if (transpiledFiles.find(f => f.fileName === entryModule) === undefined) { + if (!transpiledFiles.some(f => f.fileName === entryModule)) { return [[couldNotFindBundleEntryPoint(entryModule)], { fileName: bundleFile }]; } @@ -34,8 +34,9 @@ export function bundleTranspiledFiles( // Override `require` to read from ____modules table. const requireOverride = + `local ____originalRequire = require\n` + `function require(file) if ____modules[file] then return ____modules[file]() ` + - `else error("Could not find module '"..file.."' to require.") end end\n`; + `else print("Could not find module '"..file.."' to require."); return ____originalRequire(file) end end\n`; const entryPoint = `return require("${formatPath(entryModule)}")\n`; const bundleNode = joinSourceChunks([moduleTable, requireOverride, entryPoint]); @@ -54,7 +55,7 @@ export function bundleTranspiledFiles( ]; } -function moduleSourceNode(transpiledFile: TranspiledFileWithSourceNode): SourceNode { +function moduleSourceNode(transpiledFile: TranspiledFile): SourceNode { const tableEntryHead = `["${formatPath(transpiledFile.fileName)}"] = function() `; const tableEntryTail = `end,\n`; @@ -66,7 +67,7 @@ function moduleSourceNode(transpiledFile: TranspiledFileWithSourceNode): SourceN } function createModuleTableNode(fileChunks: SourceChunk[]): SourceNode { - const tableHead = `____modules = {\n`; + const tableHead = `local ____modules = {\n`; const tableEnd = `}\n`; return joinSourceChunks([tableHead, ...fileChunks, tableEnd]); diff --git a/src/cli/parse.ts b/src/cli/parse.ts index 8bbaad42b..f290c216b 100644 --- a/src/cli/parse.ts +++ b/src/cli/parse.ts @@ -21,9 +21,23 @@ interface CommandLineOptionOfBoolean extends CommandLineOptionBase { type: "boolean"; } -type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean; +interface CommandLineOptionOfString extends CommandLineOptionBase { + type: "string"; +} + +type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean | CommandLineOptionOfString; export const optionDeclarations: CommandLineOption[] = [ + { + name: "luaBundle", + description: "The name of the lua file to bundle output lua to. Requires luaBundleEntry.", + type: "string", + }, + { + name: "luaBundleEntry", + description: "The entry *.ts file that will be executed when entering the luaBundle. Requires luaBundle.", + type: "string", + }, { name: "luaLibImport", description: "Specifies how js standard features missing in lua are imported.", @@ -175,6 +189,17 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult { return { value }; } + case "string": { + if (typeof value !== "string") { + return { + value: undefined, + error: cliDiagnostics.compilerOptionRequiresAValueOfType(option.name, "string"), + }; + } + + return { value }; + } + case "enum": { if (typeof value !== "string") { return { diff --git a/src/utils.ts b/src/utils.ts index eb68b6b1d..72d5eda7f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,13 +2,13 @@ import * as path from "path"; export const normalizeSlashes = (filePath: string) => filePath.replace(/\\/g, "/"); -export const trimExt = (filePath: string) => filePath.slice(0, -path.extname(filePath).length); +export const trimExtension = (filePath: string) => filePath.slice(0, -path.extname(filePath).length); -export const formatPathToLuaPath = (filePath: string) => { +export function formatPathToLuaPath(filePath: string): string { filePath = filePath.replace(/\.json$/, ""); if (process.platform === "win32") { // Windows can use backslashes filePath = filePath.replace(/\.\\/g, "").replace(/\\/g, "."); } return filePath.replace(/\.\//g, "").replace(/\//g, "."); -}; +} diff --git a/test/cli/parse.spec.ts b/test/cli/parse.spec.ts index 9203a29e3..579039437 100644 --- a/test/cli/parse.spec.ts +++ b/test/cli/parse.spec.ts @@ -115,6 +115,9 @@ describe("command line", () => { ["luaTarget", "5.2", { luaTarget: tstl.LuaTarget.Lua52 }], ["luaTarget", "5.3", { luaTarget: tstl.LuaTarget.Lua53 }], ["luaTarget", "jit", { luaTarget: tstl.LuaTarget.LuaJIT }], + + ["luaBundle", "foo", { luaBundle: "foo" }], + ["luaBundleEntry", "bar", { luaBundleEntry: "bar" }], ])("--%s %s", (optionName, value, expected) => { const result = tstl.parseCommandLine([`--${optionName}`, value]); @@ -201,4 +204,33 @@ describe("tsconfig", () => { expect(result.options.noHeader).toBeUndefined(); }); }); + + describe("integration", () => { + test.each<[string, any, tstl.CompilerOptions]>([ + ["noHeader", false, { noHeader: false }], + ["noHeader", true, { noHeader: true }], + ["noHoisting", false, { noHoisting: false }], + ["noHoisting", true, { noHoisting: true }], + ["sourceMapTraceback", false, { sourceMapTraceback: false }], + ["sourceMapTraceback", true, { sourceMapTraceback: true }], + + ["luaLibImport", "none", { luaLibImport: tstl.LuaLibImportKind.None }], + ["luaLibImport", "always", { luaLibImport: tstl.LuaLibImportKind.Always }], + ["luaLibImport", "inline", { luaLibImport: tstl.LuaLibImportKind.Inline }], + ["luaLibImport", "require", { luaLibImport: tstl.LuaLibImportKind.Require }], + + ["luaTarget", "5.1", { luaTarget: tstl.LuaTarget.Lua51 }], + ["luaTarget", "5.2", { luaTarget: tstl.LuaTarget.Lua52 }], + ["luaTarget", "5.3", { luaTarget: tstl.LuaTarget.Lua53 }], + ["luaTarget", "jit", { luaTarget: tstl.LuaTarget.LuaJIT }], + + ["luaBundle", "foo", { luaBundle: "foo" }], + ["luaBundleEntry", "bar", { luaBundleEntry: "bar" }], + ])("--%s %s", (optionName, value, expected) => { + const result = parseConfigFileContent({ tstl: { [optionName]: value } }); + + expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.options).toEqual(expected); + }); + }); }); diff --git a/test/setup.ts b/test/setup.ts index 3f853efae..d3ad42a29 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -33,7 +33,7 @@ expect.extend({ toHaveErrorDiagnostics(diagnostics: ts.Diagnostic[]): jest.CustomMatcherResult { expect(diagnostics).toBeInstanceOf(Array); // @ts-ignore - const matcherHint = this.utils.matcherHint("toHaveDiagnostics", undefined, "", this); + const matcherHint = this.utils.matcherHint("toHaveErrorDiagnostics", undefined, "", this); const diagnosticMessages = ts.formatDiagnosticsWithColorAndContext(diagnostics, { getCurrentDirectory: () => "", diff --git a/test/transpile/bundle.spec.ts b/test/transpile/bundle.spec.ts new file mode 100644 index 000000000..177fef3f5 --- /dev/null +++ b/test/transpile/bundle.spec.ts @@ -0,0 +1,16 @@ +import * as path from "path"; +import { transpileProject } from "../../src"; +import { executeLua } from "../util"; + +const inputProject = path.join(__dirname, "bundle/tsconfig.json"); + +test("should transpile into one file", () => { + const transpileResult = transpileProject(inputProject); + + expect(transpileResult.diagnostics).not.toHaveErrorDiagnostics(); + expect(transpileResult.emitResult.length).toBe(1); + + const { name, text } = transpileResult.emitResult[0]; + expect(name).toBe("bundle.lua"); + expect(executeLua(text)).toBe({ myNumber: 3 }); +}); diff --git a/test/transpile/bundle/index.ts b/test/transpile/bundle/index.ts new file mode 100644 index 000000000..f189ac088 --- /dev/null +++ b/test/transpile/bundle/index.ts @@ -0,0 +1,3 @@ +import { getNumber } from "./otherFile"; + +export const myNumber = getNumber(); diff --git a/test/transpile/bundle/otherFile.ts b/test/transpile/bundle/otherFile.ts new file mode 100644 index 000000000..ad9eaceb7 --- /dev/null +++ b/test/transpile/bundle/otherFile.ts @@ -0,0 +1,3 @@ +export function getNumber(): number { + return 3; +} diff --git a/test/transpile/bundle/tsconfig.json b/test/transpile/bundle/tsconfig.json new file mode 100644 index 000000000..96dfbe47a --- /dev/null +++ b/test/transpile/bundle/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "target": "es2017", + "lib": ["es2017"], + "types": [] + }, + "tstl": { + "luaBundle": "bundle.lua", + "luaBundleEntry": "index.ts" + }, + "include": ["."] +} diff --git a/test/transpile/outFile.spec.ts b/test/transpile/outFile.spec.ts deleted file mode 100644 index 6a388bd0e..000000000 --- a/test/transpile/outFile.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as path from "path"; -import { buildVirtualProject } from "./run"; - -const inputFilePath = path.join(__dirname, "outFile/index.ts"); -test("should support absolute outFile", () => { - const { diagnostics, emittedFiles } = buildVirtualProject([inputFilePath], { - outFile: path.join(__dirname, "output.script"), - }); - - expect(diagnostics).not.toHaveErrorDiagnostics(); - expect(emittedFiles).toEqual(["output.script"]); -}); - -test("should support relative outFile", () => { - jest.spyOn(process, "cwd").mockReturnValue(__dirname); - const { diagnostics, emittedFiles } = buildVirtualProject([inputFilePath], { - outFile: "output.script", - }); - - expect(diagnostics).not.toHaveErrorDiagnostics(); - expect(emittedFiles).toEqual(["output.script"]); -}); - -test("should support outFile with declaration", () => { - const { diagnostics, emittedFiles } = buildVirtualProject([inputFilePath], { - outFile: path.join(__dirname, "output.script"), - declaration: true, - }); - - expect(diagnostics).not.toHaveErrorDiagnostics(); - expect(emittedFiles).toEqual(["output.d.ts", "output.script"]); -}); - -test("should resolve outFile relative to base directory", () => { - jest.spyOn(process, "cwd").mockReturnValue(__dirname); - const { diagnostics, emittedFiles } = buildVirtualProject([inputFilePath], { - outFile: "output.script", - outDir: "out", - declaration: true, - }); - - expect(diagnostics).not.toHaveErrorDiagnostics(); - expect(emittedFiles).toEqual(["output.d.ts", "output.script"]); -}); diff --git a/test/transpile/outFile/index.ts b/test/transpile/outFile/index.ts deleted file mode 100644 index ef22a69f1..000000000 --- a/test/transpile/outFile/index.ts +++ /dev/null @@ -1 +0,0 @@ -class Test {} diff --git a/test/unit/outfile.spec.ts b/test/unit/bundle.spec.ts similarity index 100% rename from test/unit/outfile.spec.ts rename to test/unit/bundle.spec.ts diff --git a/test/util.ts b/test/util.ts index 1f4d2ce78..f3d2ba9fa 100644 --- a/test/util.ts +++ b/test/util.ts @@ -107,6 +107,27 @@ function transpileJs(program: ts.Program): TranspileJsResult { return { transpiledFiles, diagnostics: [...diagnostics] }; } +export function executeLua(code: string): any { + const L = lauxlib.luaL_newstate(); + lualib.luaL_openlibs(L); + const status = lauxlib.luaL_dostring(L, to_luastring(code)); + + if (status === lua.LUA_OK) { + if (lua.lua_isstring(L, -1)) { + const result = eval(`(${lua.lua_tojsstring(L, -1)})`); + return result === null ? undefined : result; + } else { + const returnType = to_jsstring(lua.lua_typename(L, lua.lua_type(L, -1))); + throw new Error(`Unsupported Lua return type: ${returnType}`); + } + } else { + // Filter out control characters appearing on some systems + const luaStackString = lua.lua_tostring(L, -1).filter(c => c >= 20); + const message = to_jsstring(luaStackString).replace(/^\[string "--\.\.\."\]:\d+: /, ""); + return new ExecutionError(message); + } +} + const memoize: MethodDecorator = (_target, _propertyKey, descriptor) => { const originalFunction = descriptor.value as any; const memoized = new WeakMap(); @@ -238,24 +259,7 @@ export abstract class TestBuilder { @memoize public getLuaExecutionResult(): any { const code = this.getLuaCodeWithWrapper(); - const L = lauxlib.luaL_newstate(); - lualib.luaL_openlibs(L); - const status = lauxlib.luaL_dostring(L, to_luastring(code)); - - if (status === lua.LUA_OK) { - if (lua.lua_isstring(L, -1)) { - const result = eval(`(${lua.lua_tojsstring(L, -1)})`); - return result === null ? undefined : result; - } else { - const returnType = to_jsstring(lua.lua_typename(L, lua.lua_type(L, -1))); - throw new Error(`Unsupported Lua return type: ${returnType}`); - } - } else { - // Filter out control characters appearing on some systems - const luaStackString = lua.lua_tostring(L, -1).filter(c => c >= 20); - const message = to_jsstring(luaStackString).replace(/^\[string "--\.\.\."\]:\d+: /, ""); - return new ExecutionError(message); - } + return executeLua(code); } @memoize From e57082cb7a2942c3184eadc63cf02fa597a60faf Mon Sep 17 00:00:00 2001 From: Perryvw Date: Thu, 21 Nov 2019 22:56:42 +0100 Subject: [PATCH 05/18] Fixed bundle transpile test, broke all other bundle tests --- src/Emit.ts | 10 +++++++--- src/Transpile.ts | 3 ++- src/bundle.ts | 16 +++++++++++----- test/transpile/bundle.spec.ts | 6 ++++-- test/transpile/bundle/tsconfig.json | 3 ++- test/util.ts | 6 +++++- 6 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/Emit.ts b/src/Emit.ts index 87a12deb7..b606986d9 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -17,9 +17,7 @@ export function emitTranspiledFiles( ): OutputFile[] { let { rootDir, outDir, luaLibImport } = options; - const configFileName = options.configFilePath as string | undefined; - // TODO: Use getCommonSourceDirectory - const baseDir = configFileName ? path.dirname(configFileName) : process.cwd(); + const baseDir = resolveBaseDir(options); rootDir = rootDir || baseDir; outDir = outDir ? path.resolve(baseDir, outDir) : rootDir; @@ -71,3 +69,9 @@ export function emitTranspiledFiles( return files; } + +export function resolveBaseDir(options: CompilerOptions): string { + const configFileName = options.configFilePath as string | undefined; + // TODO: Use getCommonSourceDirectory + return configFileName ? path.dirname(configFileName) : process.cwd(); +} diff --git a/src/Transpile.ts b/src/Transpile.ts index d20abf8b3..6851a6a72 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -154,9 +154,10 @@ export function transpile({ if (options.luaBundle && options.luaBundleEntry) { const [bundleDiagnostics, bundle] = bundleTranspiledFiles( - options.luaBundleEntry, + options.luaBundle, options.luaBundleEntry, transpiledFiles, + options, emitHost ); diagnostics.push(...bundleDiagnostics); diff --git a/src/bundle.ts b/src/bundle.ts index b5c422336..d494376a8 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -1,9 +1,11 @@ import * as path from "path"; import { TranspiledFile, EmitHost } from "./Transpile"; import { SourceNode } from "source-map"; -import { formatPathToLuaPath, trimExtension } from "./utils"; +import { formatPathToLuaPath, trimExtension, normalizeSlashes } from "./utils"; import { Diagnostic } from "typescript"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; +import { resolveBaseDir } from "./Emit"; +import { CompilerOptions } from "./CompilerOptions"; const formatPath = (path: string) => formatPathToLuaPath(trimExtension(path)); @@ -11,16 +13,19 @@ export function bundleTranspiledFiles( bundleFile: string, entryModule: string, transpiledFiles: TranspiledFile[], + options: CompilerOptions, emitHost: EmitHost ): [Diagnostic[], TranspiledFile] { const diagnostics: Diagnostic[] = []; - if (!transpiledFiles.some(f => f.fileName === entryModule)) { + const baseDirectory = resolveBaseDir(options); + const resolvedEntryPoint = normalizeSlashes(path.join(baseDirectory, entryModule)); + if (!transpiledFiles.some(f => f.fileName === resolvedEntryPoint)) { return [[couldNotFindBundleEntryPoint(entryModule)], { fileName: bundleFile }]; } // For each file: [""] = function() end, - const moduleTableEntries: SourceChunk[] = transpiledFiles.map(moduleSourceNode); + const moduleTableEntries: SourceChunk[] = transpiledFiles.map(f => moduleSourceNode(f, baseDirectory)); // If any of the modules contains a require for lualib_bundle, add it to the module table. const lualibRequired = transpiledFiles.some(f => f.lua && f.lua.match(/require\("lualib_bundle"\)/)); @@ -55,8 +60,9 @@ export function bundleTranspiledFiles( ]; } -function moduleSourceNode(transpiledFile: TranspiledFile): SourceNode { - const tableEntryHead = `["${formatPath(transpiledFile.fileName)}"] = function() `; +function moduleSourceNode(transpiledFile: TranspiledFile, baseDirectory: string): SourceNode { + const modulePath = formatPath(path.relative(baseDirectory, transpiledFile.fileName)); + const tableEntryHead = `["${modulePath}"] = function() `; const tableEntryTail = `end,\n`; if (transpiledFile.lua && transpiledFile.sourceMapNode) { diff --git a/test/transpile/bundle.spec.ts b/test/transpile/bundle.spec.ts index 177fef3f5..0369bcbb7 100644 --- a/test/transpile/bundle.spec.ts +++ b/test/transpile/bundle.spec.ts @@ -1,6 +1,6 @@ import * as path from "path"; +import * as util from "../util"; import { transpileProject } from "../../src"; -import { executeLua } from "../util"; const inputProject = path.join(__dirname, "bundle/tsconfig.json"); @@ -11,6 +11,8 @@ test("should transpile into one file", () => { expect(transpileResult.emitResult.length).toBe(1); const { name, text } = transpileResult.emitResult[0]; + // Verify the name is as specified in tsconfig expect(name).toBe("bundle.lua"); - expect(executeLua(text)).toBe({ myNumber: 3 }); + // Verify exported module by executing + expect(util.executeLuaModule(text)).toEqual({ myNumber: 3 }); }); diff --git a/test/transpile/bundle/tsconfig.json b/test/transpile/bundle/tsconfig.json index 96dfbe47a..9422a5bd1 100644 --- a/test/transpile/bundle/tsconfig.json +++ b/test/transpile/bundle/tsconfig.json @@ -5,7 +5,8 @@ "noUnusedParameters": true, "target": "es2017", "lib": ["es2017"], - "types": [] + "types": [], + "rootDir": "." }, "tstl": { "luaBundle": "bundle.lua", diff --git a/test/util.ts b/test/util.ts index f3d2ba9fa..baa6549b9 100644 --- a/test/util.ts +++ b/test/util.ts @@ -107,7 +107,7 @@ function transpileJs(program: ts.Program): TranspileJsResult { return { transpiledFiles, diagnostics: [...diagnostics] }; } -export function executeLua(code: string): any { +function executeLua(code: string): any { const L = lauxlib.luaL_newstate(); lualib.luaL_openlibs(L); const status = lauxlib.luaL_dostring(L, to_luastring(code)); @@ -128,6 +128,10 @@ export function executeLua(code: string): any { } } +export function executeLuaModule(code: string): any { + return executeLua(`${minimalTestLib}return JSONStringify((function()\n${code}\nend)())`); +} + const memoize: MethodDecorator = (_target, _propertyKey, descriptor) => { const originalFunction = descriptor.value as any; const memoized = new WeakMap(); From 029a0bd7f58f7a331506178ce30d7787a7cb20f5 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sat, 23 Nov 2019 21:32:39 +0100 Subject: [PATCH 06/18] Fix bundle tests --- src/Emit.ts | 10 +++------- src/Transpile.ts | 2 +- src/bundle.ts | 29 +++++++++++++++++------------ src/typescript-internal.ts | 4 ++++ test/transpile/bundle.spec.ts | 5 +++-- test/unit/bundle.spec.ts | 21 +++++++++++++-------- 6 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/Emit.ts b/src/Emit.ts index b606986d9..87a12deb7 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -17,7 +17,9 @@ export function emitTranspiledFiles( ): OutputFile[] { let { rootDir, outDir, luaLibImport } = options; - const baseDir = resolveBaseDir(options); + const configFileName = options.configFilePath as string | undefined; + // TODO: Use getCommonSourceDirectory + const baseDir = configFileName ? path.dirname(configFileName) : process.cwd(); rootDir = rootDir || baseDir; outDir = outDir ? path.resolve(baseDir, outDir) : rootDir; @@ -69,9 +71,3 @@ export function emitTranspiledFiles( return files; } - -export function resolveBaseDir(options: CompilerOptions): string { - const configFileName = options.configFilePath as string | undefined; - // TODO: Use getCommonSourceDirectory - return configFileName ? path.dirname(configFileName) : process.cwd(); -} diff --git a/src/Transpile.ts b/src/Transpile.ts index 6851a6a72..afcd56e11 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -157,7 +157,7 @@ export function transpile({ options.luaBundle, options.luaBundleEntry, transpiledFiles, - options, + program, emitHost ); diagnostics.push(...bundleDiagnostics); diff --git a/src/bundle.ts b/src/bundle.ts index d494376a8..5e008411e 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -2,10 +2,8 @@ import * as path from "path"; import { TranspiledFile, EmitHost } from "./Transpile"; import { SourceNode } from "source-map"; import { formatPathToLuaPath, trimExtension, normalizeSlashes } from "./utils"; -import { Diagnostic } from "typescript"; +import { Diagnostic, Program } from "typescript"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; -import { resolveBaseDir } from "./Emit"; -import { CompilerOptions } from "./CompilerOptions"; const formatPath = (path: string) => formatPathToLuaPath(trimExtension(path)); @@ -13,19 +11,19 @@ export function bundleTranspiledFiles( bundleFile: string, entryModule: string, transpiledFiles: TranspiledFile[], - options: CompilerOptions, + program: Program, emitHost: EmitHost ): [Diagnostic[], TranspiledFile] { const diagnostics: Diagnostic[] = []; - const baseDirectory = resolveBaseDir(options); - const resolvedEntryPoint = normalizeSlashes(path.join(baseDirectory, entryModule)); - if (!transpiledFiles.some(f => f.fileName === resolvedEntryPoint)) { + const resolvedEntryModule = resolveAbsolutePath(program, entryModule); + if (!transpiledFiles.some(f => normalizeSlashes(f.fileName) === normalizeSlashes(resolvedEntryModule))) { return [[couldNotFindBundleEntryPoint(entryModule)], { fileName: bundleFile }]; } // For each file: [""] = function() end, - const moduleTableEntries: SourceChunk[] = transpiledFiles.map(f => moduleSourceNode(f, baseDirectory)); + const projectRootDir = getProjectRootDir(program); + const moduleTableEntries: SourceChunk[] = transpiledFiles.map(f => moduleSourceNode(f, projectRootDir)); // If any of the modules contains a require for lualib_bundle, add it to the module table. const lualibRequired = transpiledFiles.some(f => f.lua && f.lua.match(/require\("lualib_bundle"\)/)); @@ -50,7 +48,7 @@ export function bundleTranspiledFiles( return [ diagnostics, { - fileName: bundleFile, + fileName: resolveAbsolutePath(program, bundleFile), lua: code, sourceMap: map.toString(), sourceMapNode: moduleTable, @@ -60,9 +58,9 @@ export function bundleTranspiledFiles( ]; } -function moduleSourceNode(transpiledFile: TranspiledFile, baseDirectory: string): SourceNode { - const modulePath = formatPath(path.relative(baseDirectory, transpiledFile.fileName)); - const tableEntryHead = `["${modulePath}"] = function() `; +function moduleSourceNode(transpiledFile: TranspiledFile, projectRootDir: string): SourceNode { + const resolvedProjectPath = path.relative(projectRootDir, transpiledFile.fileName); + const tableEntryHead = `["${formatPath(resolvedProjectPath)}"] = function() `; const tableEntryTail = `end,\n`; if (transpiledFile.lua && transpiledFile.sourceMapNode) { @@ -84,3 +82,10 @@ function joinSourceChunks(chunks: SourceChunk[]): SourceNode { // tslint:disable-next-line:no-null-keyword return new SourceNode(null, null, null, chunks); } + +const getProjectRootDir = (program: Program) => program.getCommonSourceDirectory(); + +const resolveAbsolutePath = (program: Program, pathToResolve: string) => + path.isAbsolute(pathToResolve) + ? pathToResolve + : path.normalize(path.join(getProjectRootDir(program), pathToResolve)); diff --git a/src/typescript-internal.ts b/src/typescript-internal.ts index 6f5819c38..c94c9b41b 100644 --- a/src/typescript-internal.ts +++ b/src/typescript-internal.ts @@ -11,4 +11,8 @@ declare module "typescript" { interface Statement { jsDoc?: ts.JSDoc[]; } + + interface Program { + getCommonSourceDirectory(): string; + } } diff --git a/test/transpile/bundle.spec.ts b/test/transpile/bundle.spec.ts index 0369bcbb7..e3999211f 100644 --- a/test/transpile/bundle.spec.ts +++ b/test/transpile/bundle.spec.ts @@ -2,7 +2,8 @@ import * as path from "path"; import * as util from "../util"; import { transpileProject } from "../../src"; -const inputProject = path.join(__dirname, "bundle/tsconfig.json"); +const projectDir = path.join(__dirname, "bundle"); +const inputProject = path.join(projectDir, "tsconfig.json"); test("should transpile into one file", () => { const transpileResult = transpileProject(inputProject); @@ -12,7 +13,7 @@ test("should transpile into one file", () => { const { name, text } = transpileResult.emitResult[0]; // Verify the name is as specified in tsconfig - expect(name).toBe("bundle.lua"); + expect(path.join(name)).toBe(path.join(projectDir, "bundle.lua")); // Verify exported module by executing expect(util.executeLuaModule(text)).toEqual({ myNumber: 3 }); }); diff --git a/test/unit/bundle.spec.ts b/test/unit/bundle.spec.ts index 1643119f5..4bc3bd402 100644 --- a/test/unit/bundle.spec.ts +++ b/test/unit/bundle.spec.ts @@ -21,6 +21,19 @@ test("import module -> main", () => { .expectToEqual({ value: true }); }); +test("bundle file name", () => { + const { diagnostics, transpiledFiles } = util.testModule` + export { value } from "./module"; +` + .addExtraFile("module.ts", "export const value = true") + .setOptions({ luaBundle: "mybundle.lua", luaBundleEntry: "main.ts" }) + .getLuaResult(); + + expect(diagnostics.length).toBe(0); + expect(transpiledFiles.length).toBe(1); + expect(transpiledFiles[0].fileName).toBe("mybundle.lua"); +}); + test("import chain export -> reexport -> main", () => { util.testBundle` export { value } from "./reexport"; @@ -116,11 +129,3 @@ test("cyclic imports", () => { test("luaEntry doesn't exist", () => { util.testBundle``.setEntryPoint("entry.ts").expectToHaveExactDiagnostic(couldNotFindBundleEntryPoint("entry.ts")); }); - -test("luaEntry resolved from path specified in tsconfig", () => { - util.testBundle`` - .addExtraFile("src/main.ts", "") - .addExtraFile("src/module.ts", "") - .setOptions({ rootDir: "src" }) - .expectToHaveNoErrorDiagnostics(); -}); From 9f34b565b514e52c62d3a79f5a1a47265679e6a6 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sat, 23 Nov 2019 21:32:48 +0100 Subject: [PATCH 07/18] Added basic project transpilation test --- .../__snapshots__/basic.spec.ts.snap | 25 +++++++++++++++++++ test/transpile/basic.spec.ts | 12 +++++++++ test/transpile/basic/api.d.ts | 3 +++ test/transpile/basic/index.ts | 4 +++ test/transpile/basic/otherFile.ts | 3 +++ test/transpile/basic/tsconfig.json | 11 ++++++++ 6 files changed, 58 insertions(+) create mode 100644 test/transpile/__snapshots__/basic.spec.ts.snap create mode 100644 test/transpile/basic.spec.ts create mode 100644 test/transpile/basic/api.d.ts create mode 100644 test/transpile/basic/index.ts create mode 100644 test/transpile/basic/otherFile.ts create mode 100644 test/transpile/basic/tsconfig.json diff --git a/test/transpile/__snapshots__/basic.spec.ts.snap b/test/transpile/__snapshots__/basic.spec.ts.snap new file mode 100644 index 000000000..49580d3b6 --- /dev/null +++ b/test/transpile/__snapshots__/basic.spec.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should transpile 1`] = ` +Array [ + Object { + "name": "C:/Users/Perry/Code/Dota 2/TypescriptToLua/test/transpile/basic/otherFile.lua", + "text": "--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +function ____exports.getNumber(self) + return GetAPIValue() +end +return ____exports +", + }, + Object { + "name": "C:/Users/Perry/Code/Dota 2/TypescriptToLua/test/transpile/basic/index.lua", + "text": "--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____otherFile = require(\\"otherFile\\") +local getNumber = ____otherFile.getNumber +local myNumber = getNumber(nil) +SetAPIValue(myNumber * 5) +", + }, +] +`; diff --git a/test/transpile/basic.spec.ts b/test/transpile/basic.spec.ts new file mode 100644 index 000000000..a4e04ae67 --- /dev/null +++ b/test/transpile/basic.spec.ts @@ -0,0 +1,12 @@ +import * as path from "path"; +import { transpileProject } from "../../src"; + +const projectDir = path.join(__dirname, "basic"); +const inputProject = path.join(projectDir, "tsconfig.json"); + +test("should transpile", () => { + const transpileResult = transpileProject(inputProject); + + expect(transpileResult.diagnostics).not.toHaveErrorDiagnostics(); + expect(transpileResult.emitResult).toMatchSnapshot(); +}); diff --git a/test/transpile/basic/api.d.ts b/test/transpile/basic/api.d.ts new file mode 100644 index 000000000..dcac56a84 --- /dev/null +++ b/test/transpile/basic/api.d.ts @@ -0,0 +1,3 @@ +/** @noSelfInFile */ +declare function GetAPIValue(): number; +declare function SetAPIValue(n: number): void; diff --git a/test/transpile/basic/index.ts b/test/transpile/basic/index.ts new file mode 100644 index 000000000..bd11cd347 --- /dev/null +++ b/test/transpile/basic/index.ts @@ -0,0 +1,4 @@ +import { getNumber } from "./otherFile"; + +const myNumber = getNumber(); +SetAPIValue(myNumber * 5); diff --git a/test/transpile/basic/otherFile.ts b/test/transpile/basic/otherFile.ts new file mode 100644 index 000000000..ca4d2aa23 --- /dev/null +++ b/test/transpile/basic/otherFile.ts @@ -0,0 +1,3 @@ +export function getNumber(): number { + return GetAPIValue(); +} diff --git a/test/transpile/basic/tsconfig.json b/test/transpile/basic/tsconfig.json new file mode 100644 index 000000000..1fa9a8ae8 --- /dev/null +++ b/test/transpile/basic/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "target": "es2017", + "lib": ["es2017"], + "types": [], + "rootDir": "." + } +} From 2a1f595b43cf51c7d8c372a92611307eb8e9c40a Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sat, 23 Nov 2019 21:50:12 +0100 Subject: [PATCH 08/18] Fixed test paths --- test/transpile/__snapshots__/basic.spec.ts.snap | 4 ++-- test/transpile/basic.spec.ts | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/test/transpile/__snapshots__/basic.spec.ts.snap b/test/transpile/__snapshots__/basic.spec.ts.snap index 49580d3b6..498b36d47 100644 --- a/test/transpile/__snapshots__/basic.spec.ts.snap +++ b/test/transpile/__snapshots__/basic.spec.ts.snap @@ -3,7 +3,7 @@ exports[`should transpile 1`] = ` Array [ Object { - "name": "C:/Users/Perry/Code/Dota 2/TypescriptToLua/test/transpile/basic/otherFile.lua", + "name": "otherFile.lua", "text": "--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] local ____exports = {} function ____exports.getNumber(self) @@ -13,7 +13,7 @@ return ____exports ", }, Object { - "name": "C:/Users/Perry/Code/Dota 2/TypescriptToLua/test/transpile/basic/index.lua", + "name": "index.lua", "text": "--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] local ____otherFile = require(\\"otherFile\\") local getNumber = ____otherFile.getNumber diff --git a/test/transpile/basic.spec.ts b/test/transpile/basic.spec.ts index a4e04ae67..e1efd1b34 100644 --- a/test/transpile/basic.spec.ts +++ b/test/transpile/basic.spec.ts @@ -8,5 +8,11 @@ test("should transpile", () => { const transpileResult = transpileProject(inputProject); expect(transpileResult.diagnostics).not.toHaveErrorDiagnostics(); - expect(transpileResult.emitResult).toMatchSnapshot(); + + // Check output paths relative to projectDir + const relativeResult = transpileResult.emitResult.map(({ name, text }) => ({ + name: path.relative(projectDir, name), + text, + })); + expect(relativeResult).toMatchSnapshot(); }); From 5b2657d4c438999baf1852c53f67fb7b4adee5f0 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 24 Nov 2019 17:25:54 +0100 Subject: [PATCH 09/18] Moved resolution modules to resolve.ts, rewrote them to use configFilePath and rootDir, addressed other PR comments --- src/CompilerOptions.ts | 2 +- src/Emit.ts | 12 ++++---- src/Transpile.ts | 4 +-- src/bundle.ts | 30 ++++++++----------- src/cli/parse.ts | 16 ++-------- src/index.ts | 2 +- src/resolve.ts | 15 ++++++++++ src/tstl.ts | 4 +-- src/typescript-internal.ts | 4 +++ test/cli/parse.spec.ts | 4 +-- .../__snapshots__/directories.spec.ts.snap | 6 ++-- test/transpile/basic/tsconfig.json | 4 +-- test/transpile/bundle/tsconfig.json | 4 +-- test/unit/bundle.spec.ts | 2 +- test/util.ts | 9 +++--- 15 files changed, 60 insertions(+), 58 deletions(-) create mode 100644 src/resolve.ts diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index 4f4741fec..be3aa09b4 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -47,7 +47,7 @@ export enum LuaTarget { export function validateOptions(options: CompilerOptions): ts.Diagnostic[] { const diagnostics: ts.Diagnostic[] = []; - if (options.luaBundle && (options.luaBundleEntry === "" || !options.luaBundleEntry)) { + if (options.luaBundle && !options.luaBundleEntry) { diagnostics.push(configErrorDiagnostic(`'luaBundleEntry' is required when 'luaBundle' is enabled.`)); } diff --git a/src/Emit.ts b/src/Emit.ts index 87a12deb7..542379345 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -3,6 +3,7 @@ import * as ts from "typescript"; import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; import { EmitHost, TranspiledFile } from "./Transpile"; import { normalizeSlashes, trimExtension } from "./utils"; +import { getProjectRootDir } from "./resolve"; export interface OutputFile { name: string; @@ -11,18 +12,15 @@ export interface OutputFile { let lualibContent: string; export function emitTranspiledFiles( + program: ts.Program, options: CompilerOptions, transpiledFiles: TranspiledFile[], emitHost: EmitHost = ts.sys ): OutputFile[] { - let { rootDir, outDir, luaLibImport } = options; + let { outDir, luaLibImport } = options; - const configFileName = options.configFilePath as string | undefined; - // TODO: Use getCommonSourceDirectory - const baseDir = configFileName ? path.dirname(configFileName) : process.cwd(); - - rootDir = rootDir || baseDir; - outDir = outDir ? path.resolve(baseDir, outDir) : rootDir; + const rootDir = getProjectRootDir(program); + outDir = outDir ? path.resolve(rootDir, outDir) : rootDir; const files: OutputFile[] = []; for (const { fileName, lua, sourceMap, declaration, declarationMap } of transpiledFiles) { diff --git a/src/Transpile.ts b/src/Transpile.ts index afcd56e11..16f2dcbac 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -1,4 +1,6 @@ +import { SourceNode } from "source-map"; import * as ts from "typescript"; +import { bundleTranspiledFiles } from "./bundle"; import { CompilerOptions, validateOptions } from "./CompilerOptions"; import * as diagnosticFactories from "./diagnostics"; import { Block } from "./LuaAST"; @@ -6,8 +8,6 @@ import { LuaPrinter } from "./LuaPrinter"; import { LuaTransformer } from "./LuaTransformer"; import { TranspileError } from "./TranspileError"; import { getCustomTransformers } from "./TSTransformers"; -import { bundleTranspiledFiles } from "./bundle"; -import { SourceNode } from "source-map"; export interface TranspiledFile { fileName: string; diff --git a/src/bundle.ts b/src/bundle.ts index 5e008411e..a0dc91314 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -1,9 +1,10 @@ import * as path from "path"; -import { TranspiledFile, EmitHost } from "./Transpile"; import { SourceNode } from "source-map"; -import { formatPathToLuaPath, trimExtension, normalizeSlashes } from "./utils"; -import { Diagnostic, Program } from "typescript"; +import * as ts from "typescript"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; +import { getProjectRootDir, resolveFromRootDir } from "./resolve"; +import { EmitHost, TranspiledFile } from "./Transpile"; +import { formatPathToLuaPath, trimExtension } from "./utils"; const formatPath = (path: string) => formatPathToLuaPath(trimExtension(path)); @@ -11,13 +12,13 @@ export function bundleTranspiledFiles( bundleFile: string, entryModule: string, transpiledFiles: TranspiledFile[], - program: Program, + program: ts.Program, emitHost: EmitHost -): [Diagnostic[], TranspiledFile] { - const diagnostics: Diagnostic[] = []; +): [ts.Diagnostic[], TranspiledFile] { + const diagnostics: ts.Diagnostic[] = []; - const resolvedEntryModule = resolveAbsolutePath(program, entryModule); - if (!transpiledFiles.some(f => normalizeSlashes(f.fileName) === normalizeSlashes(resolvedEntryModule))) { + const resolvedEntryModule = resolveFromRootDir(program, entryModule); + if (!transpiledFiles.some(f => resolveFromRootDir(program, f.fileName) === resolvedEntryModule)) { return [[couldNotFindBundleEntryPoint(entryModule)], { fileName: bundleFile }]; } @@ -37,8 +38,10 @@ export function bundleTranspiledFiles( // Override `require` to read from ____modules table. const requireOverride = + `local ____moduleCache = {}\n` + `local ____originalRequire = require\n` + - `function require(file) if ____modules[file] then return ____modules[file]() ` + + `function require(file) if ____moduleCache[file] then return ____moduleCache[file] end\n` + + `if ____modules[file] then ____moduleCache[file] = ____modules[file](); return ____moduleCache[file] ` + `else print("Could not find module '"..file.."' to require."); return ____originalRequire(file) end end\n`; const entryPoint = `return require("${formatPath(entryModule)}")\n`; @@ -48,7 +51,7 @@ export function bundleTranspiledFiles( return [ diagnostics, { - fileName: resolveAbsolutePath(program, bundleFile), + fileName: path.join(getProjectRootDir(program), bundleFile), lua: code, sourceMap: map.toString(), sourceMapNode: moduleTable, @@ -82,10 +85,3 @@ function joinSourceChunks(chunks: SourceChunk[]): SourceNode { // tslint:disable-next-line:no-null-keyword return new SourceNode(null, null, null, chunks); } - -const getProjectRootDir = (program: Program) => program.getCommonSourceDirectory(); - -const resolveAbsolutePath = (program: Program, pathToResolve: string) => - path.isAbsolute(pathToResolve) - ? pathToResolve - : path.normalize(path.join(getProjectRootDir(program), pathToResolve)); diff --git a/src/cli/parse.ts b/src/cli/parse.ts index f290c216b..40f564268 100644 --- a/src/cli/parse.ts +++ b/src/cli/parse.ts @@ -178,22 +178,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: cliDiagnostics.compilerOptionRequiresAValueOfType(option.name, "boolean"), - }; - } - - return { value }; - } - - case "string": { - if (typeof value !== "string") { - return { - value: undefined, - error: cliDiagnostics.compilerOptionRequiresAValueOfType(option.name, "string"), + error: cliDiagnostics.compilerOptionRequiresAValueOfType(option.name, option.type), }; } diff --git a/src/index.ts b/src/index.ts index f9a514fb6..427fddb73 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,7 +26,7 @@ export interface TranspileFilesResult { export function transpileFiles(rootNames: string[], options: CompilerOptions = {}): TranspileFilesResult { const program = ts.createProgram(rootNames, options); const { transpiledFiles, diagnostics: transpileDiagnostics } = transpile({ program }); - const emitResult = emitTranspiledFiles(program.getCompilerOptions(), transpiledFiles); + const emitResult = emitTranspiledFiles(program, program.getCompilerOptions(), transpiledFiles); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), diff --git a/src/resolve.ts b/src/resolve.ts new file mode 100644 index 000000000..0cd66915a --- /dev/null +++ b/src/resolve.ts @@ -0,0 +1,15 @@ +import * as path from "path"; +import * as ts from "typescript"; +import { normalizeSlashes } from "./utils"; + +export function getProjectRootDir(program: ts.Program): string { + const options = program.getCompilerOptions(); + const projectDir = options.configFilePath || program.getCommonSourceDirectory(); + + return normalizeSlashes(options.rootDir ? path.resolve(projectDir, options.rootDir) : projectDir); +} + +export const resolveFromRootDir = (program: ts.Program, pathToResolve: string) => + path.isAbsolute(pathToResolve) + ? normalizeSlashes(pathToResolve) + : normalizeSlashes(path.resolve(getProjectRootDir(program), pathToResolve)); diff --git a/src/tstl.ts b/src/tstl.ts index e3c416f6e..e0055fe99 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -111,7 +111,7 @@ function performCompilation( ...transpileDiagnostics, ]); - const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); + const emitResult = tstl.emitTranspiledFiles(program, options, transpiledFiles); emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); diagnostics.forEach(reportDiagnostic); @@ -182,7 +182,7 @@ function updateWatchCompilationHost( const { diagnostics: emitDiagnostics, transpiledFiles } = tstl.transpile({ program, sourceFiles }); - const emitResult = tstl.emitTranspiledFiles(options, transpiledFiles); + const emitResult = tstl.emitTranspiledFiles(program, options, transpiledFiles); emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); const diagnostics = ts.sortAndDeduplicateDiagnostics([ diff --git a/src/typescript-internal.ts b/src/typescript-internal.ts index c94c9b41b..3a8013d61 100644 --- a/src/typescript-internal.ts +++ b/src/typescript-internal.ts @@ -15,4 +15,8 @@ declare module "typescript" { interface Program { getCommonSourceDirectory(): string; } + + interface CompilerOptions { + configFilePath?: string; + } } diff --git a/test/cli/parse.spec.ts b/test/cli/parse.spec.ts index 579039437..fc131446e 100644 --- a/test/cli/parse.spec.ts +++ b/test/cli/parse.spec.ts @@ -118,7 +118,7 @@ describe("command line", () => { ["luaBundle", "foo", { luaBundle: "foo" }], ["luaBundleEntry", "bar", { luaBundleEntry: "bar" }], - ])("--%s %s", (optionName, value, expected) => { + ])("{ %p: %p }", (optionName, value, expected) => { const result = tstl.parseCommandLine([`--${optionName}`, value]); expect(result.errors).not.toHaveErrorDiagnostics(); @@ -226,7 +226,7 @@ describe("tsconfig", () => { ["luaBundle", "foo", { luaBundle: "foo" }], ["luaBundleEntry", "bar", { luaBundleEntry: "bar" }], - ])("--%s %s", (optionName, value, expected) => { + ])("{ %p: %p }", (optionName, value, expected) => { const result = parseConfigFileContent({ tstl: { [optionName]: value } }); expect(result.errors).not.toHaveErrorDiagnostics(); diff --git a/test/transpile/__snapshots__/directories.spec.ts.snap b/test/transpile/__snapshots__/directories.spec.ts.snap index 952fd709b..901d885f4 100644 --- a/test/transpile/__snapshots__/directories.spec.ts.snap +++ b/test/transpile/__snapshots__/directories.spec.ts.snap @@ -10,17 +10,17 @@ Array [ exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 1`] = ` Array [ - "directories/basic/lualib_bundle.lua", "directories/basic/src/lib/file.lua", + "directories/basic/src/lualib_bundle.lua", "directories/basic/src/main.lua", ] `; exports[`should be able to resolve ({"name": "basic", "options": [Object]}) 2`] = ` Array [ + "directories/basic/out/lib/file.lua", "directories/basic/out/lualib_bundle.lua", - "directories/basic/out/src/lib/file.lua", - "directories/basic/out/src/main.lua", + "directories/basic/out/main.lua", ] `; diff --git a/test/transpile/basic/tsconfig.json b/test/transpile/basic/tsconfig.json index 1fa9a8ae8..8fb9d9e90 100644 --- a/test/transpile/basic/tsconfig.json +++ b/test/transpile/basic/tsconfig.json @@ -3,8 +3,8 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "target": "es2017", - "lib": ["es2017"], + "target": "esnext", + "lib": ["esnext"], "types": [], "rootDir": "." } diff --git a/test/transpile/bundle/tsconfig.json b/test/transpile/bundle/tsconfig.json index 9422a5bd1..71060927d 100644 --- a/test/transpile/bundle/tsconfig.json +++ b/test/transpile/bundle/tsconfig.json @@ -3,8 +3,8 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "target": "es2017", - "lib": ["es2017"], + "target": "esnext", + "lib": ["esnext"], "types": [], "rootDir": "." }, diff --git a/test/unit/bundle.spec.ts b/test/unit/bundle.spec.ts index 4bc3bd402..911dfe294 100644 --- a/test/unit/bundle.spec.ts +++ b/test/unit/bundle.spec.ts @@ -123,7 +123,7 @@ test("cyclic imports", () => { export const lazyValue = () => a.a; ` ) - .expectExecutionError("stack overflow"); + .expectToEqual(new util.ExecutionError("stack overflow")); }); test("luaEntry doesn't exist", () => { diff --git a/test/util.ts b/test/util.ts index baa6549b9..1000c2c53 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,11 +1,11 @@ import { lauxlib, lua, lualib, to_jsstring, to_luastring } from "fengari"; import * as fs from "fs"; +import { stringify } from "javascript-stringify"; import * as path from "path"; import * as prettyFormat from "pretty-format"; import * as ts from "typescript"; import * as vm from "vm"; import * as tstl from "../src"; -import { stringify } from "javascript-stringify"; export * from "./legacy-utils"; @@ -262,8 +262,7 @@ export abstract class TestBuilder { @memoize public getLuaExecutionResult(): any { - const code = this.getLuaCodeWithWrapper(); - return executeLua(code); + return executeLua(this.getLuaCodeWithWrapper()); } @memoize @@ -369,7 +368,7 @@ export abstract class TestBuilder { return this; } - public expectExecutionError(message: string): this { + /*public expectExecutionError(message: string): this { const luaResult = this.getLuaExecutionResult(); if (!(luaResult instanceof ExecutionError)) { expect(luaResult).toBeInstanceOf(ExecutionError); @@ -377,7 +376,7 @@ export abstract class TestBuilder { expect(luaResult.message).toContain(message); } return this; - } + }*/ public expectNoExecutionError(): this { const luaResult = this.getLuaExecutionResult(); From 34a08f3da39bb07fc67014285852c43642a98885 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 24 Nov 2019 17:54:37 +0100 Subject: [PATCH 10/18] Fixed bug with getProjectDir --- src/resolve.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/resolve.ts b/src/resolve.ts index 0cd66915a..f5e7236a5 100644 --- a/src/resolve.ts +++ b/src/resolve.ts @@ -4,7 +4,9 @@ import { normalizeSlashes } from "./utils"; export function getProjectRootDir(program: ts.Program): string { const options = program.getCompilerOptions(); - const projectDir = options.configFilePath || program.getCommonSourceDirectory(); + const projectDir = options.configFilePath + ? path.dirname(options.configFilePath) + : program.getCommonSourceDirectory(); return normalizeSlashes(options.rootDir ? path.resolve(projectDir, options.rootDir) : projectDir); } From 0942443f1fed9a259059ff8e57e40f4abf966de9 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 24 Nov 2019 20:07:47 +0100 Subject: [PATCH 11/18] Use getCommonSourcesDir in all cases --- src/Emit.ts | 9 ++++----- src/bundle.ts | 6 +++--- src/index.ts | 2 +- src/resolve.ts | 11 +---------- src/tstl.ts | 4 ++-- src/typescript-internal.ts | 4 ---- test/cli/parse.spec.ts | 2 +- 7 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/Emit.ts b/src/Emit.ts index 542379345..9119b7dc3 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -1,9 +1,8 @@ import * as path from "path"; import * as ts from "typescript"; -import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; +import { LuaLibImportKind } from "./CompilerOptions"; import { EmitHost, TranspiledFile } from "./Transpile"; import { normalizeSlashes, trimExtension } from "./utils"; -import { getProjectRootDir } from "./resolve"; export interface OutputFile { name: string; @@ -13,14 +12,14 @@ export interface OutputFile { let lualibContent: string; export function emitTranspiledFiles( program: ts.Program, - options: CompilerOptions, transpiledFiles: TranspiledFile[], emitHost: EmitHost = ts.sys ): OutputFile[] { + const options = program.getCompilerOptions(); let { outDir, luaLibImport } = options; - const rootDir = getProjectRootDir(program); - outDir = outDir ? path.resolve(rootDir, outDir) : rootDir; + const rootDir = program.getCommonSourceDirectory(); + outDir = outDir || rootDir; const files: OutputFile[] = []; for (const { fileName, lua, sourceMap, declaration, declarationMap } of transpiledFiles) { diff --git a/src/bundle.ts b/src/bundle.ts index a0dc91314..0d461f322 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -2,7 +2,7 @@ import * as path from "path"; import { SourceNode } from "source-map"; import * as ts from "typescript"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; -import { getProjectRootDir, resolveFromRootDir } from "./resolve"; +import { resolveFromRootDir } from "./resolve"; import { EmitHost, TranspiledFile } from "./Transpile"; import { formatPathToLuaPath, trimExtension } from "./utils"; @@ -23,7 +23,7 @@ export function bundleTranspiledFiles( } // For each file: [""] = function() end, - const projectRootDir = getProjectRootDir(program); + const projectRootDir = program.getCommonSourceDirectory(); const moduleTableEntries: SourceChunk[] = transpiledFiles.map(f => moduleSourceNode(f, projectRootDir)); // If any of the modules contains a require for lualib_bundle, add it to the module table. @@ -51,7 +51,7 @@ export function bundleTranspiledFiles( return [ diagnostics, { - fileName: path.join(getProjectRootDir(program), bundleFile), + fileName: path.join(program.getCommonSourceDirectory(), bundleFile), lua: code, sourceMap: map.toString(), sourceMapNode: moduleTable, diff --git a/src/index.ts b/src/index.ts index 427fddb73..fc686df20 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,7 +26,7 @@ export interface TranspileFilesResult { export function transpileFiles(rootNames: string[], options: CompilerOptions = {}): TranspileFilesResult { const program = ts.createProgram(rootNames, options); const { transpiledFiles, diagnostics: transpileDiagnostics } = transpile({ program }); - const emitResult = emitTranspiledFiles(program, program.getCompilerOptions(), transpiledFiles); + const emitResult = emitTranspiledFiles(program, transpiledFiles); const diagnostics = ts.sortAndDeduplicateDiagnostics([ ...ts.getPreEmitDiagnostics(program), diff --git a/src/resolve.ts b/src/resolve.ts index f5e7236a5..067c6fb58 100644 --- a/src/resolve.ts +++ b/src/resolve.ts @@ -2,16 +2,7 @@ import * as path from "path"; import * as ts from "typescript"; import { normalizeSlashes } from "./utils"; -export function getProjectRootDir(program: ts.Program): string { - const options = program.getCompilerOptions(); - const projectDir = options.configFilePath - ? path.dirname(options.configFilePath) - : program.getCommonSourceDirectory(); - - return normalizeSlashes(options.rootDir ? path.resolve(projectDir, options.rootDir) : projectDir); -} - export const resolveFromRootDir = (program: ts.Program, pathToResolve: string) => path.isAbsolute(pathToResolve) ? normalizeSlashes(pathToResolve) - : normalizeSlashes(path.resolve(getProjectRootDir(program), pathToResolve)); + : normalizeSlashes(path.resolve(program.getCommonSourceDirectory(), pathToResolve)); diff --git a/src/tstl.ts b/src/tstl.ts index e0055fe99..e37073442 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -111,7 +111,7 @@ function performCompilation( ...transpileDiagnostics, ]); - const emitResult = tstl.emitTranspiledFiles(program, options, transpiledFiles); + const emitResult = tstl.emitTranspiledFiles(program, transpiledFiles); emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); diagnostics.forEach(reportDiagnostic); @@ -182,7 +182,7 @@ function updateWatchCompilationHost( const { diagnostics: emitDiagnostics, transpiledFiles } = tstl.transpile({ program, sourceFiles }); - const emitResult = tstl.emitTranspiledFiles(program, options, transpiledFiles); + const emitResult = tstl.emitTranspiledFiles(program, transpiledFiles); emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text)); const diagnostics = ts.sortAndDeduplicateDiagnostics([ diff --git a/src/typescript-internal.ts b/src/typescript-internal.ts index 3a8013d61..c94c9b41b 100644 --- a/src/typescript-internal.ts +++ b/src/typescript-internal.ts @@ -15,8 +15,4 @@ declare module "typescript" { interface Program { getCommonSourceDirectory(): string; } - - interface CompilerOptions { - configFilePath?: string; - } } diff --git a/test/cli/parse.spec.ts b/test/cli/parse.spec.ts index fc131446e..70e169114 100644 --- a/test/cli/parse.spec.ts +++ b/test/cli/parse.spec.ts @@ -118,7 +118,7 @@ describe("command line", () => { ["luaBundle", "foo", { luaBundle: "foo" }], ["luaBundleEntry", "bar", { luaBundleEntry: "bar" }], - ])("{ %p: %p }", (optionName, value, expected) => { + ])("--%s %s", (optionName, value, expected) => { const result = tstl.parseCommandLine([`--${optionName}`, value]); expect(result.errors).not.toHaveErrorDiagnostics(); From 54dc975dbb0cd7691a356f386dee559e11f3ddb0 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Mon, 25 Nov 2019 22:25:57 +0100 Subject: [PATCH 12/18] Changed path resolution again, using confFilePath again --- src/Transpile.ts | 1 + src/bundle.ts | 60 ++++++++++++++++++++++++++------------ src/resolve.ts | 8 ----- src/typescript-internal.ts | 4 +++ test/unit/bundle.spec.ts | 10 ++++--- 5 files changed, 52 insertions(+), 31 deletions(-) delete mode 100644 src/resolve.ts diff --git a/src/Transpile.ts b/src/Transpile.ts index 16f2dcbac..01d8df15a 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -35,6 +35,7 @@ export interface TranspileOptions { } export interface EmitHost { + getCurrentDirectory(): string; readFile(path: string): string | undefined; } diff --git a/src/bundle.ts b/src/bundle.ts index 0d461f322..ad8005744 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -2,11 +2,13 @@ import * as path from "path"; import { SourceNode } from "source-map"; import * as ts from "typescript"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; -import { resolveFromRootDir } from "./resolve"; import { EmitHost, TranspiledFile } from "./Transpile"; import { formatPathToLuaPath, trimExtension } from "./utils"; +import { escapeString } from "./TSHelper"; +import { CompilerOptions } from "./CompilerOptions"; -const formatPath = (path: string) => formatPathToLuaPath(trimExtension(path)); +const formatPath = (path: string) => escapeString(formatPathToLuaPath(trimExtension(path))); +const modulePath = (baseDir: string, pathToResolve: string) => formatPath(path.relative(baseDir, pathToResolve)); export function bundleTranspiledFiles( bundleFile: string, @@ -17,17 +19,29 @@ export function bundleTranspiledFiles( ): [ts.Diagnostic[], TranspiledFile] { const diagnostics: ts.Diagnostic[] = []; - const resolvedEntryModule = resolveFromRootDir(program, entryModule); - if (!transpiledFiles.some(f => resolveFromRootDir(program, f.fileName) === resolvedEntryModule)) { + const options = program.getCompilerOptions() as CompilerOptions; + + const projectRootDir = options.configFilePath + ? path.dirname(options.configFilePath) + : emitHost.getCurrentDirectory(); + + // Resolve project settings relative to project file. + const resolvedEntryModule = path.resolve(projectRootDir, entryModule); + const resolvedBundleFile = path.resolve(projectRootDir, bundleFile); + + // Resolve source files relative to common source directory. + const sourceRootDir = program.getCommonSourceDirectory(); + if (!transpiledFiles.some(f => path.resolve(sourceRootDir, f.fileName) === resolvedEntryModule)) { return [[couldNotFindBundleEntryPoint(entryModule)], { fileName: bundleFile }]; } - // For each file: [""] = function() end, - const projectRootDir = program.getCommonSourceDirectory(); - const moduleTableEntries: SourceChunk[] = transpiledFiles.map(f => moduleSourceNode(f, projectRootDir)); + // For each file: [""] = function() end, + const moduleTableEntries: SourceChunk[] = transpiledFiles.map(f => + moduleSourceNode(f, modulePath(sourceRootDir, f.fileName)) + ); // If any of the modules contains a require for lualib_bundle, add it to the module table. - const lualibRequired = transpiledFiles.some(f => f.lua && f.lua.match(/require\("lualib_bundle"\)/)); + const lualibRequired = transpiledFiles.some(f => f.lua && f.lua.includes(`require("lualib_bundle")`)); if (lualibRequired) { const lualibBundle = emitHost.readFile(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua")); moduleTableEntries.push(`["lualib_bundle"] = function() ${lualibBundle} end,\n`); @@ -37,13 +51,22 @@ export function bundleTranspiledFiles( const moduleTable = createModuleTableNode(moduleTableEntries); // Override `require` to read from ____modules table. - const requireOverride = - `local ____moduleCache = {}\n` + - `local ____originalRequire = require\n` + - `function require(file) if ____moduleCache[file] then return ____moduleCache[file] end\n` + - `if ____modules[file] then ____moduleCache[file] = ____modules[file](); return ____moduleCache[file] ` + - `else print("Could not find module '"..file.."' to require."); return ____originalRequire(file) end end\n`; - const entryPoint = `return require("${formatPath(entryModule)}")\n`; + const requireOverride = ` +local ____moduleCache = {} +local ____originalRequire = require +function require(file) + if ____moduleCache[file] then return ____moduleCache[file] end + if ____modules[file] then + ____moduleCache[file] = ____modules[file]() + return ____moduleCache[file] + else + print("Could not find module '"..file.."' to require.") + return ____originalRequire(file) + end +end\n`; + + // return require("") + const entryPoint = `return require("${modulePath(sourceRootDir, resolvedEntryModule)}")\n`; const bundleNode = joinSourceChunks([moduleTable, requireOverride, entryPoint]); const { code, map } = bundleNode.toStringWithSourceMap(); @@ -51,7 +74,7 @@ export function bundleTranspiledFiles( return [ diagnostics, { - fileName: path.join(program.getCommonSourceDirectory(), bundleFile), + fileName: resolvedBundleFile, lua: code, sourceMap: map.toString(), sourceMapNode: moduleTable, @@ -61,9 +84,8 @@ export function bundleTranspiledFiles( ]; } -function moduleSourceNode(transpiledFile: TranspiledFile, projectRootDir: string): SourceNode { - const resolvedProjectPath = path.relative(projectRootDir, transpiledFile.fileName); - const tableEntryHead = `["${formatPath(resolvedProjectPath)}"] = function() `; +function moduleSourceNode(transpiledFile: TranspiledFile, modulePath: string): SourceNode { + const tableEntryHead = `["${modulePath}"] = function() `; const tableEntryTail = `end,\n`; if (transpiledFile.lua && transpiledFile.sourceMapNode) { diff --git a/src/resolve.ts b/src/resolve.ts deleted file mode 100644 index 067c6fb58..000000000 --- a/src/resolve.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as path from "path"; -import * as ts from "typescript"; -import { normalizeSlashes } from "./utils"; - -export const resolveFromRootDir = (program: ts.Program, pathToResolve: string) => - path.isAbsolute(pathToResolve) - ? normalizeSlashes(pathToResolve) - : normalizeSlashes(path.resolve(program.getCommonSourceDirectory(), pathToResolve)); diff --git a/src/typescript-internal.ts b/src/typescript-internal.ts index c94c9b41b..3a8013d61 100644 --- a/src/typescript-internal.ts +++ b/src/typescript-internal.ts @@ -15,4 +15,8 @@ declare module "typescript" { interface Program { getCommonSourceDirectory(): string; } + + interface CompilerOptions { + configFilePath?: string; + } } diff --git a/test/unit/bundle.spec.ts b/test/unit/bundle.spec.ts index 911dfe294..d69c18296 100644 --- a/test/unit/bundle.spec.ts +++ b/test/unit/bundle.spec.ts @@ -1,7 +1,9 @@ -import * as util from "../util"; -import { couldNotFindBundleEntryPoint } from "../../src/diagnostics"; -import { LuaLibImportKind } from "../../src"; +import * as path from "path"; +import * as ts from "typescript"; import { DiagnosticCategory } from "typescript"; +import { LuaLibImportKind } from "../../src"; +import { couldNotFindBundleEntryPoint } from "../../src/diagnostics"; +import * as util from "../util"; test("no entry point", () => { util.testBundle`` @@ -31,7 +33,7 @@ test("bundle file name", () => { expect(diagnostics.length).toBe(0); expect(transpiledFiles.length).toBe(1); - expect(transpiledFiles[0].fileName).toBe("mybundle.lua"); + expect(transpiledFiles[0].fileName).toBe(path.join(ts.sys.getCurrentDirectory(), "mybundle.lua")); }); test("import chain export -> reexport -> main", () => { From 89f8d72015f128adee1cca4689380db87a6da1fe Mon Sep 17 00:00:00 2001 From: Perryvw Date: Tue, 26 Nov 2019 22:27:21 +0100 Subject: [PATCH 13/18] Renamed transpile/basic test to transpile/project, normalized output path of bundle --- src/bundle.ts | 25 +++++++++++-------- ...asic.spec.ts.snap => project.spec.ts.snap} | 0 test/transpile/bundle.spec.ts | 2 +- .../{basic.spec.ts => project.spec.ts} | 2 +- test/transpile/{basic => project}/api.d.ts | 0 test/transpile/{basic => project}/index.ts | 0 .../transpile/{basic => project}/otherFile.ts | 0 .../{basic => project}/tsconfig.json | 0 test/unit/bundle.spec.ts | 4 ++- 9 files changed, 19 insertions(+), 14 deletions(-) rename test/transpile/__snapshots__/{basic.spec.ts.snap => project.spec.ts.snap} (100%) rename test/transpile/{basic.spec.ts => project.spec.ts} (91%) rename test/transpile/{basic => project}/api.d.ts (100%) rename test/transpile/{basic => project}/index.ts (100%) rename test/transpile/{basic => project}/otherFile.ts (100%) rename test/transpile/{basic => project}/tsconfig.json (100%) diff --git a/src/bundle.ts b/src/bundle.ts index ad8005744..ee33f22f0 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -3,12 +3,12 @@ import { SourceNode } from "source-map"; import * as ts from "typescript"; import { couldNotFindBundleEntryPoint } from "./diagnostics"; import { EmitHost, TranspiledFile } from "./Transpile"; -import { formatPathToLuaPath, trimExtension } from "./utils"; +import { formatPathToLuaPath, trimExtension, normalizeSlashes } from "./utils"; import { escapeString } from "./TSHelper"; import { CompilerOptions } from "./CompilerOptions"; -const formatPath = (path: string) => escapeString(formatPathToLuaPath(trimExtension(path))); -const modulePath = (baseDir: string, pathToResolve: string) => formatPath(path.relative(baseDir, pathToResolve)); +const createModulePath = (baseDir: string, pathToResolve: string) => + escapeString(formatPathToLuaPath(trimExtension(path.relative(baseDir, pathToResolve)))); export function bundleTranspiledFiles( bundleFile: string, @@ -37,7 +37,7 @@ export function bundleTranspiledFiles( // For each file: [""] = function() end, const moduleTableEntries: SourceChunk[] = transpiledFiles.map(f => - moduleSourceNode(f, modulePath(sourceRootDir, f.fileName)) + moduleSourceNode(f, createModulePath(sourceRootDir, f.fileName)) ); // If any of the modules contains a require for lualib_bundle, add it to the module table. @@ -55,18 +55,23 @@ export function bundleTranspiledFiles( local ____moduleCache = {} local ____originalRequire = require function require(file) - if ____moduleCache[file] then return ____moduleCache[file] end + if ____moduleCache[file] then + return ____moduleCache[file] + end if ____modules[file] then ____moduleCache[file] = ____modules[file]() return ____moduleCache[file] else - print("Could not find module '"..file.."' to require.") - return ____originalRequire(file) + if ____originalRequire then + return ____originalRequire(file) + else + print("Could not find module '" .. file .. "' to require.") + end end end\n`; // return require("") - const entryPoint = `return require("${modulePath(sourceRootDir, resolvedEntryModule)}")\n`; + const entryPoint = `return require("${createModulePath(sourceRootDir, resolvedEntryModule)}")\n`; const bundleNode = joinSourceChunks([moduleTable, requireOverride, entryPoint]); const { code, map } = bundleNode.toStringWithSourceMap(); @@ -74,12 +79,10 @@ end\n`; return [ diagnostics, { - fileName: resolvedBundleFile, + fileName: normalizeSlashes(resolvedBundleFile), lua: code, sourceMap: map.toString(), sourceMapNode: moduleTable, - declaration: undefined, - declarationMap: undefined, }, ]; } diff --git a/test/transpile/__snapshots__/basic.spec.ts.snap b/test/transpile/__snapshots__/project.spec.ts.snap similarity index 100% rename from test/transpile/__snapshots__/basic.spec.ts.snap rename to test/transpile/__snapshots__/project.spec.ts.snap diff --git a/test/transpile/bundle.spec.ts b/test/transpile/bundle.spec.ts index e3999211f..d13150fa3 100644 --- a/test/transpile/bundle.spec.ts +++ b/test/transpile/bundle.spec.ts @@ -13,7 +13,7 @@ test("should transpile into one file", () => { const { name, text } = transpileResult.emitResult[0]; // Verify the name is as specified in tsconfig - expect(path.join(name)).toBe(path.join(projectDir, "bundle.lua")); + expect(name).toBe(path.join(projectDir, "bundle.lua").replace(/\\/g, "/")); // Verify exported module by executing expect(util.executeLuaModule(text)).toEqual({ myNumber: 3 }); }); diff --git a/test/transpile/basic.spec.ts b/test/transpile/project.spec.ts similarity index 91% rename from test/transpile/basic.spec.ts rename to test/transpile/project.spec.ts index e1efd1b34..ceaf3fd22 100644 --- a/test/transpile/basic.spec.ts +++ b/test/transpile/project.spec.ts @@ -1,7 +1,7 @@ import * as path from "path"; import { transpileProject } from "../../src"; -const projectDir = path.join(__dirname, "basic"); +const projectDir = path.join(__dirname, "project"); const inputProject = path.join(projectDir, "tsconfig.json"); test("should transpile", () => { diff --git a/test/transpile/basic/api.d.ts b/test/transpile/project/api.d.ts similarity index 100% rename from test/transpile/basic/api.d.ts rename to test/transpile/project/api.d.ts diff --git a/test/transpile/basic/index.ts b/test/transpile/project/index.ts similarity index 100% rename from test/transpile/basic/index.ts rename to test/transpile/project/index.ts diff --git a/test/transpile/basic/otherFile.ts b/test/transpile/project/otherFile.ts similarity index 100% rename from test/transpile/basic/otherFile.ts rename to test/transpile/project/otherFile.ts diff --git a/test/transpile/basic/tsconfig.json b/test/transpile/project/tsconfig.json similarity index 100% rename from test/transpile/basic/tsconfig.json rename to test/transpile/project/tsconfig.json diff --git a/test/unit/bundle.spec.ts b/test/unit/bundle.spec.ts index d69c18296..883b58320 100644 --- a/test/unit/bundle.spec.ts +++ b/test/unit/bundle.spec.ts @@ -33,7 +33,9 @@ test("bundle file name", () => { expect(diagnostics.length).toBe(0); expect(transpiledFiles.length).toBe(1); - expect(transpiledFiles[0].fileName).toBe(path.join(ts.sys.getCurrentDirectory(), "mybundle.lua")); + expect(transpiledFiles[0].fileName).toBe( + path.join(ts.sys.getCurrentDirectory(), "mybundle.lua").replace(/\\/g, "/") + ); }); test("import chain export -> reexport -> main", () => { From 12fcf2d572ad3ab010790f6450754a934939b298 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Tue, 26 Nov 2019 22:52:50 +0100 Subject: [PATCH 14/18] Reverted toHaveDiagnostics change --- test/cli/parse.spec.ts | 42 ++++++++++----------- test/cli/tsconfig.spec.ts | 6 +-- test/legacy-utils.ts | 2 +- test/setup.ts | 6 +-- test/transpile/bundle.spec.ts | 2 +- test/transpile/directories.spec.ts | 2 +- test/transpile/project.spec.ts | 2 +- test/unit/bundle.spec.ts | 22 +++++++---- test/unit/transformers/transformers.spec.ts | 2 +- test/util.ts | 4 +- 10 files changed, 48 insertions(+), 42 deletions(-) diff --git a/test/cli/parse.spec.ts b/test/cli/parse.spec.ts index 70e169114..ae95751e9 100644 --- a/test/cli/parse.spec.ts +++ b/test/cli/parse.spec.ts @@ -12,7 +12,7 @@ describe("command line", () => { const commandLine = "--project tsconfig.json --noHeader -t es3 -lt 5.3"; const result = tstl.parseCommandLine(commandLine.split(" ")); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options).toEqual({ project: "tsconfig.json", noHeader: true, @@ -24,13 +24,13 @@ describe("command line", () => { test("should error on unknown options", () => { const result = tstl.parseCommandLine(["--unknownOption"]); - expect(result.errors).toHaveErrorDiagnostics(); + expect(result.errors).toHaveDiagnostics(); }); test("should parse options case-insensitively", () => { const result = tstl.parseCommandLine(["--NoHeader"]); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); }); @@ -38,7 +38,7 @@ describe("command line", () => { test("should parse enums", () => { const result = tstl.parseCommandLine(["--luaTarget", "5.1"]); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua51); }); @@ -46,7 +46,7 @@ describe("command line", () => { for (const value of ["jit", "JiT", "JIT"]) { const result = tstl.parseCommandLine(["--luaTarget", value]); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.LuaJIT); } }); @@ -54,7 +54,7 @@ describe("command line", () => { test("should error on invalid value", () => { const result = tstl.parseCommandLine(["--luaTarget", "invalid"]); - expect(result.errors).toHaveErrorDiagnostics(); + expect(result.errors).toHaveDiagnostics(); }); }); @@ -62,14 +62,14 @@ describe("command line", () => { test.each([true, false])("should parse booleans (%p)", value => { const result = tstl.parseCommandLine(["--noHeader", value.toString()]); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(value); }); test("should be case-sensitive", () => { const result = tstl.parseCommandLine(["--noHeader", "FALSE"]); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); expect(result.fileNames).toEqual(["FALSE"]); }); @@ -77,14 +77,14 @@ describe("command line", () => { test("should be parsed without a value", () => { const result = tstl.parseCommandLine(["--noHeader"]); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); }); test("shouldn't parse following arguments as values", () => { const result = tstl.parseCommandLine(["--noHeader", "--noHoisting"]); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); expect(result.options.noHoisting).toBe(true); }); @@ -92,7 +92,7 @@ describe("command line", () => { test("shouldn't parse following files as values", () => { const result = tstl.parseCommandLine(["--noHeader", "file.ts"]); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(true); }); }); @@ -121,7 +121,7 @@ describe("command line", () => { ])("--%s %s", (optionName, value, expected) => { const result = tstl.parseCommandLine([`--${optionName}`, value]); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options).toEqual(expected); }); }); @@ -146,21 +146,21 @@ describe("tsconfig", () => { test("should allow unknown root-level options", () => { const result = parseConfigFileContent({ unknownOption: true }); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.unknownOption).toBeUndefined(); }); test("should error on unknown namespaced options", () => { const result = parseConfigFileContent({ tstl: { unknownOption: true } }); - expect(result.errors).toHaveErrorDiagnostics(); + expect(result.errors).toHaveDiagnostics(); expect(result.options.unknownOption).toBeUndefined(); }); test("should parse options case-sensitively", () => { const result = parseConfigFileContent({ tstl: { NoHeader: true } }); - expect(result.errors).toHaveErrorDiagnostics(); + expect(result.errors).toHaveDiagnostics(); expect(result.options.NoHeader).toBeUndefined(); expect(result.options.noHeader).toBeUndefined(); }); @@ -169,7 +169,7 @@ describe("tsconfig", () => { test("should parse enums", () => { const result = parseConfigFileContent({ tstl: { luaTarget: "5.1" } }); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.Lua51); }); @@ -177,7 +177,7 @@ describe("tsconfig", () => { for (const value of ["jit", "JiT", "JIT"]) { const result = parseConfigFileContent({ tstl: { luaTarget: value } }); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.luaTarget).toBe(tstl.LuaTarget.LuaJIT); } }); @@ -185,7 +185,7 @@ describe("tsconfig", () => { test("should error on invalid value", () => { const result = parseConfigFileContent({ tstl: { luaTarget: "invalid" } }); - expect(result.errors).toHaveErrorDiagnostics(); + expect(result.errors).toHaveDiagnostics(); }); }); @@ -193,14 +193,14 @@ describe("tsconfig", () => { test.each([true, false])("should parse booleans (%p)", value => { const result = parseConfigFileContent({ tstl: { noHeader: value } }); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options.noHeader).toBe(value); }); test("shouldn't parse strings", () => { const result = parseConfigFileContent({ tstl: { noHeader: "true" } }); - expect(result.errors).toHaveErrorDiagnostics(); + expect(result.errors).toHaveDiagnostics(); expect(result.options.noHeader).toBeUndefined(); }); }); @@ -229,7 +229,7 @@ describe("tsconfig", () => { ])("{ %p: %p }", (optionName, value, expected) => { const result = parseConfigFileContent({ tstl: { [optionName]: value } }); - expect(result.errors).not.toHaveErrorDiagnostics(); + expect(result.errors).not.toHaveDiagnostics(); expect(result.options).toEqual(expected); }); }); diff --git a/test/cli/tsconfig.spec.ts b/test/cli/tsconfig.spec.ts index 3d09eb8e3..b4fac5cbf 100644 --- a/test/cli/tsconfig.spec.ts +++ b/test/cli/tsconfig.spec.ts @@ -79,15 +79,15 @@ describe("inferred", () => { describe("errors", () => { test("specified file does not exist", () => { - expect([locate("tsconfig.json")]).toHaveErrorDiagnostics(); + expect([locate("tsconfig.json")]).toHaveDiagnostics(); }); test("specified directory does not exist", () => { - expect([locate("project")]).toHaveErrorDiagnostics(); + expect([locate("project")]).toHaveDiagnostics(); }); test("cannot be mixed", async () => { await fs.outputFile("tsconfig.json", ""); - expect([locate("tsconfig.json", [""])]).toHaveErrorDiagnostics(); + expect([locate("tsconfig.json", [""])]).toHaveDiagnostics(); }); }); diff --git a/test/legacy-utils.ts b/test/legacy-utils.ts index 18dde8b90..4b9815d17 100644 --- a/test/legacy-utils.ts +++ b/test/legacy-utils.ts @@ -14,7 +14,7 @@ export function transpileString( expect(file.lua).toBeDefined(); const errors = diagnostics.filter(d => !ignoreDiagnostics || d.source === "typescript-to-lua"); - expect(errors).not.toHaveErrorDiagnostics(); + expect(errors).not.toHaveDiagnostics(); return file.lua!.trim(); } diff --git a/test/setup.ts b/test/setup.ts index d3ad42a29..419eed6fb 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -5,7 +5,7 @@ declare global { namespace jest { interface Matchers { toThrowExactError(error: Error): R; - toHaveErrorDiagnostics(): R; + toHaveDiagnostics(): R; } } } @@ -30,7 +30,7 @@ expect.extend({ return { pass: true, message: () => "" }; }, - toHaveErrorDiagnostics(diagnostics: ts.Diagnostic[]): jest.CustomMatcherResult { + toHaveDiagnostics(diagnostics: ts.Diagnostic[]): jest.CustomMatcherResult { expect(diagnostics).toBeInstanceOf(Array); // @ts-ignore const matcherHint = this.utils.matcherHint("toHaveErrorDiagnostics", undefined, "", this); @@ -42,7 +42,7 @@ expect.extend({ }); return { - pass: diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length > 0, + pass: diagnostics.length > 0, message: () => { return ( matcherHint + diff --git a/test/transpile/bundle.spec.ts b/test/transpile/bundle.spec.ts index d13150fa3..a5afd90b0 100644 --- a/test/transpile/bundle.spec.ts +++ b/test/transpile/bundle.spec.ts @@ -8,7 +8,7 @@ const inputProject = path.join(projectDir, "tsconfig.json"); test("should transpile into one file", () => { const transpileResult = transpileProject(inputProject); - expect(transpileResult.diagnostics).not.toHaveErrorDiagnostics(); + expect(transpileResult.diagnostics).not.toHaveDiagnostics(); expect(transpileResult.emitResult.length).toBe(1); const { name, text } = transpileResult.emitResult[0]; diff --git a/test/transpile/directories.spec.ts b/test/transpile/directories.spec.ts index 18422dfe7..458c14fec 100644 --- a/test/transpile/directories.spec.ts +++ b/test/transpile/directories.spec.ts @@ -28,6 +28,6 @@ test.each([ ); const { diagnostics, emittedFiles } = buildVirtualProject(fileNames, options); - expect(diagnostics).not.toHaveErrorDiagnostics(); + expect(diagnostics).not.toHaveDiagnostics(); expect(emittedFiles).toMatchSnapshot(); }); diff --git a/test/transpile/project.spec.ts b/test/transpile/project.spec.ts index ceaf3fd22..a8afc4229 100644 --- a/test/transpile/project.spec.ts +++ b/test/transpile/project.spec.ts @@ -7,7 +7,7 @@ const inputProject = path.join(projectDir, "tsconfig.json"); test("should transpile", () => { const transpileResult = transpileProject(inputProject); - expect(transpileResult.diagnostics).not.toHaveErrorDiagnostics(); + expect(transpileResult.diagnostics).not.toHaveDiagnostics(); // Check output paths relative to projectDir const relativeResult = transpileResult.emitResult.map(({ name, text }) => ({ diff --git a/test/unit/bundle.spec.ts b/test/unit/bundle.spec.ts index 883b58320..7bca27ae5 100644 --- a/test/unit/bundle.spec.ts +++ b/test/unit/bundle.spec.ts @@ -87,17 +87,22 @@ test("entry point in directory", () => { .expectToEqual({ value: true }); }); -test.each([LuaLibImportKind.Inline, LuaLibImportKind.Require])("LuaLibs", lualibOption => { - util.testBundle` +test.each([LuaLibImportKind.Inline, LuaLibImportKind.Require])("LuaLib %p", lualibOption => { + const testBundle = util.testBundle` export const result = [1, 2]; result.push(3); - ` - .setOptions({ luaLibImport: lualibOption }) - .expectToEqual({ result: [1, 2, 3] }); + `.setOptions({ luaLibImport: lualibOption }); + + if (lualibOption === LuaLibImportKind.Inline) { + testBundle.expectToHaveDiagnostic(d => d.category === DiagnosticCategory.Warning); + } else { + expect(testBundle.getLuaResult().diagnostics).toEqual([]); + } + expect(testBundle.getLuaExecutionResult()).toEqual({ result: [1, 2, 3] }); }); test("LuaBundle and LuaLibImport.Inline generate warning", () => { - util.testBundle` + const testBundle = util.testBundle` export const result = [1, 2]; result.push(3); ` @@ -108,8 +113,9 @@ test("LuaBundle and LuaLibImport.Inline generate warning", () => { d.messageText === `Using 'luaBundle' with 'luaLibImport: "inline"' might generate duplicate code. ` + `It is recommended to use 'luaLibImport: "require"'` - ) - .expectToEqual({ result: [1, 2, 3] }); // Result should still be the same + ); + + expect(testBundle.getLuaExecutionResult()).toEqual({ result: [1, 2, 3] }); // Result should still be the same }); test("cyclic imports", () => { diff --git a/test/unit/transformers/transformers.spec.ts b/test/unit/transformers/transformers.spec.ts index 237950376..db36c27dc 100644 --- a/test/unit/transformers/transformers.spec.ts +++ b/test/unit/transformers/transformers.spec.ts @@ -44,7 +44,7 @@ describe("resolution", () => { const transform = path.join(__dirname, "error.ts"); const options = optionsOfTransformer({ transform }); const { diagnostics } = util.transpileStringResult("", options); - expect(diagnostics).toHaveErrorDiagnostics(); + expect(diagnostics).toHaveDiagnostics(); }); }); diff --git a/test/util.ts b/test/util.ts index 1000c2c53..4915788cb 100644 --- a/test/util.ts +++ b/test/util.ts @@ -351,7 +351,7 @@ export abstract class TestBuilder { } public expectToHaveErrorDiagnostics(): this { - expect(this.getLuaDiagnostics()).toHaveErrorDiagnostics(); + expect(this.getLuaDiagnostics()).toHaveDiagnostics(); return this; } @@ -364,7 +364,7 @@ export abstract class TestBuilder { } public expectToHaveNoErrorDiagnostics(): this { - expect(this.getLuaDiagnostics()).not.toHaveErrorDiagnostics(); + expect(this.getLuaDiagnostics()).not.toHaveDiagnostics(); return this; } From 8da608dd0cab1e469272b436c3a9cd1ee5e597e6 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Wed, 27 Nov 2019 20:32:32 +0100 Subject: [PATCH 15/18] Reverted some more forgotten stuff --- src/bundle.ts | 2 +- .../functions/noImplicitSelfOption.spec.ts | 8 ++++---- .../functionExpressionTypeInference.spec.ts | 2 +- test/unit/printer/sourcemaps.spec.ts | 8 ++++---- test/util.ts | 20 +++++-------------- 5 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/bundle.ts b/src/bundle.ts index ee33f22f0..20476b43b 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -65,7 +65,7 @@ function require(file) if ____originalRequire then return ____originalRequire(file) else - print("Could not find module '" .. file .. "' to require.") + error("module '" .. file .. "' not found") end end end\n`; diff --git a/test/unit/functions/noImplicitSelfOption.spec.ts b/test/unit/functions/noImplicitSelfOption.spec.ts index 5ecf2c39c..906d80a65 100644 --- a/test/unit/functions/noImplicitSelfOption.spec.ts +++ b/test/unit/functions/noImplicitSelfOption.spec.ts @@ -6,7 +6,7 @@ test("enables noSelfInFile behaviour for functions", () => { const test: (this: void) => void = fooBar; ` .setOptions({ noImplicitSelf: true }) - .expectToHaveNoErrorDiagnostics(); + .expectToHaveNoDiagnostics(); }); test("enables noSelfInFile behaviour for methods", () => { @@ -18,7 +18,7 @@ test("enables noSelfInFile behaviour for methods", () => { const test: (this: any) => void = fooBar.fooBar; ` .setOptions({ noImplicitSelf: true }) - .expectToHaveNoErrorDiagnostics(); + .expectToHaveNoDiagnostics(); }); test("generates declaration files with @noSelfInFile", () => { @@ -26,7 +26,7 @@ test("generates declaration files with @noSelfInFile", () => { export function bar() {} ` .setOptions({ declaration: true, noImplicitSelf: true }) - .expectToHaveNoErrorDiagnostics(); + .expectToHaveNoDiagnostics(); const declarationFile = builder.getLuaResult().transpiledFiles.find(f => f.declaration); if (!util.expectToBeDefined(declarationFile) || !util.expectToBeDefined(declarationFile.declaration)) return; @@ -36,5 +36,5 @@ test("generates declaration files with @noSelfInFile", () => { const test: (this: void) => void = bar; ` .addExtraFile("foo.d.ts", declarationFile.declaration) - .expectToHaveNoErrorDiagnostics(); + .expectToHaveNoDiagnostics(); }); diff --git a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts index 00620a655..e475f2216 100644 --- a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts +++ b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts @@ -24,7 +24,7 @@ test("noSelfInFile works when first statement has other annotations", () => { function foo() {} const test: (this: void) => void = foo; - `.expectToHaveNoErrorDiagnostics(); + `.expectToHaveNoDiagnostics(); }); test.each(["(this: void, s: string) => string", "(this: any, s: string) => string", "(s: string) => string"])( diff --git a/test/unit/printer/sourcemaps.spec.ts b/test/unit/printer/sourcemaps.spec.ts index b4f8b3128..4023dad19 100644 --- a/test/unit/printer/sourcemaps.spec.ts +++ b/test/unit/printer/sourcemaps.spec.ts @@ -152,7 +152,7 @@ test.each([ ])("Source map has correct mapping (%p)", async ({ code, assertPatterns }) => { const file = util .testModule(code) - .expectToHaveNoErrorDiagnostics() + .expectToHaveNoDiagnostics() .getMainLuaFileResult(); const consumer = await new SourceMapConsumer(file.sourceMap); @@ -169,7 +169,7 @@ test("Source map has correct sources", async () => { const file = util.testModule` const foo = "foo" ` - .expectToHaveNoErrorDiagnostics() + .expectToHaveNoDiagnostics() .getMainLuaFileResult(); const consumer = await new SourceMapConsumer(file.sourceMap); @@ -181,7 +181,7 @@ test("Source map has correct source root", async () => { const file = util.testModule` const foo = "foo" ` - .expectToHaveNoErrorDiagnostics() + .expectToHaveNoDiagnostics() .getMainLuaFileResult(); const sourceMap = JSON.parse(file.sourceMap); @@ -199,7 +199,7 @@ test.each([ ])("Source map has correct name mappings (%p)", async ({ code, name }) => { const file = util .testModule(code) - .expectToHaveNoErrorDiagnostics() + .expectToHaveNoDiagnostics() .getMainLuaFileResult(); const consumer = await new SourceMapConsumer(file.sourceMap); diff --git a/test/util.ts b/test/util.ts index 4915788cb..93f0a0b22 100644 --- a/test/util.ts +++ b/test/util.ts @@ -363,21 +363,11 @@ export abstract class TestBuilder { return this; } - public expectToHaveNoErrorDiagnostics(): this { + public expectToHaveNoDiagnostics(): this { expect(this.getLuaDiagnostics()).not.toHaveDiagnostics(); return this; } - /*public expectExecutionError(message: string): this { - const luaResult = this.getLuaExecutionResult(); - if (!(luaResult instanceof ExecutionError)) { - expect(luaResult).toBeInstanceOf(ExecutionError); - } else { - expect(luaResult.message).toContain(message); - } - return this; - }*/ - public expectNoExecutionError(): this { const luaResult = this.getLuaExecutionResult(); if (luaResult instanceof ExecutionError) { @@ -388,7 +378,7 @@ export abstract class TestBuilder { } public expectToMatchJsResult(allowErrors = false): this { - this.expectToHaveNoErrorDiagnostics(); + this.expectToHaveNoDiagnostics(); if (!allowErrors) this.expectNoExecutionError(); const luaResult = this.getLuaExecutionResult(); @@ -399,20 +389,20 @@ export abstract class TestBuilder { } public expectToEqual(expected: any): this { - this.expectToHaveNoErrorDiagnostics(); + this.expectToHaveNoDiagnostics(); const luaResult = this.getLuaExecutionResult(); expect(luaResult).toEqual(expected); return this; } public expectLuaToMatchSnapshot(): this { - this.expectToHaveNoErrorDiagnostics(); + this.expectToHaveNoDiagnostics(); expect(this.getMainLuaCodeChunk()).toMatchSnapshot(); return this; } public expectResultToMatchSnapshot(): this { - this.expectToHaveNoErrorDiagnostics(); + this.expectToHaveNoDiagnostics(); expect(this.getLuaExecutionResult()).toMatchSnapshot(); return this; } From 2308ebf9910f728a48835f727c724c7ba34e4862 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Wed, 27 Nov 2019 20:38:23 +0100 Subject: [PATCH 16/18] Final stuff to revert --- test/setup.ts | 2 +- test/unit/modules/resolution.spec.ts | 2 +- test/util.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/setup.ts b/test/setup.ts index 419eed6fb..dff900cb9 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -33,7 +33,7 @@ expect.extend({ toHaveDiagnostics(diagnostics: ts.Diagnostic[]): jest.CustomMatcherResult { expect(diagnostics).toBeInstanceOf(Array); // @ts-ignore - const matcherHint = this.utils.matcherHint("toHaveErrorDiagnostics", undefined, "", this); + const matcherHint = this.utils.matcherHint("toHaveDiagnostics", undefined, "", this); const diagnosticMessages = ts.formatDiagnosticsWithColorAndContext(diagnostics, { getCurrentDirectory: () => "", diff --git a/test/unit/modules/resolution.spec.ts b/test/unit/modules/resolution.spec.ts index 1ed523bc3..6c1dbe105 100644 --- a/test/unit/modules/resolution.spec.ts +++ b/test/unit/modules/resolution.spec.ts @@ -82,7 +82,7 @@ test("doesn't resolve paths out of root dir", () => { .setMainFileName("src/main.ts") .setOptions({ rootDir: "./src" }) .disableSemanticCheck() - .expectToHaveErrorDiagnostics(); + .expectToHaveDiagnostics(); }); test.each([ diff --git a/test/util.ts b/test/util.ts index 93f0a0b22..b1dc96934 100644 --- a/test/util.ts +++ b/test/util.ts @@ -350,13 +350,13 @@ export abstract class TestBuilder { return this; } - public expectToHaveErrorDiagnostics(): this { + public expectToHaveDiagnostics(): this { expect(this.getLuaDiagnostics()).toHaveDiagnostics(); return this; } public expectToHaveDiagnosticOfError(error: Error): this { - this.expectToHaveErrorDiagnostics(); + this.expectToHaveDiagnostics(); expect(this.getLuaDiagnostics()).toHaveLength(1); const firstDiagnostic = this.getLuaDiagnostics()[0]; expect(firstDiagnostic).toMatchObject({ messageText: error.message }); From 54924f0a620cb2ffb5f29a2ca55aa89c6a742a02 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Wed, 27 Nov 2019 23:30:15 +0100 Subject: [PATCH 17/18] Do not emit lualib bundle when bundling --- src/Emit.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Emit.ts b/src/Emit.ts index 9119b7dc3..371d65351 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -16,7 +16,7 @@ export function emitTranspiledFiles( emitHost: EmitHost = ts.sys ): OutputFile[] { const options = program.getCompilerOptions(); - let { outDir, luaLibImport } = options; + let { outDir, luaLibImport, luaBundle } = options; const rootDir = program.getCommonSourceDirectory(); outDir = outDir || rootDir; @@ -48,7 +48,7 @@ export function emitTranspiledFiles( } } - if (luaLibImport === LuaLibImportKind.Require || luaLibImport === LuaLibImportKind.Always) { + if (!luaBundle && (luaLibImport === LuaLibImportKind.Require || luaLibImport === LuaLibImportKind.Always)) { if (lualibContent === undefined) { const lualibBundle = emitHost.readFile(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua")); if (lualibBundle !== undefined) { From f807b79277259e9fa8992c8cf9f5510f5d741650 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Thu, 28 Nov 2019 21:31:15 +0100 Subject: [PATCH 18/18] Updated emit lualib_bundle logic --- src/Emit.ts | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Emit.ts b/src/Emit.ts index 371d65351..dd3208eb9 100644 --- a/src/Emit.ts +++ b/src/Emit.ts @@ -48,22 +48,30 @@ export function emitTranspiledFiles( } } - if (!luaBundle && (luaLibImport === LuaLibImportKind.Require || luaLibImport === LuaLibImportKind.Always)) { - if (lualibContent === undefined) { - const lualibBundle = emitHost.readFile(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua")); - if (lualibBundle !== undefined) { - lualibContent = lualibBundle; - } else { - throw new Error("Could not load lualib bundle from ./dist/lualib/lualib_bundle.lua"); + if ( + !luaBundle && + (luaLibImport === undefined || + luaLibImport === LuaLibImportKind.Require || + luaLibImport === LuaLibImportKind.Always) + ) { + const lualibRequired = files.some(f => f.text && f.text.includes(`require("lualib_bundle")`)); + if (lualibRequired) { + if (lualibContent === undefined) { + const lualibBundle = emitHost.readFile(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua")); + if (lualibBundle !== undefined) { + lualibContent = lualibBundle; + } else { + throw new Error("Could not load lualib bundle from ./dist/lualib/lualib_bundle.lua"); + } } - } - let outPath = path.resolve(rootDir, "lualib_bundle.lua"); - if (outDir !== rootDir) { - outPath = path.join(outDir, path.relative(rootDir, outPath)); - } + let outPath = path.resolve(rootDir, "lualib_bundle.lua"); + if (outDir !== rootDir) { + outPath = path.join(outDir, path.relative(rootDir, outPath)); + } - files.push({ name: normalizeSlashes(outPath), text: lualibContent }); + files.push({ name: normalizeSlashes(outPath), text: lualibContent }); + } } return files;