From 5a19a373359f47d57cf2e49e5959d4fec4aa32e0 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Tue, 24 Sep 2019 15:45:17 +0200 Subject: [PATCH 01/36] track Scope in transformClassExpression --- src/LuaTransformer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 43111e5d1..cd699bf13 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3679,7 +3679,10 @@ export class LuaTransformer { className = tstl.createAnonymousIdentifier(); } + this.pushScope(ScopeType.Function); const classDeclaration = this.transformClassDeclaration(expression, className); + this.popScope(); + return this.createImmediatelyInvokedFunctionExpression( this.statementVisitResultToArray(classDeclaration), className, From 46fdc43e4508d7eb68cb38d4471069fa36940c4b Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Tue, 24 Sep 2019 14:49:46 +0200 Subject: [PATCH 02/36] allow throwing arbitrary types and add builtin error classes --- src/LuaLib.ts | 1 + src/LuaTransformer.ts | 48 ++++++++++++--- src/TSTLErrors.ts | 3 - src/lualib/Error.ts | 93 +++++++++++++++++++++++++++++ src/lualib/declarations/global.d.ts | 1 + test/unit/error.spec.ts | 67 ++++++++++++++++++--- 6 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 src/lualib/Error.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 050e1eaf3..be9efcc5a 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -25,6 +25,7 @@ export enum LuaLibFeature { ClassIndex = "ClassIndex", ClassNewIndex = "ClassNewIndex", Decorate = "Decorate", + Error = "Error", FunctionApply = "FunctionApply", FunctionBind = "FunctionBind", FunctionCall = "FunctionCall", diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index cd699bf13..fc96554b5 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2843,19 +2843,14 @@ export class LuaTransformer { } public transformThrowStatement(statement: ts.ThrowStatement): StatementVisitResult { + const error = tstl.createIdentifier("error"); if (statement.expression === undefined) { - throw TSTLErrors.InvalidThrowExpression(statement); - } - - const type = this.checker.getTypeAtLocation(statement.expression); - if (tsHelper.isStringType(type, this.checker, this.program)) { - const error = tstl.createIdentifier("error"); + return tstl.createExpressionStatement(tstl.createCallExpression(error, []), statement); + } else { return tstl.createExpressionStatement( tstl.createCallExpression(error, [this.transformExpression(statement.expression)]), statement ); - } else { - throw TSTLErrors.InvalidThrowExpression(statement.expression); } } @@ -4208,6 +4203,7 @@ export class LuaTransformer { const expressionType = this.checker.getTypeAtLocation(expression.expression); if (tsHelper.isStandardLibraryType(expressionType, undefined, this.program)) { + this.checkForLuaLibType(expressionType); const result = this.transformGlobalFunctionCall(expression); if (result) { return result; @@ -5517,6 +5513,42 @@ export class LuaTransformer { case "WeakSet": this.importLuaLibFeature(LuaLibFeature.WeakSet); return; + case "Error": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "ErrorConstructor": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "RangeError": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "RangeErrorConstructor": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "ReferenceError": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "ReferenceErrorConstructor": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "SyntaxError": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "SyntaxErrorConstructor": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "TypeError": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "TypeErrorConstructor": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "URIError": + this.importLuaLibFeature(LuaLibFeature.Error); + return; + case "URIErrorConstructor": + this.importLuaLibFeature(LuaLibFeature.Error); + return; } } } diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index 89867e01d..c32318f45 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -54,9 +54,6 @@ export const InvalidPropertyCall = (node: ts.Node) => export const InvalidElementCall = (node: ts.Node) => new TranspileError(`Tried to transpile a non-element call as an element call.`, node); -export const InvalidThrowExpression = (node: ts.Node) => - new TranspileError(`Invalid throw expression, only strings can be thrown.`, node); - export const ForbiddenStaticClassPropertyName = (node: ts.Node, name: string) => new TranspileError(`Cannot use "${name}" as a static class property or method name.`, node); diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts new file mode 100644 index 000000000..c3fc66f04 --- /dev/null +++ b/src/lualib/Error.ts @@ -0,0 +1,93 @@ +Error = class { + public static captureStackTrace(targetObject: {}, depth: number): void { + targetObject["stack"] = debug.traceback(undefined, depth); + } + + public message: string; + public name: string; + public stack?: string; + + constructor(message?: string, name?: string) { + this.message = message || ""; + this.name = name || "Error"; + Error["captureStackTrace"](this, name ? 5 : 4); + } + + public __tostring(): string { + return `${this.name}: ${this.message}`; + } +} as any; + +setmetatable(Error, { __call: (_self: any, message: string) => new Error(message) }); + +/** Standard error types */ + +RangeError = class extends Error { + constructor(message?: string) { + // @ts-ignore + super(message, "RangeError"); + } + public __tostring(): string { + return `${this.name}: ${this.message}`; + } +} as any; +setmetatable(RangeError, { + __call: (_self: any, message: string) => new RangeError(message), + __index: getmetatable(RangeError), +}); + +ReferenceError = class extends Error { + constructor(message?: string) { + // @ts-ignore + super(message, "ReferenceError"); + } + public __tostring(): string { + return `${this.name}: ${this.message}`; + } +} as any; +setmetatable(ReferenceError, { + __call: (_self: any, message: string) => new ReferenceError(message), + __index: getmetatable(ReferenceError), +}); + +SyntaxError = class extends Error { + constructor(message?: string) { + // @ts-ignore + super(message, "SyntaxError"); + } + public __tostring(): string { + return `${this.name}: ${this.message}`; + } +} as any; +setmetatable(SyntaxError, { + __call: (_self: any, message: string) => new SyntaxError(message), + __index: getmetatable(SyntaxError), +}); + +TypeError = class extends Error { + constructor(message?: string) { + // @ts-ignore + super(message, "TypeError"); + } + public __tostring(): string { + return `${this.name}: ${this.message}`; + } +} as any; +setmetatable(TypeError, { + __call: (_self: any, message: string) => new TypeError(message), + __index: getmetatable(TypeError), +}); + +URIError = class extends Error { + constructor(message?: string) { + // @ts-ignore + super(message, "URIError"); + } + public __tostring(): string { + return `${this.name}: ${this.message}`; + } +} as any; +setmetatable(URIError, { + __call: (_self: any, message: string) => new URIError(message), + __index: getmetatable(URIError), +}); diff --git a/src/lualib/declarations/global.d.ts b/src/lualib/declarations/global.d.ts index e27b9ecea..aafe7d562 100644 --- a/src/lualib/declarations/global.d.ts +++ b/src/lualib/declarations/global.d.ts @@ -10,6 +10,7 @@ declare function type( value: any ): "nil" | "number" | "string" | "boolean" | "table" | "function" | "thread" | "userdata"; declare function setmetatable(table: T, metatable: any): T; +declare function getmetatable(table: T): any; declare function rawget(table: T, key: K): T[K]; declare function rawset(table: T, key: K, val: T[K]): void; /** @tupleReturn */ diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 17a6bc15c..af145afaf 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -1,4 +1,3 @@ -import * as TSTLErrors from "../../src/TSTLErrors"; import * as util from "../util"; test("throwString", () => { @@ -7,12 +6,6 @@ test("throwString", () => { `.expectToEqual(new util.ExecutionError("Some Error")); }); -test("throwError", () => { - util.testFunction` - throw Error("Some Error") - `.expectToHaveDiagnosticOfError(TSTLErrors.InvalidThrowExpression(util.nodeStub)); -}); - test.skip.each([0, 1, 2])("re-throw (%p)", i => { util.testFunction` const i: number = ${i}; @@ -292,3 +285,63 @@ test("return from nested finally", () => { `; expect(util.transpileAndExecute(code)).toBe("finally AB"); }); + +test("throw and catch custom error object", () => { + const code = ` + try { + throw {x: "Hello error object!"}; + } catch (error) { + return error.x; + } +`; + expect(util.transpileAndExecute(code)).toBe("Hello error object!"); +}); + +test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( + "throw builtin Errors as classes", + errorType => { + const code = ` + try { + throw new ${errorType}("message") + } catch (error) { + if (error instanceof Error) { + return \`\${error}\`; + } + } + `; + expect(util.transpileAndExecute(code)).toBe(`${errorType}: message`); + } +); + +test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( + "throw builtin Errors as functions", + errorType => { + const code = ` + try { + throw ${errorType}("message") + } catch (error) { + if (error instanceof Error) { + return \`\${error}\`; + } + } + `; + expect(util.transpileAndExecute(code)).toBe(`${errorType}: message`); + } +); + +test("get stack from builtin error object", () => { + const code = ` + function innerFunctionThatThrows() { throw RangeError(); } + function outerFunctionThatThrows() { innerFunctionThatThrows(); } + try { + outerFunctionThatThrows(); + } catch (error) { + if (error instanceof Error) { + return error.stack; + } + } + `; + const stack = util.transpileAndExecute(code); + expect(stack).toMatch("innerFunctionThatThrows"); + expect(stack).toMatch("outerFunctionThatThrows"); +}); From a0e3f1454a2c7e9c6fd9a1f997c1c7353d88229d Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Tue, 24 Sep 2019 16:03:09 +0200 Subject: [PATCH 03/36] allow subclassing Error --- src/LuaTransformer.ts | 4 ++++ test/unit/error.spec.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index fc96554b5..c6de12239 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -613,6 +613,10 @@ export class LuaTransformer { // Get type that is extended const extendsType = tsHelper.getExtendedType(statement, this.checker); + if (extendsType) { + this.checkForLuaLibType(extendsType); + } + if (!(isExtension || isMetaExtension) && extendsType) { // Non-extensions cannot extend extension classes const extendsDecorators = tsHelper.getCustomDecorators(extendsType, this.checker); diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index af145afaf..c66d24069 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -345,3 +345,16 @@ test("get stack from builtin error object", () => { expect(stack).toMatch("innerFunctionThatThrows"); expect(stack).toMatch("outerFunctionThatThrows"); }); + +test("subclass Error", () => { + const code = ` + class MyError extends Error { } + + try { + throw new MyError(); + } catch (error) { + return error instanceof Error; + } + `; + expect(util.transpileAndExecute(code)).toBe(true); +}); From d70d3f9cc6dae0d705f1858038d15b9dc7b20bf0 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 25 Sep 2019 00:48:46 +0200 Subject: [PATCH 04/36] Update test/unit/error.spec.ts Co-Authored-By: ark120202 --- test/unit/error.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index c66d24069..4d7ea5510 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -289,7 +289,7 @@ test("return from nested finally", () => { test("throw and catch custom error object", () => { const code = ` try { - throw {x: "Hello error object!"}; + throw { x: "Hello error object!" }; } catch (error) { return error.x; } From 4f5734b9b2688bc2ec3f000623f07bafedf2a3f0 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 25 Sep 2019 14:32:14 +0200 Subject: [PATCH 05/36] use custom traceback in errors --- src/LuaLib.ts | 1 + src/lualib/Error.ts | 149 +++++++++++++---------------- src/lualib/SourceMapTraceBack.ts | 27 ++++-- src/lualib/declarations/debug.d.ts | 1 + 4 files changed, 86 insertions(+), 92 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index be9efcc5a..9a7a0b4c5 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -64,6 +64,7 @@ export enum LuaLibFeature { const luaLibDependencies: { [lib in LuaLibFeature]?: LuaLibFeature[] } = { ArrayFlat: [LuaLibFeature.ArrayConcat], ArrayFlatMap: [LuaLibFeature.ArrayConcat], + Error: [LuaLibFeature.ArrayPush, LuaLibFeature.ArrayMap, LuaLibFeature.Index], InstanceOf: [LuaLibFeature.Symbol], Iterator: [LuaLibFeature.Symbol], ObjectFromEntries: [LuaLibFeature.Iterator, LuaLibFeature.Symbol], diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index c3fc66f04..0eb4d6af1 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -1,93 +1,76 @@ -Error = class { - public static captureStackTrace(targetObject: {}, depth: number): void { - targetObject["stack"] = debug.traceback(undefined, depth); - } - - public message: string; - public name: string; - public stack?: string; +type TSTLCapturedErrorStack = Array<{ + namewhat: string; + name?: string; + source: string; + short_src: string; + currentline: number; + func: Function; +}>; - constructor(message?: string, name?: string) { - this.message = message || ""; - this.name = name || "Error"; - Error["captureStackTrace"](this, name ? 5 : 4); +function __TS__GetErrorStack(constructor: Function): TSTLCapturedErrorStack { + const functionFrames = []; + let level = 1; + while (true) { + const info = debug.getinfo(level, "f"); + level += 1; + if (!info || info.func === constructor) { + break; + } } - - public __tostring(): string { - return `${this.name}: ${this.message}`; + while (true) { + const info = debug.getinfo(level, "Snl"); + if (!info) break; + if (info.currentline !== -1) { + functionFrames.push(info); + } + level += 1; } -} as any; + return functionFrames; +} -setmetatable(Error, { __call: (_self: any, message: string) => new Error(message) }); +function __TS__ConvertErrorStack(stack: TSTLCapturedErrorStack): string { + const info = stack + .map(v => { + if (v.namewhat === "") { + return `${v.short_src}:${v.currentline}`; + } else { + return `${v.short_src}:${v.currentline} in ${v.namewhat} ${v.name}`; + } + }) + .join("\n"); + const transform = (globalThis as any).__TS__SourceMapTransform; + return transform ? transform(info) : info; +} -/** Standard error types */ +function __TS__GetErrorString(this: void, error: Error): string { + return error.message !== "" ? `${error.name}: ${error.message}` : error.name; +} -RangeError = class extends Error { - constructor(message?: string) { - // @ts-ignore - super(message, "RangeError"); - } - public __tostring(): string { - return `${this.name}: ${this.message}`; - } -} as any; -setmetatable(RangeError, { - __call: (_self: any, message: string) => new RangeError(message), - __index: getmetatable(RangeError), -}); +function __TS__InitErrorClass(Type: any): any { + Type.prototype.__tostring = __TS__GetErrorString; + return setmetatable(Type, { + __index: getmetatable(Type), + __call: (_self: any, message: string) => new Type(message), + }); +} -ReferenceError = class extends Error { - constructor(message?: string) { - // @ts-ignore - super(message, "ReferenceError"); - } - public __tostring(): string { - return `${this.name}: ${this.message}`; - } -} as any; -setmetatable(ReferenceError, { - __call: (_self: any, message: string) => new ReferenceError(message), - __index: getmetatable(ReferenceError), -}); +Error = __TS__InitErrorClass( + class { + public message: string; + public name = "Error"; + public stack: string; -SyntaxError = class extends Error { - constructor(message?: string) { - // @ts-ignore - super(message, "SyntaxError"); + constructor(message = "") { + this.message = message; + this.stack = __TS__ConvertErrorStack(__TS__GetErrorStack((this.constructor as any).new)); + } } - public __tostring(): string { - return `${this.name}: ${this.message}`; - } -} as any; -setmetatable(SyntaxError, { - __call: (_self: any, message: string) => new SyntaxError(message), - __index: getmetatable(SyntaxError), -}); - -TypeError = class extends Error { - constructor(message?: string) { - // @ts-ignore - super(message, "TypeError"); - } - public __tostring(): string { - return `${this.name}: ${this.message}`; - } -} as any; -setmetatable(TypeError, { - __call: (_self: any, message: string) => new TypeError(message), - __index: getmetatable(TypeError), -}); +); -URIError = class extends Error { - constructor(message?: string) { - // @ts-ignore - super(message, "URIError"); - } - public __tostring(): string { - return `${this.name}: ${this.message}`; - } -} as any; -setmetatable(URIError, { - __call: (_self: any, message: string) => new URIError(message), - __index: getmetatable(URIError), -}); +for (const errorName of ["RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"]) { + globalThis[errorName] = __TS__InitErrorClass( + class extends Error { + public name = errorName; + } + ); +} diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts index 374f52b10..53f516cbc 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -1,5 +1,22 @@ // TODO: In the future, change this to __TS__RegisterFileInfo and provide tstl interface to // get some metadata about transpilation. + +function __TS__SourceMapTransform(trace: string): string { + const sourceMaps = globalThis.__TS__sourcemap; + if (sourceMaps) { + const [result] = string.gsub(trace, "(%S+).lua:(%d+)", (file, line) => { + const fileSourceMap = sourceMaps[file + ".lua"]; + if (fileSourceMap && fileSourceMap[line]) { + return `${file}.ts:${fileSourceMap[line]}`; + } + return `${file}.lua:${line}`; + }); + return result; + } else { + return trace; + } +} + function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap: { [line: number]: number }): void { globalThis.__TS__sourcemap = globalThis.__TS__sourcemap || {}; globalThis.__TS__sourcemap[fileName] = sourceMap; @@ -8,15 +25,7 @@ function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap: { [li globalThis.__TS__originalTraceback = debug.traceback; debug.traceback = (thread, message, level) => { const trace = globalThis.__TS__originalTraceback(thread, message, level); - const [result] = string.gsub(trace, "(%S+).lua:(%d+)", (file, line) => { - const fileSourceMap = globalThis.__TS__sourcemap[file + ".lua"]; - if (fileSourceMap && fileSourceMap[line]) { - return `${file}.ts:${fileSourceMap[line]}`; - } - return `${file}.lua:${line}`; - }); - - return result; + return __TS__SourceMapTransform(trace); }; } } diff --git a/src/lualib/declarations/debug.d.ts b/src/lualib/declarations/debug.d.ts index d5bd489d4..48b14f78a 100644 --- a/src/lualib/declarations/debug.d.ts +++ b/src/lualib/declarations/debug.d.ts @@ -2,4 +2,5 @@ declare namespace debug { function traceback(...args: any[]): string; + function getinfo(i: number, what?: string): any; } From e88392be721b454819d5e9ca488a1325861c066e Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 25 Sep 2019 14:58:17 +0200 Subject: [PATCH 06/36] return full stack on fallback --- src/lualib/Error.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index 0eb4d6af1..c22cc4ed7 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -13,7 +13,11 @@ function __TS__GetErrorStack(constructor: Function): TSTLCapturedErrorStack { while (true) { const info = debug.getinfo(level, "f"); level += 1; - if (!info || info.func === constructor) { + if (info.func === constructor) { + break; + } else if (!info) { + // constructor not in call stack + level = 1; break; } } From d30982f2b053f32e635de448e38d03f90053c1fc Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 25 Sep 2019 14:58:31 +0200 Subject: [PATCH 07/36] use builder API for tests --- test/unit/error.spec.ts | 50 +++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 4d7ea5510..0c951e397 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -287,50 +287,55 @@ test("return from nested finally", () => { }); test("throw and catch custom error object", () => { - const code = ` + util.testFunction` try { throw { x: "Hello error object!" }; } catch (error) { return error.x; } -`; - expect(util.transpileAndExecute(code)).toBe("Hello error object!"); + `.expectToMatchJsResult(); }); test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( "throw builtin Errors as classes", errorType => { - const code = ` + util.testFunction` try { throw new ${errorType}("message") } catch (error) { if (error instanceof Error) { return \`\${error}\`; + } else { + throw TypeError(); } } - `; - expect(util.transpileAndExecute(code)).toBe(`${errorType}: message`); + ` + .expectNoExecutionError() + .expectToMatchJsResult(); } ); test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( "throw builtin Errors as functions", errorType => { - const code = ` + util.testFunction` try { throw ${errorType}("message") } catch (error) { if (error instanceof Error) { return \`\${error}\`; + } else { + throw TypeError(); } } - `; - expect(util.transpileAndExecute(code)).toBe(`${errorType}: message`); + ` + .expectNoExecutionError() + .expectToMatchJsResult(); } ); test("get stack from builtin error object", () => { - const code = ` + const stack = util.testFunction` function innerFunctionThatThrows() { throw RangeError(); } function outerFunctionThatThrows() { innerFunctionThatThrows(); } try { @@ -338,23 +343,34 @@ test("get stack from builtin error object", () => { } catch (error) { if (error instanceof Error) { return error.stack; + } else { + throw TypeError(); } } - `; - const stack = util.transpileAndExecute(code); + ` + .expectNoExecutionError() + .getLuaExecutionResult(); + expect(stack).toMatch("innerFunctionThatThrows"); expect(stack).toMatch("outerFunctionThatThrows"); }); test("subclass Error", () => { - const code = ` - class MyError extends Error { } + util.testFunction` + class MyError extends Error { + name: "MyError" + } try { throw new MyError(); } catch (error) { - return error instanceof Error; + if (error instanceof Error) { + return error.name; + } else { + throw TypeError(); + } } - `; - expect(util.transpileAndExecute(code)).toBe(true); + ` + .expectNoExecutionError() + .expectToMatchJsResult(); }); From 771152defbe8a771992a92b8168195613be6d04a Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 25 Sep 2019 15:12:33 +0200 Subject: [PATCH 08/36] cleanup --- src/lualib/Error.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index c22cc4ed7..0022edcc2 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -4,7 +4,6 @@ type TSTLCapturedErrorStack = Array<{ source: string; short_src: string; currentline: number; - func: Function; }>; function __TS__GetErrorStack(constructor: Function): TSTLCapturedErrorStack { @@ -13,12 +12,12 @@ function __TS__GetErrorStack(constructor: Function): TSTLCapturedErrorStack { while (true) { const info = debug.getinfo(level, "f"); level += 1; - if (info.func === constructor) { - break; - } else if (!info) { + if (!info) { // constructor not in call stack level = 1; break; + } else if (info.func === constructor) { + break; } } while (true) { @@ -42,7 +41,7 @@ function __TS__ConvertErrorStack(stack: TSTLCapturedErrorStack): string { } }) .join("\n"); - const transform = (globalThis as any).__TS__SourceMapTransform; + const transform = globalThis.__TS__SourceMapTransform; return transform ? transform(info) : info; } @@ -53,7 +52,6 @@ function __TS__GetErrorString(this: void, error: Error): string { function __TS__InitErrorClass(Type: any): any { Type.prototype.__tostring = __TS__GetErrorString; return setmetatable(Type, { - __index: getmetatable(Type), __call: (_self: any, message: string) => new Type(message), }); } From 91363b9849a464a240d61fdc6b664c02659b787d Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Thu, 26 Sep 2019 11:51:55 +0200 Subject: [PATCH 09/36] add error name to type and reduce dependencies --- src/LuaLib.ts | 2 +- src/LuaTransformer.ts | 15 +++++++-------- src/lualib/Error.ts | 16 ++++++++++------ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 9a7a0b4c5..c8d0c70f8 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -64,7 +64,7 @@ export enum LuaLibFeature { const luaLibDependencies: { [lib in LuaLibFeature]?: LuaLibFeature[] } = { ArrayFlat: [LuaLibFeature.ArrayConcat], ArrayFlatMap: [LuaLibFeature.ArrayConcat], - Error: [LuaLibFeature.ArrayPush, LuaLibFeature.ArrayMap, LuaLibFeature.Index], + Error: [LuaLibFeature.ArrayMap], InstanceOf: [LuaLibFeature.Symbol], Iterator: [LuaLibFeature.Symbol], ObjectFromEntries: [LuaLibFeature.Iterator, LuaLibFeature.Symbol], diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index c6de12239..803c6658e 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2847,15 +2847,14 @@ export class LuaTransformer { } public transformThrowStatement(statement: ts.ThrowStatement): StatementVisitResult { - const error = tstl.createIdentifier("error"); - if (statement.expression === undefined) { - return tstl.createExpressionStatement(tstl.createCallExpression(error, []), statement); - } else { - return tstl.createExpressionStatement( - tstl.createCallExpression(error, [this.transformExpression(statement.expression)]), - statement - ); + const parameters: tstl.Expression[] = []; + if (statement.expression) { + parameters.push(this.transformExpression(statement.expression)); } + return tstl.createExpressionStatement( + tstl.createCallExpression(tstl.createIdentifier("error"), parameters), + statement + ); } public transformContinueStatement(statement: ts.ContinueStatement): StatementVisitResult { diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index 0022edcc2..4bc99a9cc 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -24,7 +24,7 @@ function __TS__GetErrorStack(constructor: Function): TSTLCapturedErrorStack { const info = debug.getinfo(level, "Snl"); if (!info) break; if (info.currentline !== -1) { - functionFrames.push(info); + functionFrames[functionFrames.length] = info; } level += 1; } @@ -49,22 +49,25 @@ function __TS__GetErrorString(this: void, error: Error): string { return error.message !== "" ? `${error.name}: ${error.message}` : error.name; } -function __TS__InitErrorClass(Type: any): any { - Type.prototype.__tostring = __TS__GetErrorString; +function __TS__InitErrorClass(Type: any, name?: string): any { + if (name) { + Type.name = name; + } return setmetatable(Type, { __call: (_self: any, message: string) => new Type(message), }); } Error = __TS__InitErrorClass( - class { - public message: string; + class Error { public name = "Error"; + public message: string; public stack: string; constructor(message = "") { this.message = message; this.stack = __TS__ConvertErrorStack(__TS__GetErrorStack((this.constructor as any).new)); + getmetatable(this).__tostring = __TS__GetErrorString; } } ); @@ -73,6 +76,7 @@ for (const errorName of ["RangeError", "ReferenceError", "SyntaxError", "TypeErr globalThis[errorName] = __TS__InitErrorClass( class extends Error { public name = errorName; - } + }, + errorName ); } From 11b8610f36ba28c5de41e56bf2daf9a9066e0e3f Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Thu, 26 Sep 2019 12:07:45 +0200 Subject: [PATCH 10/36] test subclasses for stack --- test/unit/error.spec.ts | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 0c951e397..3fe00140f 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -334,43 +334,48 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", } ); -test("get stack from builtin error object", () => { - const stack = util.testFunction` - function innerFunctionThatThrows() { throw RangeError(); } - function outerFunctionThatThrows() { innerFunctionThatThrows(); } +test("subclass Error", () => { + util.testFunction` + class MyError extends Error { + name: "MyError" + } + try { - outerFunctionThatThrows(); + throw new MyError(); } catch (error) { if (error instanceof Error) { - return error.stack; + return error.name; } else { throw TypeError(); } } ` .expectNoExecutionError() - .getLuaExecutionResult(); - - expect(stack).toMatch("innerFunctionThatThrows"); - expect(stack).toMatch("outerFunctionThatThrows"); + .expectToMatchJsResult(); }); -test("subclass Error", () => { - util.testFunction` +test.each(["Error", "RangeError", "MyError"])("get stack from error", errorType => { + const stack = util.testFunction` class MyError extends Error { name: "MyError" } - + + function innerFunctionThatThrows() { throw new ${errorType}(); } + function outerFunctionThatThrows() { innerFunctionThatThrows(); } + try { - throw new MyError(); + outerFunctionThatThrows(); } catch (error) { if (error instanceof Error) { - return error.name; + return error.stack; } else { throw TypeError(); } } ` .expectNoExecutionError() - .expectToMatchJsResult(); + .getLuaExecutionResult(); + + expect(stack).toMatch("innerFunctionThatThrows"); + expect(stack).toMatch("outerFunctionThatThrows"); }); From cdb275494f301a2cb959f79e97f8cf8665693115 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Thu, 26 Sep 2019 12:13:15 +0200 Subject: [PATCH 11/36] test string representation of subclassed errors --- test/unit/error.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 3fe00140f..0b746dc44 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -315,12 +315,16 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", } ); -test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( +test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "MyError"])( "throw builtin Errors as functions", errorType => { util.testFunction` + class MyError extends Error { + name: "MyError" + } + try { - throw ${errorType}("message") + throw new ${errorType}("message") } catch (error) { if (error instanceof Error) { return \`\${error}\`; From 92b680d2fb0bccd0d27bf7bf710d8250fc124d2d Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Thu, 26 Sep 2019 12:25:19 +0200 Subject: [PATCH 12/36] fix throwing as function test --- test/unit/error.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 0b746dc44..151e53143 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -296,10 +296,14 @@ test("throw and catch custom error object", () => { `.expectToMatchJsResult(); }); -test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( +test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "MyError"])( "throw builtin Errors as classes", errorType => { util.testFunction` + class MyError extends Error { + name: "MyError" + } + try { throw new ${errorType}("message") } catch (error) { @@ -315,16 +319,12 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", } ); -test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "MyError"])( +test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( "throw builtin Errors as functions", errorType => { util.testFunction` - class MyError extends Error { - name: "MyError" - } - try { - throw new ${errorType}("message") + throw ${errorType}("message") } catch (error) { if (error instanceof Error) { return \`\${error}\`; From 99584de2f124c4486e92d6db2dd4f9cb9ef30fd8 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Thu, 26 Sep 2019 17:44:53 +0200 Subject: [PATCH 13/36] style changes --- src/LuaTransformer.ts | 42 +++++------------------------------------ src/TSHelper.ts | 19 +++++++++++++++++++ src/lualib/Error.ts | 7 ++++++- test/unit/error.spec.ts | 8 ++------ 4 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 803c6658e..52c250365 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -5503,7 +5503,8 @@ export class LuaTransformer { protected checkForLuaLibType(type: ts.Type): void { if (type.symbol) { - switch (this.checker.getFullyQualifiedName(type.symbol)) { + const name = this.checker.getFullyQualifiedName(type.symbol); + switch (name) { case "Map": this.importLuaLibFeature(LuaLibFeature.Map); return; @@ -5516,42 +5517,9 @@ export class LuaTransformer { case "WeakSet": this.importLuaLibFeature(LuaLibFeature.WeakSet); return; - case "Error": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "ErrorConstructor": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "RangeError": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "RangeErrorConstructor": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "ReferenceError": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "ReferenceErrorConstructor": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "SyntaxError": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "SyntaxErrorConstructor": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "TypeError": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "TypeErrorConstructor": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "URIError": - this.importLuaLibFeature(LuaLibFeature.Error); - return; - case "URIErrorConstructor": - this.importLuaLibFeature(LuaLibFeature.Error); - return; + } + if (tsHelper.isBuiltinErrorTypeName(name)) { + this.importLuaLibFeature(LuaLibFeature.Error); } } } diff --git a/src/TSHelper.ts b/src/TSHelper.ts index c68c07298..0eae8ff82 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -33,6 +33,21 @@ const defaultArrayCallMethodNames = new Set([ "flatMap", ]); +const builtinErrorTypeNames = new Set([ + "Error", + "ErrorConstructor", + "RangeError", + "RangeErrorConstructor", + "ReferenceError", + "ReferenceErrorConstructor", + "SyntaxError", + "SyntaxErrorConstructor", + "TypeError", + "TypeErrorConstructor", + "URIError", + "URIErrorConstructor", +]); + export function getExtendedTypeNode( node: ts.ClassLikeDeclarationBase, checker: ts.TypeChecker @@ -1041,3 +1056,7 @@ export function formatPathToLuaPath(filePath: string): string { } return filePath.replace(/\.\//g, "").replace(/\//g, "."); } + +export function isBuiltinErrorTypeName(name: string): boolean { + return builtinErrorTypeNames.has(name); +} diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index 4bc99a9cc..9c9af047d 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -6,6 +6,11 @@ type TSTLCapturedErrorStack = Array<{ currentline: number; }>; +interface ErrorType extends Function { + name: string; + new (...args: any[]): T; +} + function __TS__GetErrorStack(constructor: Function): TSTLCapturedErrorStack { const functionFrames = []; let level = 1; @@ -49,7 +54,7 @@ function __TS__GetErrorString(this: void, error: Error): string { return error.message !== "" ? `${error.name}: ${error.message}` : error.name; } -function __TS__InitErrorClass(Type: any, name?: string): any { +function __TS__InitErrorClass(Type: ErrorType, name?: string): any { if (name) { Type.name = name; } diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 151e53143..765e4085e 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -296,14 +296,10 @@ test("throw and catch custom error object", () => { `.expectToMatchJsResult(); }); -test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "MyError"])( +test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( "throw builtin Errors as classes", errorType => { util.testFunction` - class MyError extends Error { - name: "MyError" - } - try { throw new ${errorType}("message") } catch (error) { @@ -348,7 +344,7 @@ test("subclass Error", () => { throw new MyError(); } catch (error) { if (error instanceof Error) { - return error.name; + return \'\$error\'; } else { throw TypeError(); } From d1ca812534608a887fb92ab89b92d6cb4c269d57 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Thu, 26 Sep 2019 19:17:03 +0200 Subject: [PATCH 14/36] formatting --- src/LuaTransformer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 52c250365..eeb128aeb 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -5518,6 +5518,7 @@ export class LuaTransformer { this.importLuaLibFeature(LuaLibFeature.WeakSet); return; } + if (tsHelper.isBuiltinErrorTypeName(name)) { this.importLuaLibFeature(LuaLibFeature.Error); } From f0fab202597f959a4bd964992d6aac96f337bedf Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Thu, 26 Sep 2019 19:17:52 +0200 Subject: [PATCH 15/36] add sourceMapTraceback option to test --- test/unit/error.spec.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 765e4085e..f09689d37 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -1,4 +1,5 @@ import * as util from "../util"; +import * as tstl from "../../src"; test("throwString", () => { util.testFunction` @@ -354,8 +355,10 @@ test("subclass Error", () => { .expectToMatchJsResult(); }); -test.each(["Error", "RangeError", "MyError"])("get stack from error", errorType => { - const stack = util.testFunction` +test.each([["Error", false], ["Error", true], ["RangeError", false], ["MyError", false]] as Array<[string, boolean]>)( + "get stack from error", + (errorType, sourceMapTraceback) => { + const stack = util.testFunction` class MyError extends Error { name: "MyError" } @@ -373,9 +376,11 @@ test.each(["Error", "RangeError", "MyError"])("get stack from error", errorType } } ` - .expectNoExecutionError() - .getLuaExecutionResult(); + .setOptions({ sourceMapTraceback, luaLibImport: tstl.LuaLibImportKind.Inline }) + .expectNoExecutionError() + .getLuaExecutionResult(); - expect(stack).toMatch("innerFunctionThatThrows"); - expect(stack).toMatch("outerFunctionThatThrows"); -}); + expect(stack).toMatch("innerFunctionThatThrows"); + expect(stack).toMatch("outerFunctionThatThrows"); + } +); From f28da88045f94d4ffbf61fc8469f36922ed85fec Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Thu, 26 Sep 2019 20:03:31 +0200 Subject: [PATCH 16/36] add 'This error serves to prevent false positives from .expectNoExecutionError()' --- test/unit/error.spec.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index f09689d37..2de162ef6 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -307,7 +307,7 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", if (error instanceof Error) { return \`\${error}\`; } else { - throw TypeError(); + throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } } ` @@ -326,7 +326,7 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", if (error instanceof Error) { return \`\${error}\`; } else { - throw TypeError(); + throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } } ` @@ -347,7 +347,7 @@ test("subclass Error", () => { if (error instanceof Error) { return \'\$error\'; } else { - throw TypeError(); + throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } } ` @@ -362,7 +362,6 @@ test.each([["Error", false], ["Error", true], ["RangeError", false], ["MyError", class MyError extends Error { name: "MyError" } - function innerFunctionThatThrows() { throw new ${errorType}(); } function outerFunctionThatThrows() { innerFunctionThatThrows(); } @@ -372,7 +371,7 @@ test.each([["Error", false], ["Error", true], ["RangeError", false], ["MyError", if (error instanceof Error) { return error.stack; } else { - throw TypeError(); + throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } } ` From 5cd860a576f4ca95caca06ed07bd73a6b88b772d Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Fri, 27 Sep 2019 11:04:18 +0200 Subject: [PATCH 17/36] Update test/unit/error.spec.ts Co-Authored-By: ark120202 --- test/unit/error.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 2de162ef6..e6d85e804 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -360,7 +360,7 @@ test.each([["Error", false], ["Error", true], ["RangeError", false], ["MyError", (errorType, sourceMapTraceback) => { const stack = util.testFunction` class MyError extends Error { - name: "MyError" + public name = "MyError"; } function innerFunctionThatThrows() { throw new ${errorType}(); } function outerFunctionThatThrows() { innerFunctionThatThrows(); } From 37ba17a50a3d753814003df77213dc91c6395460 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sat, 28 Sep 2019 14:02:35 +0200 Subject: [PATCH 18/36] rollback to use debug.traceback --- src/LuaLib.ts | 1 - src/LuaTransformer.ts | 2 ++ src/TSHelper.ts | 1 + src/lualib/Error.ts | 39 +++------------------------- src/lualib/SourceMapTraceBack.ts | 27 +++++++------------- src/lualib/declarations/debug.d.ts | 16 +++++++++++- test/unit/error.spec.ts | 41 ++++++++++-------------------- 7 files changed, 45 insertions(+), 82 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index c8d0c70f8..be9efcc5a 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -64,7 +64,6 @@ export enum LuaLibFeature { const luaLibDependencies: { [lib in LuaLibFeature]?: LuaLibFeature[] } = { ArrayFlat: [LuaLibFeature.ArrayConcat], ArrayFlatMap: [LuaLibFeature.ArrayConcat], - Error: [LuaLibFeature.ArrayMap], InstanceOf: [LuaLibFeature.Symbol], Iterator: [LuaLibFeature.Symbol], ObjectFromEntries: [LuaLibFeature.Iterator, LuaLibFeature.Symbol], diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index eeb128aeb..3708e137b 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2848,9 +2848,11 @@ export class LuaTransformer { public transformThrowStatement(statement: ts.ThrowStatement): StatementVisitResult { const parameters: tstl.Expression[] = []; + if (statement.expression) { parameters.push(this.transformExpression(statement.expression)); } + return tstl.createExpressionStatement( tstl.createCallExpression(tstl.createIdentifier("error"), parameters), statement diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 0eae8ff82..3bac0cfda 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -33,6 +33,7 @@ const defaultArrayCallMethodNames = new Set([ "flatMap", ]); +// TODO [2019-09-27/Perry]: Refactor lualib detection to consistent map const builtinErrorTypeNames = new Set([ "Error", "ErrorConstructor", diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index 9c9af047d..1cbd6a7a0 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -1,53 +1,22 @@ -type TSTLCapturedErrorStack = Array<{ - namewhat: string; - name?: string; - source: string; - short_src: string; - currentline: number; -}>; - interface ErrorType extends Function { name: string; new (...args: any[]): T; } -function __TS__GetErrorStack(constructor: Function): TSTLCapturedErrorStack { - const functionFrames = []; +function __TS__GetErrorStack(constructor: Function): string { let level = 1; while (true) { const info = debug.getinfo(level, "f"); level += 1; if (!info) { - // constructor not in call stack + // constructor is not in call stack level = 1; break; } else if (info.func === constructor) { break; } } - while (true) { - const info = debug.getinfo(level, "Snl"); - if (!info) break; - if (info.currentline !== -1) { - functionFrames[functionFrames.length] = info; - } - level += 1; - } - return functionFrames; -} - -function __TS__ConvertErrorStack(stack: TSTLCapturedErrorStack): string { - const info = stack - .map(v => { - if (v.namewhat === "") { - return `${v.short_src}:${v.currentline}`; - } else { - return `${v.short_src}:${v.currentline} in ${v.namewhat} ${v.name}`; - } - }) - .join("\n"); - const transform = globalThis.__TS__SourceMapTransform; - return transform ? transform(info) : info; + return debug.traceback(undefined, level); } function __TS__GetErrorString(this: void, error: Error): string { @@ -71,7 +40,7 @@ Error = __TS__InitErrorClass( constructor(message = "") { this.message = message; - this.stack = __TS__ConvertErrorStack(__TS__GetErrorStack((this.constructor as any).new)); + this.stack = __TS__GetErrorStack((this.constructor as any).new); getmetatable(this).__tostring = __TS__GetErrorString; } } diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts index 53f516cbc..374f52b10 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -1,22 +1,5 @@ // TODO: In the future, change this to __TS__RegisterFileInfo and provide tstl interface to // get some metadata about transpilation. - -function __TS__SourceMapTransform(trace: string): string { - const sourceMaps = globalThis.__TS__sourcemap; - if (sourceMaps) { - const [result] = string.gsub(trace, "(%S+).lua:(%d+)", (file, line) => { - const fileSourceMap = sourceMaps[file + ".lua"]; - if (fileSourceMap && fileSourceMap[line]) { - return `${file}.ts:${fileSourceMap[line]}`; - } - return `${file}.lua:${line}`; - }); - return result; - } else { - return trace; - } -} - function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap: { [line: number]: number }): void { globalThis.__TS__sourcemap = globalThis.__TS__sourcemap || {}; globalThis.__TS__sourcemap[fileName] = sourceMap; @@ -25,7 +8,15 @@ function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap: { [li globalThis.__TS__originalTraceback = debug.traceback; debug.traceback = (thread, message, level) => { const trace = globalThis.__TS__originalTraceback(thread, message, level); - return __TS__SourceMapTransform(trace); + const [result] = string.gsub(trace, "(%S+).lua:(%d+)", (file, line) => { + const fileSourceMap = globalThis.__TS__sourcemap[file + ".lua"]; + if (fileSourceMap && fileSourceMap[line]) { + return `${file}.ts:${fileSourceMap[line]}`; + } + return `${file}.lua:${line}`; + }); + + return result; }; } } diff --git a/src/lualib/declarations/debug.d.ts b/src/lualib/declarations/debug.d.ts index 48b14f78a..c6ebf25eb 100644 --- a/src/lualib/declarations/debug.d.ts +++ b/src/lualib/declarations/debug.d.ts @@ -2,5 +2,19 @@ declare namespace debug { function traceback(...args: any[]): string; - function getinfo(i: number, what?: string): any; + + interface FunctionInfo { + func: T; + name?: string; + namewhat: "global" | "local" | "method" | "field" | ""; + source: string; + short_src: string; + linedefined: number; + lastlinedefined: number; + what: "Lua" | "C" | "main"; + currentline: number; + nups: number; + } + + function getinfo(i: number, what?: string): Partial; } diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index e6d85e804..8f0fef8aa 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -1,5 +1,4 @@ import * as util from "../util"; -import * as tstl from "../../src"; test("throwString", () => { util.testFunction` @@ -289,11 +288,11 @@ test("return from nested finally", () => { test("throw and catch custom error object", () => { util.testFunction` - try { - throw { x: "Hello error object!" }; - } catch (error) { - return error.x; - } + try { + throw { x: "Hello error object!" }; + } catch (error) { + return error.x; + } `.expectToMatchJsResult(); }); @@ -310,9 +309,7 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } } - ` - .expectNoExecutionError() - .expectToMatchJsResult(); + `.expectToMatchJsResult(); } ); @@ -329,9 +326,7 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } } - ` - .expectNoExecutionError() - .expectToMatchJsResult(); + `.expectToMatchJsResult(); } ); @@ -350,15 +345,11 @@ test("subclass Error", () => { throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } } - ` - .expectNoExecutionError() - .expectToMatchJsResult(); + `.expectToMatchJsResult(); }); -test.each([["Error", false], ["Error", true], ["RangeError", false], ["MyError", false]] as Array<[string, boolean]>)( - "get stack from error", - (errorType, sourceMapTraceback) => { - const stack = util.testFunction` +test.each(["Error", "RangeError", "MyError"])("get stack from error", errorType => { + const stack = util.testFunction` class MyError extends Error { public name = "MyError"; } @@ -374,12 +365,8 @@ test.each([["Error", false], ["Error", true], ["RangeError", false], ["MyError", throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } } - ` - .setOptions({ sourceMapTraceback, luaLibImport: tstl.LuaLibImportKind.Inline }) - .expectNoExecutionError() - .getLuaExecutionResult(); + `.getLuaExecutionResult(); - expect(stack).toMatch("innerFunctionThatThrows"); - expect(stack).toMatch("outerFunctionThatThrows"); - } -); + expect(stack).toMatch("innerFunctionThatThrows"); + expect(stack).toMatch("outerFunctionThatThrows"); +}); From 21c27ea7c4449540b81d21ceabe53d837921c8e6 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sat, 28 Sep 2019 22:50:10 +0200 Subject: [PATCH 19/36] Update src/lualib/Error.ts Co-Authored-By: ark120202 --- src/lualib/Error.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index 1cbd6a7a0..369cb5162 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -16,6 +16,7 @@ function __TS__GetErrorStack(constructor: Function): string { break; } } + return debug.traceback(undefined, level); } From 893cc7a34fe94de0eff9c1e80a498339aa10b068 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sun, 29 Sep 2019 22:30:57 +0200 Subject: [PATCH 20/36] fix throwing strings and update tests --- src/LuaTransformer.ts | 1 + src/lualib/Error.ts | 10 ++++++---- test/unit/error.spec.ts | 21 ++++++++++++--------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 3708e137b..edb31fda3 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2851,6 +2851,7 @@ export class LuaTransformer { if (statement.expression) { parameters.push(this.transformExpression(statement.expression)); + parameters.push(tstl.createNumericLiteral(0)); } return tstl.createExpressionStatement( diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index 369cb5162..e99e4d6b2 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -24,7 +24,7 @@ function __TS__GetErrorString(this: void, error: Error): string { return error.message !== "" ? `${error.name}: ${error.message}` : error.name; } -function __TS__InitErrorClass(Type: ErrorType, name?: string): any { +function __TS__InitErrorClass(Type: ErrorType, name: string): any { if (name) { Type.name = name; } @@ -34,7 +34,7 @@ function __TS__InitErrorClass(Type: ErrorType, name?: string): any { } Error = __TS__InitErrorClass( - class Error { + class { public name = "Error"; public message: string; public stack: string; @@ -42,9 +42,11 @@ Error = __TS__InitErrorClass( constructor(message = "") { this.message = message; this.stack = __TS__GetErrorStack((this.constructor as any).new); - getmetatable(this).__tostring = __TS__GetErrorString; + const mt = getmetatable(this); + mt.__tostring = mt.__tostring || __TS__GetErrorString; } - } + }, + "Error" ); for (const errorName of ["RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"]) { diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 8f0fef8aa..b72289a28 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -286,15 +286,18 @@ test("return from nested finally", () => { expect(util.transpileAndExecute(code)).toBe("finally AB"); }); -test("throw and catch custom error object", () => { - util.testFunction` +test.each([`"Hello error string!"`, `42`, `true`, `false`, `undefined`, `{ x: "Hello error object!" }`])( + "throw and catch custom error object", + error => { + util.testFunction` try { - throw { x: "Hello error object!" }; + throw ${error}; } catch (error) { - return error.x; + return error; } `.expectToMatchJsResult(); -}); + } +); test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( "throw builtin Errors as classes", @@ -304,7 +307,7 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", throw new ${errorType}("message") } catch (error) { if (error instanceof Error) { - return \`\${error}\`; + return error.toString(); } else { throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } @@ -321,7 +324,7 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", throw ${errorType}("message") } catch (error) { if (error instanceof Error) { - return \`\${error}\`; + return error.toString(); } else { throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } @@ -333,14 +336,14 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", test("subclass Error", () => { util.testFunction` class MyError extends Error { - name: "MyError" + public name = "MyError"; } try { throw new MyError(); } catch (error) { if (error instanceof Error) { - return \'\$error\'; + return error.toString(); } else { throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); } From ffa9d6dc93a4f2393e668d92bd31fd2b4fac6ebd Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sun, 29 Sep 2019 22:34:22 +0200 Subject: [PATCH 21/36] Update src/lualib/Error.ts Co-Authored-By: ark120202 --- src/lualib/Error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index e99e4d6b2..5761a1602 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -39,7 +39,7 @@ Error = __TS__InitErrorClass( public message: string; public stack: string; - constructor(message = "") { + constructor(public message = "") { this.message = message; this.stack = __TS__GetErrorStack((this.constructor as any).new); const mt = getmetatable(this); From 613d1188ea21502e61a7e31fbfb6caeaf7542629 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sun, 29 Sep 2019 22:38:17 +0200 Subject: [PATCH 22/36] test extending from error with custom toString() --- test/unit/error.spec.ts | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index b72289a28..62d68a7f5 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -290,12 +290,12 @@ test.each([`"Hello error string!"`, `42`, `true`, `false`, `undefined`, `{ x: "H "throw and catch custom error object", error => { util.testFunction` - try { - throw ${error}; - } catch (error) { - return error; - } - `.expectToMatchJsResult(); + try { + throw ${error}; + } catch (error) { + return error; + } + `.expectToMatchJsResult(); } ); @@ -333,7 +333,7 @@ test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", } ); -test("subclass Error", () => { +test("extending from Error", () => { util.testFunction` class MyError extends Error { public name = "MyError"; @@ -351,6 +351,24 @@ test("subclass Error", () => { `.expectToMatchJsResult(); }); +test("extending from Error with custom toString()", () => { + util.testFunction` + class MyError extends Error { + toString(){ return "Custom error message"; } + } + + try { + throw new MyError(); + } catch (error) { + if (error instanceof Error) { + return error.toString(); + } else { + throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); + } + } + `.expectToMatchJsResult(); +}); + test.each(["Error", "RangeError", "MyError"])("get stack from error", errorType => { const stack = util.testFunction` class MyError extends Error { From dd772949862bac25f6b77165f49cfca846452513 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sun, 29 Sep 2019 22:41:52 +0200 Subject: [PATCH 23/36] update identifiers test for new exception handling --- test/unit/identifiers.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/identifiers.spec.ts b/test/unit/identifiers.spec.ts index bde4477fa..3cd3753f6 100644 --- a/test/unit/identifiers.spec.ts +++ b/test/unit/identifiers.spec.ts @@ -356,7 +356,7 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { const error = "foobar"; throw error;`; - expect(() => util.transpileAndExecute(code)).toThrow(/^LUA ERROR: .+ foobar$/); + expect(() => util.transpileAndExecute(code)).toThrow(/^LUA ERROR: foobar$/); }); test("variable (assert)", () => { From df05bb6534490afb97f258b927724776a36bd081 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sun, 29 Sep 2019 22:48:34 +0200 Subject: [PATCH 24/36] use constructor assignment for message property --- src/lualib/Error.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index 5761a1602..ecca889e1 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -36,11 +36,9 @@ function __TS__InitErrorClass(Type: ErrorType, name: string): any { Error = __TS__InitErrorClass( class { public name = "Error"; - public message: string; public stack: string; constructor(public message = "") { - this.message = message; this.stack = __TS__GetErrorStack((this.constructor as any).new); const mt = getmetatable(this); mt.__tostring = mt.__tostring || __TS__GetErrorString; From cbd980dda722fbfdc886a5557dae1b2a5ffc90ec Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sun, 29 Sep 2019 23:39:44 +0200 Subject: [PATCH 25/36] print stack for uncaught errors --- src/lualib/Error.ts | 10 ++++++++-- src/lualib/declarations/debug.d.ts | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index ecca889e1..fa19e2484 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -20,8 +20,14 @@ function __TS__GetErrorStack(constructor: Function): string { return debug.traceback(undefined, level); } -function __TS__GetErrorString(this: void, error: Error): string { - return error.message !== "" ? `${error.name}: ${error.message}` : error.name; +function __TS__GetErrorString(this: void, errorObj: Error): string { + const description = errorObj.message !== "" ? `${errorObj.name}: ${errorObj.message}` : errorObj.name; + const caller = debug.getinfo(3, "f"); + if (_VERSION === "Lua 5.1" || (caller && caller.func !== error)) { + return description; + } else { + return `${description}\n${errorObj.stack}`; + } } function __TS__InitErrorClass(Type: ErrorType, name: string): any { diff --git a/src/lualib/declarations/debug.d.ts b/src/lualib/declarations/debug.d.ts index c6ebf25eb..a451fe07f 100644 --- a/src/lualib/declarations/debug.d.ts +++ b/src/lualib/declarations/debug.d.ts @@ -1,5 +1,8 @@ /** @noSelfInFile */ +declare const _VERSION: string; +declare function error(...args: any[]): never; + declare namespace debug { function traceback(...args: any[]): string; From 52f15c6ca37754eae6d12a4f0eaf9c8a28e3b1bb Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Tue, 1 Oct 2019 09:28:17 +0200 Subject: [PATCH 26/36] Update test/unit/error.spec.ts Co-Authored-By: ark120202 --- test/unit/error.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 62d68a7f5..a35c37ae7 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -354,7 +354,9 @@ test("extending from Error", () => { test("extending from Error with custom toString()", () => { util.testFunction` class MyError extends Error { - toString(){ return "Custom error message"; } + toString() { + return "Custom error message"; + } } try { From ebf0c1c72f20ccd41ac5160cdc8a2fa8588b05a4 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Tue, 1 Oct 2019 09:28:27 +0200 Subject: [PATCH 27/36] Update test/unit/error.spec.ts Co-Authored-By: ark120202 --- test/unit/error.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index a35c37ae7..226b65ba3 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -371,7 +371,7 @@ test("extending from Error with custom toString()", () => { `.expectToMatchJsResult(); }); -test.each(["Error", "RangeError", "MyError"])("get stack from error", errorType => { +test.each(["Error", "RangeError", "MyError"])("get stack from %s", errorType => { const stack = util.testFunction` class MyError extends Error { public name = "MyError"; From 371c841e67651a71f2454f30e8834e683e5fab57 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Tue, 1 Oct 2019 10:21:32 +0200 Subject: [PATCH 28/36] rollback toString() support --- src/lualib/Error.ts | 14 +++-- test/unit/error.spec.ts | 114 +++++++++++++++------------------------- 2 files changed, 50 insertions(+), 78 deletions(-) diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index fa19e2484..0d8e36e88 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -31,9 +31,7 @@ function __TS__GetErrorString(this: void, errorObj: Error): string { } function __TS__InitErrorClass(Type: ErrorType, name: string): any { - if (name) { - Type.name = name; - } + Type.name = name; return setmetatable(Type, { __call: (_self: any, message: string) => new Type(message), }); @@ -46,8 +44,10 @@ Error = __TS__InitErrorClass( constructor(public message = "") { this.stack = __TS__GetErrorStack((this.constructor as any).new); - const mt = getmetatable(this); - mt.__tostring = mt.__tostring || __TS__GetErrorString; + } + + public toString(): string { + return __TS__GetErrorString(this); } }, "Error" @@ -57,6 +57,10 @@ for (const errorName of ["RangeError", "ReferenceError", "SyntaxError", "TypeErr globalThis[errorName] = __TS__InitErrorClass( class extends Error { public name = errorName; + + public toString(): string { + return __TS__GetErrorString(this); + } }, errorName ); diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 226b65ba3..64fa15666 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -286,94 +286,62 @@ test("return from nested finally", () => { expect(util.transpileAndExecute(code)).toBe("finally AB"); }); -test.each([`"Hello error string!"`, `42`, `true`, `false`, `undefined`, `{ x: "Hello error object!" }`])( - "throw and catch custom error object", - error => { - util.testFunction` +test.each([ + `"error string"`, + `42`, + `3.141`, + `true`, + `false`, + `undefined`, + `{ x: "error object" }`, + `() => "error function"`, +])("throw and catch arbitrary types", error => { + util.testFunction` try { throw ${error}; } catch (error) { - return error; - } - `.expectToMatchJsResult(); - } -); - -test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( - "throw builtin Errors as classes", - errorType => { - util.testFunction` - try { - throw new ${errorType}("message") - } catch (error) { - if (error instanceof Error) { - return error.toString(); + if (typeof error == 'function') { + return error(); } else { - throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); + return error; } } `.expectToMatchJsResult(); - } -); +}); -test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])( - "throw builtin Errors as functions", - errorType => { - util.testFunction` - try { - throw ${errorType}("message") - } catch (error) { - if (error instanceof Error) { - return error.toString(); - } else { - throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); - } - } +test.each([ + "Error", + "RangeError", + "ReferenceError", + "SyntaxError", + "TypeError", + "URIError", + "new Error", + "new RangeError", + "new ReferenceError", + "new SyntaxError", + "new TypeError", + "new URIError", +])("test builtin error properties", errorType => { + util.testFunction` + const error = ${errorType}(); + return error.name; `.expectToMatchJsResult(); - } -); -test("extending from Error", () => { util.testFunction` - class MyError extends Error { - public name = "MyError"; - } - - try { - throw new MyError(); - } catch (error) { - if (error instanceof Error) { - return error.toString(); - } else { - throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); - } - } - `.expectToMatchJsResult(); -}); + const error = ${errorType}(); + return error.message; + `.expectToMatchJsResult(); -test("extending from Error with custom toString()", () => { util.testFunction` - class MyError extends Error { - toString() { - return "Custom error message"; - } - } - - try { - throw new MyError(); - } catch (error) { - if (error instanceof Error) { - return error.toString(); - } else { - throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); - } - } - `.expectToMatchJsResult(); + const error = ${errorType}(); + return error.toString(); + `.expectToMatchJsResult(); }); -test.each(["Error", "RangeError", "MyError"])("get stack from %s", errorType => { +test.each(["Error", "RangeError", "CustomError"])("get stack from %s", errorType => { const stack = util.testFunction` - class MyError extends Error { + class CustomError extends Error { public name = "MyError"; } function innerFunctionThatThrows() { throw new ${errorType}(); } @@ -385,7 +353,7 @@ test.each(["Error", "RangeError", "MyError"])("get stack from %s", errorType => if (error instanceof Error) { return error.stack; } else { - throw Error("This error serves to prevent false positives from .expectNoExecutionError()"); + throw Error("This error serves to prevent false positives"); } } `.getLuaExecutionResult(); From b9fdd4071561211838d709a906e484b9a213d93e Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Tue, 1 Oct 2019 10:30:56 +0200 Subject: [PATCH 29/36] add more test cases --- test/unit/error.spec.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 64fa15666..b5ef41c05 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -339,12 +339,27 @@ test.each([ `.expectToMatchJsResult(); }); -test.each(["Error", "RangeError", "CustomError"])("get stack from %s", errorType => { +test.each([ + "Error", + "RangeError", + "ReferenceError", + "SyntaxError", + "TypeError", + "URIError", + "new Error", + "new RangeError", + "new ReferenceError", + "new SyntaxError", + "new TypeError", + "new URIError", + "new CustomError", +])("get stack from %s", errorType => { const stack = util.testFunction` class CustomError extends Error { - public name = "MyError"; + public name = "CustomError"; } - function innerFunctionThatThrows() { throw new ${errorType}(); } + + function innerFunctionThatThrows() { throw ${errorType}(); } function outerFunctionThatThrows() { innerFunctionThatThrows(); } try { From a0b63e13f8aed2eb541c89bb6554ff17ca475c20 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Tue, 1 Oct 2019 12:26:30 +0200 Subject: [PATCH 30/36] formatting --- test/unit/error.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index b5ef41c05..42ade353a 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -326,17 +326,17 @@ test.each([ util.testFunction` const error = ${errorType}(); return error.name; - `.expectToMatchJsResult(); + `.expectToMatchJsResult(); util.testFunction` const error = ${errorType}(); return error.message; - `.expectToMatchJsResult(); + `.expectToMatchJsResult(); util.testFunction` - const error = ${errorType}(); - return error.toString(); - `.expectToMatchJsResult(); + const error = ${errorType}(); + return error.toString(); + `.expectToMatchJsResult(); }); test.each([ From 6066a7e242ec7439307673a8567c2074856a1adb Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sat, 5 Oct 2019 18:35:49 +0200 Subject: [PATCH 31/36] set __tostring metamethod in constructor --- src/LuaLib.ts | 1 + src/lualib/Error.ts | 29 ++++++++++++++++------------- test/unit/error.spec.ts | 12 +----------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/LuaLib.ts b/src/LuaLib.ts index be9efcc5a..3c5404309 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -64,6 +64,7 @@ export enum LuaLibFeature { const luaLibDependencies: { [lib in LuaLibFeature]?: LuaLibFeature[] } = { ArrayFlat: [LuaLibFeature.ArrayConcat], ArrayFlatMap: [LuaLibFeature.ArrayConcat], + Error: [LuaLibFeature.FunctionCall], InstanceOf: [LuaLibFeature.Symbol], Iterator: [LuaLibFeature.Symbol], ObjectFromEntries: [LuaLibFeature.Iterator, LuaLibFeature.Symbol], diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index 0d8e36e88..a6dba1a79 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -20,14 +20,16 @@ function __TS__GetErrorStack(constructor: Function): string { return debug.traceback(undefined, level); } -function __TS__GetErrorString(this: void, errorObj: Error): string { - const description = errorObj.message !== "" ? `${errorObj.name}: ${errorObj.message}` : errorObj.name; - const caller = debug.getinfo(3, "f"); - if (_VERSION === "Lua 5.1" || (caller && caller.func !== error)) { - return description; - } else { - return `${description}\n${errorObj.stack}`; - } +function __TS__WrapErrorToString(getDescription: (this: T) => string): (this: T) => string { + return function(this: Error): string { + const description = getDescription.call(this); + const caller = debug.getinfo(3, "f"); + if (_VERSION === "Lua 5.1" || (caller && caller.func !== error)) { + return description; + } else { + return `${description}\n${this.stack}`; + } + }; } function __TS__InitErrorClass(Type: ErrorType, name: string): any { @@ -44,10 +46,15 @@ Error = __TS__InitErrorClass( constructor(public message = "") { this.stack = __TS__GetErrorStack((this.constructor as any).new); + const metatable = getmetatable(this); + if (!metatable.__errorToStringPatched) { + metatable.__errorToStringPatched = true; + metatable.__tostring = __TS__WrapErrorToString(metatable.__tostring); + } } public toString(): string { - return __TS__GetErrorString(this); + return this.message !== "" ? `${this.name}: ${this.message}` : this.name; } }, "Error" @@ -57,10 +64,6 @@ for (const errorName of ["RangeError", "ReferenceError", "SyntaxError", "TypeErr globalThis[errorName] = __TS__InitErrorClass( class extends Error { public name = errorName; - - public toString(): string { - return __TS__GetErrorString(this); - } }, errorName ); diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 42ade353a..f986a21b9 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -325,17 +325,7 @@ test.each([ ])("test builtin error properties", errorType => { util.testFunction` const error = ${errorType}(); - return error.name; - `.expectToMatchJsResult(); - - util.testFunction` - const error = ${errorType}(); - return error.message; - `.expectToMatchJsResult(); - - util.testFunction` - const error = ${errorType}(); - return error.toString(); + return { name: error.name, message: error.message, string: error.toString() }; `.expectToMatchJsResult(); }); From 5400f89042234a85ebd9fa141b116310c0b97f88 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 9 Oct 2019 12:26:28 +0200 Subject: [PATCH 32/36] update tests --- test/unit/error.spec.ts | 61 ++++++++++++----------------------------- 1 file changed, 17 insertions(+), 44 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index f986a21b9..dd6177504 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -309,60 +309,33 @@ test.each([ `.expectToMatchJsResult(); }); -test.each([ - "Error", - "RangeError", - "ReferenceError", - "SyntaxError", - "TypeError", - "URIError", - "new Error", - "new RangeError", - "new ReferenceError", - "new SyntaxError", - "new TypeError", - "new URIError", -])("test builtin error properties", errorType => { - util.testFunction` +const builtinErrors = ["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"]; + +test.each([...builtinErrors, ...builtinErrors.map(type => `new ${type}`)])( + "test builtin error properties", + errorType => { + util.testFunction` const error = ${errorType}(); return { name: error.name, message: error.message, string: error.toString() }; `.expectToMatchJsResult(); -}); + } +); -test.each([ - "Error", - "RangeError", - "ReferenceError", - "SyntaxError", - "TypeError", - "URIError", - "new Error", - "new RangeError", - "new ReferenceError", - "new SyntaxError", - "new TypeError", - "new URIError", - "new CustomError", -])("get stack from %s", errorType => { +test.each([...builtinErrors, `CustomError`])("get stack from %s", errorType => { const stack = util.testFunction` class CustomError extends Error { public name = "CustomError"; } - function innerFunctionThatThrows() { throw ${errorType}(); } - function outerFunctionThatThrows() { innerFunctionThatThrows(); } + let stack: string | undefined; - try { - outerFunctionThatThrows(); - } catch (error) { - if (error instanceof Error) { - return error.stack; - } else { - throw Error("This error serves to prevent false positives"); - } - } + function innerFunction() { stack = new ${errorType}().stack; } + function outerFunction() { innerFunction(); } + outerFunction(); + + return stack; `.getLuaExecutionResult(); - expect(stack).toMatch("innerFunctionThatThrows"); - expect(stack).toMatch("outerFunctionThatThrows"); + expect(stack).toMatch("innerFunction"); + expect(stack).toMatch("outerFunction"); }); From 817b4e728a962a53c5472616742ac3860eca77f3 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 9 Oct 2019 12:38:15 +0200 Subject: [PATCH 33/36] remove generic from ErrorType --- src/lualib/Error.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lualib/Error.ts b/src/lualib/Error.ts index a6dba1a79..c91223f65 100644 --- a/src/lualib/Error.ts +++ b/src/lualib/Error.ts @@ -1,6 +1,6 @@ -interface ErrorType extends Function { +interface ErrorType { name: string; - new (...args: any[]): T; + new (...args: any[]): Error; } function __TS__GetErrorStack(constructor: Function): string { @@ -32,7 +32,7 @@ function __TS__WrapErrorToString(getDescription: (this: T) => s }; } -function __TS__InitErrorClass(Type: ErrorType, name: string): any { +function __TS__InitErrorClass(Type: ErrorType, name: string): any { Type.name = name; return setmetatable(Type, { __call: (_self: any, message: string) => new Type(message), @@ -40,7 +40,7 @@ function __TS__InitErrorClass(Type: ErrorType, name: string): any { } Error = __TS__InitErrorClass( - class { + class implements Error { public name = "Error"; public stack: string; From 7b23ae316ab173d67dcb24060703c91390a04847 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 9 Oct 2019 12:50:12 +0200 Subject: [PATCH 34/36] add info string to tests --- test/unit/error.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index dd6177504..bd2f9883f 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -295,7 +295,7 @@ test.each([ `undefined`, `{ x: "error object" }`, `() => "error function"`, -])("throw and catch arbitrary types", error => { +])("throw and catch %s", error => { util.testFunction` try { throw ${error}; @@ -312,7 +312,7 @@ test.each([ const builtinErrors = ["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"]; test.each([...builtinErrors, ...builtinErrors.map(type => `new ${type}`)])( - "test builtin error properties", + "test builtin error %s", errorType => { util.testFunction` const error = ${errorType}(); From d6d285547135214e6819813b72256859cc342f65 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 9 Oct 2019 12:53:55 +0200 Subject: [PATCH 35/36] style changes --- test/unit/error.spec.ts | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index bd2f9883f..412f0fdf8 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -297,31 +297,28 @@ test.each([ `() => "error function"`, ])("throw and catch %s", error => { util.testFunction` - try { - throw ${error}; - } catch (error) { - if (typeof error == 'function') { - return error(); - } else { - return error; - } + try { + throw ${error}; + } catch (error) { + if (typeof error == 'function') { + return error(); + } else { + return error; } + } `.expectToMatchJsResult(); }); const builtinErrors = ["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"]; -test.each([...builtinErrors, ...builtinErrors.map(type => `new ${type}`)])( - "test builtin error %s", - errorType => { - util.testFunction` - const error = ${errorType}(); - return { name: error.name, message: error.message, string: error.toString() }; - `.expectToMatchJsResult(); - } -); +test.each([...builtinErrors, ...builtinErrors.map(type => `new ${type}`)])("test builtin error %s", errorType => { + util.testFunction` + const error = ${errorType}(); + return { name: error.name, message: error.message, string: error.toString() }; + `.expectToMatchJsResult(); +}); -test.each([...builtinErrors, `CustomError`])("get stack from %s", errorType => { +test.each([...builtinErrors, "CustomError"])("get stack from %s", errorType => { const stack = util.testFunction` class CustomError extends Error { public name = "CustomError"; @@ -334,7 +331,7 @@ test.each([...builtinErrors, `CustomError`])("get stack from %s", errorType => { outerFunction(); return stack; - `.getLuaExecutionResult(); + `.getLuaExecutionResult(); expect(stack).toMatch("innerFunction"); expect(stack).toMatch("outerFunction"); From 3dea2cc163c03f445ee7d24362108ed922cea614 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Wed, 9 Oct 2019 13:12:34 +0200 Subject: [PATCH 36/36] style changes --- test/unit/error.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 412f0fdf8..e1959abb8 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -306,16 +306,16 @@ test.each([ return error; } } - `.expectToMatchJsResult(); + `.expectToMatchJsResult(); }); const builtinErrors = ["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"]; -test.each([...builtinErrors, ...builtinErrors.map(type => `new ${type}`)])("test builtin error %s", errorType => { +test.each([...builtinErrors, ...builtinErrors.map(type => `new ${type}`)])("%s properties", errorType => { util.testFunction` - const error = ${errorType}(); - return { name: error.name, message: error.message, string: error.toString() }; - `.expectToMatchJsResult(); + const error = ${errorType}(); + return { name: error.name, message: error.message, string: error.toString() }; + `.expectToMatchJsResult(); }); test.each([...builtinErrors, "CustomError"])("get stack from %s", errorType => { @@ -331,7 +331,7 @@ test.each([...builtinErrors, "CustomError"])("get stack from %s", errorType => { outerFunction(); return stack; - `.getLuaExecutionResult(); + `.getLuaExecutionResult(); expect(stack).toMatch("innerFunction"); expect(stack).toMatch("outerFunction");