Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2ffeb5e
Update external-lua-code.md
Zamiell Feb 22, 2023
1f5471e
rename page
Zamiell Feb 22, 2023
3c8095a
Update external-code.md
Zamiell Feb 22, 2023
f7a9ac9
Update external-code.md
Zamiell Feb 22, 2023
787db19
Update external-code.md
Zamiell Feb 23, 2023
8a3f8ac
Update external-code.md
Zamiell Feb 23, 2023
4a42f83
Update docs/external-code.md
Zamiell Feb 23, 2023
539815b
Update external-code.md
Zamiell Feb 23, 2023
50fb84a
Update external-code.md
Zamiell Feb 23, 2023
5b87da7
feat: adding more info
Zamiell Feb 24, 2023
1d00161
fix: prettier
Zamiell Feb 24, 2023
953485c
Update getting-started.md
Zamiell Feb 24, 2023
1fea1c1
better instructions for installing type declarations
Zamiell Feb 24, 2023
07acebc
more edits
Zamiell Feb 24, 2023
434f2cc
fix
Zamiell Feb 24, 2023
064a6d7
fix
Zamiell Feb 24, 2023
a3cc8f0
fix: link
Zamiell Feb 24, 2023
0eb801d
fix: links
Zamiell Feb 24, 2023
05b41d6
fix: link
Zamiell Feb 24, 2023
683e1c0
fix: link
Zamiell Feb 24, 2023
5e4e13f
fix: lint
Zamiell Feb 24, 2023
8179823
fix: build
Zamiell Feb 24, 2023
fd665f9
feat: add sorting caveat
Zamiell Feb 24, 2023
6e2c348
fix: no wipe
Zamiell Feb 24, 2023
c2b232f
rename file
Zamiell Feb 24, 2023
89ca9bf
fix: package.json fields
Zamiell Feb 24, 2023
37a3611
fix: basic limitations
Zamiell Feb 24, 2023
c1c74a6
fix: 5 year old
Zamiell Feb 24, 2023
43fa924
fix: buzzwords
Zamiell Feb 24, 2023
33c7bb8
fix: remove mixed edge case
Zamiell Feb 24, 2023
5b69da2
fix: package.json files field
Zamiell Feb 24, 2023
cdfa321
fix: basic limitations
Zamiell Feb 24, 2023
3875aa8
fix: tsconfig
Zamiell Feb 24, 2023
02b4435
feat: add computerCraft
Zamiell Feb 24, 2023
2f5f236
feat: move file
Zamiell Feb 26, 2023
7358179
feat: adding sidebar categories + adding missing package.json script
Zamiell Feb 26, 2023
cc2e3b4
feat: section on null
Zamiell Feb 28, 2023
329fe62
Update docs/advanced/writing-declarations.md
Zamiell Feb 28, 2023
14e7b6d
fix: remove isaacscript-lint
Zamiell Feb 28, 2023
906cf77
fix: rename files
Zamiell Feb 28, 2023
9f7c4d2
fix: less text in undefined section
Zamiell Feb 28, 2023
7c4ee3f
fix: prettier
Zamiell Feb 28, 2023
efbb9d0
Merge branch 'source' into patch-1
Perryvw Mar 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions docs/advanced/writing-declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
title: Writing Declarations
---

The real power of the transpiler is unlocked when combining it with declarations for your target environment. Declarations tell TypeScript which Lua API is available in your target context.
The best way to use TypeScript is to provide it with information about the format/types of the external functions and variables that you will be using (specific to your environment). This allows the compiler to check your code for mistakes when compiling, instead of having to run the code to find issues. To give TypeScript this information, you will need to provide it with type declarations. You can write these declarations yourself or, if available, install an existing type declarations package for your environment from npm.

