From cfb6e2b388c854df2bb30f97c9cda8c4cd61595d Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Tue, 24 Mar 2026 21:29:17 +0000 Subject: [PATCH 01/19] fix try/finally not re-throwing errors when there is no catch clause (#1692) * fix try/finally not re-throwing errors when there is no catch clause (#1667) When a try block had a finally but no catch, pcall's error result was discarded, silently swallowing the exception. Now the error is captured and re-thrown after the finally block executes. * Nicer test return values --- src/transformation/visitors/errors.ts | 20 ++++++++++++++++++++ test/unit/error.spec.ts | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/transformation/visitors/errors.ts b/src/transformation/visitors/errors.ts index f81519e4c..7233d709c 100644 --- a/src/transformation/visitors/errors.ts +++ b/src/transformation/visitors/errors.ts @@ -146,6 +146,10 @@ export const transformTryStatement: FunctionVisitor = (statemen returnedIdentifier, lua.SyntaxKind.AndOperator ); + } else if (statement.finallyBlock) { + // try without catch, but with finally โ€” need to capture error for re-throw + const errorIdentifier = lua.createIdentifier("____error"); + result.push(lua.createVariableDeclarationStatement([tryResultIdentifier, errorIdentifier], tryCall)); } else { // try without return or catch result.push(lua.createExpressionStatement(tryCall)); @@ -155,6 +159,22 @@ export const transformTryStatement: FunctionVisitor = (statemen result.push(...context.transformStatements(statement.finallyBlock)); } + // Re-throw error if try had no catch but had a finally + if (!statement.catchClause && statement.finallyBlock) { + const notTryCondition = lua.createUnaryExpression( + lua.cloneIdentifier(tryResultIdentifier), + lua.SyntaxKind.NotOperator + ); + const errorIdentifier = lua.createIdentifier("____error"); + const rethrow = lua.createExpressionStatement( + lua.createCallExpression(lua.createIdentifier("error"), [ + lua.cloneIdentifier(errorIdentifier), + lua.createNumericLiteral(0), + ]) + ); + result.push(lua.createIfStatement(notTryCondition, lua.createBlock([rethrow]))); + } + if (returnCondition && returnedIdentifier) { const returnValues: lua.Expression[] = []; diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index af69d7e1d..78444c60a 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -256,6 +256,28 @@ test("multi return from catch->finally", () => { .expectToMatchJsResult(); }); +test("throw propagates through finally to outer catch", () => { + util.testFunction` + function thrower() { + try { + throw "Error"; + } finally { + } + } + + function caller() { + try { + thrower(); + return "NoCatch"; + } catch (e) { + return e; + } + } + + return caller(); + `.expectToMatchJsResult(); +}); + test("return from nested finally", () => { util.testFunction` let x = ""; From c3dc829944358a793a566b2f5d4ea1653aea26dd Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Tue, 24 Mar 2026 21:40:14 +0000 Subject: [PATCH 02/19] fix Set iterator not reflecting mutations after creation (#1691) * fix Set iterator not reflecting mutations after creation (#1671) Set.values(), keys(), and entries() eagerly captured firstKey at iterator creation time. If the Set was mutated (e.g. via delete) before the iterator was consumed, the iterator still saw the stale firstKey. Read firstKey lazily on the first next() call instead. * Add tests for Set iterator mutation and exhaustion behavior * Fix Map iterator mutation and add tests for mutation and exhaustion behavior --- src/lualib/Map.ts | 42 ++++++++++++++++++++++++---------- src/lualib/Set.ts | 42 ++++++++++++++++++++++++---------- test/unit/builtins/map.spec.ts | 38 ++++++++++++++++++++++++++++++ test/unit/builtins/set.spec.ts | 42 ++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 24 deletions(-) diff --git a/src/lualib/Map.ts b/src/lualib/Map.ts index 16fa7e453..c2bcb43a4 100644 --- a/src/lualib/Map.ts +++ b/src/lualib/Map.ts @@ -113,46 +113,64 @@ export class Map { } public entries(): IterableIterator<[K, V]> { + const getFirstKey = () => this.firstKey; const { items, nextKey } = this; - let key = this.firstKey; + let key: K | undefined; + let started = false; return { [Symbol.iterator](): IterableIterator<[K, V]> { return this; }, next(): IteratorResult<[K, V]> { - const result = { done: !key, value: [key, items.get(key!)] as [K, V] }; - key = nextKey.get(key!); - return result; + if (!started) { + started = true; + key = getFirstKey(); + } else { + key = nextKey.get(key!); + } + return { done: !key, value: [key!, items.get(key!)] as [K, V] }; }, }; } public keys(): IterableIterator { + const getFirstKey = () => this.firstKey; const nextKey = this.nextKey; - let key = this.firstKey; + let key: K | undefined; + let started = false; return { [Symbol.iterator](): IterableIterator { return this; }, next(): IteratorResult { - const result = { done: !key, value: key }; - key = nextKey.get(key!); - return result as IteratorResult; + if (!started) { + started = true; + key = getFirstKey(); + } else { + key = nextKey.get(key!); + } + return { done: !key, value: key! }; }, }; } public values(): IterableIterator { + const getFirstKey = () => this.firstKey; const { items, nextKey } = this; - let key = this.firstKey; + let key: K | undefined; + let started = false; return { [Symbol.iterator](): IterableIterator { return this; }, next(): IteratorResult { - const result = { done: !key, value: items.get(key!) }; - key = nextKey.get(key!); - return result; + if (!started) { + started = true; + key = getFirstKey(); + } else { + key = nextKey.get(key!); + } + return { done: !key, value: items.get(key!) }; }, }; } diff --git a/src/lualib/Set.ts b/src/lualib/Set.ts index b46555edf..e302e4094 100644 --- a/src/lualib/Set.ts +++ b/src/lualib/Set.ts @@ -102,46 +102,64 @@ export class Set { } public entries(): IterableIterator<[T, T]> { + const getFirstKey = () => this.firstKey; const nextKey = this.nextKey; - let key: T = this.firstKey!; + let key: T | undefined; + let started = false; return { [Symbol.iterator](): IterableIterator<[T, T]> { return this; }, next(): IteratorResult<[T, T]> { - const result = { done: !key, value: [key, key] as [T, T] }; - key = nextKey.get(key); - return result; + if (!started) { + started = true; + key = getFirstKey(); + } else { + key = nextKey.get(key!); + } + return { done: !key, value: [key!, key!] as [T, T] }; }, }; } public keys(): IterableIterator { + const getFirstKey = () => this.firstKey; const nextKey = this.nextKey; - let key: T = this.firstKey!; + let key: T | undefined; + let started = false; return { [Symbol.iterator](): IterableIterator { return this; }, next(): IteratorResult { - const result = { done: !key, value: key }; - key = nextKey.get(key); - return result; + if (!started) { + started = true; + key = getFirstKey(); + } else { + key = nextKey.get(key!); + } + return { done: !key, value: key! }; }, }; } public values(): IterableIterator { + const getFirstKey = () => this.firstKey; const nextKey = this.nextKey; - let key: T = this.firstKey!; + let key: T | undefined; + let started = false; return { [Symbol.iterator](): IterableIterator { return this; }, next(): IteratorResult { - const result = { done: !key, value: key }; - key = nextKey.get(key); - return result; + if (!started) { + started = true; + key = getFirstKey(); + } else { + key = nextKey.get(key!); + } + return { done: !key, value: key! }; }, }; } diff --git a/test/unit/builtins/map.spec.ts b/test/unit/builtins/map.spec.ts index f2e1cbfbe..27b539589 100644 --- a/test/unit/builtins/map.spec.ts +++ b/test/unit/builtins/map.spec.ts @@ -210,6 +210,44 @@ describe.each(iterationMethods)("map.%s() preserves insertion order", iterationM }); }); +describe.each(iterationMethods)("map.%s() handles mutation", iterationMethod => { + test("iterator persists after delete", () => { + util.testFunction` + const map = new Map([[1, "a"], [2, "b"]]); + const iter = map.${iterationMethod}(); + map.delete(1); + return iter.next().value; + `.expectToMatchJsResult(); + }); + + test("iterator with delete and add between iterations", () => { + util.testFunction` + const map = new Map([[1, "a"], [2, "b"], [3, "c"]]); + const iter = map.${iterationMethod}(); + iter.next(); // 1 + map.delete(2); + map.set(4, "d"); + const results: IteratorResult[] = []; + let r = iter.next(); + while (!r.done) { results.push({ done: r.done, value: r.value }); r = iter.next(); } + return results; + `.expectToMatchJsResult(); + }); + + test("iterator does not restart after exhaustion", () => { + util.testFunction` + const map = new Map([[1, "a"], [2, "b"]]); + const iter = map.${iterationMethod}(); + const results: boolean[] = []; + results.push(iter.next().done!); + results.push(iter.next().done!); + results.push(iter.next().done!); // should be done + results.push(iter.next().done!); // should still be done, not restart + return results; + `.expectToMatchJsResult(); + }); +}); + describe("Map.groupBy", () => { test("empty", () => { util.testFunction` diff --git a/test/unit/builtins/set.spec.ts b/test/unit/builtins/set.spec.ts index a03f27ab2..65ee4aef0 100644 --- a/test/unit/builtins/set.spec.ts +++ b/test/unit/builtins/set.spec.ts @@ -195,6 +195,48 @@ describe.each(iterationMethods)("set.%s() preserves insertion order", iterationM }); }); +describe.each(iterationMethods)("set.%s() handles mutation", iterationMethod => { + test("iterator persists after delete", () => { + util.testFunction` + const set1 = new Set(); + set1.add(42); + set1.add("forty two"); + + const iterator1 = set1.${iterationMethod}(); + set1.delete(42); + + return iterator1.next().value; + `.expectToMatchJsResult(); + }); + + test("iterator with delete and add between iterations", () => { + util.testFunction` + const set = new Set([1, 2, 3]); + const iter = set.${iterationMethod}(); + iter.next(); // 1 + set.delete(2); + set.add(4); + const results: IteratorResult[] = []; + let r = iter.next(); + while (!r.done) { results.push({ done: r.done, value: r.value }); r = iter.next(); } + return results; + `.expectToMatchJsResult(); + }); + + test("iterator does not restart after exhaustion", () => { + util.testFunction` + const set = new Set([1, 2]); + const iter = set.${iterationMethod}(); + const results: boolean[] = []; + results.push(iter.next().done!); + results.push(iter.next().done!); + results.push(iter.next().done!); // should be done + results.push(iter.next().done!); // should still be done, not restart + return results; + `.expectToMatchJsResult(); + }); +}); + test("instanceof Set without creating set", () => { util.testFunction` const myset = 3 as any; From 2bb18e12b03570978e4c5373c953925d4e35cb09 Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Sat, 28 Mar 2026 12:55:05 +0000 Subject: [PATCH 03/19] Fix/dead code after break (#1694) * strip dead code after break in printer * change tests from translation to no execution error * remove unused expectAllVersions * improve break dead code tests: target affected versions, check output values --- src/LuaPrinter.ts | 2 +- test/unit/printer/deadCodeAfterBreak.spec.ts | 93 ++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 test/unit/printer/deadCodeAfterBreak.spec.ts diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index b0a02dfaf..26b55421b 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -328,7 +328,7 @@ export class LuaPrinter { statementNodes.push(node); - if (lua.isReturnStatement(statement)) break; + if (lua.isReturnStatement(statement) || lua.isBreakStatement(statement)) break; } return statementNodes.length > 0 ? [...intersperse(statementNodes, "\n"), "\n"] : []; diff --git a/test/unit/printer/deadCodeAfterBreak.spec.ts b/test/unit/printer/deadCodeAfterBreak.spec.ts new file mode 100644 index 000000000..fe1b9073c --- /dev/null +++ b/test/unit/printer/deadCodeAfterBreak.spec.ts @@ -0,0 +1,93 @@ +import * as tstl from "../../../src"; +import * as util from "../../util"; + +// In Lua 5.0, 5.1, and LuaJIT, break must be the last statement in a block. +// Any code after break is a syntax error (e.g. `while true do break; local b = 8 end` +// fails with "'end' expected near 'local'"). Lua 5.2+ relaxed this restriction. +// TSTL should strip dead code after break on all targets to avoid these errors. + +function expectNoDeadCode(builder: util.TestBuilder) { + const lua = builder.getMainLuaCodeChunk(); + expect(lua).not.toContain("local b = 8"); +} + +const affectedVersions: Record void) | boolean> = { + [tstl.LuaTarget.Universal]: false, + [tstl.LuaTarget.Lua50]: builder => builder.tap(expectNoDeadCode).expectToMatchJsResult(), + [tstl.LuaTarget.Lua51]: builder => builder.tap(expectNoDeadCode).expectToMatchJsResult(), + [tstl.LuaTarget.Lua52]: false, + [tstl.LuaTarget.Lua53]: false, + [tstl.LuaTarget.Lua54]: false, + [tstl.LuaTarget.Lua55]: false, + [tstl.LuaTarget.LuaJIT]: builder => builder.tap(expectNoDeadCode), + [tstl.LuaTarget.Luau]: false, +}; + +util.testEachVersion( + "for dead code after break", + () => util.testFunction` + let result = 0; + for (let i = 0; i < 10; i++) { result = i; break; const b = 8; } + return result; + `, + affectedVersions +); + +util.testEachVersion( + "for..in dead code after break", + () => util.testFunction` + let result = ""; + for (let a in {"a": 5, "b": 8}) { result = a; break; const b = 8; } + return result; + `, + affectedVersions +); + +util.testEachVersion( + "for..of dead code after break", + () => util.testFunction` + let result = 0; + for (let a of [1,2,4]) { result = a; break; const b = 8; } + return result; + `, + affectedVersions +); + +util.testEachVersion( + "while dead code after break", + () => util.testFunction` + let result = "done"; + while (true) { break; const b = 8; } + return result; + `, + affectedVersions +); + +util.testEachVersion( + "switch dead code after break", + () => util.testFunction` + let result = "none"; + switch ("abc" as string) { + case "def": + result = "def"; + break; + let abc = 4; + case "abc": + result = "abc"; + break; + let def = 6; + } + return result; + `, + affectedVersions +); + +util.testEachVersion( + "do-while dead code after break", + () => util.testFunction` + let result = "done"; + do { break; const b = 8; } while (true); + return result; + `, + affectedVersions +); From 8c856f1d4f0c2c60bcb30718cf9c69bc70258b7e Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Sat, 28 Mar 2026 12:58:01 +0000 Subject: [PATCH 04/19] fix Promise.finally() to return new promise via .then() (#1696) * fix Promise.finally() to return new promise via .then() * simplify Promise.finally test to use expectToMatchJsResult Remove redundant tests that relied on synchronous polyfill behavior differing from JS. Keep only the identity check which matches JS. * add tests for Promise.finally value preservation and rejection passthrough Fix issue reference (#1660, not #1659). --- src/lualib/Promise.ts | 40 +++++++++++-------------- test/unit/builtins/promise.spec.ts | 48 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/src/lualib/Promise.ts b/src/lualib/Promise.ts index c5b6aa2a5..187e5380d 100644 --- a/src/lualib/Promise.ts +++ b/src/lualib/Promise.ts @@ -45,7 +45,6 @@ export class __TS__Promise implements Promise { private fulfilledCallbacks: Array> = []; private rejectedCallbacks: PromiseReject[] = []; - private finallyCallbacks: Array<() => void> = []; // @ts-ignore public [Symbol.toStringTag]: string; // Required to implement interface, no output Lua @@ -124,16 +123,23 @@ export class __TS__Promise implements Promise { } // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally + // Delegates to .then() so that a new Promise is returned (per ES spec ยง27.2.5.3) + // and the original fulfillment value / rejection reason is preserved. public finally(onFinally?: () => void): Promise { - if (onFinally) { - this.finallyCallbacks.push(onFinally); - - if (this.state !== PromiseState.Pending) { - // If promise already resolved or rejected, immediately fire finally callback - onFinally(); - } - } - return this; + return this.then( + onFinally + ? (value: T): T => { + onFinally(); + return value; + } + : undefined, + onFinally + ? (reason: any): never => { + onFinally(); + throw reason; + } + : undefined + ); } private resolve(value: T | PromiseLike): void { @@ -168,25 +174,13 @@ export class __TS__Promise implements Promise { private invokeCallbacks(callbacks: ReadonlyArray<(value: T) => void>, value: T): void { const callbacksLength = callbacks.length; - const finallyCallbacks = this.finallyCallbacks; - const finallyCallbacksLength = finallyCallbacks.length; if (callbacksLength !== 0) { for (const i of $range(1, callbacksLength - 1)) { callbacks[i - 1](value); } // Tail call optimization for a common case. - if (finallyCallbacksLength === 0) { - return callbacks[callbacksLength - 1](value); - } - callbacks[callbacksLength - 1](value); - } - - if (finallyCallbacksLength !== 0) { - for (const i of $range(1, finallyCallbacksLength - 1)) { - finallyCallbacks[i - 1](); - } - return finallyCallbacks[finallyCallbacksLength - 1](); + return callbacks[callbacksLength - 1](value); } } diff --git a/test/unit/builtins/promise.spec.ts b/test/unit/builtins/promise.spec.ts index cb18db994..f323b030f 100644 --- a/test/unit/builtins/promise.spec.ts +++ b/test/unit/builtins/promise.spec.ts @@ -1323,3 +1323,51 @@ describe("Promise.race", () => { }); }); }); + +// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1660 +describe("Promise.finally", () => { + test("returns a different promise instance", () => { + util.testFunction` + const p1 = new Promise(() => {}); + const p2 = p1.finally(); + return p1 === p2; + `.expectToMatchJsResult(); + }); + + test("preserves fulfillment value", () => { + util.testFunction` + const result = Promise.resolve(42).finally(() => {}) as any; + return result.value; + `.expectToEqual(42); + }); + + test("preserves rejection reason", () => { + util.testFunction` + const result = Promise.reject("err").finally(() => {}) as any; + return result.rejectionReason; + `.expectToEqual("err"); + }); + + test("callback executes on fulfillment", () => { + util.testFunction` + let called = false; + Promise.resolve(1).finally(() => { called = true; }); + return called; + `.expectToEqual(true); + }); + + test("callback executes on rejection", () => { + util.testFunction` + let called = false; + Promise.reject("err").finally(() => { called = true; }); + return called; + `.expectToEqual(true); + }); + + test("finally with undefined callback", () => { + util.testFunction` + const result = Promise.resolve(99).finally(undefined) as any; + return result.value; + `.expectToEqual(99); + }); +}); From 57459c6e0f2077f8166a0302fc318afbf697517b Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Sat, 28 Mar 2026 13:04:23 +0000 Subject: [PATCH 05/19] fix Object.defineProperty sharing values across instances (#1697) * fix Object.defineProperty sharing values across instances When Object.defineProperty is called on an instance (not a prototype), create a per-instance metatable if the shared metatable already has descriptors, so that each instance gets its own descriptor storage. Fixes #1625 * fix Object.defineProperty instance isolation for first instance Use a marker to always create per-instance metatables, not just when _descriptors already exists on the shared metatable. Add more thorough instance isolation tests. --- src/lualib/SetDescriptor.ts | 11 +++++ test/unit/builtins/object.spec.ts | 75 +++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/src/lualib/SetDescriptor.ts b/src/lualib/SetDescriptor.ts index 4339c8cc1..ccb4bf3f0 100644 --- a/src/lualib/SetDescriptor.ts +++ b/src/lualib/SetDescriptor.ts @@ -26,6 +26,17 @@ export function __TS__SetDescriptor( setmetatable(target, metatable); } + // When setting a descriptor on an instance (not a prototype), ensure it has + // its own metatable so descriptors are not shared across instances. + // See: https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1625 + if (!isPrototype && !rawget(metatable, "_isOwnDescriptorMetatable")) { + const instanceMetatable: any = {}; + instanceMetatable._isOwnDescriptorMetatable = true; + setmetatable(instanceMetatable, metatable); + setmetatable(target, instanceMetatable); + metatable = instanceMetatable; + } + const value = rawget(target, key); if (value !== undefined) rawset(target, key, undefined); diff --git a/test/unit/builtins/object.spec.ts b/test/unit/builtins/object.spec.ts index 1edc8a9d6..dfed73502 100644 --- a/test/unit/builtins/object.spec.ts +++ b/test/unit/builtins/object.spec.ts @@ -196,6 +196,81 @@ describe("Object.defineProperty", () => { return { prop: foo.bar, err }; `.expectToMatchJsResult(); }); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1625 + test("instance isolation", () => { + util.testFunction` + class Test { + declare obj: object; + constructor() { + Object.defineProperty(this, "obj", { value: {}, writable: true, configurable: true }); + } + } + const t1 = new Test(); + const t2 = new Test(); + return t1.obj === t2.obj; + `.expectToMatchJsResult(); + }); + + test("instance isolation with three instances", () => { + util.testFunction` + class Test { + declare obj: object; + constructor() { + Object.defineProperty(this, "obj", { value: {}, writable: true, configurable: true }); + } + } + const t1 = new Test(); + const t2 = new Test(); + const t3 = new Test(); + return [t1.obj === t2.obj, t1.obj === t3.obj, t2.obj === t3.obj]; + `.expectToMatchJsResult(); + }); + + test("instance isolation with mutation", () => { + util.testFunction` + class Test { + declare value: number; + constructor(v: number) { + Object.defineProperty(this, "value", { value: v, writable: true, configurable: true }); + } + } + const t1 = new Test(1); + const t2 = new Test(2); + return [t1.value, t2.value]; + `.expectToMatchJsResult(); + }); + + test("instance isolation with multiple properties", () => { + util.testFunction` + class Test { + declare a: string; + declare b: string; + constructor(a: string, b: string) { + Object.defineProperty(this, "a", { value: a, writable: true, configurable: true }); + Object.defineProperty(this, "b", { value: b, writable: true, configurable: true }); + } + } + const t1 = new Test("x", "y"); + const t2 = new Test("p", "q"); + return [t1.a, t1.b, t2.a, t2.b]; + `.expectToMatchJsResult(); + }); + + test("instance isolation preserves prototype methods", () => { + util.testFunction` + class Test { + declare val: number; + constructor(v: number) { + Object.defineProperty(this, "val", { value: v, writable: true, configurable: true }); + } + getVal() { return this.val; } + } + const t1 = new Test(10); + const t2 = new Test(20); + return [t1.getVal(), t2.getVal()]; + `.expectToMatchJsResult(); + }); }); describe("Object.getOwnPropertyDescriptor", () => { From ec26bdf4e15be9db6119e4ad3cb269aa45d7bab2 Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Sun, 29 Mar 2026 19:46:48 +0100 Subject: [PATCH 06/19] fix try/finally rethrow when try block has return paths (#1699) * fix try/finally rethrow when try block has return paths Fixes a regression from #1692 where try/finally (no catch) with both return and throw paths silently lost the error. The re-throw used an undeclared ____error variable; use ____hasReturned instead, which is where pcall places the error on failure. * add test for try/finally with return+throw and non-empty finally body --- src/transformation/visitors/errors.ts | 13 ++--- test/unit/error.spec.ts | 76 +++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/transformation/visitors/errors.ts b/src/transformation/visitors/errors.ts index 7233d709c..b2c9d36d1 100644 --- a/src/transformation/visitors/errors.ts +++ b/src/transformation/visitors/errors.ts @@ -159,18 +159,19 @@ export const transformTryStatement: FunctionVisitor = (statemen result.push(...context.transformStatements(statement.finallyBlock)); } - // Re-throw error if try had no catch but had a finally + // Re-throw error if try had no catch but had a finally. + // On pcall failure the error is the second return value, which lands in + // ____hasReturned (when functionReturned) or ____error (otherwise). if (!statement.catchClause && statement.finallyBlock) { const notTryCondition = lua.createUnaryExpression( lua.cloneIdentifier(tryResultIdentifier), lua.SyntaxKind.NotOperator ); - const errorIdentifier = lua.createIdentifier("____error"); + const errorIdentifier = tryScope.functionReturned + ? lua.cloneIdentifier(returnedIdentifier) + : lua.createIdentifier("____error"); const rethrow = lua.createExpressionStatement( - lua.createCallExpression(lua.createIdentifier("error"), [ - lua.cloneIdentifier(errorIdentifier), - lua.createNumericLiteral(0), - ]) + lua.createCallExpression(lua.createIdentifier("error"), [errorIdentifier, lua.createNumericLiteral(0)]) ); result.push(lua.createIfStatement(notTryCondition, lua.createBlock([rethrow]))); } diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 78444c60a..89ceb49df 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -417,6 +417,82 @@ test("sourceMapTraceback maps anonymous function locations in .lua files (#1665) expect(result).toContain(""); }); +test("try/finally rethrow preserves error value", () => { + util.testFunction` + function foo() { + try { + throw "oops"; + } finally { + } + } + try { foo(); return "no error"; } catch(e) { return e; } + `.expectToMatchJsResult(); +}); + +test("try/finally with return and throw paths", () => { + util.testFunction` + function foo(shouldReturn: boolean) { + try { + if (shouldReturn) return "returned"; + throw "thrown"; + } finally { + } + } + const results: any[] = []; + results.push(foo(true)); + try { foo(false); } catch(e) { results.push(e); } + return results; + `.expectToMatchJsResult(); +}); + +test("try/finally runs finally side effect before rethrow", () => { + util.testFunction` + let sideEffect = false; + function foo() { + try { + throw "err"; + } finally { + sideEffect = true; + } + } + try { foo(); } catch(e) {} + return sideEffect; + `.expectToMatchJsResult(); +}); + +test("try/finally with return and throw paths and non-empty finally body", () => { + util.testFunction` + let sideEffect = false; + function foo(shouldReturn: boolean) { + try { + if (shouldReturn) return "ok"; + throw "err"; + } finally { + sideEffect = true; + } + } + const results: any[] = []; + results.push(foo(true)); + results.push(sideEffect); + sideEffect = false; + try { foo(false); } catch(e) { results.push(e); } + results.push(sideEffect); + return results; + `.expectToMatchJsResult(); +}); + +test("try/finally rethrow with non-string error", () => { + util.testFunction` + function foo() { + try { + throw 42; + } finally { + } + } + try { foo(); return "no error"; } catch(e) { return e; } + `.expectToMatchJsResult(); +}); + util.testEachVersion( "error stacktrace omits constructor and __TS_New", () => util.testFunction` From 84ef0b2d09b8ed1b1d91a9066db8819a4e13ff15 Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Wed, 1 Apr 2026 19:50:15 +0100 Subject: [PATCH 07/19] fix Math.atan2 emitting math.atan2 instead of math.atan for Lua 5.4+ (#1700) * fix Math.atan2 emitting math.atan2 instead of math.atan for Lua 5.4+ math.atan2 was removed in Lua 5.3 (replaced by two-arg math.atan). The codegen only special-cased 5.3 but not 5.4 or 5.5, which also lack math.atan2 (5.4 has it behind a compat flag, 5.5 drops it). * invert atan2 condition to default future Lua targets to math.atan Enumerate the older targets that need math.atan2 instead of the newer ones that need math.atan, so any future Lua target (5.6+) defaults to the modern behavior rather than silently regressing. --- src/transformation/builtins/math.ts | 10 ++++++++-- test/unit/builtins/math.spec.ts | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/transformation/builtins/math.ts b/src/transformation/builtins/math.ts index ab831d52f..82a118ec1 100644 --- a/src/transformation/builtins/math.ts +++ b/src/transformation/builtins/math.ts @@ -42,14 +42,20 @@ export function transformMathCall( const expressionName = calledMethod.name.text; switch (expressionName) { - // Lua 5.3: math.atan(y, x) + // Lua 5.3+: math.atan(y, x) // Otherwise: math.atan2(y, x) case "atan2": { if (context.luaTarget === LuaTarget.Universal) { return transformLuaLibFunction(context, LuaLibFeature.MathAtan2, node, ...params); } - const method = lua.createStringLiteral(context.luaTarget === LuaTarget.Lua53 ? "atan" : "atan2"); + const useAtan2 = + context.luaTarget === LuaTarget.Lua50 || + context.luaTarget === LuaTarget.Lua51 || + context.luaTarget === LuaTarget.Lua52 || + context.luaTarget === LuaTarget.LuaJIT || + context.luaTarget === LuaTarget.Luau; + const method = lua.createStringLiteral(useAtan2 ? "atan2" : "atan"); return lua.createCallExpression(lua.createTableIndexExpression(math, method), params, node); } diff --git a/test/unit/builtins/math.spec.ts b/test/unit/builtins/math.spec.ts index f3ff1f306..d09b331cd 100644 --- a/test/unit/builtins/math.spec.ts +++ b/test/unit/builtins/math.spec.ts @@ -95,8 +95,8 @@ util.testEachVersion("Math.atan2", () => util.testExpression`Math.atan2(4, 5)`, [tstl.LuaTarget.Lua51]: builder => builder.tap(expectMathAtan2), [tstl.LuaTarget.Lua52]: builder => builder.tap(expectMathAtan2), [tstl.LuaTarget.Lua53]: builder => builder.tap(expectMathAtan), - [tstl.LuaTarget.Lua54]: builder => builder.tap(expectMathAtan2), - [tstl.LuaTarget.Lua55]: builder => builder.tap(expectMathAtan2), + [tstl.LuaTarget.Lua54]: builder => builder.tap(expectMathAtan), + [tstl.LuaTarget.Lua55]: builder => builder.tap(expectMathAtan), [tstl.LuaTarget.Luau]: builder => builder.tap(expectMathAtan2), }); From c043cdcc68902924ae34b2015e64fe05e2a29cea Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Wed, 1 Apr 2026 19:52:25 +0100 Subject: [PATCH 08/19] fix Number.MAX_SAFE_INTEGER, MIN_SAFE_INTEGER, MIN_VALUE, MAX_VALUE emitting wrong Lua (#1698) All four constants evaluated to Infinity or -Infinity in Lua. Additionally, MIN_SAFE_INTEGER and MIN_VALUE emitted identical code, as did MAX_SAFE_INTEGER and MAX_VALUE. The existing test did not catch this because it only checked Lua-side results with comparisons that the wrong values accidentally satisfied. --- src/transformation/builtins/number.ts | 55 +++++++++++++++++++++------ test/unit/builtins/numbers.spec.ts | 24 +++++++----- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/transformation/builtins/number.ts b/src/transformation/builtins/number.ts index 17c44b6fd..ecb98da64 100644 --- a/src/transformation/builtins/number.ts +++ b/src/transformation/builtins/number.ts @@ -77,31 +77,62 @@ export function transformNumberProperty( node ); case "MIN_VALUE": + // 2 ^ -1074 = 5e-324 (smallest positive double) return lua.createBinaryExpression( - lua.createNumericLiteral(-2), - lua.createNumericLiteral(1074), + lua.createNumericLiteral(2), + lua.createNumericLiteral(-1074), lua.SyntaxKind.PowerOperator, node ); case "MIN_SAFE_INTEGER": - return lua.createBinaryExpression( - lua.createNumericLiteral(-2), - lua.createNumericLiteral(1074), - lua.SyntaxKind.PowerOperator, + // -(2 ^ 53 - 1) = -9007199254740991 + return lua.createUnaryExpression( + lua.createParenthesizedExpression( + lua.createBinaryExpression( + lua.createBinaryExpression( + lua.createNumericLiteral(2), + lua.createNumericLiteral(53), + lua.SyntaxKind.PowerOperator + ), + lua.createNumericLiteral(1), + lua.SyntaxKind.SubtractionOperator + ) + ), + lua.SyntaxKind.NegationOperator, node ); case "MAX_SAFE_INTEGER": + // 2 ^ 53 - 1 = 9007199254740991 return lua.createBinaryExpression( - lua.createNumericLiteral(2), - lua.createNumericLiteral(1024), - lua.SyntaxKind.PowerOperator, + lua.createBinaryExpression( + lua.createNumericLiteral(2), + lua.createNumericLiteral(53), + lua.SyntaxKind.PowerOperator + ), + lua.createNumericLiteral(1), + lua.SyntaxKind.SubtractionOperator, node ); case "MAX_VALUE": + // (2 - 2 ^ -52) * 2 ^ 1023 = 1.7976931348623157e+308 return lua.createBinaryExpression( - lua.createNumericLiteral(2), - lua.createNumericLiteral(1024), - lua.SyntaxKind.PowerOperator, + lua.createParenthesizedExpression( + lua.createBinaryExpression( + lua.createNumericLiteral(2), + lua.createBinaryExpression( + lua.createNumericLiteral(2), + lua.createNumericLiteral(-52), + lua.SyntaxKind.PowerOperator + ), + lua.SyntaxKind.SubtractionOperator + ) + ), + lua.createBinaryExpression( + lua.createNumericLiteral(2), + lua.createNumericLiteral(1023), + lua.SyntaxKind.PowerOperator + ), + lua.SyntaxKind.MultiplicationOperator, node ); diff --git a/test/unit/builtins/numbers.spec.ts b/test/unit/builtins/numbers.spec.ts index aa3a8927d..9570e4af2 100644 --- a/test/unit/builtins/numbers.spec.ts +++ b/test/unit/builtins/numbers.spec.ts @@ -212,17 +212,23 @@ test.each(["42", "undefined"])("prototype call on nullable number (%p)", value = }); test.each([ - "Number.NEGATIVE_INFINITY <= Number.MIN_VALUE", - "Number.MIN_VALUE <= Number.MIN_SAFE_INTEGER", - - "Number.MAX_SAFE_INTEGER <= Number.MAX_VALUE", - "Number.MAX_VALUE <= Number.POSITIVE_INFINITY", + // Full ordering: NEG_INF < MIN_SAFE < 0 < MIN_VALUE < EPSILON < MAX_SAFE < MAX_VALUE < POS_INF + "Number.NEGATIVE_INFINITY < Number.MIN_SAFE_INTEGER", "Number.MIN_SAFE_INTEGER < 0", - - "0 < Number.EPSILON", + "0 < Number.MIN_VALUE", + "Number.MIN_VALUE < Number.EPSILON", "Number.EPSILON < Number.MAX_SAFE_INTEGER", -])("Numer constants have correct relative sizes (%p)", comparison => { - util.testExpression(comparison).expectToEqual(true); + "Number.MAX_SAFE_INTEGER < Number.MAX_VALUE", + "Number.MAX_VALUE < Number.POSITIVE_INFINITY", + + // Verify specific values + "Number.MIN_VALUE > 0", + "Number.MIN_SAFE_INTEGER === -(2**53 - 1)", + "Number.MAX_SAFE_INTEGER === 2**53 - 1", + "Number.MAX_SAFE_INTEGER + 1 !== Number.MAX_SAFE_INTEGER", + "Number.MIN_SAFE_INTEGER - 1 !== Number.MIN_SAFE_INTEGER", +])("Number constants have correct relative sizes (%p)", comparison => { + util.testExpression(comparison).expectToMatchJsResult(); }); // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1629 From 5176fcd3a9fa9ca90cdf765723e539398d410f73 Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Fri, 3 Apr 2026 19:41:19 +0100 Subject: [PATCH 09/19] fix >>> (unsigned right shift) producing wrong results on Lua 5.3+ (#1703) * fix >>> (unsigned right shift) producing wrong results on Lua 5.3+ TS >>> is a logical right shift (zero-fills from the left), but TSTL was mapping it to Lua's >> which is arithmetic (sign-extends). This gave wrong results for negative numbers: e.g. -1 >>> 0 should be 4294967295 but produced -1. Fix by masking to unsigned 32-bit first: (left & 0xFFFFFFFF) >> right. The Lua 5.2 (bit32.rshift) and LuaJIT (bit.rshift) paths were already correct. * lint --- .../visitors/binary-expression/bit.ts | 16 ++++++++++ .../__snapshots__/expressions.spec.ts.snap | 12 ++++---- test/unit/expressions.spec.ts | 29 +++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/transformation/visitors/binary-expression/bit.ts b/src/transformation/visitors/binary-expression/bit.ts index 79e9c1f3c..5ae876630 100644 --- a/src/transformation/visitors/binary-expression/bit.ts +++ b/src/transformation/visitors/binary-expression/bit.ts @@ -75,6 +75,22 @@ export function transformBinaryBitOperation( case LuaTarget.Lua52: return transformBinaryBitLibOperation(node, left, right, operator, "bit32"); default: + // Lua 5.3+ `>>` is arithmetic (sign-extending), but TS `>>>` is logical (zero-fill). + // Emit `(left & 0xFFFFFFFF) >> right` to convert to unsigned 32-bit first. + if (operator === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken) { + const mask = lua.createBinaryExpression( + left, + lua.createNumericLiteral(0xffffffff, node), + lua.SyntaxKind.BitwiseAndOperator, + node + ); + return lua.createBinaryExpression( + lua.createParenthesizedExpression(mask, node), + right, + lua.SyntaxKind.BitwiseRightShiftOperator, + node + ); + } const luaOperator = transformBitOperatorToLuaOperator(context, node, operator); return lua.createBinaryExpression(left, right, luaOperator, node); } diff --git a/test/unit/__snapshots__/expressions.spec.ts.snap b/test/unit/__snapshots__/expressions.spec.ts.snap index e2adff370..720f7014e 100644 --- a/test/unit/__snapshots__/expressions.spec.ts.snap +++ b/test/unit/__snapshots__/expressions.spec.ts.snap @@ -374,14 +374,14 @@ return ____exports" exports[`Bitop [5.3] ("a>>>=b") 1`] = ` "local ____exports = {} -a = a >> b +a = (a & 4294967295) >> b ____exports.__result = a return ____exports" `; exports[`Bitop [5.3] ("a>>>b") 1`] = ` "local ____exports = {} -____exports.__result = a >> b +____exports.__result = (a & 4294967295) >> b return ____exports" `; @@ -445,14 +445,14 @@ return ____exports" exports[`Bitop [5.4] ("a>>>=b") 1`] = ` "local ____exports = {} -a = a >> b +a = (a & 4294967295) >> b ____exports.__result = a return ____exports" `; exports[`Bitop [5.4] ("a>>>b") 1`] = ` "local ____exports = {} -____exports.__result = a >> b +____exports.__result = (a & 4294967295) >> b return ____exports" `; @@ -516,14 +516,14 @@ return ____exports" exports[`Bitop [5.5] ("a>>>=b") 1`] = ` "local ____exports = {} -a = a >> b +a = (a & 4294967295) >> b ____exports.__result = a return ____exports" `; exports[`Bitop [5.5] ("a>>>b") 1`] = ` "local ____exports = {} -____exports.__result = a >> b +____exports.__result = (a & 4294967295) >> b return ____exports" `; diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index ee0eab5f4..12fd85be1 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -117,6 +117,35 @@ test.each(unsupportedIn53And54)("Unsupported bitop 5.4 (%p)", input => { .expectDiagnosticsToMatchSnapshot([unsupportedRightShiftOperator.code]); }); +// Execution tests: verify >>> produces correct results matching JS semantics +for (const expression of ["-5 >>> 0", "-1 >>> 0", "1 >>> 0", "-1 >>> 16", "255 >>> 4"]) { + util.testEachVersion(`Unsigned right shift execution (${expression})`, () => util.testExpression(expression), { + [tstl.LuaTarget.Universal]: false, + [tstl.LuaTarget.Lua50]: false, // No bit library in WASM runtime + [tstl.LuaTarget.Lua51]: false, // No bit library in WASM runtime + [tstl.LuaTarget.Lua52]: builder => builder.expectToMatchJsResult(), + [tstl.LuaTarget.Lua53]: builder => builder.expectToMatchJsResult(), + [tstl.LuaTarget.Lua54]: builder => builder.expectToMatchJsResult(), + [tstl.LuaTarget.Lua55]: builder => builder.expectToMatchJsResult(), + [tstl.LuaTarget.LuaJIT]: false, // Can't execute LuaJIT in tests + [tstl.LuaTarget.Luau]: false, + }); +} + +for (const code of ["let a = -5; a >>>= 0; return a;", "let a = -1; a >>>= 16; return a;"]) { + util.testEachVersion(`Unsigned right shift assignment execution (${code})`, () => util.testFunction(code), { + [tstl.LuaTarget.Universal]: false, + [tstl.LuaTarget.Lua50]: false, // No bit library in WASM runtime + [tstl.LuaTarget.Lua51]: false, // No bit library in WASM runtime + [tstl.LuaTarget.Lua52]: builder => builder.expectToMatchJsResult(), + [tstl.LuaTarget.Lua53]: builder => builder.expectToMatchJsResult(), + [tstl.LuaTarget.Lua54]: builder => builder.expectToMatchJsResult(), + [tstl.LuaTarget.Lua55]: builder => builder.expectToMatchJsResult(), + [tstl.LuaTarget.LuaJIT]: false, // Can't execute LuaJIT in tests + [tstl.LuaTarget.Luau]: false, + }); +} + test.each(["1+1", "-1+1", "1*30+4", "1*(3+4)", "1*(3+4*2)", "10-(4+5)"])( "Binary expressions ordering parentheses (%p)", input => { From c3b3a8bb85ed93aeb4d32ed9b9eed790370ba659 Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Fri, 17 Apr 2026 04:58:51 +0900 Subject: [PATCH 10/19] Fix/object assign non table source (#1642) (#1710) * fix Object.assign crashing when source is a non-table value `{ ...(cond && obj) }` short-circuits to `false` when `cond` is falsy. JS treats this as a no-op (Object.assign coerces primitives to wrapper objects with no own enumerable properties), but `__TS__ObjectAssign` was iterating every source unconditionally, so `pairs(false)` errored at runtime. Skip non-table sources, matching the spec for null/undefined/primitive args. * refactor __TS__ObjectAssign to emit cleaner Lua Invert the type guard so it nests the spread loop instead of using `continue`, which lowered to a `repeat ... until true` + `break` dance. Same behavior, the bundled helper now reads like the hand-written original. --- src/lualib/ObjectAssign.ts | 6 ++++-- test/unit/builtins/object.spec.ts | 10 ++++++++++ test/unit/spread.spec.ts | 9 +++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/lualib/ObjectAssign.ts b/src/lualib/ObjectAssign.ts index a7f691433..7d0a3a9b1 100644 --- a/src/lualib/ObjectAssign.ts +++ b/src/lualib/ObjectAssign.ts @@ -2,8 +2,10 @@ export function __TS__ObjectAssign(this: void, target: T, ...sources: T[]): T { for (const i of $range(1, sources.length)) { const source = sources[i - 1]; - for (const key in source) { - target[key] = source[key]; + if (type(source) === "table") { + for (const key in source) { + target[key] = source[key]; + } } } diff --git a/test/unit/builtins/object.spec.ts b/test/unit/builtins/object.spec.ts index dfed73502..192f17d9f 100644 --- a/test/unit/builtins/object.spec.ts +++ b/test/unit/builtins/object.spec.ts @@ -10,6 +10,16 @@ test.each([ util.testExpression`Object.assign(${util.formatCode(initial)}, ${argsString})`.expectToMatchJsResult(); }); +test.each([ + "Object.assign({}, false)", + "Object.assign({}, null)", + "Object.assign({}, undefined)", + "Object.assign({}, null, undefined)", + "Object.assign({ a: 1 }, false, { b: 2 })", +])("Object.assign skips non-object sources (%p)", expression => { + util.testExpression(expression).expectToMatchJsResult(); +}); + test.each([{}, { abc: 3 }, { abc: 3, def: "xyz" }])("Object.entries (%p)", obj => { const testBuilder = util.testExpressionTemplate`Object.entries(${obj})`; // Need custom matcher because order is not guaranteed in neither JS nor Lua diff --git a/test/unit/spread.spec.ts b/test/unit/spread.spec.ts index 53898aae5..79262c185 100644 --- a/test/unit/spread.spec.ts +++ b/test/unit/spread.spec.ts @@ -119,6 +119,15 @@ describe("in object literal", () => { util.testExpression(expression).expectToMatchJsResult(); }); + test.each([ + "{ ...((false && { a: 1 }) as any) }", + "{ ...((true && { a: 1 }) as any) }", + "{ a: 1, ...((false && { b: 2 }) as any) }", + "{ ...(null as any), ...(undefined as any) }", + ])("of short-circuited operand (%p)", expression => { + util.testExpression(expression).expectToMatchJsResult(); + }); + test("of object reference", () => { util.testFunction` const object = { x: 0, y: 1 }; From 781db8421db0ec56880857e0569ae1c36d1391d9 Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Fri, 17 Apr 2026 05:01:34 +0900 Subject: [PATCH 11/19] fix array destructuring prefix operator side effects (#1405) (#1708) * fix array destructuring not preserving evaluation order with side effects (#1405) * add more test cases for array destructuring side effects --- .../visitors/variable-declaration.ts | 4 +-- test/unit/destructuring.spec.ts | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index f15ed0115..61d50cad4 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -12,7 +12,7 @@ import { createCallableTable, isFunctionTypeWithProperties } from "./function"; import { transformIdentifier } from "./identifier"; import { isMultiReturnCall } from "./language-extensions/multi"; import { transformPropertyName } from "./literal"; -import { moveToPrecedingTemp } from "./expression-list"; +import { moveToPrecedingTemp, transformExpressionList } from "./expression-list"; export function transformArrayBindingElement( context: TransformationContext, @@ -205,7 +205,7 @@ export function transformBindingVariableDeclaration( // Don't unpack array literals const values = initializer.elements.length > 0 - ? initializer.elements.map(e => context.transformExpression(e)) + ? transformExpressionList(context, initializer.elements) : lua.createNilLiteral(); statements.push(...createLocalOrExportedOrGlobalDeclaration(context, vars, values, initializer)); } else { diff --git a/test/unit/destructuring.spec.ts b/test/unit/destructuring.spec.ts index 65ecf95a3..4820876a4 100644 --- a/test/unit/destructuring.spec.ts +++ b/test/unit/destructuring.spec.ts @@ -226,6 +226,33 @@ describe("array destructuring optimization", () => { .expectToMatchJsResult(); }); + test("array literal with side effects in elements", () => { + util.testFunction` + const arr = [1, 2]; + let i = 0; + let [v1, v2] = [arr[i], arr[++i]]; + return { v1, v2 }; + `.expectToMatchJsResult(); + }); + + test("array literal with many side effects in elements", () => { + util.testFunction` + const arr = [10, 20, 30, 40]; + let i = 0; + let [v1, v2, v3, v4] = [arr[i++], arr[i++], arr[i++], arr[i++]]; + return { v1, v2, v3, v4 }; + `.expectToMatchJsResult(); + }); + + test("array literal with mixed pure and impure elements", () => { + util.testFunction` + const arr = [10, 20, 30]; + let i = 0; + let [v1, v2, v3] = [1, arr[++i], 2]; + return { v1, v2, v3, i }; + `.expectToMatchJsResult(); + }); + test("array union", () => { util.testFunction` const array: [string] | [] = ["bar"]; From 5753d6cecbd1ae4ed0ae4f4ca0e231ae4020a95e Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Fri, 17 Apr 2026 05:04:08 +0900 Subject: [PATCH 12/19] fix return, break and continue inside try in async functions (#1706) (#1707) * fix return, break and continue inside try in async functions (#1706) * more tests * add multi-return test for async try * simplify * reorder functions --- src/transformation/utils/scope.ts | 20 ++ src/transformation/visitors/break-continue.ts | 29 ++- src/transformation/visitors/errors.ts | 90 ++++++-- src/transformation/visitors/return.ts | 74 ++++--- test/unit/builtins/async-await.spec.ts | 192 ++++++++++++++++++ 5 files changed, 364 insertions(+), 41 deletions(-) diff --git a/src/transformation/utils/scope.ts b/src/transformation/utils/scope.ts index fa66ee3e2..314f1d795 100644 --- a/src/transformation/utils/scope.ts +++ b/src/transformation/utils/scope.ts @@ -38,6 +38,9 @@ export interface Scope { importStatements?: lua.Statement[]; loopContinued?: LoopContinued; functionReturned?: boolean; + asyncTryHasReturn?: boolean; + asyncTryHasBreak?: boolean; + asyncTryHasContinue?: LoopContinued; } export interface HoistingResult { @@ -84,6 +87,23 @@ export function findScope(context: TransformationContext, scopeTypes: ScopeType) } } +export function findAsyncTryScopeInStack(context: TransformationContext): Scope | undefined { + for (const scope of walkScopesUp(context)) { + if (scope.type === ScopeType.Function) return undefined; + if (scope.type === ScopeType.Try || scope.type === ScopeType.Catch) return scope; + } + return undefined; +} + +/** Like findAsyncTryScopeInStack, but also stops at Loop boundaries. */ +export function findAsyncTryScopeBeforeLoop(context: TransformationContext): Scope | undefined { + for (const scope of walkScopesUp(context)) { + if (scope.type === ScopeType.Function || scope.type === ScopeType.Loop) return undefined; + if (scope.type === ScopeType.Try || scope.type === ScopeType.Catch) return scope; + } + return undefined; +} + export function addScopeVariableDeclaration(scope: Scope, declaration: lua.VariableDeclarationStatement) { scope.variableDeclarations ??= []; diff --git a/src/transformation/visitors/break-continue.ts b/src/transformation/visitors/break-continue.ts index 2e64a38eb..e41a7934b 100644 --- a/src/transformation/visitors/break-continue.ts +++ b/src/transformation/visitors/break-continue.ts @@ -2,10 +2,22 @@ import * as ts from "typescript"; import { LuaTarget } from "../../CompilerOptions"; import * as lua from "../../LuaAST"; import { FunctionVisitor } from "../context"; -import { findScope, LoopContinued, ScopeType } from "../utils/scope"; +import { findAsyncTryScopeBeforeLoop, findScope, LoopContinued, ScopeType } from "../utils/scope"; +import { isInAsyncFunction } from "../utils/typescript"; export const transformBreakStatement: FunctionVisitor = (breakStatement, context) => { - void context; + const tryScope = isInAsyncFunction(breakStatement) ? findAsyncTryScopeBeforeLoop(context) : undefined; + if (tryScope) { + tryScope.asyncTryHasBreak = true; + return [ + lua.createAssignmentStatement( + lua.createIdentifier("____hasBroken"), + lua.createBooleanLiteral(true), + breakStatement + ), + lua.createReturnStatement([], breakStatement), + ]; + } return lua.createBreakStatement(breakStatement); }; @@ -28,6 +40,19 @@ export const transformContinueStatement: FunctionVisitor = scope.loopContinued = continuedWith; } + const tryScope = isInAsyncFunction(statement) ? findAsyncTryScopeBeforeLoop(context) : undefined; + if (tryScope) { + tryScope.asyncTryHasContinue = continuedWith; + return [ + lua.createAssignmentStatement( + lua.createIdentifier("____hasContinued"), + lua.createBooleanLiteral(true), + statement + ), + lua.createReturnStatement([], statement), + ]; + } + const label = `__continue${scope?.id ?? ""}`; switch (continuedWith) { diff --git a/src/transformation/visitors/errors.ts b/src/transformation/visitors/errors.ts index b2c9d36d1..cc73b2ac0 100644 --- a/src/transformation/visitors/errors.ts +++ b/src/transformation/visitors/errors.ts @@ -5,7 +5,7 @@ import { FunctionVisitor, TransformationContext } from "../context"; import { unsupportedForTarget, unsupportedForTargetButOverrideAvailable } from "../utils/diagnostics"; import { createUnpackCall } from "../utils/lua-ast"; import { transformLuaLibFunction } from "../utils/lualib"; -import { Scope, ScopeType } from "../utils/scope"; +import { findScope, LoopContinued, Scope, ScopeType } from "../utils/scope"; import { isInAsyncFunction, isInGeneratorFunction } from "../utils/typescript"; import { wrapInAsyncAwaiter } from "./async-await"; import { transformScopeBlock } from "./block"; @@ -14,7 +14,7 @@ import { isInMultiReturnFunction } from "./language-extensions/multi"; import { createReturnStatement } from "./return"; const transformAsyncTry: FunctionVisitor = (statement, context) => { - const [tryBlock] = transformScopeBlock(context, statement.tryBlock, ScopeType.Try); + const [tryBlock, tryScope] = transformScopeBlock(context, statement.tryBlock, ScopeType.Try); if ( (context.options.luaTarget === LuaTarget.Lua50 || context.options.luaTarget === LuaTarget.Lua51) && @@ -31,13 +31,14 @@ const transformAsyncTry: FunctionVisitor = (statement, context) return tryBlock.statements; } - // __TS__AsyncAwaiter() + // __TS__AsyncAwaiter() const awaiter = wrapInAsyncAwaiter(context, tryBlock.statements, false); const awaiterIdentifier = lua.createIdentifier("____try"); const awaiterDefinition = lua.createVariableDeclarationStatement(awaiterIdentifier, awaiter); - // local ____try = __TS__AsyncAwaiter() - const result: lua.Statement[] = [awaiterDefinition]; + // Transform catch/finally and collect scope info before building the result + let catchScope: Scope | undefined; + const chainCalls: lua.Statement[] = []; if (statement.finallyBlock) { const awaiterFinally = lua.createTableIndexExpression(awaiterIdentifier, lua.createStringLiteral("finally")); @@ -49,27 +50,88 @@ const transformAsyncTry: FunctionVisitor = (statement, context) [awaiterIdentifier, finallyFunction], statement.finallyBlock ); - // ____try.finally() - result.push(lua.createExpressionStatement(finallyCall)); + chainCalls.push(lua.createExpressionStatement(finallyCall)); } if (statement.catchClause) { - // ____try.catch() - const [catchFunction] = transformCatchClause(context, statement.catchClause); + const [catchFunction, cScope] = transformCatchClause(context, statement.catchClause); + catchScope = cScope; if (catchFunction.params) { catchFunction.params.unshift(lua.createAnonymousIdentifier()); } const awaiterCatch = lua.createTableIndexExpression(awaiterIdentifier, lua.createStringLiteral("catch")); const catchCall = lua.createCallExpression(awaiterCatch, [awaiterIdentifier, catchFunction]); - - // await ____try.catch() const promiseAwait = transformLuaLibFunction(context, LuaLibFeature.Await, statement, catchCall); - result.push(lua.createExpressionStatement(promiseAwait, statement)); + chainCalls.push(lua.createExpressionStatement(promiseAwait, statement)); } else { - // await ____try const promiseAwait = transformLuaLibFunction(context, LuaLibFeature.Await, statement, awaiterIdentifier); - result.push(lua.createExpressionStatement(promiseAwait, statement)); + chainCalls.push(lua.createExpressionStatement(promiseAwait, statement)); + } + + const hasReturn = tryScope.asyncTryHasReturn ?? catchScope?.asyncTryHasReturn; + const hasBreak = tryScope.asyncTryHasBreak ?? catchScope?.asyncTryHasBreak; + const hasContinue = tryScope.asyncTryHasContinue ?? catchScope?.asyncTryHasContinue; + + // Build result in output order: flag declarations, awaiter, chain calls, post-checks + const result: lua.Statement[] = []; + + if (hasReturn || hasBreak || hasContinue !== undefined) { + const flagDecls: lua.Identifier[] = []; + if (hasReturn) { + flagDecls.push(lua.createIdentifier("____hasReturned")); + flagDecls.push(lua.createIdentifier("____returnValue")); + } + if (hasBreak) { + flagDecls.push(lua.createIdentifier("____hasBroken")); + } + if (hasContinue !== undefined) { + flagDecls.push(lua.createIdentifier("____hasContinued")); + } + result.push(lua.createVariableDeclarationStatement(flagDecls)); + } + + result.push(awaiterDefinition); + result.push(...chainCalls); + + if (hasReturn) { + result.push( + lua.createIfStatement( + lua.createIdentifier("____hasReturned"), + lua.createBlock([createReturnStatement(context, [lua.createIdentifier("____returnValue")], statement)]) + ) + ); + } + + if (hasBreak) { + result.push( + lua.createIfStatement(lua.createIdentifier("____hasBroken"), lua.createBlock([lua.createBreakStatement()])) + ); + } + + if (hasContinue !== undefined) { + const loopScope = findScope(context, ScopeType.Loop); + const label = `__continue${loopScope?.id ?? ""}`; + + const continueStatements: lua.Statement[] = []; + switch (hasContinue) { + case LoopContinued.WithGoto: + continueStatements.push(lua.createGotoStatement(label)); + break; + case LoopContinued.WithContinue: + continueStatements.push(lua.createContinueStatement()); + break; + case LoopContinued.WithRepeatBreak: + continueStatements.push( + lua.createAssignmentStatement(lua.createIdentifier(label), lua.createBooleanLiteral(true)) + ); + continueStatements.push(lua.createBreakStatement()); + break; + } + + result.push( + lua.createIfStatement(lua.createIdentifier("____hasContinued"), lua.createBlock(continueStatements)) + ); } return result; diff --git a/src/transformation/visitors/return.ts b/src/transformation/visitors/return.ts index 14d785166..3a61314ff 100644 --- a/src/transformation/visitors/return.ts +++ b/src/transformation/visitors/return.ts @@ -3,7 +3,7 @@ import * as lua from "../../LuaAST"; import { FunctionVisitor, TransformationContext } from "../context"; import { validateAssignment } from "../utils/assignment-validation"; import { createUnpackCall, wrapInTable } from "../utils/lua-ast"; -import { ScopeType, walkScopesUp } from "../utils/scope"; +import { findAsyncTryScopeInStack, ScopeType, walkScopesUp } from "../utils/scope"; import { transformArguments } from "./call"; import { returnsMultiType, @@ -16,11 +16,7 @@ import { import { invalidMultiFunctionReturnType } from "../utils/diagnostics"; import { isInAsyncFunction } from "../utils/typescript"; -function transformExpressionsInReturn( - context: TransformationContext, - node: ts.Expression, - insideTryCatch: boolean -): lua.Expression[] { +function transformExpressionsInReturn(context: TransformationContext, node: ts.Expression): lua.Expression[] { const expressionType = context.checker.getTypeAtLocation(node); // skip type assertions @@ -36,20 +32,7 @@ function transformExpressionsInReturn( context.diagnostics.push(invalidMultiFunctionReturnType(innerNode)); } - let returnValues = transformArguments(context, innerNode.arguments); - if (insideTryCatch) { - returnValues = [wrapInTable(...returnValues)]; // Wrap results when returning inside try/catch - } - return returnValues; - } - - // Force-wrap LuaMultiReturn when returning inside try/catch - if ( - insideTryCatch && - returnsMultiType(context, innerNode) && - !shouldMultiReturnCallBeWrapped(context, innerNode) - ) { - return [wrapInTable(context.transformExpression(node))]; + return transformArguments(context, innerNode.arguments); } } else if (isInMultiReturnFunction(context, innerNode) && isMultiReturnType(expressionType)) { // Unpack objects typed as LuaMultiReturn @@ -63,12 +46,32 @@ export function transformExpressionBodyToReturnStatement( context: TransformationContext, node: ts.Expression ): lua.Statement { - const expressions = transformExpressionsInReturn(context, node, false); + const expressions = transformExpressionsInReturn(context, node); return createReturnStatement(context, expressions, node); } +function transformReturnExpressionForTryCatch(context: TransformationContext, node: ts.Expression): lua.Expression { + const innerNode = ts.skipOuterExpressions(node, ts.OuterExpressionKinds.Assertions); + + if (ts.isCallExpression(innerNode)) { + if (isMultiFunctionCall(context, innerNode)) { + const type = context.checker.getContextualType(node); + if (type && !canBeMultiReturnType(type)) { + context.diagnostics.push(invalidMultiFunctionReturnType(innerNode)); + } + return wrapInTable(...transformArguments(context, innerNode.arguments)); + } + + if (returnsMultiType(context, innerNode) && !shouldMultiReturnCallBeWrapped(context, innerNode)) { + return wrapInTable(context.transformExpression(node)); + } + } + + return context.transformExpression(node); +} + export const transformReturnStatement: FunctionVisitor = (statement, context) => { - let results: lua.Expression[]; + const asyncTryScope = isInAsyncFunction(statement) ? findAsyncTryScopeInStack(context) : undefined; if (statement.expression) { const expressionType = context.checker.getTypeAtLocation(statement.expression); @@ -76,11 +79,32 @@ export const transformReturnStatement: FunctionVisitor = (st if (returnType) { validateAssignment(context, statement, expressionType, returnType); } + } - results = transformExpressionsInReturn(context, statement.expression, isInTryCatch(context)); - } else { - // Empty return + if (asyncTryScope) { + asyncTryScope.asyncTryHasReturn = true; + const stmts: lua.Statement[] = [ + lua.createAssignmentStatement( + lua.createIdentifier("____hasReturned"), + lua.createBooleanLiteral(true), + statement + ), + ]; + if (statement.expression) { + const returnValue = transformReturnExpressionForTryCatch(context, statement.expression); + stmts.push(lua.createAssignmentStatement(lua.createIdentifier("____returnValue"), returnValue, statement)); + } + stmts.push(lua.createReturnStatement([], statement)); + return stmts; + } + + let results: lua.Expression[]; + if (!statement.expression) { results = []; + } else if (isInTryCatch(context)) { + results = [transformReturnExpressionForTryCatch(context, statement.expression)]; + } else { + results = transformExpressionsInReturn(context, statement.expression); } return createReturnStatement(context, results, statement); diff --git a/test/unit/builtins/async-await.spec.ts b/test/unit/builtins/async-await.spec.ts index 45adb4518..7a511d7b1 100644 --- a/test/unit/builtins/async-await.spec.ts +++ b/test/unit/builtins/async-await.spec.ts @@ -815,4 +815,196 @@ describe("try/catch in async function", () => { }, }); }); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1706 + test("return inside try with deferred promise (#1706)", () => { + util.testFunction` + let resolveLater!: (value: string) => void; + + function deferredPromise(): Promise { + return new Promise(resolve => { + resolveLater = (v) => resolve(v); + }); + } + + async function fn(): Promise { + try { + return await deferredPromise(); + } catch { + return 'caught'; + } + log('unreachable!'); + } + + const promise = fn(); + resolveLater('ok'); + promise.then(v => log(v)); + + return allLogs; + ` + .setTsHeader(promiseTestLib) + .expectToEqual(["ok"]); + }); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1706 + test("return inside try in loop with deferred promise (#1706)", () => { + util.testFunction` + let resolveLater!: (value: string) => void; + + function deferredPromise(): Promise { + return new Promise(resolve => { + resolveLater = (v) => resolve(v); + }); + } + + async function fn(): Promise { + while (true) { + try { + return await deferredPromise(); + } catch { + return 'caught'; + } + log('unreachable!'); + } + } + + const promise = fn(); + resolveLater('ok'); + promise.then(v => log(v)); + + return allLogs; + ` + .setTsHeader(promiseTestLib) + .expectToEqual(["ok"]); + }); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1706 + test("return from catch with deferred promise (#1706)", () => { + util.testFunction` + let rejectLater!: (reason: string) => void; + + function deferredPromise(): Promise { + return new Promise((_, reject) => { + rejectLater = (r) => reject(r); + }); + } + + async function fn(): Promise { + try { + return await deferredPromise(); + } catch (e) { + return 'caught: ' + e; + } + log('unreachable!'); + } + + const promise = fn(); + rejectLater('oops'); + promise.then(v => log(v)); + + return allLogs; + ` + .setTsHeader(promiseTestLib) + .expectToEqual(["caught: oops"]); + }); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1706 + util.testEachVersion( + "break inside try in async loop (#1706)", + () => util.testModule` + export let result = "not set"; + async function fn(): Promise { + while (true) { + try { + await Promise.resolve(); + break; + } catch {} + } + result = "done"; + } + fn(); + `, + { + ...util.expectEachVersionExceptJit(builder => builder.expectToEqual({ result: "done" })), + [LuaTarget.Lua50]: builder => + builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), + [LuaTarget.Lua51]: builder => + builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), + } + ); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1706 + util.testEachVersion( + "continue inside try in async loop (#1706)", + () => util.testModule` + export const results: number[] = []; + async function fn(): Promise { + for (let i = 0; i < 3; i++) { + try { + await Promise.resolve(); + if (i === 1) continue; + } catch {} + results.push(i); + } + } + fn(); + `, + { + ...util.expectEachVersionExceptJit(builder => builder.expectToEqual({ results: [0, 2] })), + [LuaTarget.Lua50]: builder => + builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), + [LuaTarget.Lua51]: builder => + builder.expectToHaveDiagnostics([unsupportedForTargetButOverrideAvailable.code]), + } + ); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1706 + test("multi return from try in async function (#1706)", () => { + util.testFunction` + async function fn(): Promise> { + try { + await Promise.resolve(); + return $multi("foo", "bar"); + } catch { + return $multi("err", "err"); + } + } + + let result: string[] = []; + fn().then(v => { const [a, b] = v; result = [a, b]; }); + + return result; + ` + .withLanguageExtensions() + .expectToEqual(["foo", "bar"]); + }); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1706 + test("return inside try with finally (#1706)", () => { + util.testFunction` + let resolveLater!: (value: string) => void; + + function deferredPromise(): Promise { + return new Promise(resolve => { + resolveLater = (v) => resolve(v); + }); + } + + async function fn(): Promise { + try { + return await deferredPromise(); + } finally { + log('finally'); + } + } + + const promise = fn(); + resolveLater('ok'); + promise.then(v => log(v)); + + return allLogs; + ` + .setTsHeader(promiseTestLib) + .expectToEqual(["finally", "ok"]); + }); }); From 2fc2e99680aada83ac3524166197760696ec08d2 Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Fri, 17 Apr 2026 05:17:41 +0900 Subject: [PATCH 13/19] fix dots in file/directory names breaking Lua require resolution (#1445) (#1702) * fix dots in file/directory names breaking Lua require resolution (#1445) Lua's require() uses dots as path separators, so a file at Foo.Bar/index.lua is unreachable via require("Foo.Bar.index") since Lua looks for Foo/Bar/index.lua. Expand dotted path segments into nested directories in the emit output, and emit a diagnostic when this expansion causes output path collisions. * replace dots with underscores in output paths instead of expanding to nested directories * more tests --- src/transpilation/diagnostics.ts | 6 ++++ src/transpilation/transpiler.ts | 26 +++++++++++---- test/unit/modules/resolution.spec.ts | 49 +++++++++++++++++++++++++++- test/util.ts | 3 +- 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/transpilation/diagnostics.ts b/src/transpilation/diagnostics.ts index 348542ac5..b7282f0f2 100644 --- a/src/transpilation/diagnostics.ts +++ b/src/transpilation/diagnostics.ts @@ -59,3 +59,9 @@ export const unsupportedJsxEmit = createDiagnosticFactory(() => 'JSX is only sup export const pathsWithoutBaseUrl = createDiagnosticFactory( () => "When configuring 'paths' in tsconfig.json, the option 'baseUrl' must also be provided." ); + +export const emitPathCollision = createDiagnosticFactory( + (outputPath: string, file1: string, file2: string) => + `Output path '${outputPath}' is used by both '${file1}' and '${file2}'. ` + + `Dots in file/directory names are replaced with underscores for Lua module resolution.` +); diff --git a/src/transpilation/transpiler.ts b/src/transpilation/transpiler.ts index 9bac1f5bf..7f79af35d 100644 --- a/src/transpilation/transpiler.ts +++ b/src/transpilation/transpiler.ts @@ -4,6 +4,7 @@ import { CompilerOptions, isBundleEnabled, LuaLibImportKind, LuaTarget } from ". import { buildMinimalLualibBundle, findUsedLualibFeatures, getLuaLibBundle } from "../LuaLib"; import { normalizeSlashes, trimExtension } from "../utils"; import { getBundleResult } from "./bundle"; +import { emitPathCollision } from "./diagnostics"; import { getPlugins, Plugin } from "./plugins"; import { resolveDependencies } from "./resolve"; import { getProgramTranspileResult, TranspileOptions } from "./transpile"; @@ -143,10 +144,17 @@ export class Transpiler { diagnostics.push(...bundleDiagnostics); emitPlan = [bundleFile]; } else { - emitPlan = resolutionResult.resolvedFiles.map(file => ({ - ...file, - outputPath: getEmitPath(file.fileName, program), - })); + const outputPathMap = new Map(); + emitPlan = resolutionResult.resolvedFiles.map(file => { + const outputPath = getEmitPath(file.fileName, program); + const existing = outputPathMap.get(outputPath); + if (existing) { + diagnostics.push(emitPathCollision(outputPath, existing, file.fileName)); + } else { + outputPathMap.set(outputPath, file.fileName); + } + return { ...file, outputPath }; + }); } performance.endSection("getEmitPlan"); @@ -189,11 +197,17 @@ export function getEmitPathRelativeToOutDir(fileName: string, program: ts.Progra emitPathSplits[0] = "lua_modules"; } + // Replace dots with underscores in path segments so that Lua's require() + // resolves correctly. Dots are path separators in Lua's module system, so + // "Foo.Bar/index.lua" would be unreachable via require("Foo.Bar.index") + // since Lua interprets it as "Foo/Bar/index.lua". + emitPathSplits[emitPathSplits.length - 1] = trimExtension(emitPathSplits[emitPathSplits.length - 1]); + emitPathSplits = emitPathSplits.map(segment => segment.replace(/\./g, "_")); + // Set extension const extension = ((program.getCompilerOptions() as CompilerOptions).extension ?? "lua").trim(); const trimmedExtension = extension.startsWith(".") ? extension.substring(1) : extension; - emitPathSplits[emitPathSplits.length - 1] = - trimExtension(emitPathSplits[emitPathSplits.length - 1]) + "." + trimmedExtension; + emitPathSplits[emitPathSplits.length - 1] += "." + trimmedExtension; return path.join(...emitPathSplits); } diff --git a/test/unit/modules/resolution.spec.ts b/test/unit/modules/resolution.spec.ts index 26c147c68..c6f97d5f9 100644 --- a/test/unit/modules/resolution.spec.ts +++ b/test/unit/modules/resolution.spec.ts @@ -1,5 +1,6 @@ +import * as path from "path"; import * as ts from "typescript"; -import { couldNotResolveRequire } from "../../../src/transpilation/diagnostics"; +import { couldNotResolveRequire, emitPathCollision } from "../../../src/transpilation/diagnostics"; import * as util from "../../util"; const requireRegex = /require\("(.*?)"\)/; @@ -166,6 +167,52 @@ test.each([ .tap(expectToRequire(expectedPath)); }); +// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1445 +// Can't test this via execution because the test harness uses package.preload +// instead of real filesystem resolution, so require() always finds the module +// regardless of output path. We check the output path directly instead. +test("dots in directory names are replaced with underscores in output", () => { + const { transpiledFiles } = util.testModule` + import { answer } from "./Foo.Bar"; + export const result = answer; + ` + .addExtraFile("Foo.Bar/index.ts", "export const answer = 42;") + .setOptions({ rootDir: "." }) + .getLuaResult(); + + const dottedFile = transpiledFiles.find(f => f.lua?.includes("answer = 42")); + expect(dottedFile).toBeDefined(); + expect(dottedFile!.outPath).toContain(path.join("Foo_Bar", "index.lua")); + expect(dottedFile!.outPath).not.toContain("Foo.Bar"); +}); + +test("dots in file names are replaced with underscores in output", () => { + const { transpiledFiles } = util.testModule` + import { answer } from "./foo.test"; + export const result = answer; + ` + .addExtraFile("foo.test.ts", "export const answer = 42;") + .setOptions({ rootDir: "." }) + .getLuaResult(); + + const dottedFile = transpiledFiles.find(f => f.lua?.includes("answer = 42")); + expect(dottedFile).toBeDefined(); + expect(dottedFile!.outPath).toContain("foo_test.lua"); + expect(dottedFile!.outPath).not.toContain("foo.test"); +}); + +test("dots in paths that collide with existing paths produce a diagnostic", () => { + util.testModule` + import { a } from "./Foo.Bar"; + import { b } from "./Foo_Bar"; + export const result = a + b; + ` + .addExtraFile("Foo.Bar/index.ts", "export const a = 1;") + .addExtraFile("Foo_Bar/index.ts", "export const b = 2;") + .setOptions({ rootDir: "." }) + .expectToHaveDiagnostics([emitPathCollision.code]); +}); + test("import = require", () => { util.testModule` import foo = require("./foo/bar"); diff --git a/test/util.ts b/test/util.ts index 2871f6e5a..8c5e69f8f 100644 --- a/test/util.ts +++ b/test/util.ts @@ -534,8 +534,9 @@ end)());`; const moduleExports = {}; globalContext.exports = moduleExports; globalContext.module = { exports: moduleExports }; + const baseName = fileName.replace("./", ""); const transpiledExtraFile = transpiledFiles.find(({ sourceFiles }) => - sourceFiles.some(f => f.fileName === fileName.replace("./", "") + ".ts") + sourceFiles.some(f => f.fileName === baseName + ".ts" || f.fileName === baseName + "/index.ts") ); if (transpiledExtraFile?.js) { From 3d14be87bb9c87c0f820b76938e09391bec67d8a Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Sat, 18 Apr 2026 21:28:54 +0900 Subject: [PATCH 14/19] fix await inside catch/finally in async try (#1659) (#1709) * fix await inside catch/finally in async try * add test for awaited return value from catch in async try --- src/transformation/visitors/errors.ts | 45 ++++++---- test/unit/builtins/async-await.spec.ts | 114 +++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 18 deletions(-) diff --git a/src/transformation/visitors/errors.ts b/src/transformation/visitors/errors.ts index cc73b2ac0..77067dda8 100644 --- a/src/transformation/visitors/errors.ts +++ b/src/transformation/visitors/errors.ts @@ -40,35 +40,44 @@ const transformAsyncTry: FunctionVisitor = (statement, context) let catchScope: Scope | undefined; const chainCalls: lua.Statement[] = []; - if (statement.finallyBlock) { - const awaiterFinally = lua.createTableIndexExpression(awaiterIdentifier, lua.createStringLiteral("finally")); - const finallyFunction = lua.createFunctionExpression( - lua.createBlock(context.transformStatements(statement.finallyBlock.statements)) - ); - const finallyCall = lua.createCallExpression( - awaiterFinally, - [awaiterIdentifier, finallyFunction], - statement.finallyBlock - ); - chainCalls.push(lua.createExpressionStatement(finallyCall)); - } - if (statement.catchClause) { + // ____try = ____try.catch() const [catchFunction, cScope] = transformCatchClause(context, statement.catchClause); catchScope = cScope; if (catchFunction.params) { catchFunction.params.unshift(lua.createAnonymousIdentifier()); } + const catchBodyStatements = catchFunction.body ? catchFunction.body.statements : []; + const asyncWrappedCatch = wrapInAsyncAwaiter(context, [...catchBodyStatements], false); + catchFunction.body = lua.createBlock([lua.createReturnStatement([asyncWrappedCatch])]); + const awaiterCatch = lua.createTableIndexExpression(awaiterIdentifier, lua.createStringLiteral("catch")); const catchCall = lua.createCallExpression(awaiterCatch, [awaiterIdentifier, catchFunction]); - const promiseAwait = transformLuaLibFunction(context, LuaLibFeature.Await, statement, catchCall); - chainCalls.push(lua.createExpressionStatement(promiseAwait, statement)); - } else { - const promiseAwait = transformLuaLibFunction(context, LuaLibFeature.Await, statement, awaiterIdentifier); - chainCalls.push(lua.createExpressionStatement(promiseAwait, statement)); + chainCalls.push(lua.createAssignmentStatement(lua.cloneIdentifier(awaiterIdentifier), catchCall)); } + if (statement.finallyBlock) { + // ____try = ____try.finally() + const finallyStatements = context.transformStatements(statement.finallyBlock.statements); + const asyncWrappedFinally = wrapInAsyncAwaiter(context, finallyStatements, false); + const finallyFunction = lua.createFunctionExpression( + lua.createBlock([lua.createReturnStatement([asyncWrappedFinally])]) + ); + + const awaiterFinally = lua.createTableIndexExpression(awaiterIdentifier, lua.createStringLiteral("finally")); + const finallyCall = lua.createCallExpression( + awaiterFinally, + [awaiterIdentifier, finallyFunction], + statement.finallyBlock + ); + chainCalls.push(lua.createAssignmentStatement(lua.cloneIdentifier(awaiterIdentifier), finallyCall)); + } + + // __TS__Await(____try) + const promiseAwait = transformLuaLibFunction(context, LuaLibFeature.Await, statement, awaiterIdentifier); + chainCalls.push(lua.createExpressionStatement(promiseAwait, statement)); + const hasReturn = tryScope.asyncTryHasReturn ?? catchScope?.asyncTryHasReturn; const hasBreak = tryScope.asyncTryHasBreak ?? catchScope?.asyncTryHasBreak; const hasContinue = tryScope.asyncTryHasContinue ?? catchScope?.asyncTryHasContinue; diff --git a/test/unit/builtins/async-await.spec.ts b/test/unit/builtins/async-await.spec.ts index 7a511d7b1..546589355 100644 --- a/test/unit/builtins/async-await.spec.ts +++ b/test/unit/builtins/async-await.spec.ts @@ -816,6 +816,120 @@ describe("try/catch in async function", () => { }); }); + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1659 + test("await inside catch handler resolves correctly (#1659)", () => { + util.testFunction` + let reject: (reason: string) => void = () => {}; + + async function failing() { + return new Promise((_, rej) => { reject = rej; }); + } + + async function run() { + try { + await failing(); + } catch (e) { + log("catch"); + const a = await Promise.resolve(true); + log("a", a); + } + } + + run(); + reject("error"); + + return allLogs; + ` + .setTsHeader(promiseTestLib) + .expectToEqual(["catch", "a", true]); + }); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1659 + test("await inside finally handler resolves correctly (#1659)", () => { + util.testFunction` + let reject: (reason: string) => void = () => {}; + + async function failing() { + return new Promise((_, rej) => { reject = rej; }); + } + + async function run() { + try { + await failing(); + } finally { + log("finally"); + const a = await Promise.resolve(true); + log("a", a); + } + } + + run().catch(() => {}); + reject("error"); + + return allLogs; + ` + .setTsHeader(promiseTestLib) + .expectToEqual(["finally", "a", true]); + }); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1659 + test("await inside both catch and finally handlers (#1659)", () => { + util.testFunction` + let reject: (reason: string) => void = () => {}; + + async function failing() { + return new Promise((_, rej) => { reject = rej; }); + } + + async function run() { + try { + await failing(); + } catch (e) { + log("catch"); + const a = await Promise.resolve("caught"); + log("a", a); + } finally { + log("finally"); + const b = await Promise.resolve("done"); + log("b", b); + } + } + + run(); + reject("error"); + + return allLogs; + ` + .setTsHeader(promiseTestLib) + .expectToEqual(["catch", "a", "caught", "finally", "b", "done"]); + }); + + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1659 + test("awaited value in catch is returned from async function (#1659)", () => { + util.testFunction` + const failing = defer(); + const recovery = defer(); + + async function run() { + try { + await failing.promise; + return "succeeded"; + } catch (e) { + return await recovery.promise; + } + } + + run().then(value => log("result", value)); + + failing.reject("error"); + recovery.resolve("recovered"); + + return allLogs; + ` + .setTsHeader(promiseTestLib) + .expectToEqual(["result", "recovered"]); + }); + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1706 test("return inside try with deferred promise (#1706)", () => { util.testFunction` From 843544dbf9e3b1a6780efbb626e5e6669a5be9a2 Mon Sep 17 00:00:00 2001 From: Cold Fry Date: Sat, 18 Apr 2026 21:35:07 +0900 Subject: [PATCH 15/19] apply pilaoda's fix (#1711) --- src/transformation/utils/typescript/index.ts | 6 ++++-- test/unit/using.spec.ts | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/transformation/utils/typescript/index.ts b/src/transformation/utils/typescript/index.ts index e5984b08f..f12bf4721 100644 --- a/src/transformation/utils/typescript/index.ts +++ b/src/transformation/utils/typescript/index.ts @@ -14,12 +14,14 @@ export function hasExportEquals(sourceFile: ts.SourceFile): boolean { * Search up until finding a node satisfying the callback */ export function findFirstNodeAbove(node: ts.Node, callback: (n: ts.Node) => n is T): T | undefined { - let current = node; + // Synthetic nodes (created by pre-transformers like usingTransformer) may have an unset .parent. + // Fall back to ts.getOriginalNode so we can still walk the source-parsed parent chain. + let current = ts.getOriginalNode(node); while (current.parent) { if (callback(current.parent)) { return current.parent; } else { - current = current.parent; + current = ts.getOriginalNode(current.parent); } } } diff --git a/test/unit/using.spec.ts b/test/unit/using.spec.ts index bfe5312b0..60364f680 100644 --- a/test/unit/using.spec.ts +++ b/test/unit/using.spec.ts @@ -194,6 +194,26 @@ test("await using no extra diagnostics (#1571)", () => { `.expectToHaveNoDiagnostics(); }); +// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1622 +test("await-using with nested async arrow that also has await-using (runtime divergence)", () => { + util.testFunction` + const logs: any[] = []; + async function getA(): Promise { + return { [Symbol.asyncDispose]: async () => {} }; + } + async function outer(): Promise { + await using a = await getA(); + const inner = async (): Promise => { + await using b = await getA(); + return 42; + }; + return inner(); + } + outer().then(v => logs.push(v)); + return logs; + `.expectToEqual([42]); +}); + // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1584 test("works with disposable classes (#1584)", () => { util.testFunction` From 591989b646e3cd459c45061f92df47679a6ac66d Mon Sep 17 00:00:00 2001 From: Perry van Wesel Date: Tue, 21 Apr 2026 22:02:57 +0200 Subject: [PATCH 16/19] Upgrade to TypeScript 6.0 (#1704) * Upgarde to TypeScript 6.0 * fix module-resolution tests * fix array tests except splice * fix more tests * update deps to ts6 support * fix more tests * updated snapshots * fix more tests * fix optional chaining tests * only module-resolution test failing * revert utils * fix last test * remove debug code from module-resolution.spec.ts * update benchmark to nodenext module --- benchmark/tsconfig.json | 3 +- package-lock.json | 729 ++++++++---------- package.json | 16 +- src/CompilerOptions.ts | 4 - src/transformation/utils/function-context.ts | 4 +- .../visitors/variable-declaration.ts | 2 +- src/transpilation/diagnostics.ts | 4 - src/transpilation/resolve.ts | 24 +- src/utils.ts | 2 +- test/setup.ts | 2 +- .../module-resolution.spec.ts.snap | 2 +- test/transpile/module-resolution.spec.ts | 8 +- .../paths-base-tsconfig/tsconfig.base.json | 1 + .../paths-simple/myprogram/tsconfig.json | 2 +- .../project-with-node-modules/tsconfig.json | 3 +- test/transpile/transformers/fixtures.ts | 2 +- .../__snapshots__/identifiers.spec.ts.snap | 8 +- test/unit/__snapshots__/loops.spec.ts.snap | 2 +- .../annotations/customConstructor.spec.ts | 4 +- test/unit/assignments.spec.ts | 6 +- test/unit/builtins/array.spec.ts | 41 +- test/unit/builtins/async-await.spec.ts | 9 +- test/unit/builtins/map.spec.ts | 2 +- test/unit/builtins/object.spec.ts | 8 +- test/unit/builtins/set.spec.ts | 2 +- test/unit/builtins/string.spec.ts | 2 +- test/unit/builtins/weakMap.spec.ts | 2 +- test/unit/classes/classes.spec.ts | 2 +- test/unit/classes/decorators.spec.ts | 86 +-- test/unit/conditionals.spec.ts | 2 +- test/unit/destructuring.spec.ts | 64 +- test/unit/enum.spec.ts | 2 +- test/unit/error.spec.ts | 6 +- .../unit/functions/functionProperties.spec.ts | 6 +- test/unit/functions/functions.spec.ts | 37 +- test/unit/functions/generators.spec.ts | 2 +- .../invalidFunctionAssignments.spec.ts.snap | 96 +-- .../functionExpressionTypeInference.spec.ts | 2 +- .../validation/functionPermutations.ts | 16 +- .../invalidFunctionAssignments.spec.ts | 8 +- test/unit/hoisting.spec.ts | 2 +- test/unit/identifiers.spec.ts | 3 +- .../__snapshots__/multi.spec.ts.snap | 42 +- test/unit/language-extensions/multi.spec.ts | 13 +- test/unit/loops.spec.ts | 6 +- test/unit/modules/modules.spec.ts | 3 +- test/unit/namespaces.spec.ts | 12 +- test/unit/optionalChaining.spec.ts | 45 +- test/unit/overloads.spec.ts | 8 +- test/unit/precedingStatements.spec.ts | 11 +- test/unit/spread.spec.ts | 4 +- test/unit/switch.spec.ts | 2 +- test/unit/using.spec.ts | 6 +- test/util.ts | 12 +- tsconfig.json | 1 - 55 files changed, 668 insertions(+), 725 deletions(-) diff --git a/benchmark/tsconfig.json b/benchmark/tsconfig.json index e1c9c2941..5422f1770 100644 --- a/benchmark/tsconfig.json +++ b/benchmark/tsconfig.json @@ -4,7 +4,8 @@ "lib": ["esnext"], // Dev types are JIT "types": ["lua-types/jit", "@typescript-to-lua/language-extensions"], - "moduleResolution": "node", + "module": "nodenext", + "moduleResolution": "nodenext", "outDir": "dist", "rootDir": "src", "strict": true, diff --git a/package-lock.json b/package-lock.json index 1b482b466..760133928 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,25 +25,25 @@ "@types/node": "^22.10.0", "@types/picomatch": "^2.3.0", "@types/resolve": "1.14.0", - "eslint": "^9.22.0", - "eslint-plugin-jest": "^28.8.3", + "eslint": "^9.39.4", + "eslint-plugin-jest": "^28.14.0", "fs-extra": "^8.1.0", "javascript-stringify": "^2.0.1", - "jest": "^29.5.0", + "jest": "^29.7.0", "jest-circus": "^29.7.0", "lua-types": "^2.14.1", "lua-wasm-bindings": "^0.5.3", "prettier": "^2.8.8", - "ts-jest": "^29.2.5", + "ts-jest": "^29.4.9", "ts-node": "^10.9.2", - "typescript": "5.9.3", - "typescript-eslint": "^8.46.3" + "typescript": "6.0.2", + "typescript-eslint": "^8.58.0" }, "engines": { "node": ">=16.10.0" }, "peerDependencies": { - "typescript": "5.9.3" + "typescript": "6.0.2" } }, "node_modules/@ampproject/remapping": { @@ -616,9 +616,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -648,24 +648,25 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -698,20 +699,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -722,9 +723,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -1317,44 +1318,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1602,22 +1565,21 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz", - "integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.3", - "@typescript-eslint/type-utils": "8.46.3", - "@typescript-eslint/utils": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1627,9 +1589,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.3", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "@typescript-eslint/parser": "^8.58.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -1643,18 +1605,18 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", - "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1664,20 +1626,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", - "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.3", - "@typescript-eslint/types": "^8.46.3", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1687,18 +1649,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", - "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3" + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1709,9 +1671,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", - "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", "dev": true, "license": "MIT", "engines": { @@ -1722,21 +1684,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz", - "integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3", - "@typescript-eslint/utils": "8.46.3", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1746,14 +1708,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz", - "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", "dev": true, "license": "MIT", "engines": { @@ -1765,22 +1727,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", - "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.3", - "@typescript-eslint/tsconfig-utils": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1790,46 +1751,59 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", - "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1839,19 +1813,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", - "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.58.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1861,6 +1835,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-to-lua/language-extensions": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@typescript-to-lua/language-extensions/-/language-extensions-1.19.0.tgz", @@ -1905,9 +1892,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -1991,13 +1978,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -2132,9 +2112,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -2421,9 +2401,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -2500,22 +2480,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.27", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz", @@ -2590,26 +2554,26 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -2628,7 +2592,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2651,9 +2615,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "28.8.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.3.tgz", - "integrity": "sha512-HIQ3t9hASLKm2IhIOqnu+ifw7uLZkIlR7RYNv7fMcEi/p0CIiJmfriStQS2LDkgtY4nyLbIZAD+JL347Yc2ETQ==", + "version": "28.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.14.0.tgz", + "integrity": "sha512-P9s/qXSMTpRTerE2FQ0qJet2gKbcGyFTPAJipoKxmWqR6uuFqIqk8FuEfg5yBieOezVrEfAMZrEwJ6yEp+1MFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2918,36 +2882,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2962,16 +2896,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -2995,39 +2919,6 @@ "node": ">=16.0.0" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3073,9 +2964,9 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -3222,12 +3113,37 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/has-flag": { "version": "4.0.0", @@ -3520,25 +3436,6 @@ "node": ">=8" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/javascript-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", @@ -3594,6 +3491,7 @@ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -4711,16 +4609,6 @@ "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4758,6 +4646,16 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4772,6 +4670,13 @@ "dev": true, "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4971,9 +4876,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -5156,27 +5061,6 @@ ], "license": "MIT" }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -5254,45 +5138,10 @@ "node": ">=10" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -5533,6 +5382,55 @@ "node": ">=8" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -5554,9 +5452,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -5567,19 +5465,20 @@ } }, "node_modules/ts-jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", - "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", "dev": true, + "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", - "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", + "handlebars": "^4.7.9", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.6.3", + "semver": "^7.7.4", + "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, "bin": { @@ -5590,11 +5489,12 @@ }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" }, "peerDependenciesMeta": { "@babel/core": { @@ -5611,9 +5511,25 @@ }, "esbuild": { "optional": true + }, + "jest-util": { + "optional": true } } }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -5695,9 +5611,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -5710,16 +5626,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.3.tgz", - "integrity": "sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.3", - "@typescript-eslint/parser": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3", - "@typescript-eslint/utils": "8.46.3" + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5729,8 +5645,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" } }, "node_modules/undici-types": { @@ -5848,6 +5778,13 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index fe6771138..412c08da7 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "lint:prettier": "prettier --check . || (echo 'Run `npm run fix:prettier` to fix it.' && exit 1)", "lint:eslint": "eslint .", "fix:prettier": "prettier --write .", - "check:language-extensions": "tsc --strict language-extensions/index.d.ts", + "check:language-extensions": "tsc --strict --ignoreConfig language-extensions/index.d.ts", "preversion": "npm run build && npm test", "postversion": "git push && git push --tags" }, @@ -42,7 +42,7 @@ "node": ">=16.10.0" }, "peerDependencies": { - "typescript": "5.9.3" + "typescript": "6.0.2" }, "dependencies": { "@typescript-to-lua/language-extensions": "1.19.0", @@ -58,18 +58,18 @@ "@types/node": "^22.10.0", "@types/picomatch": "^2.3.0", "@types/resolve": "1.14.0", - "eslint": "^9.22.0", - "eslint-plugin-jest": "^28.8.3", + "eslint": "^9.39.4", + "eslint-plugin-jest": "^28.14.0", "fs-extra": "^8.1.0", "javascript-stringify": "^2.0.1", - "jest": "^29.5.0", + "jest": "^29.7.0", "jest-circus": "^29.7.0", "lua-types": "^2.14.1", "lua-wasm-bindings": "^0.5.3", "prettier": "^2.8.8", - "ts-jest": "^29.2.5", + "ts-jest": "^29.4.9", "ts-node": "^10.9.2", - "typescript": "5.9.3", - "typescript-eslint": "^8.46.3" + "typescript": "6.0.2", + "typescript-eslint": "^8.58.0" } } diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index 47ada916e..1a167e7c3 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -99,9 +99,5 @@ export function validateOptions(options: CompilerOptions): ts.Diagnostic[] { diagnostics.push(diagnosticFactories.unsupportedJsxEmit()); } - if (options.paths && !options.baseUrl) { - diagnostics.push(diagnosticFactories.pathsWithoutBaseUrl()); - } - return diagnostics; } diff --git a/src/transformation/utils/function-context.ts b/src/transformation/utils/function-context.ts index d61ffb5bf..e28950e2e 100644 --- a/src/transformation/utils/function-context.ts +++ b/src/transformation/utils/function-context.ts @@ -108,9 +108,7 @@ function computeDeclarationContextType(context: TransformationContext, signature const thisParameter = getExplicitThisParameter(signatureDeclaration); if (thisParameter) { // Explicit 'this' - return thisParameter.type && thisParameter.type.kind === ts.SyntaxKind.VoidKeyword - ? ContextType.Void - : ContextType.NonVoid; + return thisParameter.type?.kind === ts.SyntaxKind.VoidKeyword ? ContextType.Void : ContextType.NonVoid; } // noSelf declaration on function signature diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 61d50cad4..9ed5ec553 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -62,7 +62,7 @@ export function transformBindingPattern( } // Build the path to the table - const tableExpression = propertyAccessStack.reduce( + const tableExpression = propertyAccessStack.reduce( (path, property) => lua.createTableIndexExpression(path, transformPropertyName(context, property)), table ); diff --git a/src/transpilation/diagnostics.ts b/src/transpilation/diagnostics.ts index b7282f0f2..1ee589708 100644 --- a/src/transpilation/diagnostics.ts +++ b/src/transpilation/diagnostics.ts @@ -56,10 +56,6 @@ export const cannotBundleLibrary = createDiagnosticFactory( export const unsupportedJsxEmit = createDiagnosticFactory(() => 'JSX is only supported with "react" jsx option.'); -export const pathsWithoutBaseUrl = createDiagnosticFactory( - () => "When configuring 'paths' in tsconfig.json, the option 'baseUrl' must also be provided." -); - export const emitPathCollision = createDiagnosticFactory( (outputPath: string, file1: string, file2: string) => `Output path '${outputPath}' is used by both '${file1}' and '${file2}'. ` + diff --git a/src/transpilation/resolve.ts b/src/transpilation/resolve.ts index bd16773e9..e011f4350 100644 --- a/src/transpilation/resolve.ts +++ b/src/transpilation/resolve.ts @@ -27,7 +27,7 @@ interface ResolutionResult { } class ResolutionContext { - private noResolvePaths: picomatch.Matcher[]; + private noResolvePaths: picomatch.Glob[]; public diagnostics: ts.Diagnostic[] = []; public resolvedFiles = new Map(); @@ -38,9 +38,7 @@ class ResolutionContext { private readonly emitHost: EmitHost, private readonly plugins: Plugin[] ) { - const unique = [...new Set(options.noResolvePaths)]; - const matchers = unique.map(x => picomatch(x)); - this.noResolvePaths = matchers; + this.noResolvePaths = [...new Set(options.noResolvePaths)]; } public addAndResolveDependencies(file: ProcessedFile): void { @@ -73,7 +71,7 @@ class ResolutionContext { return; } - if (this.noResolvePaths.find(isMatch => isMatch(required.requirePath))) { + if (this.noResolvePaths.find(glob => picomatch.isMatch(required.requirePath, glob))) { if (this.options.tstlVerbose) { console.log( `Skipping module resolution of ${required.requirePath} as it is in the tsconfig noResolvePaths.` @@ -215,14 +213,16 @@ class ResolutionContext { const fileFromPath = this.getFileFromPath(resolvedPath); if (fileFromPath) return fileFromPath; - if (this.options.paths && this.options.baseUrl) { + if (this.options.paths) { // If no file found yet and paths are present, try to find project file via paths mappings - const fileFromPaths = this.tryGetModuleNameFromPaths( - dependencyPath, - this.options.paths, - this.options.baseUrl - ); - if (fileFromPaths) return fileFromPaths; + // When baseUrl is not set, resolve paths relative to the tsconfig directory (TS 6.0+ behavior) + const pathsBase = + this.options.baseUrl ?? + (this.options.configFilePath ? path.dirname(this.options.configFilePath) : undefined); + if (pathsBase) { + const fileFromPaths = this.tryGetModuleNameFromPaths(dependencyPath, this.options.paths, pathsBase); + if (fileFromPaths) return fileFromPaths; + } } // Not a TS file in our project sources, use resolver to check if we can find dependency diff --git a/src/utils.ts b/src/utils.ts index b1877e368..70cdb2940 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -77,7 +77,7 @@ export function cast( } export function assert(value: any, message?: string | Error): asserts value { - nativeAssert(value, message); + nativeAssert.ok(value, message); } export function assertNever(_value: never): never { diff --git a/test/setup.ts b/test/setup.ts index e431d8519..704c59bed 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -13,7 +13,7 @@ declare global { expect.extend({ toHaveDiagnostics(diagnostics: ts.Diagnostic[], expected?: number[]): jest.CustomMatcherResult { - assert(Array.isArray(diagnostics)); + assert.ok(Array.isArray(diagnostics)); // @ts-ignore const matcherHint = this.utils.matcherHint("toHaveDiagnostics", undefined, "", this); diff --git a/test/transpile/__snapshots__/module-resolution.spec.ts.snap b/test/transpile/__snapshots__/module-resolution.spec.ts.snap index c9f0be298..e87569ded 100644 --- a/test/transpile/__snapshots__/module-resolution.spec.ts.snap +++ b/test/transpile/__snapshots__/module-resolution.spec.ts.snap @@ -10,8 +10,8 @@ exports[`supports complicated paths configuration 1`] = ` exports[`supports paths configuration 1`] = ` [ - "/paths-simple/myprogram/dist/main.lua", "/paths-simple/myprogram/dist/mypackage/bar.lua", "/paths-simple/myprogram/dist/mypackage/index.lua", + "/paths-simple/myprogram/dist/myprogram/main.lua", ] `; diff --git a/test/transpile/module-resolution.spec.ts b/test/transpile/module-resolution.spec.ts index ff29ac9b5..e6543df69 100644 --- a/test/transpile/module-resolution.spec.ts +++ b/test/transpile/module-resolution.spec.ts @@ -4,7 +4,6 @@ import * as util from "../util"; import * as ts from "typescript"; import { BuildMode } from "../../src"; import { normalizeSlashes } from "../../src/utils"; -import { pathsWithoutBaseUrl } from "../../src/transpilation/diagnostics"; describe("basic module resolution", () => { const projectPath = path.resolve(__dirname, "module-resolution", "project-with-node-modules"); @@ -457,6 +456,7 @@ describe("module resolution should not try to resolve modules in noResolvePaths" export const result = b.foo(); ` + .addExtraFile("preload.d.ts", `declare module "preload" {}`) .addExtraFile("preload.lua", 'package.preload["ignoreme"] = function() return nil end') .addExtraFile( "actualfile.ts", @@ -598,7 +598,7 @@ test("module resolution uses baseURL to resolve imported files", () => { return { baz = function() return "baz" end } ` ) - .setOptions({ baseUrl: "./myproject/mydeps" }) + .setOptions({ baseUrl: "./myproject/mydeps", ignoreDeprecations: "6.0" }) .expectToEqual({ fooResult: "foo", barResult: "bar", @@ -707,10 +707,6 @@ test("supports complicated paths configuration", () => { .expectToEqual({ foo: 314, bar: 271 }); }); -test("paths without baseUrl is error", () => { - util.testFunction``.setOptions({ paths: {} }).expectToHaveDiagnostics([pathsWithoutBaseUrl.code]); -}); - test("module resolution using plugin", () => { const baseProjectPath = path.resolve(__dirname, "module-resolution", "project-with-module-resolution-plugin"); const projectTsConfig = path.join(baseProjectPath, "tsconfig.json"); diff --git a/test/transpile/module-resolution/paths-base-tsconfig/tsconfig.base.json b/test/transpile/module-resolution/paths-base-tsconfig/tsconfig.base.json index 3f961c78a..4d3934b0d 100644 --- a/test/transpile/module-resolution/paths-base-tsconfig/tsconfig.base.json +++ b/test/transpile/module-resolution/paths-base-tsconfig/tsconfig.base.json @@ -2,6 +2,7 @@ "compilerOptions": { "rootDir": ".", "baseUrl": ".", + "ignoreDeprecations": "6.0", "paths": { "mypackage": ["packages/mypackage/src/index.ts"], "mypackage/*": ["packages/mypackage/src/*"] diff --git a/test/transpile/module-resolution/paths-simple/myprogram/tsconfig.json b/test/transpile/module-resolution/paths-simple/myprogram/tsconfig.json index f01271ac9..a21584fac 100644 --- a/test/transpile/module-resolution/paths-simple/myprogram/tsconfig.json +++ b/test/transpile/module-resolution/paths-simple/myprogram/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "baseUrl": ".", + "rootDir": "..", "outDir": "dist", "paths": { "myOtherPackage": ["../mypackage"], diff --git a/test/transpile/module-resolution/project-with-node-modules/tsconfig.json b/test/transpile/module-resolution/project-with-node-modules/tsconfig.json index 935b64af6..faa93f0e3 100644 --- a/test/transpile/module-resolution/project-with-node-modules/tsconfig.json +++ b/test/transpile/module-resolution/project-with-node-modules/tsconfig.json @@ -7,6 +7,7 @@ "target": "esnext", "lib": ["esnext"], "types": [], - "rootDir": "." + "rootDir": ".", + "noUncheckedSideEffectImports": false } } diff --git a/test/transpile/transformers/fixtures.ts b/test/transpile/transformers/fixtures.ts index dff7667be..4bb890135 100644 --- a/test/transpile/transformers/fixtures.ts +++ b/test/transpile/transformers/fixtures.ts @@ -36,7 +36,7 @@ export const compilerOptions = (options: tstl.CompilerOptions): ts.TransformerFactory => context => file => { - assert(options.plugins?.length === 1); + assert.ok(options.plugins?.length === 1); return visitAndReplace(context, file, node => { if (!ts.isReturnStatement(node)) return; return ts.factory.updateReturnStatement(node, ts.factory.createTrue()); diff --git a/test/unit/__snapshots__/identifiers.spec.ts.snap b/test/unit/__snapshots__/identifiers.spec.ts.snap index b90b10398..cfd11a893 100644 --- a/test/unit/__snapshots__/identifiers.spec.ts.snap +++ b/test/unit/__snapshots__/identifiers.spec.ts.snap @@ -52,18 +52,14 @@ exports[`ambient identifier must be a valid lua identifier ("enum $$ {}"): code exports[`ambient identifier must be a valid lua identifier ("enum $$ {}"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`; -exports[`ambient identifier must be a valid lua identifier ("function $$();"): code 1`] = `"local ____ = _____24_24_24"`; +exports[`ambient identifier must be a valid lua identifier ("function $$(): void;"): code 1`] = `"local ____ = _____24_24_24"`; -exports[`ambient identifier must be a valid lua identifier ("function $$();"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`; +exports[`ambient identifier must be a valid lua identifier ("function $$(): void;"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`; exports[`ambient identifier must be a valid lua identifier ("let $$: any;"): code 1`] = `"local ____ = _____24_24_24"`; exports[`ambient identifier must be a valid lua identifier ("let $$: any;"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`; -exports[`ambient identifier must be a valid lua identifier ("module $$ { export const bar: any; }"): code 1`] = `"local ____ = _____24_24_24"`; - -exports[`ambient identifier must be a valid lua identifier ("module $$ { export const bar: any; }"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`; - exports[`ambient identifier must be a valid lua identifier ("namespace $$ { export const bar: any; }"): code 1`] = `"local ____ = _____24_24_24"`; exports[`ambient identifier must be a valid lua identifier ("namespace $$ { export const bar: any; }"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`; diff --git a/test/unit/__snapshots__/loops.spec.ts.snap b/test/unit/__snapshots__/loops.spec.ts.snap index 29b609730..886964ebc 100644 --- a/test/unit/__snapshots__/loops.spec.ts.snap +++ b/test/unit/__snapshots__/loops.spec.ts.snap @@ -3,7 +3,7 @@ exports[`forin[Array]: code 1`] = ` "local ____exports = {} function ____exports.__main(self) - local array = {} + local array = {1, 2, 3} for key in pairs(array) do end end diff --git a/test/unit/annotations/customConstructor.spec.ts b/test/unit/annotations/customConstructor.spec.ts index 86858f700..c9ddcf739 100644 --- a/test/unit/annotations/customConstructor.spec.ts +++ b/test/unit/annotations/customConstructor.spec.ts @@ -11,8 +11,8 @@ test("CustomCreate", () => { const tsHeader = ` /** @customConstructor Point2DCreate */ class Point2D { - public x: number; - public y: number; + public x!: number; + public y!: number; constructor(x: number, y: number) { // No values assigned } diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index 53291f50f..3ad7da149 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -342,7 +342,7 @@ test.each([ test("local variable declaration referencing self indirectly", () => { util.testFunction` - let cb: () => void; + let cb: () => void = () => { throw "Expecting this to be overwritten"; }; function foo(newCb: () => void) { cb = newCb; @@ -360,7 +360,7 @@ test("local variable declaration referencing self indirectly", () => { test("local multiple variable declaration referencing self indirectly", () => { util.testFunction` - let cb: () => void; + let cb: () => void = () => { throw "Expecting this to be overwritten"; }; function foo(newCb: () => void) { cb = newCb; @@ -395,7 +395,7 @@ describe.each(["x &&= y", "x ||= y"])("boolean compound assignment (%p)", assign test.each([undefined, 3])("nullish coalescing compound assignment", initialValue => { util.testFunction` - let x: number = ${util.formatCode(initialValue)}; + let x: number | undefined = ${util.formatCode(initialValue)}; x ??= 5; return x; `.expectToMatchJsResult(); diff --git a/test/unit/builtins/array.spec.ts b/test/unit/builtins/array.spec.ts index dfb6807ec..5e3cd8887 100644 --- a/test/unit/builtins/array.spec.ts +++ b/test/unit/builtins/array.spec.ts @@ -325,7 +325,7 @@ test.each([ { array: [0, 2, 4, 8], predicate: "false" }, ])("array.find (%p)", ({ array, predicate }) => { util.testFunction` - const array = ${util.formatCode(array)}; + const array: number[] = ${util.formatCode(array)}; return array.find((elem, index, arr) => ${predicate} && arr[index] === elem); `.expectToMatchJsResult(); }); @@ -337,7 +337,7 @@ test.each([ { array: [0, 2, 4, 8], searchElement: 8 }, ])("array.findIndex (%p)", ({ array, searchElement }) => { util.testFunction` - const array = ${util.formatCode(array)}; + const array: number[] = ${util.formatCode(array)}; return array.findIndex((elem, index, arr) => elem === ${searchElement} && arr[index] === elem); `.expectToMatchJsResult(); }); @@ -421,9 +421,16 @@ test.each([ { array: [0, 1, 2, 3], start: 1, deleteCount: undefined }, { array: [0, 1, 2, 3], start: 1, deleteCount: null }, ])("array.splice (%p)", ({ array, start, deleteCount, newElements = [] }) => { + const deleteCountCode = + deleteCount === undefined + ? "undefined as any" + : deleteCount === null + ? "null as any" + : util.formatCode(deleteCount); + const newElementsCode = newElements.length > 0 ? ", " + util.formatCode(...newElements) : ""; util.testFunction` - const array = ${util.formatCode(array)}; - array.splice(${util.formatCode(start, deleteCount, ...newElements)}); + const array: number[] = ${util.formatCode(array)}; + array.splice(${util.formatCode(start)}, ${deleteCountCode}${newElementsCode}); return array; `.expectToMatchJsResult(); }); @@ -510,8 +517,11 @@ test("array.join without separator argument", () => { util.testExpression`["test1", "test2"].join()`.expectToMatchJsResult(); }); +test("array.indexOf empty array", () => { + util.testExpression`([] as string[]).indexOf("test1")`.expectToMatchJsResult(); +}); + test.each([ - { array: [], args: ["test1"] }, { array: ["test1"], args: ["test1"] }, { array: ["test1", "test2"], args: ["test2"] }, { array: ["test1", "test2", "test3"], args: ["test3", 1] }, @@ -571,7 +581,7 @@ test.each([{ array: [1, 2, 3] }, { array: [1, 2, 3, 4] }, { array: [1] }, { arra "array.reverse (%p)", ({ array }) => { util.testFunction` - const array = ${util.formatCode(array)}; + const array: number[] = ${util.formatCode(array)}; array.reverse(); return array; `.expectToMatchJsResult(); @@ -580,7 +590,7 @@ test.each([{ array: [1, 2, 3] }, { array: [1, 2, 3, 4] }, { array: [1] }, { arra test.each([{ array: [1, 2, 3] }, { array: [1] }, { array: [] }])("array.shift (%p)", ({ array }) => { util.testFunction` - const array = ${util.formatCode(array)}; + const array: number[] = ${util.formatCode(array)}; const value = array.shift(); return { array, value }; `.expectToMatchJsResult(); @@ -593,7 +603,7 @@ test.each([ { array: [], args: [1] }, ])("array.unshift (%p)", ({ array, args }) => { util.testFunction` - const array = ${util.formatCode(array)}; + const array: number[] = ${util.formatCode(array)}; const value = array.unshift(${util.formatCode(...args)}); return { array, value }; `.expectToMatchJsResult(); @@ -601,7 +611,7 @@ test.each([ test.each([{ array: [4, 5, 3, 2, 1] }, { array: [1] }, { array: [] }])("array.sort (%p)", ({ array }) => { util.testFunctionTemplate` - const array = ${array}; + const array: number[] = ${array}; array.sort(); return array; `.expectToMatchJsResult(); @@ -659,7 +669,7 @@ describe.each(["reduce", "reduceRight"])("array.%s", reduce => { }); test("empty no initial", () => { - util.testExpression`[].${reduce}(() => {})`.expectToMatchJsResult(true); + util.testExpression`([] as any[]).${reduce}(() => {})`.expectToMatchJsResult(true); }); test("undefined returning callback", () => { @@ -675,7 +685,7 @@ test.each([{ array: [] }, { array: ["a", "b", "c"] }, { array: [{ foo: "foo" }, "array.entries (%p)", ({ array }) => { util.testFunction` - const array = ${util.formatCode(array)}; + const array: any[] = ${util.formatCode(array)}; const result = []; for (const [i, v] of array.entries()) { result.push([i, v]); @@ -800,9 +810,12 @@ test.each([ }); describe("array.fill", () => { - test.each(["[]", "[1]", "[1,2,3,4]"])("Fills full length of array without other parameters (%p)", arr => { - util.testExpression`${arr}.fill(5)`.expectToMatchJsResult(); - }); + test.each(["([] as number[])", "[1]", "[1,2,3,4]"])( + "Fills full length of array without other parameters (%p)", + arr => { + util.testExpression`${arr}.fill(5)`.expectToMatchJsResult(); + } + ); test.each(["[1,2,3]", "[1,2,3,4,5,6]"])("Fills starting from start parameter (%p)", arr => { util.testExpression`${arr}.fill(5, 3)`.expectToMatchJsResult(); diff --git a/test/unit/builtins/async-await.spec.ts b/test/unit/builtins/async-await.spec.ts index 546589355..ad64f031d 100644 --- a/test/unit/builtins/async-await.spec.ts +++ b/test/unit/builtins/async-await.spec.ts @@ -38,7 +38,7 @@ test("high amount of chained awaits doesn't cause stack overflow", () => { await delay(); } result = "success"; - } catch (e) { + } catch (e: any) { result = e; } } @@ -51,7 +51,7 @@ test("high amount of chained awaits doesn't cause stack overflow", () => { test("can await already resolved promise", () => { util.testFunction` - const result = []; + const result: unknown[] = []; async function abc() { return await Promise.resolve(30); } @@ -63,7 +63,7 @@ test("can await already resolved promise", () => { test("can await already rejected promise", () => { util.testFunction` - const result = []; + const result: unknown[] = []; async function abc() { return await Promise.reject("test rejection"); } @@ -604,7 +604,7 @@ describe("try/catch in async function", () => { () => util.testModule` export let reason = ""; - let reject: (reason: string) => void; + let reject: (reason: string) => void = () => { throw "should be overridden!"; } async function foo(): Promise { try { @@ -675,6 +675,7 @@ describe("try/catch in async function", () => { } catch { + return "catch"; } } diff --git a/test/unit/builtins/map.spec.ts b/test/unit/builtins/map.spec.ts index 27b539589..c9f6c1a2b 100644 --- a/test/unit/builtins/map.spec.ts +++ b/test/unit/builtins/map.spec.ts @@ -251,7 +251,7 @@ describe.each(iterationMethods)("map.%s() handles mutation", iterationMethod => describe("Map.groupBy", () => { test("empty", () => { util.testFunction` - const array = []; + const array: number[] = []; const map = Map.groupBy(array, (num, index) => { return num % 2 === 0 ? "even": "odd"; diff --git a/test/unit/builtins/object.spec.ts b/test/unit/builtins/object.spec.ts index 192f17d9f..411da903b 100644 --- a/test/unit/builtins/object.spec.ts +++ b/test/unit/builtins/object.spec.ts @@ -157,7 +157,7 @@ describe("Object.defineProperty", () => { test.each(trueFalseTests)("configurable (%p)", value => { util.testFunction` - const foo = { bar: true }; + const foo: { bar?: boolean } = { bar: true }; Object.defineProperty(foo, "bar", { configurable: ${value} }); try { delete foo.bar } catch {}; return foo.bar; @@ -306,7 +306,7 @@ describe("Object.getOwnPropertyDescriptors", () => { describe("delete from object", () => { test("delete from object", () => { util.testFunction` - const obj = { foo: "bar", bar: "baz" }; + const obj: { foo?: string, bar: string } = { foo: "bar", bar: "baz" }; return [delete obj["foo"], obj]; `.expectToMatchJsResult(); }); @@ -321,7 +321,7 @@ describe("delete from object", () => { // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/993 test("delete from object with metatable", () => { util.testFunction` - const obj = { foo: "bar", bar: "baz" }; + const obj: { foo?: string, bar: string } = { foo: "bar", bar: "baz" }; setmetatable(obj, {}); return [delete obj["foo"], obj]; ` @@ -353,7 +353,7 @@ describe("delete from object", () => { describe("Object.groupBy", () => { test("empty", () => { util.testFunction` - const array = []; + const array: number[] = []; return Object.groupBy(array, (num, index) => { return num % 2 === 0 ? "even": "odd"; diff --git a/test/unit/builtins/set.spec.ts b/test/unit/builtins/set.spec.ts index 65ee4aef0..234be91a7 100644 --- a/test/unit/builtins/set.spec.ts +++ b/test/unit/builtins/set.spec.ts @@ -100,7 +100,7 @@ test("set has false", () => { test("set has null", () => { util.testFunction` - let myset = new Set(["a", "c"]); + let myset = new Set(["a", "c"]); return myset.has(null); `.expectToMatchJsResult(); }); diff --git a/test/unit/builtins/string.spec.ts b/test/unit/builtins/string.spec.ts index 977944e55..7f2ad312d 100644 --- a/test/unit/builtins/string.spec.ts +++ b/test/unit/builtins/string.spec.ts @@ -84,7 +84,7 @@ describe.each(["replace", "replaceAll"])("string.%s", method => { test.each(testCases)("function replacer %p", ({ inp, searchValue, replaceValue }) => { util.testFunction` const result = { - args: [], + args: [] as string[], string: "" } function replacer(...args: any[]): string { diff --git a/test/unit/builtins/weakMap.spec.ts b/test/unit/builtins/weakMap.spec.ts index 812e43622..8ff5d4c2e 100644 --- a/test/unit/builtins/weakMap.spec.ts +++ b/test/unit/builtins/weakMap.spec.ts @@ -74,7 +74,7 @@ test("weakMap has null", () => { util.testFunction` ${initRefsTs} let mymap = new WeakMap([[{}, true]]); - return mymap.has(null); + return mymap.has(null as any); `.expectToMatchJsResult(); }); diff --git a/test/unit/classes/classes.spec.ts b/test/unit/classes/classes.spec.ts index f28c3b43d..e7b8cc304 100644 --- a/test/unit/classes/classes.spec.ts +++ b/test/unit/classes/classes.spec.ts @@ -894,7 +894,7 @@ test("get inherted __index member from super (DotA 2 inheritance) (#1537)", () = interface Connected extends I {} class Connected {} - declare function setmetatable(this: void, t: any, mt: any); + declare function setmetatable(this: void, t: any, mt: any): void; const A = { foo() { diff --git a/test/unit/classes/decorators.spec.ts b/test/unit/classes/decorators.spec.ts index 4782746be..095bd8e3a 100644 --- a/test/unit/classes/decorators.spec.ts +++ b/test/unit/classes/decorators.spec.ts @@ -22,8 +22,8 @@ test("Class decorator with no parameters", () => { } return { decoratedClass: new TestClass(), context: { - kind: classDecoratorContext.kind, - name: classDecoratorContext.name, + kind: classDecoratorContext!.kind, + name: classDecoratorContext!.name, } }; `.expectToMatchJsResult(); }); @@ -40,7 +40,7 @@ test("Class decorator with parameters", () => { @setNum(420) class TestClass { - public decoratorNum; + public decoratorNum?: number; } return new TestClass(); @@ -64,8 +64,8 @@ test("Multiple class decorators", () => { @setTen @setNum class TestClass { - public decoratorTen; - public decoratorNum; + public decoratorTen?: number; + public decoratorNum?: number; } return new TestClass(); @@ -95,7 +95,7 @@ test("Class decorator with inheritance", () => { test("Class decorators are applied in order and executed in reverse order", () => { util.testFunction` - const order = []; + const order: string[] = []; function pushOrder(index: number) { order.push("eval " + index); @@ -232,10 +232,10 @@ test("class method decorator", () => { } return { result: new TestClass().myMethod(4), context: { - kind: methodDecoratorContext.kind, - name: methodDecoratorContext.name, - private: methodDecoratorContext.private, - static: methodDecoratorContext.static + kind: methodDecoratorContext!.kind, + name: methodDecoratorContext!.name, + private: methodDecoratorContext!.private, + static: methodDecoratorContext!.static } }; `.expectToMatchJsResult(); }); @@ -243,7 +243,7 @@ test("class method decorator", () => { test("this in decorator points to class being decorated", () => { util.testFunction` function methodDecorator(method: (v: number) => number, context: ClassMethodDecoratorContext) { - return function() { + return function(this: TestClass) { const thisCallTime = this.myInstanceVariable; return thisCallTime; }; @@ -280,10 +280,10 @@ test("class getter decorator", () => { } return { result: new TestClass().getterValue, context: { - kind: getterDecoratorContext.kind, - name: getterDecoratorContext.name, - private: getterDecoratorContext.private, - static: getterDecoratorContext.static + kind: getterDecoratorContext!.kind, + name: getterDecoratorContext!.name, + private: getterDecoratorContext!.private, + static: getterDecoratorContext!.static } }; `.expectToMatchJsResult(); }); @@ -295,13 +295,13 @@ test("class setter decorator", () => { function setterDecorator(setter: (v: number) => void, context: ClassSetterDecoratorContext) { setterDecoratorContext = context; - return function(v: number) { + return function(this: TestClass, v: number) { setter.call(this, v + 15); }; } class TestClass { - public value: number; + public value?: number; @setterDecorator set valueSetter(v: number) { this.value = v; } @@ -310,10 +310,10 @@ test("class setter decorator", () => { const instance = new TestClass(); instance.valueSetter = 23; return { result: instance.value, context: { - kind: setterDecoratorContext.kind, - name: setterDecoratorContext.name, - private: setterDecoratorContext.private, - static: setterDecoratorContext.static + kind: setterDecoratorContext!.kind, + name: setterDecoratorContext!.name, + private: setterDecoratorContext!.private, + static: setterDecoratorContext!.static } }; `.expectToMatchJsResult(); }); @@ -332,10 +332,10 @@ test("class field decorator", () => { } return { result: new TestClass(), context: { - kind: fieldDecoratorContext.kind, - name: fieldDecoratorContext.name, - private: fieldDecoratorContext.private, - static: fieldDecoratorContext.static, + kind: fieldDecoratorContext!.kind, + name: fieldDecoratorContext!.name, + private: fieldDecoratorContext!.private, + static: fieldDecoratorContext!.static, } }; `.expectToEqual({ result: { @@ -399,7 +399,7 @@ describe("legacy experimentalDecorators", () => { @setNum(420) class TestClass { - public decoratorNum; + public decoratorNum?: number; } return new TestClass(); @@ -425,8 +425,8 @@ describe("legacy experimentalDecorators", () => { @setTen @setNum class TestClass { - public decoratorTen; - public decoratorNum; + public decoratorTen?: number; + public decoratorNum?: number; } return new TestClass(); @@ -460,7 +460,7 @@ describe("legacy experimentalDecorators", () => { test("Class decorators are applied in order and executed in reverse order", () => { util.testFunction` - const order = []; + const order: string[] = []; function pushOrder(index: number) { order.push("eval " + index); @@ -509,25 +509,25 @@ describe("legacy experimentalDecorators", () => { test.each([ ["@decorator method() {}"], - ["@decorator property;"], + ["@decorator property: any;"], ["@decorator propertyWithInitializer = () => {};"], - ["@decorator ['evaluated property'];"], + ["@decorator ['evaluated property']: any;"], ["@decorator get getter() { return 5 }"], - ["@decorator set setter(value) {}"], + ["@decorator set setter(value: any) {}"], ["@decorator static method() {}"], - ["@decorator static property;"], + ["@decorator static property: any;"], ["@decorator static propertyWithInitializer = () => {}"], ["@decorator static get getter() { return 5 }"], - ["@decorator static set setter(value) {}"], - ["@decorator static ['evaluated property'];"], - ["method(@decorator a) {}"], - ["static method(@decorator a) {}"], - ["constructor(@decorator a) {}"], + ["@decorator static set setter(value: any) {}"], + ["@decorator static ['evaluated property']: any;"], + ["method(@decorator a: any) {}"], + ["static method(@decorator a: any) {}"], + ["constructor(@decorator a: any) {}"], ])("Decorate class member (%p)", classMember => { util.testFunction` let decoratorParameters: any; - const decorator = (target, key, index?) => { + const decorator = (target: any, key: any, index?: any) => { const targetKind = target === Foo ? "Foo" : target === Foo.prototype ? "Foo.prototype" : "unknown"; decoratorParameters = { targetKind, key, index: typeof index }; }; @@ -548,10 +548,10 @@ describe("legacy experimentalDecorators", () => { ["desc.writable = true", "return { configurable: true }"], ])("Combine decorators (%p + %p)", (decorateA, decorateB) => { util.testFunction` - const A = (target, key, desc): any => { ${decorateA} }; - const B = (target, key, desc): any => { ${decorateB} }; + const A = (target: any, key: any, desc: any): any => { ${decorateA} }; + const B = (target: any, key: any, desc: any): any => { ${decorateB} }; class Foo { @A @B static method() {} } - const { value, ...rest } = Object.getOwnPropertyDescriptor(Foo, "method"); + const { value, ...rest } = Object.getOwnPropertyDescriptor(Foo, "method")!; return rest; ` .setOptions({ experimentalDecorators: true }) @@ -562,7 +562,7 @@ describe("legacy experimentalDecorators", () => { "Use decorator to override method value %s", overrideStatement => { util.testFunction` - const decorator = (target, key, desc): any => { ${overrideStatement} }; + const decorator = (target: any, key: any, desc: any): any => { ${overrideStatement} }; class Foo { @decorator static method() {} } return Foo.method; ` diff --git a/test/unit/conditionals.spec.ts b/test/unit/conditionals.spec.ts index f862ca8a3..59ca587bc 100644 --- a/test/unit/conditionals.spec.ts +++ b/test/unit/conditionals.spec.ts @@ -75,7 +75,7 @@ test.each([ ])("Ternary operator (%p)", ({ input, options }) => { util.testFunction` const literalValue = "literal"; - let variableValue: string; + let variableValue: string = "variable"; let maybeBooleanValue: string | boolean = false; let maybeUndefinedValue: string | undefined; return ${input}; diff --git a/test/unit/destructuring.spec.ts b/test/unit/destructuring.spec.ts index 4820876a4..36b73daaf 100644 --- a/test/unit/destructuring.spec.ts +++ b/test/unit/destructuring.spec.ts @@ -3,40 +3,50 @@ import * as util from "../util"; const allBindings = "x, y, z, rest"; const testCases = [ - { binding: "{ x }", value: { x: true } }, - { binding: "{ x, y }", value: { x: false, y: true } }, - { binding: "{ x: z, y }", value: { x: true, y: false } }, - { binding: "{ x: { x, y }, z }", value: { x: { x: true, y: false }, z: false } }, - { binding: "{ x, y = true }", value: { x: false, y: false } }, - { binding: "{ x = true }", value: {} }, - { binding: "{ x, y = true }", value: { x: false } }, - { binding: "{ ...rest }", value: {} }, - { binding: "{ x, ...rest }", value: { x: "x" } }, - { binding: "{ x, ...rest }", value: { x: "x", y: "y", z: "z" } }, - { binding: "{ x, ...y }", value: { x: "x", y: "y", z: "z" } }, - { binding: "{ x: y, ...z }", value: { x: "x", y: "y", z: "z" } }, + { binding: "{ x }", type: "{ x: boolean }", value: { x: true } }, + { binding: "{ x, y }", type: "{ x: boolean, y: boolean }", value: { x: false, y: true } }, + { binding: "{ x: z, y }", type: "{ x: boolean, y?: boolean, z?: boolean }", value: { x: true, y: false } }, + { + binding: "{ x: { x, y }, z }", + type: "{ x: { x: boolean, y: boolean }, z: boolean }", + value: { x: { x: true, y: false }, z: false }, + }, + { binding: "{ x, y = true }", type: "{ x: boolean, y?: boolean }", value: { x: false, y: false } }, + { binding: "{ x = true }", type: "{ x?: boolean }", value: {} }, + { binding: "{ x, y = true }", type: "{ x: boolean, y?: boolean }", value: { x: false } }, + { binding: "{ ...rest }", type: "any", value: {} }, + { binding: "{ x, ...rest }", type: "any", value: { x: "x" } }, + { binding: "{ x, ...rest }", type: "any", value: { x: "x", y: "y", z: "z" } }, + { binding: "{ x, ...y }", type: "any", value: { x: "x", y: "y", z: "z" } }, + { binding: "{ x: y, ...z }", type: "any", value: { x: "x", y: "y", z: "z" } }, - { binding: "[]", value: [] }, - { binding: "[x, y]", value: ["x", "y"] }, - { binding: "[x, , y]", value: ["x", "", "y"] }, - { binding: "[x = true]", value: [false] }, - { binding: "[[x, y]]", value: [["x", "y"]] }, - { binding: "[x, ...rest]", value: ["x"] }, - { binding: "[x, ...rest]", value: ["x", "y", "z"] }, + { binding: "[]", type: "boolean[]", value: [] }, + { binding: "[x, y]", type: "[string, string]", value: ["x", "y"] }, + { binding: "[x, , y]", type: "string[]", value: ["x", "", "y"] }, + { binding: "[x = true]", type: "boolean[]", value: [false] }, + { binding: "[x = true]", type: "boolean[]", value: [] }, + { binding: "[[x, y]]", type: "Array", value: [["x", "y"]] }, + { binding: "[x, ...rest]", type: "string[]", value: ["x"] }, + { binding: "[x, ...rest]", type: "string[]", value: ["x", "y", "z"] }, - { binding: "{ y: [z = true] }", value: { y: [false] } }, - { binding: "{ x: [x, y] }", value: { x: ["x", "y"] } }, - { binding: "{ x: [{ y }] }", value: { x: [{ y: "y" }] } }, -].map(({ binding, value }) => ({ binding, value: util.formatCode(value) })); + { binding: "{ y: [z = true] }", type: "{ y: boolean[] }", value: { y: [false] } }, + { binding: "{ y: [z = true] }", type: "{ y: boolean[] }", value: { y: [] } }, + { binding: "{ x: [x, y] }", type: "{ x: [string, string] }", value: { x: ["x", "y"] } }, + { binding: "{ x: [{ y }] }", type: "{ x: [{ y: string }] }", value: { x: [{ y: "y" }] } }, +].map(({ binding, type, value }) => ({ binding, type, value: util.formatCode(value) })); test.each([ ...testCases, - { binding: "{ x, y }, z", value: "{ x: false, y: false }, true" }, - { binding: "{ x, y }, { z }", value: "{ x: false, y: false }, { z: true }" }, -])("in function parameter (%p)", ({ binding, value }) => { + { binding: "{ x, y }", type: "{ x: boolean, y: boolean }, z: boolean", value: "{ x: false, y: false }, true" }, + { + binding: "{ x, y }", + type: "{ x: boolean, y: boolean }, { z }: { z: boolean }", + value: "{ x: false, y: false }, { z: true }", + }, +])("in function parameter (%p)", ({ binding, type, value }) => { util.testFunction` let ${allBindings}; - function test(${binding}) { + function test(${binding}: ${type}) { return { ${allBindings} }; } diff --git a/test/unit/enum.spec.ts b/test/unit/enum.spec.ts index 3ff4ce3fe..ae552b9c1 100644 --- a/test/unit/enum.spec.ts +++ b/test/unit/enum.spec.ts @@ -4,7 +4,7 @@ import * as util from "../util"; const serializeEnum = (identifier: string) => `(() => { const mappedTestEnum: any = {}; for (const key in ${identifier}) { - mappedTestEnum[(key as any).toString()] = ${identifier}[key]; + mappedTestEnum[(key as any).toString()] = (${identifier} as any)[key]; } return mappedTestEnum; })()`; diff --git a/test/unit/error.spec.ts b/test/unit/error.spec.ts index 89ceb49df..8204cc36a 100644 --- a/test/unit/error.spec.ts +++ b/test/unit/error.spec.ts @@ -88,7 +88,7 @@ test("multi return from try", () => { } catch { } } - const [foo, bar] = foobar(); + const [foo, bar] = foobar()!; return foo + bar; `.withLanguageExtensions(); expect(testBuilder.getMainLuaCodeChunk()).not.toMatch("unpack(foobar"); @@ -129,7 +129,7 @@ test("multi return from catch", () => { function foobar(): LuaMultiReturn<[string, string]> { try { throw "foobar"; - } catch (e) { + } catch (e: any) { return $multi(e.toString(), " catch"); } } @@ -243,7 +243,7 @@ test("multi return from catch->finally", () => { function foobar() { try { throw "foo"; - } catch (e) { + } catch (e: any) { return $multi(evaluate(e), "bar"); } finally { return $multi("final", "ly"); diff --git a/test/unit/functions/functionProperties.spec.ts b/test/unit/functions/functionProperties.spec.ts index 16b61ab46..85b3ec155 100644 --- a/test/unit/functions/functionProperties.spec.ts +++ b/test/unit/functions/functionProperties.spec.ts @@ -160,7 +160,7 @@ test("async arrow function with property assigned to variable", () => { test("call function with property using call method", () => { util.testFunction` - function foo(s: string) { return this + s; } + function foo(this: string, s: string) { return this + s; } foo.baz = "baz"; return foo.call("foo", "bar") + foo.baz; `.expectToMatchJsResult(); @@ -168,7 +168,7 @@ test("call function with property using call method", () => { test("call function with property using apply method", () => { util.testFunction` - function foo(s: string) { return this + s; } + function foo(this: string, s: string) { return this + s; } foo.baz = "baz"; return foo.apply("foo", ["bar"]) + foo.baz; `.expectToMatchJsResult(); @@ -176,7 +176,7 @@ test("call function with property using apply method", () => { test("call function with property using bind method", () => { util.testFunction` - function foo(s: string) { return this + s; } + function foo(this: string, s: string) { return this + s; } foo.baz = "baz"; return foo.bind("foo", "bar")() + foo.baz; `.expectToMatchJsResult(); diff --git a/test/unit/functions/functions.spec.ts b/test/unit/functions/functions.spec.ts index 7bdf6b040..27b48a1ee 100644 --- a/test/unit/functions/functions.spec.ts +++ b/test/unit/functions/functions.spec.ts @@ -5,7 +5,7 @@ import { unsupportedForTarget } from "../../../src/transformation/utils/diagnost test("Arrow Function Expression", () => { util.testFunction` - const add = (a, b) => a + b; + const add = (a: number, b: number) => a + b; return add(1, 2); `.expectToMatchJsResult(); }); @@ -25,17 +25,22 @@ test.each(["i++", "i--", "++i", "--i"])("Arrow function unary expression (%p)", `.expectToMatchJsResult(); }); -test.each(["b => a = b", "b => a += b", "b => a -= b", "b => a *= b", "b => a /= b", "b => a **= b", "b => a %= b"])( - "Arrow function assignment (%p)", - lambda => { - util.testFunction` +test.each([ + "(b: number) => a = b", + "(b: number) => a += b", + "(b: number) => a -= b", + "(b: number) => a *= b", + "(b: number) => a /= b", + "(b: number) => a **= b", + "(b: number) => a %= b", +])("Arrow function assignment (%p)", lambda => { + util.testFunction` let a = 10; let lambda = ${lambda}; lambda(5); return a; `.expectToMatchJsResult(); - } -); +}); test.each([{ args: [] }, { args: [1] }, { args: [1, 2] }])("Arrow default values (%p)", ({ args }) => { util.testFunction` @@ -46,7 +51,7 @@ test.each([{ args: [] }, { args: [1] }, { args: [1, 2] }])("Arrow default values test("Function Expression", () => { util.testFunction` - let add = function(a, b) {return a+b}; + let add = function(a: number, b: number) {return a+b}; return add(1,2); `.expectToMatchJsResult(); }); @@ -251,13 +256,13 @@ test.each(functionTypeDeclarations)("Function call (%s)", (_, type) => { }); test.each([ - "function fn() {}", - "function fn(x, y, z) {}", - "function fn(x, y, z, ...args) {}", - "function fn(...args) {}", - "function fn(this: void) {}", - "function fn(this: void, x, y, z) {}", - "function fnReference(x, y, z) {} const fn = fnReference;", + "function fn(): void {}", + "function fn(x: any, y: any, z: any): void {}", + "function fn(x: any, y: any, z: any, ...args: any[]): void {}", + "function fn(...args: any[]): void {}", + "function fn(this: void): void {}", + "function fn(this: void, x: any, y: any, z: any): void {}", + "function fnReference(x: any, y: any, z: any): void {} const fn = fnReference;", "const wrap = (fn: (...args: any[]) => any) => (...args: any[]) => fn(...args); const fn = wrap((x, y, z) => {});", ])("function.length (%p)", declaration => { util.testFunction` @@ -432,7 +437,7 @@ test("Complex element access call no args", () => { test("Complex element access call statement", () => { util.testFunction` - let foo: string; + let foo: string | undefined; class C { prop = "bar"; method(s: string) { foo = s + this.prop; } diff --git a/test/unit/functions/generators.spec.ts b/test/unit/functions/generators.spec.ts index aa62d719d..6cf935528 100644 --- a/test/unit/functions/generators.spec.ts +++ b/test/unit/functions/generators.spec.ts @@ -27,7 +27,7 @@ test(".next()", () => { test(".next() with parameters", () => { util.testFunction` - function* generator() { + function* generator(): Generator { return yield 0; } diff --git a/test/unit/functions/validation/__snapshots__/invalidFunctionAssignments.spec.ts.snap b/test/unit/functions/validation/__snapshots__/invalidFunctionAssignments.spec.ts.snap index b7175df75..87cb07b9d 100644 --- a/test/unit/functions/validation/__snapshots__/invalidFunctionAssignments.spec.ts.snap +++ b/test/unit/functions/validation/__snapshots__/invalidFunctionAssignments.spec.ts.snap @@ -340,17 +340,17 @@ exports[`Invalid function argument ({"definition": "namespace NoSelfFuncNs { exports[`Invalid function argument ({"definition": "namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncSelf(s: string): string { return s; } } /** @noSelf */ namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncNoSelf(s: string) { return s; } }", "value": "SelfAnonFuncNSMergedNoSelfNS.nsFuncSelf"}): diagnostics 1`] = `"main.ts(5,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function argument ({"value": "(function(this: any, s) { return s; })"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function argument ({"value": "(function(this: any, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function argument ({"value": "(function(this: void, s) { return s; })"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function argument ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function argument ({"value": "(function(this: void, s) { return s; })"}): diagnostics 2`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function argument ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 2`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function argument ({"value": "function(this: any, s) { return s; }"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function argument ({"value": "function(this: any, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function argument ({"value": "function(this: void, s) { return s; }"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function argument ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function argument ({"value": "function(this: void, s) { return s; }"}): diagnostics 2`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function argument ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 2`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; exports[`Invalid function argument with cast ({"definition": "/** @noSelfInFile */ let noSelfInFileFunc: {(s: string): string} = function(s) { return s; };", "value": "noSelfInFileFunc"}): diagnostics 1`] = ` "main.ts(4,23): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'. @@ -732,17 +732,17 @@ exports[`Invalid function assignment ({"definition": "namespace NoSelfFuncNs { exports[`Invalid function assignment ({"definition": "namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncSelf(s: string): string { return s; } } /** @noSelf */ namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncNoSelf(s: string) { return s; } }", "value": "SelfAnonFuncNSMergedNoSelfNS.nsFuncSelf"}): diagnostics 1`] = `"main.ts(5,18): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function assignment ({"value": "(function(this: any, s) { return s; })"}): diagnostics 1`] = `"main.ts(4,18): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function assignment ({"value": "(function(this: any, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(4,18): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function assignment ({"value": "(function(this: void, s) { return s; })"}): diagnostics 1`] = `"main.ts(4,18): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function assignment ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(4,18): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function assignment ({"value": "(function(this: void, s) { return s; })"}): diagnostics 2`] = `"main.ts(4,18): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function assignment ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 2`] = `"main.ts(4,18): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function assignment ({"value": "function(this: any, s) { return s; }"}): diagnostics 1`] = `"main.ts(4,18): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function assignment ({"value": "function(this: any, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(4,18): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function assignment ({"value": "function(this: void, s) { return s; }"}): diagnostics 1`] = `"main.ts(4,18): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function assignment ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(4,18): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function assignment ({"value": "function(this: void, s) { return s; }"}): diagnostics 2`] = `"main.ts(4,18): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function assignment ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 2`] = `"main.ts(4,18): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; exports[`Invalid function assignment with cast ({"definition": "/** @noSelfInFile */ let noSelfInFileFunc: {(s: string): string} = function(s) { return s; };", "value": "noSelfInFileFunc"}): diagnostics 1`] = ` "main.ts(4,14): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'. @@ -1124,17 +1124,17 @@ exports[`Invalid function generic argument ({"definition": "namespace NoSelfFunc exports[`Invalid function generic argument ({"definition": "namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncSelf(s: string): string { return s; } } /** @noSelf */ namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncNoSelf(s: string) { return s; } }", "value": "SelfAnonFuncNSMergedNoSelfNS.nsFuncSelf"}): diagnostics 1`] = `"main.ts(5,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function generic argument ({"value": "(function(this: any, s) { return s; })"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function generic argument ({"value": "(function(this: any, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function generic argument ({"value": "(function(this: void, s) { return s; })"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function generic argument ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function generic argument ({"value": "(function(this: void, s) { return s; })"}): diagnostics 2`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function generic argument ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 2`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function generic argument ({"value": "function(this: any, s) { return s; }"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function generic argument ({"value": "function(this: any, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function generic argument ({"value": "function(this: void, s) { return s; }"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function generic argument ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function generic argument ({"value": "function(this: void, s) { return s; }"}): diagnostics 2`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function generic argument ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 2`] = `"main.ts(4,27): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; exports[`Invalid function overload assignment ("(this: void, s: string) => string"): diagnostics 1`] = `"main.ts(7,52): error TSTL: Unsupported assignment of function with different overloaded types for 'this'. Overloads should all have the same type for 'this'."`; @@ -1484,17 +1484,17 @@ exports[`Invalid function return ({"definition": "namespace NoSelfFuncNs { exports[`Invalid function return ({"definition": "namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncSelf(s: string): string { return s; } } /** @noSelf */ namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncNoSelf(s: string) { return s; } }", "value": "SelfAnonFuncNSMergedNoSelfNS.nsFuncSelf"}): diagnostics 1`] = `"main.ts(5,17): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function return ({"value": "(function(this: any, s) { return s; })"}): diagnostics 1`] = `"main.ts(4,17): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function return ({"value": "(function(this: any, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(4,17): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function return ({"value": "(function(this: void, s) { return s; })"}): diagnostics 1`] = `"main.ts(4,17): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function return ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(4,17): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function return ({"value": "(function(this: void, s) { return s; })"}): diagnostics 2`] = `"main.ts(4,17): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function return ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 2`] = `"main.ts(4,17): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function return ({"value": "function(this: any, s) { return s; }"}): diagnostics 1`] = `"main.ts(4,17): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function return ({"value": "function(this: any, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(4,17): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function return ({"value": "function(this: void, s) { return s; }"}): diagnostics 1`] = `"main.ts(4,17): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function return ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(4,17): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function return ({"value": "function(this: void, s) { return s; }"}): diagnostics 2`] = `"main.ts(4,17): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function return ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 2`] = `"main.ts(4,17): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; exports[`Invalid function return with cast ({"definition": "/** @noSelfInFile */ let noSelfInFileFunc: {(s: string): string} = function(s) { return s; };", "value": "noSelfInFileFunc"}): diagnostics 1`] = ` "main.ts(4,17): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'. @@ -1885,17 +1885,17 @@ exports[`Invalid function variable declaration ({"definition": "namespace NoSelf exports[`Invalid function variable declaration ({"definition": "namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncSelf(s: string): string { return s; } } /** @noSelf */ namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncNoSelf(s: string) { return s; } }", "value": "SelfAnonFuncNSMergedNoSelfNS.nsFuncSelf"}): diagnostics 1`] = `"main.ts(4,59): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function variable declaration ({"value": "(function(this: any, s) { return s; })"}): diagnostics 1`] = `"main.ts(3,59): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function variable declaration ({"value": "(function(this: any, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(3,59): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function variable declaration ({"value": "(function(this: void, s) { return s; })"}): diagnostics 1`] = `"main.ts(3,47): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function variable declaration ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 1`] = `"main.ts(3,47): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function variable declaration ({"value": "(function(this: void, s) { return s; })"}): diagnostics 2`] = `"main.ts(3,58): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function variable declaration ({"value": "(function(this: void, s: string) { return s; })"}): diagnostics 2`] = `"main.ts(3,58): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function variable declaration ({"value": "function(this: any, s) { return s; }"}): diagnostics 1`] = `"main.ts(3,59): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid function variable declaration ({"value": "function(this: any, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(3,59): error TSTL: Unable to convert function with a 'this' parameter to function with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid function variable declaration ({"value": "function(this: void, s) { return s; }"}): diagnostics 1`] = `"main.ts(3,47): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function variable declaration ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 1`] = `"main.ts(3,47): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid function variable declaration ({"value": "function(this: void, s) { return s; }"}): diagnostics 2`] = `"main.ts(3,58): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid function variable declaration ({"value": "function(this: void, s: string) { return s; }"}): diagnostics 2`] = `"main.ts(3,58): error TSTL: Unable to convert function with no 'this' parameter to function with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; exports[`Invalid interface method assignment: diagnostics 1`] = `"main.ts(5,22): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; @@ -2260,17 +2260,17 @@ exports[`Invalid object with method argument ({"definition": "namespace NoSelfFu exports[`Invalid object with method argument ({"definition": "namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncSelf(s: string): string { return s; } } /** @noSelf */ namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncNoSelf(s: string) { return s; } }", "value": "SelfAnonFuncNSMergedNoSelfNS.nsFuncSelf"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(5,35): error TSTL: Unable to convert function with a 'this' parameter to function 'obj.fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid object with method argument ({"value": "(function(this: any, s) { return s; })"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with a 'this' parameter to function 'obj.fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid object with method argument ({"value": "(function(this: any, s: string) { return s; })"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with a 'this' parameter to function 'obj.fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid object with method argument ({"value": "(function(this: void, s) { return s; })"}, "(s: string) => string", true): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with no 'this' parameter to function 'obj.fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method argument ({"value": "(function(this: void, s: string) { return s; })"}, "(s: string) => string", true): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with no 'this' parameter to function 'obj.fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid object with method argument ({"value": "(function(this: void, s) { return s; })"}, "(this: any, s: string) => string", true): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with no 'this' parameter to function 'obj.fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method argument ({"value": "(function(this: void, s: string) { return s; })"}, "(this: any, s: string) => string", true): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with no 'this' parameter to function 'obj.fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid object with method argument ({"value": "function(this: any, s) { return s; }"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with a 'this' parameter to function 'obj.fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid object with method argument ({"value": "function(this: any, s: string) { return s; }"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with a 'this' parameter to function 'obj.fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid object with method argument ({"value": "function(this: void, s) { return s; }"}, "(s: string) => string", true): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with no 'this' parameter to function 'obj.fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method argument ({"value": "function(this: void, s: string) { return s; }"}, "(s: string) => string", true): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with no 'this' parameter to function 'obj.fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid object with method argument ({"value": "function(this: void, s) { return s; }"}, "(this: any, s: string) => string", true): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with no 'this' parameter to function 'obj.fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method argument ({"value": "function(this: void, s: string) { return s; }"}, "(this: any, s: string) => string", true): diagnostics 1`] = `"main.ts(4,35): error TSTL: Unable to convert function with no 'this' parameter to function 'obj.fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; exports[`Invalid object with method assignment ({"definition": "/** @noSelf */ class AnonFuncNSMergedNoSelfClass { method(s: string): string { return s; } } namespace AnonFuncNSMergedNoSelfClass { export function nsFunc(s: string) { return s; } }", "value": "AnonFuncNSMergedNoSelfClass.nsFunc"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(5,19): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; @@ -2629,17 +2629,17 @@ exports[`Invalid object with method assignment ({"definition": "namespace NoSelf exports[`Invalid object with method assignment ({"definition": "namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncSelf(s: string): string { return s; } } /** @noSelf */ namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncNoSelf(s: string) { return s; } }", "value": "SelfAnonFuncNSMergedNoSelfNS.nsFuncSelf"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(5,19): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid object with method assignment ({"value": "(function(this: any, s) { return s; })"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid object with method assignment ({"value": "(function(this: any, s: string) { return s; })"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid object with method assignment ({"value": "(function(this: void, s) { return s; })"}, "(s: string) => string", true): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method assignment ({"value": "(function(this: void, s: string) { return s; })"}, "(s: string) => string", true): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid object with method assignment ({"value": "(function(this: void, s) { return s; })"}, "(this: any, s: string) => string", true): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method assignment ({"value": "(function(this: void, s: string) { return s; })"}, "(this: any, s: string) => string", true): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid object with method assignment ({"value": "function(this: any, s) { return s; }"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid object with method assignment ({"value": "function(this: any, s: string) { return s; }"}, "(this: void, s: string) => string", false): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid object with method assignment ({"value": "function(this: void, s) { return s; }"}, "(s: string) => string", true): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method assignment ({"value": "function(this: void, s: string) { return s; }"}, "(s: string) => string", true): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid object with method assignment ({"value": "function(this: void, s) { return s; }"}, "(this: any, s: string) => string", true): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method assignment ({"value": "function(this: void, s: string) { return s; }"}, "(this: any, s: string) => string", true): diagnostics 1`] = `"main.ts(4,19): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; exports[`Invalid object with method assignment with cast ({"definition": "/** @noSelfInFile */ let noSelfInFileFunc: {(s: string): string} = function(s) { return s; };", "value": "noSelfInFileFunc"}, "(noSelfInFileFunc) as ((this: any, s: string) => string)", false): diagnostics 1`] = ` "main.ts(4,19): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'. @@ -3038,14 +3038,14 @@ exports[`Invalid object with method variable declaration ({"definition": "namesp exports[`Invalid object with method variable declaration ({"definition": "namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncSelf(s: string): string { return s; } } /** @noSelf */ namespace SelfAnonFuncNSMergedNoSelfNS { export function nsFuncNoSelf(s: string) { return s; } }", "value": "SelfAnonFuncNSMergedNoSelfNS.nsFuncSelf"}, "(this: void, s: string) => string"): diagnostics 1`] = `"main.ts(4,68): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid object with method variable declaration ({"value": "(function(this: any, s) { return s; })"}, "(this: void, s: string) => string"): diagnostics 1`] = `"main.ts(3,68): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid object with method variable declaration ({"value": "(function(this: any, s: string) { return s; })"}, "(this: void, s: string) => string"): diagnostics 1`] = `"main.ts(3,68): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid object with method variable declaration ({"value": "(function(this: void, s) { return s; })"}, "(s: string) => string"): diagnostics 1`] = `"main.ts(3,56): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method variable declaration ({"value": "(function(this: void, s: string) { return s; })"}, "(s: string) => string"): diagnostics 1`] = `"main.ts(3,56): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid object with method variable declaration ({"value": "(function(this: void, s) { return s; })"}, "(this: any, s: string) => string"): diagnostics 1`] = `"main.ts(3,67): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method variable declaration ({"value": "(function(this: void, s: string) { return s; })"}, "(this: any, s: string) => string"): diagnostics 1`] = `"main.ts(3,67): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid object with method variable declaration ({"value": "function(this: any, s) { return s; }"}, "(this: void, s: string) => string"): diagnostics 1`] = `"main.ts(3,68): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; +exports[`Invalid object with method variable declaration ({"value": "function(this: any, s: string) { return s; }"}, "(this: void, s: string) => string"): diagnostics 1`] = `"main.ts(3,68): error TSTL: Unable to convert function with a 'this' parameter to function 'fn' with no 'this'. To fix, wrap in an arrow function, or declare with 'this: void'."`; -exports[`Invalid object with method variable declaration ({"value": "function(this: void, s) { return s; }"}, "(s: string) => string"): diagnostics 1`] = `"main.ts(3,56): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method variable declaration ({"value": "function(this: void, s: string) { return s; }"}, "(s: string) => string"): diagnostics 1`] = `"main.ts(3,56): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; -exports[`Invalid object with method variable declaration ({"value": "function(this: void, s) { return s; }"}, "(this: any, s: string) => string"): diagnostics 1`] = `"main.ts(3,67): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; +exports[`Invalid object with method variable declaration ({"value": "function(this: void, s: string) { return s; }"}, "(this: any, s: string) => string"): diagnostics 1`] = `"main.ts(3,67): error TSTL: Unable to convert function with no 'this' parameter to function 'fn' with 'this'. To fix, wrap in an arrow function, or declare with 'this: any'."`; diff --git a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts index abac4e483..9220902b2 100644 --- a/test/unit/functions/validation/functionExpressionTypeInference.spec.ts +++ b/test/unit/functions/validation/functionExpressionTypeInference.spec.ts @@ -79,7 +79,7 @@ test.each([ test("Function expression type inference in object literal assigned to narrower type", () => { util.testFunction` - let foo: {} = {bar: s => s}; + let foo: {} = {bar: (s: string) => s}; return (foo as {bar: (a: any) => any}).bar("foobar"); `.expectToMatchJsResult(); }); diff --git a/test/unit/functions/validation/functionPermutations.ts b/test/unit/functions/validation/functionPermutations.ts index 14e478498..4947bc9a4 100644 --- a/test/unit/functions/validation/functionPermutations.ts +++ b/test/unit/functions/validation/functionPermutations.ts @@ -352,20 +352,20 @@ const noSelfInFileTestFunctions: TestFunction[] = [ ]; export const anonTestFunctionExpressions: TestFunction[] = [ - { value: "s => s" }, - { value: "(s => s)" }, - { value: "function(s) { return s; }" }, - { value: "(function(s) { return s; })" }, + { value: "(s: string) => s" }, + { value: "((s: string) => s)" }, + { value: "function(s: string) { return s; }" }, + { value: "(function(s: string) { return s; })" }, ]; export const selfTestFunctionExpressions: TestFunction[] = [ - { value: "function(this: any, s) { return s; }" }, - { value: "(function(this: any, s) { return s; })" }, + { value: "function(this: any, s: string) { return s; }" }, + { value: "(function(this: any, s: string) { return s; })" }, ]; export const noSelfTestFunctionExpressions: TestFunction[] = [ - { value: "function(this: void, s) { return s; }" }, - { value: "(function(this: void, s) { return s; })" }, + { value: "function(this: void, s: string) { return s; }" }, + { value: "(function(this: void, s: string) { return s; })" }, ]; export const anonTestFunctionType = "(s: string) => string"; diff --git a/test/unit/functions/validation/invalidFunctionAssignments.spec.ts b/test/unit/functions/validation/invalidFunctionAssignments.spec.ts index 95b26b7d0..2b9fd61db 100644 --- a/test/unit/functions/validation/invalidFunctionAssignments.spec.ts +++ b/test/unit/functions/validation/invalidFunctionAssignments.spec.ts @@ -96,7 +96,7 @@ test.each(invalidTestFunctionAssignments)( (testFunction, functionType, isSelfConversion) => { util.testModule` ${testFunction.definition ?? ""} - declare function takesFunction(fn: ${functionType}); + declare function takesFunction(fn: ${functionType}): void; takesFunction(${testFunction.value}); `.expectDiagnosticsToMatchSnapshot( [isSelfConversion ? unsupportedSelfFunctionConversion.code : unsupportedNoSelfFunctionConversion.code], @@ -110,7 +110,7 @@ test.each(invalidTestMethodAssignments)( (testFunction, functionType, isSelfConversion) => { util.testModule` ${testFunction.definition ?? ""} - declare function takesObjectWithMethod(obj: { fn: ${functionType} }); + declare function takesObjectWithMethod(obj: { fn: ${functionType} }): void; takesObjectWithMethod({fn: ${testFunction.value}}); `.expectDiagnosticsToMatchSnapshot( [isSelfConversion ? unsupportedSelfFunctionConversion.code : unsupportedNoSelfFunctionConversion.code], @@ -130,7 +130,7 @@ test("Invalid lua lib function argument", () => { test.each(invalidTestFunctionCasts)("Invalid function argument with cast (%p)", (testFunction, castedFunction) => { util.testModule` ${testFunction.definition ?? ""} - declare function takesFunction(fn: typeof ${testFunction.value}); + declare function takesFunction(fn: typeof ${testFunction.value}): void; takesFunction(${castedFunction}); `.expectDiagnosticsToMatchSnapshot( [unsupportedNoSelfFunctionConversion.code, unsupportedSelfFunctionConversion.code], @@ -143,7 +143,7 @@ test.each(invalidTestFunctionAssignments)( (testFunction, functionType, isSelfConversion) => { util.testModule` ${testFunction.definition ?? ""} - declare function takesFunction(fn: T); + declare function takesFunction(fn: T): void; takesFunction(${testFunction.value}); `.expectDiagnosticsToMatchSnapshot( [isSelfConversion ? unsupportedSelfFunctionConversion.code : unsupportedNoSelfFunctionConversion.code], diff --git a/test/unit/hoisting.spec.ts b/test/unit/hoisting.spec.ts index 144460067..e08a7c083 100644 --- a/test/unit/hoisting.spec.ts +++ b/test/unit/hoisting.spec.ts @@ -254,7 +254,7 @@ test("Hoisting variable without initializer", () => { function foo() { return x; } - let x: number; + let x: number | undefined; return foo(); `.expectToMatchJsResult(); }); diff --git a/test/unit/identifiers.spec.ts b/test/unit/identifiers.spec.ts index f148c3c72..ddac778fc 100644 --- a/test/unit/identifiers.spec.ts +++ b/test/unit/identifiers.spec.ts @@ -93,9 +93,8 @@ test.each([ "const foo: any, bar: any, $$$: any;", "class $$$ {}", "namespace $$$ { export const bar: any; }", - "module $$$ { export const bar: any; }", "enum $$$ {}", - "function $$$();", + "function $$$(): void;", ])("ambient identifier must be a valid lua identifier (%p)", statement => { util.testModule` declare ${statement} diff --git a/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap b/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap index a3f6de381..4420b90e0 100644 --- a/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap +++ b/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap @@ -160,22 +160,7 @@ return ____exports" exports[`invalid direct $multi function use (const ar = [1]; const [a] = $multi(...ar)): diagnostics 1`] = `"main.ts(7,37): error TSTL: The $multi function must be called in a return statement."`; -exports[`invalid direct $multi function use (let a; [a] = $multi()): code 1`] = ` -"local ____exports = {} -local function multi(self, ...) - return ... -end -local a -local ____temp_0 = {____(nil)} -a = ____temp_0[1] -____exports.a = a -____exports.a = a -return ____exports" -`; - -exports[`invalid direct $multi function use (let a; [a] = $multi()): diagnostics 1`] = `"main.ts(7,22): error TSTL: The $multi function must be called in a return statement."`; - -exports[`invalid direct $multi function use (let a; for ([a] = $multi(1, 2); false; 1) {}): code 1`] = ` +exports[`invalid direct $multi function use (let a: any; for ([a] = $multi(1, 2); false; 1) {}): code 1`] = ` "local ____exports = {} local function multi(self, ...) return ... @@ -193,9 +178,9 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (let a; for ([a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,27): error TSTL: The $multi function must be called in a return statement."`; +exports[`invalid direct $multi function use (let a: any; for ([a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,32): error TSTL: The $multi function must be called in a return statement."`; -exports[`invalid direct $multi function use (let a; for (const [a] = $multi(1, 2); false; 1) {}): code 1`] = ` +exports[`invalid direct $multi function use (let a: any; for (const [a] = $multi(1, 2); false; 1) {}): code 1`] = ` "local ____exports = {} local function multi(self, ...) return ... @@ -211,9 +196,9 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (let a; for (const [a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,33): error TSTL: The $multi function must be called in a return statement."`; +exports[`invalid direct $multi function use (let a: any; for (const [a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,38): error TSTL: The $multi function must be called in a return statement."`; -exports[`invalid direct $multi function use (let a; if ([a] = $multi(1)) { ++a; }): code 1`] = ` +exports[`invalid direct $multi function use (let a: any; if ([a] = $multi(1)) { ++a; }): code 1`] = ` "local ____exports = {} local function multi(self, ...) return ... @@ -230,4 +215,19 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (let a; if ([a] = $multi(1)) { ++a; }): diagnostics 1`] = `"main.ts(7,26): error TSTL: The $multi function must be called in a return statement."`; +exports[`invalid direct $multi function use (let a: any; if ([a] = $multi(1)) { ++a; }): diagnostics 1`] = `"main.ts(7,31): error TSTL: The $multi function must be called in a return statement."`; + +exports[`invalid direct $multi function use (let a; [a] = $multi()): code 1`] = ` +"local ____exports = {} +local function multi(self, ...) + return ... +end +local a +local ____temp_0 = {____(nil)} +a = ____temp_0[1] +____exports.a = a +____exports.a = a +return ____exports" +`; + +exports[`invalid direct $multi function use (let a; [a] = $multi()): diagnostics 1`] = `"main.ts(7,22): error TSTL: The $multi function must be called in a return statement."`; diff --git a/test/unit/language-extensions/multi.spec.ts b/test/unit/language-extensions/multi.spec.ts index 72b330887..90fc54200 100644 --- a/test/unit/language-extensions/multi.spec.ts +++ b/test/unit/language-extensions/multi.spec.ts @@ -42,6 +42,7 @@ test("Destructuring assignment of LuaMultiReturn returning nil", () => { export {a, b}; ` .withLanguageExtensions() + .setOptions({ strict: false }) .expectToEqual({ a: undefined, b: [] }); }); @@ -58,7 +59,7 @@ test.each<[string, any]>([ }); const multiFunction = ` -function multi(...args) { +function multi(...args: unknown[]) { return $multi(...args); } `; @@ -71,9 +72,9 @@ const createCasesThatCall = (name: string): Array<[string, any]> => [ [`const [a = 1] = ${name}(2)`, 2], [`const ar = [1]; const [a] = ${name}(...ar)`, 1], [`const _ = null, [a] = ${name}(1)`, 1], - [`let a; for (const [a] = ${name}(1, 2); false; 1) {}`, undefined], - [`let a; for ([a] = ${name}(1, 2); false; 1) {}`, 1], - [`let a; if ([a] = ${name}(1)) { ++a; }`, 2], + [`let a: any; for (const [a] = ${name}(1, 2); false; 1) {}`, undefined], + [`let a: any; for ([a] = ${name}(1, 2); false; 1) {}`, 1], + [`let a: any; if ([a] = ${name}(1)) { ++a; }`, 2], ]; test.each<[string, any]>(createCasesThatCall("$multi"))("invalid direct $multi function use (%s)", statement => { @@ -272,7 +273,7 @@ test("return $multi from try", () => { } catch { } } - const [_, a] = multiTest(); + const [_, a] = multiTest()!; return a; ` .withLanguageExtensions() @@ -304,7 +305,7 @@ test("return LuaMultiReturn from try", () => { } catch { } } - const [_, a] = multiTest(); + const [_, a] = multiTest()!; return a; ` .withLanguageExtensions() diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index b88e0dbeb..e6ceab5db 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -208,7 +208,7 @@ test.each([ }, ])("forin[Object] (%p)", ({ inp }) => { util.testFunctionTemplate` - let objTest = ${inp}; + let objTest: Record = ${inp}; for (let key in objTest) { objTest[key] = objTest[key] + 1; } @@ -218,7 +218,7 @@ test.each([ test("forin[Array]", () => { util.testFunction` - const array = []; + const array = [1,2,3]; for (const key in array) {} `.expectDiagnosticsToMatchSnapshot([forbiddenForIn.code]); }); @@ -238,7 +238,7 @@ test.each( ) )("forin with continue (%s %p)", (luaTarget, { inp }) => { util.testFunctionTemplate` - let obj = ${inp}; + let obj: Record = ${inp}; for (let i in obj) { if (obj[i] % 2 == 0) { continue; diff --git a/test/unit/modules/modules.spec.ts b/test/unit/modules/modules.spec.ts index d15f18463..1969806d0 100644 --- a/test/unit/modules/modules.spec.ts +++ b/test/unit/modules/modules.spec.ts @@ -94,8 +94,7 @@ test("Default Import and Export Expression", () => { test("Import and Export Assignment", () => { util.testModule` - // @ts-ignore - import * as m from "./module"; + import m = require("./module"); export const value = m; ` .setOptions({ module: ts.ModuleKind.CommonJS }) diff --git a/test/unit/namespaces.spec.ts b/test/unit/namespaces.spec.ts index 01b2e3f92..7c98e7ef9 100644 --- a/test/unit/namespaces.spec.ts +++ b/test/unit/namespaces.spec.ts @@ -1,15 +1,5 @@ import * as util from "../util"; -test("legacy internal module syntax", () => { - util.testModule` - module Foo { - export const foo = "bar"; - } - - export const foo = Foo.foo; - `.expectToMatchJsResult(); -}); - test("global scoping", () => { util.testFunction("return a.foo();") .setTsHeader('namespace a { export function foo() { return "bar"; } }') @@ -42,7 +32,7 @@ test("context in namespace function", () => { util.testModule` namespace a { export const foo = "foo"; - export function bar() { return this.foo + "bar"; } + export function bar(this: typeof a) { return this.foo + "bar"; } } export const result = a.bar(); diff --git a/test/unit/optionalChaining.spec.ts b/test/unit/optionalChaining.spec.ts index d99f18be9..10de7e792 100644 --- a/test/unit/optionalChaining.spec.ts +++ b/test/unit/optionalChaining.spec.ts @@ -1,10 +1,9 @@ import { notAllowedOptionalAssignment } from "../../src/transformation/utils/diagnostics"; import * as util from "../util"; -import { ScriptTarget } from "typescript"; test.each(["null", "undefined", '{ foo: "foo" }'])("optional chaining (%p)", value => { util.testFunction` - const obj: {foo: string} | null | undefined = ${value}; + const obj = ${value} as {foo: string} | null | undefined; return obj?.foo; ` .expectToMatchJsResult() @@ -24,7 +23,7 @@ test("long optional chain", () => { test.each(["undefined", "{}", "{ foo: {} }", "{ foo: {bar: 'baz'}}"])("nested optional chaining (%p)", value => { util.testFunction` - const obj: { foo?: { bar?: string } } | undefined = ${value}; + const obj = ${value} as { foo?: { bar?: string } } | undefined; return obj?.foo?.bar; `.expectToMatchJsResult(); }); @@ -33,7 +32,7 @@ test.each(["undefined", "{}", "{ foo: {} }", "{ foo: {bar: 'baz'}}"])( "nested optional chaining combined with coalescing (%p)", value => { util.testFunction` - const obj: { foo?: { bar?: string } } | undefined = ${value}; + const obj = ${value} as { foo?: { bar?: string } } | undefined; return obj?.foo?.bar ?? "not found"; `.expectToMatchJsResult(); } @@ -108,7 +107,7 @@ test("unused call", () => { // should use if statement, as result is not used }); -test.each(["undefined", "{ foo: v=>v }"])("with preceding statements on right side", value => { +test.each(["undefined", "{ foo: (v: any)=>v }"])("with preceding statements on right side", value => { util.testFunction` let i = 0 const obj: any = ${value}; @@ -120,12 +119,12 @@ test.each(["undefined", "{ foo: v=>v }"])("with preceding statements on right si }); // unused, with preceding statements on right side -test.each(["undefined", "{ foo(val) {return val} }"])( +test.each(["undefined", "{ foo(val: any) {return val} }"])( "unused result with preceding statements on right side", value => { util.testFunction` let i = 0 - const obj = ${value}; + const obj = ${value} as any; obj?.foo(i++); return i ` @@ -135,8 +134,10 @@ test.each(["undefined", "{ foo(val) {return val} }"])( } ); -test.each(["undefined", "{ foo(v) { return v} }"])("with preceding statements on right side modifying left", value => { - util.testFunction` +test.each(["undefined", "{ foo(v: any) { return v} }"])( + "with preceding statements on right side modifying left", + value => { + util.testFunction` let i = 0 let obj: any = ${value}; function bar() { @@ -147,10 +148,11 @@ test.each(["undefined", "{ foo(v) { return v} }"])("with preceding statements on return {result: obj?.foo(bar(), i++), obj, i} ` - .expectToMatchJsResult() - .expectLuaToMatchSnapshot(); - // should use if statement, as there are preceding statements -}); + .expectToMatchJsResult() + .expectLuaToMatchSnapshot(); + // should use if statement, as there are preceding statements + } +); test("does not suppress error if left side is false", () => { const result = util.testFunction` @@ -233,7 +235,7 @@ test("does not crash when incorrectly used in assignment (#1044)", () => { describe("optional chaining function calls", () => { test.each(["() => 4", "undefined"])("stand-alone optional function (%p)", value => { util.testFunction` - const f: (() => number) | undefined = ${value}; + const f = (${value}) as (() => number) | undefined; return f?.(); `.expectToMatchJsResult(); }); @@ -255,7 +257,7 @@ describe("optional chaining function calls", () => { test("object with method can be undefined", () => { util.testFunction` - const objWithMethods: { foo: () => number, bar: (this: void) => number } | undefined = undefined; + const objWithMethods = undefined as { foo: () => number, bar: (this: void) => number } | undefined; return [objWithMethods?.foo() ?? "no foo", objWithMethods?.bar() ?? "no bar"]; `.expectToMatchJsResult(); }); @@ -326,8 +328,8 @@ describe("optional chaining function calls", () => { test.each([undefined, "[1, 2, 3, 4]"])("Array: %p", expr => { util.testFunction` - const value: any[] | undefined = ${expr} - return value?.map(x=>x+1) + const value = ${expr} as any[] | undefined; + return value?.map(x=>x+1); `.expectToMatchJsResult(); }); }); @@ -342,7 +344,6 @@ describe("optional chaining function calls", () => { ` .setOptions({ strict, - target: ScriptTarget.ES5, }) .expectToMatchJsResult(); }); @@ -397,7 +398,7 @@ describe("Unsupported optional chains", () => { describe("optional delete", () => { test("successful", () => { util.testFunction` - const table = { + const table: { bar?: number } = { bar: 3 } return [delete table?.bar, table] @@ -415,9 +416,9 @@ describe("optional delete", () => { test("delete on undefined", () => { util.testFunction` - const table : { - bar: number - } | undefined = undefined + const table = undefined as { + bar?: number + } | undefined; return [delete table?.bar, table ?? "nil"] `.expectToMatchJsResult(); }); diff --git a/test/unit/overloads.spec.ts b/test/unit/overloads.spec.ts index d18daa000..85752a64a 100644 --- a/test/unit/overloads.spec.ts +++ b/test/unit/overloads.spec.ts @@ -67,8 +67,8 @@ test("overload method2", () => { test("constructor1", () => { util.testFunction` class myclass { - num: number; - str: string; + num?: number; + str?: string; constructor(def: number); constructor(def: string); @@ -88,8 +88,8 @@ test("constructor1", () => { test("constructor2", () => { util.testFunction` class myclass { - num: number; - str: string; + num?: number; + str?: string; constructor(def: number); constructor(def: string); diff --git a/test/unit/precedingStatements.spec.ts b/test/unit/precedingStatements.spec.ts index 1ce24da19..6dd8c6473 100644 --- a/test/unit/precedingStatements.spec.ts +++ b/test/unit/precedingStatements.spec.ts @@ -101,13 +101,16 @@ describe("execution order", () => { util.testFunction` const o = {a: "A", b: "B", c: "C"}; let i = 0; - const literal = ${literal}; + const literal: Record = ${literal}; const result: Record = {}; (Object.keys(result) as Array).forEach( key => { result[key.toString()] = literal[key]; } ); return result; - `.expectToMatchJsResult(); + ` + // TS2783: duplicate property in spread โ€” intentional, testing execution order + .ignoreDiagnostics([2783]) + .expectToMatchJsResult(); }); test("object literal with computed property names", () => { @@ -516,8 +519,8 @@ describe("assignment execution order", () => { test("function method call", () => { util.testFunction` let o = {val: 3}; - let a = function(x: number) { return this.val + x; }; - let b = function(x: number) { return (this.val + x) * 10; }; + let a = function(this: typeof o, x: number) { return this.val + x; }; + let b = function(this: typeof o, x: number) { return (this.val + x) * 10; }; function foo(x: number) { return (x > 0) ? b : a; } let i = 0; const result = foo(i).call(o, i++); diff --git a/test/unit/spread.spec.ts b/test/unit/spread.spec.ts index 79262c185..93936f29b 100644 --- a/test/unit/spread.spec.ts +++ b/test/unit/spread.spec.ts @@ -116,7 +116,9 @@ describe("in object literal", () => { "{ ...{ x: false }, x: true }", "{ ...{ x: false }, x: false, ...{ x: true } }", ])("of object literal (%p)", expression => { - util.testExpression(expression).expectToMatchJsResult(); + util.testExpression(expression) + .ignoreDiagnostics([2783]) // duplicate property in spread โ€” intentional, testing spread override behavior + .expectToMatchJsResult(); }); test.each([ diff --git a/test/unit/switch.spec.ts b/test/unit/switch.spec.ts index 4fb72c40c..1b8410a68 100644 --- a/test/unit/switch.spec.ts +++ b/test/unit/switch.spec.ts @@ -137,7 +137,7 @@ test("switch using variable re-declared in cases", () => { let foo: number = 0; switch (foo) { case 0: - let foo = true; + let foo: boolean | undefined = true; case 1: return foo; } diff --git a/test/unit/using.spec.ts b/test/unit/using.spec.ts index 60364f680..33749cb37 100644 --- a/test/unit/using.spec.ts +++ b/test/unit/using.spec.ts @@ -116,7 +116,7 @@ test("using disposes even when error happens", () => { test("await using disposes object with await at end of function", () => { util.testModule` - let disposeAsync; + let disposeAsync: (() => void) | undefined; function loggedAsyncDisposable(id: string): AsyncDisposable { logs.push(\`Creating \${id}\`); @@ -145,7 +145,7 @@ test("await using disposes object with await at end of function", () => { logs.push("function returned"); - disposeAsync(); + disposeAsync!(); ` .setTsHeader(usingTestLib) .setOptions({ luaLibImport: LuaLibImportKind.Inline }) @@ -217,7 +217,7 @@ test("await-using with nested async arrow that also has await-using (runtime div // https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1584 test("works with disposable classes (#1584)", () => { util.testFunction` - const log = []; + const log: string[] = []; class Scoped { action(): void { diff --git a/test/util.ts b/test/util.ts index 8c5e69f8f..1e191fb28 100644 --- a/test/util.ts +++ b/test/util.ts @@ -51,7 +51,7 @@ function getLuaBindingsForVersion(target: tstl.LuaTarget): { lauxlib: LauxLib; l } export function assert(value: any, message?: string | Error): asserts value { - nativeAssert(value, message); + nativeAssert.ok(value, message); } export const formatCode = (...values: unknown[]) => values.map(e => stringify(e)).join(", "); @@ -92,17 +92,15 @@ export function expectEachVersionExceptJit( }; } -const memoize: MethodDecorator = (_target, _propertyKey, descriptor) => { - const originalFunction = descriptor.value as any; +const memoize = (originalFunction: any) => { const memoized = new WeakMap(); - descriptor.value = function (this: any, ...args: any[]): any { + return function (this: any, ...args: any[]): any { if (!memoized.has(this)) { memoized.set(this, originalFunction.apply(this, args)); } return memoized.get(this); - } as any; - return descriptor; + }; }; export class ExecutionError extends Error { @@ -163,7 +161,7 @@ export abstract class TestBuilder { skipLibCheck: true, target: ts.ScriptTarget.ES2017, lib: ["lib.esnext.d.ts"], - moduleResolution: ts.ModuleResolutionKind.Node10, + moduleResolution: ts.ModuleResolutionKind.Bundler, resolveJsonModule: true, sourceMap: true, }; diff --git a/tsconfig.json b/tsconfig.json index bf121bd20..1fcbb6d79 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,6 @@ "lib": ["es2019"], "types": ["node"], "module": "commonjs", - "experimentalDecorators": true, "rootDir": "src", "outDir": "dist", From 7f835eda4efeb988b9e7647204a3df855b53ec07 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Tue, 21 Apr 2026 22:25:31 +0200 Subject: [PATCH 17/19] CHANGELOG.md 1.35.0 --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c8014962..abe5cf9c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 1.35.0 + +- **[Breaking]** Upgraded to TypeScript 6.0 + Thanks @RealColdFry for the following fixes: +- Fixed many bugs with try/catch/finally + - Also fixed some bugs with try/catch/finally in promises +- Fixed a bug where collection iterators would not correctly update when the collection updated +- No longer generate dead code after `break` statements +- Fixed a bug where math.atan2 was incorrectly used instead of math.atan for Lua 5.4 +- Fixed some inconsistencies with number constants (Number.MAX_SAFE_INTEGER, Number.MIN_VALUE, etc) +- Fixed a bug with the `>>>` operator for Lua 5.3 +- Fixed incorrect side effects for array destructors +- Fixed broken code when generating requires for files with `.` in the file name (e.g. `foo.tests.ts`), now the periods will be translated to `_` +- Fixed some incorrect handling of synthetic nodes +- Fixed a bug where object spread could lead to incorrect code being generated +- Fixed a bug with Object.defineProperty sharing values accross object instances + ## 1.34.0 - Added support for the Lua 5.5 target (it mostly does the same as the 5.4 target for now) From 06e4263d6119b5540708d9fd216988b6a2ea9452 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Tue, 21 Apr 2026 22:28:29 +0200 Subject: [PATCH 18/19] 1.35.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 760133928..bdad6125f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typescript-to-lua", - "version": "1.34.0", + "version": "1.35.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "typescript-to-lua", - "version": "1.34.0", + "version": "1.35.0", "license": "MIT", "dependencies": { "@typescript-to-lua/language-extensions": "1.19.0", diff --git a/package.json b/package.json index 412c08da7..331d32b72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typescript-to-lua", - "version": "1.34.0", + "version": "1.35.0", "description": "A generic TypeScript to Lua transpiler. Write your code in TypeScript and publish Lua!", "repository": "https://github.com/TypeScriptToLua/TypeScriptToLua", "homepage": "https://typescripttolua.github.io/", From d3c33ac54d21d6803864ec1a39ae337a62c131a2 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Tue, 21 Apr 2026 22:29:28 +0200 Subject: [PATCH 19/19] 1.36.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bdad6125f..28a32847e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typescript-to-lua", - "version": "1.35.0", + "version": "1.36.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "typescript-to-lua", - "version": "1.35.0", + "version": "1.36.0", "license": "MIT", "dependencies": { "@typescript-to-lua/language-extensions": "1.19.0", diff --git a/package.json b/package.json index 331d32b72..aff9ba384 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typescript-to-lua", - "version": "1.35.0", + "version": "1.36.0", "description": "A generic TypeScript to Lua transpiler. Write your code in TypeScript and publish Lua!", "repository": "https://github.com/TypeScriptToLua/TypeScriptToLua", "homepage": "https://typescripttolua.github.io/",