@gwigz/slua

Gotchas

Subtle issues to keep an eye out for

TSTL converts TypeScript syntax faithfully, but a few runtime semantics differ between JavaScript and Lua. These differences survive transpiling and can quietly change behavior, so they are worth knowing.

Truthiness

In JavaScript, 0, "", NaN, null, and undefined are all falsy. In Lua, only nil and false are falsy, which means 0 and "" are truthy. Any condition that relies on a number or string being falsy means something different once transpiled.

const pos = text.indexOf("name=")

if (pos) {
  // ...
}

indexOf returns 0 when the match is at the start (falsy in JS) and -1 when there is no match (truthy in JS), so this branch is already misleading in JavaScript. In Lua it is worse: both 0 and -1 are truthy, so the branch always runs. Compare explicitly instead:

if (pos !== -1) {
  // ...
}

The same applies to strings (if (str.length > 0), not if (str)) and numeric counters (if (count !== 0), not if (count)).

Enforce with oxlint

@gwigz/slua-oxlint-config enables strict-boolean-expressions to flag bare numbers and strings used as conditions. It is type-aware, so run oxlint --type-aware.

null and undefined are both nil

Both null and undefined translate to nil, so they are indistinguishable at runtime. x === null and x === undefined compile to the same x == nil check, and a missing table key reads back as undefined.

Strings are bytes, not characters

Lua strings are byte arrays, so .length and indexing count UTF-8 bytes rather than code points. A character outside ASCII is more than one byte:

"héllo".length // 5 in JavaScript, 6 in SLua (é is two bytes)

The same applies to slice, substring, indexOf, and friends. For character-aware work, use the utf8 library.

Object key order is not guaranteed

for...in and Object.keys() iterate with Lua's pairs, which has no defined order. JavaScript preserves insertion order for string keys; SLua does not. Sort the keys yourself if order matters.

Arrays can't hold nil

Arrays are plain Lua tables, and storing undefined (which becomes nil) punches a hole in them. .length (#) and iteration stop at the first hole, so the values after it are effectively lost. Rebuild the array with filter instead of assigning undefined to an element.

On this page