If you need tips or help writing declarations, feel free to [join our Discord](https://discord.gg/BWAq58Y).
For more information about installing existing type definition packages, see the [getting started page](getting-started.md#type-declarations).

## About Declaration Files
This page has more information about how to write your own type declarations. This can be tricky, so if you need help, feel free to [join our Discord server](https://discord.gg/BWAq58Y).

Declaration files end with the extension _.d.ts_. These contain pure ambient code.
## About Declaration Files

For TypeScriptToLua, these files should contain information that describes the target Lua environment.
Declaration files end with the extension `.d.ts` (which stands for "declaration TypeScript file"). Declaration files are different from normal `.ts` files in that they must only contain _ambient_ code. In the context of TypeScript, _ambient_ refers to code that only exists at compile-time and is not emitted into the program output.

This means functions, modules, variables and other members of the target Lua environment are primarily described in these files.
In other words, anything you put into a `.d.ts` file will inform the TypeScript compiler about what the format of something is. And it will never appear in the generated `.lua` file(s).

They don't contain code that you would execute. Similar to how you'd write an interface in some other languages. TypeScriptToLua doesn't output any information from these files either.
For TypeScriptToLua, these files should contain information that describes the target Lua environment. This means functions, modules, variables and other members of the target Lua environment are primarily described in these files.

:::note
You can write ambient declarations inside _.ts_ files as well.
Expand All @@ -38,7 +38,7 @@ declare const _VERSION: number;

/**
* Receives any number of arguments, and prints their values to stdout, using the
* tostring function to convert them to strings. print is not intended for
* `tostring` function to convert them to strings. print is not intended for
* formatted output, but only as a quick way to show a value, typically for
* debugging. For formatted output, use string.format.
* @param args Arguments to print
Expand Down Expand Up @@ -120,7 +120,7 @@ This allows users to modify `this` inside a function and expect behaviour simila

But obviously Lua does not have a `self` parameter for every function, so one of the three options must happen to tell TypeScriptToLua there is no "contextual parameter" (`self`):

1. Use `this: void` as the first parameter of the function / method. This formally describes to TypeScript to not allow `this` to be modified inside this function. (you could also use the [noImplicitThis](../configuration.md#custom-options) option to disallow `this` to be modified if `this` is of an `any` type).
1. Use `this: void` as the first parameter of the function / method. This formally describes to TypeScript to not allow `this` to be modified inside this function. (you could also use the [noImplicitThis](configuration.md#custom-options) option to disallow `this` to be modified if `this` is of an `any` type).
2. Use `@noSelf` in the comments of the declaration's owner (the namespace, module, object, etc).
3. Use `@noSelfInFile` at the beginning of the file in a comment to make sure every function defined in this file does not use a "contextual parameter".

Expand Down Expand Up @@ -172,7 +172,7 @@ Here are some commonly used TSDoc tags used in comments:
| `@param <name> <description>` | Defines a parameter. e.g. A parameter for a function |
| `@return <description>` | Describes the return value of a function / method |

TypeScriptToLua takes this further. Some "tags" change how the transpiler translates certain pieces of code. These are referred to as [annotations](compiler-annotations.md).
TypeScriptToLua takes this further. Some "tags" change how the transpiler translates certain pieces of code. These are referred to as [annotations](advanced/compiler-annotations.md).

As an example, `@tupleReturn` marks a function as something which returns multiple values instead of its array.

Expand All @@ -197,7 +197,7 @@ let [c, d] = array();
// local c, d = unpack(array())
```

See [Compiler Annotations](compiler-annotations.md) page for more information.
See [Compiler Annotations](advanced/compiler-annotations.md) page for more information.

## Environmental Declarations

Expand All @@ -223,7 +223,7 @@ We recommend reading about Mapped and Conditional types. These things can be use

## Declaration Merging

https://www.typescriptlang.org/docs/handbook/declaration-merging.html
Declaration merging is a feature of TypeScript that allows you to combine new declarations with ones that already exist. For more information, see [the TypeScript documentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html).

Some examples of declaration merging have been shown in the above examples.

Expand Down Expand Up @@ -532,7 +532,7 @@ const v3 = (v1 * 4) as Vector;
const d = v3.dot(v2);
```

The second option was added in version [0.38.0](https://github.com/TypeScriptToLua/TypeScriptToLua/blob/master/CHANGELOG.md#0380). You can now use [language extensions](https://typescripttolua.github.io/docs/advanced/language-extensions) that allow declaring special functions which will transpile to operators. This will be completely type safe if the operators are declared correctly. See [Operator Map Types](language-extensions.md#operator-map-types) for more information.
The second option was added in version [0.38.0](https://github.com/TypeScriptToLua/TypeScriptToLua/blob/master/CHANGELOG.md#0380). You can now use [language extensions](https://typescripttolua.github.io/docs/advanced/language-extensions) that allow declaring special functions which will transpile to operators. This will be completely type safe if the operators are declared correctly. See [Operator Map Types](advanced/language-extensions.md#operator-map-types) for more information.

### Import and export

Expand All @@ -555,7 +555,7 @@ declare module "mymodule" {
}
```

## NPM Publishing
## npm Publishing

It is possible to publish a list of declarations for other users to easily download via [npm](https://www.npmjs.com/).

Expand Down
73 changes: 55 additions & 18 deletions docs/caveats.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
title: Caveats
---

Luckily, for most use-cases, you can write modern, idiomatic TypeScript, and TSTL will produce transpiled Lua that will work flawlessly. In other words, you probably will not have to worry about the idiomatic quirks of Lua or the internal decisions that TSTL makes when converting code.

With that said, TSTL does have some "gotchas" that you might run into. This page covers some of those edge-cases.

## Feature support

| Feature | Lua 5.0 | Lua 5.1 | Lua 5.2 | Lua 5.3 | LuaJIT |
Expand All @@ -17,7 +21,7 @@ title: Caveats

## Differences from JavaScript

This project aims for both compilation results to have the same behavior as much as possible, but not at all costs. Since TypeScript is based on JavaScript it also inherited some of the quirks in JavaScript that are not present in Lua. This is where behavior between Lua and JavaScript compilation targets diverge. TypeScriptToLua aims to keep identical behavior as long as **sane** TypeScript is used: if JavaScript-specific quirks are used behavior might differ.
This project aims for both compilation results to have the same behavior as much as possible, but not at all costs. Since TypeScript is based on JavaScript, it also inherited some of the quirks in JavaScript that are not present in Lua. This is where behavior between Lua and JavaScript compilation targets diverge. TypeScriptToLua aims to keep identical behavior as long as **sane** TypeScript is used: if JavaScript-specific quirks are used, behavior might differ.

Below are some of the cases where resulting Lua intentionally behaves different from compiled JS.

Expand All @@ -39,49 +43,82 @@ JavaScript and Lua differ in what they evaluate to true/false. TypeScriptToLua a
| `0` | `false` | ⚠️`true` |
| (Everything else) | `true` | `true` |

We recommend that you use the [`strict-boolean-expression`](https://typescript-eslint.io/rules/strict-boolean-expressions/) ESLint rule in your TSTL projects, which will force you to be explicit and prevent this class of bug entirely.

### [Loose equality](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#Loose_equality_using)

TypeScriptToLua makes no difference between `==` and `===` when compiling to Lua, treating all comparisons as strict (`===`).

We recommend that you use the [`eqeqeq`](https://eslint.org/docs/latest/rules/eqeqeq) ESLint rule, which will force you to be explicit and prevent this class of bug entirely.

### `undefined` and `null`

`nil` is the Lua equivalent for `undefined`, so TSTL converts `undefined` to `nil`. However, there is no Lua equivlanet for `null`, so TSTL converts `null` to `nil` as well.

This means that TSTL programs with `null` will have different behavior than JavaScript/TypeScript programs. For example:

```ts
const foo = {
someProp1: 123,
someProp2: null,
someProp3: undefined,
};
```

If we iterated over `foo` in a TSTL program, we would _only_ get `someProp1`, instead of both `someProp1` and `someProp2` like we would in a JavaScript/TypeScript program.

In general, we recommend keeping `null` out of your TSTL codebases in favor of `undefined`. Not only will this represent the transpiled Lua code better, but [it is more idiomatic in TypeScript to prefer `undefined` over `null` when both would accomplish the same thing](https://basarat.gitbook.io/typescript/recap/null-undefined).

### Array Length

`Array.prototype.length` is translated to Lua's `#` operator. Due to the way lists are implemented in Lua there can be differences between JavaScript's `list.length` and Lua's `#list`. The transpiler does not do anything to remedy these differences, so when working with lists, the transpiled Lua will use the standard Lua conventions. Generally speaking, the situation where these differences occur happen when adding/removing items to a list in a hacky way, or when setting list items to `undefined`/`null`.
`Array.prototype.length` is translated to Lua's `#` operator. Due to the way arrays are implemented in Lua, there can be differences between JavaScript's `myArray.length` and Lua's `#myArray`. The transpiler does not do anything to remedy these differences. Thus, when working with arrays, the transpiled Lua will use the standard Lua conventions. Generally speaking, the situation where these differences occur happen when adding/removing items to an array in a hacky way, or when setting array items to `undefined` / `null`.

**Examples:**
For example:

**Safe (no difference):**
#### Safe (no difference)

```ts
const myList = [1, 2, 3];
myList.push(4);
myList.pop();
myList.splice(1, 1);
// myList.length == 2
const myArray = [1, 2, 3];
myArray.push(4);
myArray.pop();
myArray.splice(1, 1);
// myArray.length == 2
```

**Differences might occur:**
#### Differences might occur

```ts
const myList = [1, 2, 3];
myList[1] = undefined;
// myList.length == 1 (3 in JavaScript)
const myArray = [1, 2, 3];
myArray[1] = undefined;
// myArray.length == 1 (which would be 3 in JavaScript)
```

```ts
const myList = [1, 2, 3];
myList[4] = 5;
// myList.length == 3 (5 in JavaScript)
const myArray = [1, 2, 3];
myArray[4] = 5;
// myArray.length == 3 (which would be 5 in JavaScript)
```

### Key Iteration Order

Even though iterating over object keys with `for ... in` does not guarantee order in either JavaScript or Lua. Therefore, the iteration order in JavaScript is likely different from the order in Lua.

**Note:** If a specific order is required, it is better to use ordered collections like lists instead.
**Note:** If a specific order is required, it is better to use ordered collections like arrays instead.

### Iterating an array with `for ... in`

Not allowed.
Not allowed. Use a `for of` loop instead to iterate over an array.

### Sorting

A sorting algorithm is [said to be stable](https://stackoverflow.com/questions/1517793/what-is-stability-in-sorting-algorithms-and-why-is-it-important) if two objects with equal keys appear in the same order in sorted output as they appear in the input array to be sorted.

- Sorting is part of the JavaScript standard library via the `Array.sort` method. It is guaraunteed to be [stable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).
- Sorting is also part of the Lua standard library via the `table.sort` method. It is **not** guaraunteed to be [stable](https://www.lua.org/manual/5.3/manual.html#pdf-table.sort).

TypeScriptToLua relies on the Lua standard library for sorting. In other words, it transpiles `[1, 2, 3].sort();` to `table.sort({1, 2, 3})`. So beware that your sorts will no longer be stable!

If you need stable sorting, you have to manually use a custom sorting function. For some examples of this, see the [sorting helper functions from `isaacscript-common`](https://github.com/IsaacScript/isaacscript/blob/main/packages/isaacscript-common/src/functions/sort.ts).

### Local Variable Limit

Expand Down
133 changes: 133 additions & 0 deletions docs/external-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
title: External Code
---

In your `tstl` project, you might want to import some existing Lua code. Or, you might want to import a library from [npm](https://www.npmjs.com/). This page describes how to use external code.

:::note
This page is about importing code that **actually executes something**. In a `tstl` project, it is common to depend on external library that provide type declarations. Type declaration libraries only provide types: they do not contribute any code to your actual program output. Thus, they work a little differently from what is discussed on this page. For information on how type declarations work, see the [type declarations page](advanced/writing-declarations.md).
:::

## Adding Lua files to your project sources

The most straightforward way to add Lua code is to put the Lua file directly next to your TypeScript files. Next, you add [a declaration file](advanced/writing-declarations.md) with the same name. Then, you can import the Lua code in your TypeScript.

For example, a project might look like this:

```text
project/
├── main.ts
├── someLua.lua
├── someLua.d.ts
└── tsconfig.json
```

```ts title=main.ts
import { foo, bar } from "./someLua";

foo();
bar();
```

```lua title=someLua.lua
local someLua = {}

function someLua:foo()
print("hello")
end

function someLua:bar()
print("world")
end

return someLua
```

```ts title=someLua.d.ts
export function foo(): void;
export function bar(): void;
```

## Importing a Lua module that only exports an array

Building on the previous section, you might want also want to import a Lua file that exports an array. For example, something like:

```lua title=things.lua
return {
{
foo = 123,
bar = 456,
},
{
foo = 789,
bar = 987,
},
}
```

Writing a definitions file for this is tricky, since the Lua file has no named imports and no default export. Here, you have to use `export =` syntax, like so:

```ts title=things.d.ts
interface Thing {
foo: number;
bar: number;
}

declare const things: Thing[];
export = things;
```

Then, in your TypeScript code, you can import it like:

```ts title=main.ts
import * as things from "./module";

print(things[0].foo); // Prints "123"
```

For more information about this export syntax, see [the official TypeScript documentation](https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require).

## Importing Lua packages from npm

`tstl` supports module resolution for libraries, which means you can _use_ and _create_ npm packages containing `.lua` files. (Most packages on npm contain JavaScript files, but npm allows you to create packages with whatever kinds of files you want.)

### Using Lua packages

To use a Lua package, install it via npm and use it in the same way that you would in a normal TypeScript project. In other words:

```sh
# If you use npm:
npm install foo --save

# If you use yarn:
yarn add foo

# If you use pnpm:
pnpm add foo
```

And then use it in your code:

```ts
import { someFunction } from "foo";

someFunction();
```

Since the npm package was presumably made for `tstl` users, it will almost certainly include `.d.ts` files alongside the `.lua` files, which is necessary for `tstl` to import the Lua files properly. If there are no `.d.ts` files, you can try [creating some for the package yourself](advanced/writing-declarations.md).

### Creating Lua packages

For more information on creating a Lua package yourself, see [the page on publishing modules](publishing-modules.md).

## Importing JavaScript or TypeScript packages from npm

**Importing JavaScript or TypeScript packages from npm will not work.** This means that it is impossible to use common JavaScript/TypeScript libraries like [Underscore.js](https://underscorejs.org/) and so on.

(It is not possible for `tstl` to work with a generic npm package because most TypeScript libraries only publish the compiled JavaScript to npm. And `tstl` can't convert JavaScript to Lua because it needs the type information to create correct code. For example, `tstl` needs to be able to distinguish between arrays and objects in order to write the correct index.)

As a workaround, you can copy paste TypeScript code from a package repository directly into your project. (That way, it will be compiled by `tstl` alongside your normal code.)

Alternatively, you could fork an existing package and re-publish it as Lua files (instead of JavaScript) so that it can be directly consumed by other `tstl` projects. However, doing this kind of thing will only work for really basic packages, since you would have to also fork all of the dependencies and convert those to Lua as well.

Obviously, `tstl` programs will not have access to any of the Node.js standard libraries or APIs (like e.g. `import path from "path";`), so make sure that any code that you integrate into your project is not Node-specific.
Loading