luaut for VS CodeThin client for The server is bundled into the extension ( The server comes from npm like any other dependency, so this project builds on its own — no checkout of anything else required. luaut is Luau, written the TypeScript wayluaut compiles to Luau and keeps Luau's semantics: the same values, the same
Everything below is a difference you can see in this editor. Everything not
listed is Luau as you know it: Blocks — braces, and a parenthesised condition
There is no The parentheses around a condition are not decoration:
Declarations —
|
| Luau | luaut | |
|---|---|---|
| object | { x = 1, y = 2 } |
{ x: 1, y: 2 } |
| array | { 1, 2, 3 } |
[1, 2, 3] |
| computed key | { [k] = v } |
{ [k]: v } |
| shorthand | — | { x, y } |
| spread | — | { ...base, c: 3 }, [...a, ...b] |
Arrays are still Luau arrays underneath — the first element is index 1.
One arrow, for the type and for the function
Luau writes a function type with ->. luaut writes both a function type and
a function value with =>, and decides which one from where it stands,
since a type and a value never share a place. -> is gone.
type Reducer = (total: number, value: number) => number
const double = (x: number) => x * 2
const add: Reducer = (a, b) => a + b -- typed by the contract
each(n => print(n)) -- one parameter needs no parens
const wrap = (n: number) => ({ value: n }) -- an object body is parenthesised
An arrow is just a short function expression — there is no second kind of
function — so this inside one is the this of the method around it.
Optionality — no T?
? in type position always belongs to a conditional type, and in expression
position to a ternary or an optional chain. Optionality is TypeScript's:
name?: T -- may be absent; its type is `T | nil`
name: T | nil -- must be written, but may be nil
Omitting an argument requires ? or a default — a parameter typed T | nil
still has to be passed something.
Things Luau does not have at all
- Classes —
class Dog extends Animal { … },constructor,get/set,static,super,new Dog(…), genericclass Box<T>. It is sugar over the usual metatable idiom, andnew Dog(x)isDog.new(x). - Modules —
import/exportinstead ofrequire, includingimport type, re-exports andexport default. Imports are read-only. - Optional chaining —
a?.banda?:m(x), which stop the whole chain when the receiver is nil, and narrow what they tested. - Ternary —
c ? a : b. - Template strings —
`hello ${name}`. - Rest and spread —
...parts: string[]in a signature,f(...names)at a call. Bare...is still Lua's pack. - Array and string methods —
names:filter(…):map(…),text:trim(), written with:as JavaScript writes them with.. Which methods exist comes from the type library, not the language.
A real type system
This is the part that shows up as squiggles in the editor. luaut checks what TypeScript checks, not what Luau checks:
strictNullChecksalways on — reading a member of a possibly-nil value is an error until a check narrows the nil away.- TypeScript's narrowing model: references (
x.a.b) rather than just variables, discriminated unions,and/or, early return,break, user type guards (v is T), assertion signatures (asserts v). - Unions, intersections, tuples,
keyof,T[K], conditional types withinfer, mapped types, template literal types, generics with constraints and defaults,satisfies,as const, overload sets, branded types. - Only
nilandfalseare falsy —0and""are truthy, as in Lua, not as in JavaScript.
No globals are built in — not print, not game. A project lists its
type libraries in luaut.config.json, the way TypeScript uses @types/*:
// luaut.config.json
{
"types": ["roblox"], // npm i -D @luaut/roblox
"paths": { "@shared/*": ["src/shared/*"] },
"sourceMap": "sourcemap.json" // a Rojo sourcemap, or null
}
The extension validates this file as you type it, and the server reloads when it changes.
Moving a file over
npx tsx scripts/to-braces.ts <file|dir> in the luaut-parser checkout
rewrites Lua-spelled source into braces. Each rewrite is parsed and compared
against the tree the original made; a file it cannot say the same thing about
is left alone.
Full language reference: the luaut-parser README.
Three ways to run it
1. Develop it — F5
npm install
npm run build
Open this folder in VS Code and press F5. A second window opens
on sample/, with hello.luaut to poke at: hover a name, ctrl-click it, type
part., watch the deliberate type error appear as you edit.
Changed the server? Rebuild (Ctrl+Shift+B) and run luaut: Restart Language Server in the dev window — no need to restart the host. To debug the server itself, run the Attach to server launch configuration while the dev window is open (port 6009).
2. Install it into your own VS Code
npm run reinstall # package, then install into your VS Code
Then Developer: Reload Window — the installed extension is a copy, so
npm run build alone changes nothing you can see there (that is what F5 is
for). code --uninstall-extension luaut.luaut-vscode removes
it. (Or: Extensions view → ... → Install from VSIX….)
This is also the file to hand someone else — it runs anywhere without a checkout.
3. Publish it to the Marketplace
One-time setup:
- Create a publisher at https://marketplace.visualstudio.com/manage and put
its id in
package.json→"publisher"(currentlyuav1010, which is almost certainly not yours). - Create an Azure DevOps personal access token — https://dev.azure.com → User settings → Personal access tokens → All accessible organizations, scope Marketplace ▸ Manage.
npx vsce login <publisher-id>and paste the token.
Then, per release: bump version, and
npm run publish # or: npx vsce publish minor
For the VS Codium / Cursor / Gitpod side, publish the same .vsix to
Open VSX: npx ovsx publish luaut-vscode-<version>.vsix -p <token>.
Before the first publish, add a LICENSE file (vsce warns without one) and,
if you want the listing to look finished, a 128×128 icon.
Settings
| setting | what it does |
|---|---|
luaut.server.path |
absolute path to a cli.js/server.cjs to use instead of the bundled server |
luaut.trace.server |
messages / verbose logs LSP traffic to the luaut output channel |
What is in here
src/extension.ts— locate the server, start it, register the restart commandsyntaxes/luaut.tmLanguage.json— TextMate grammar (colours only; the server does the understanding)language-configuration.json— comments, brackets, indentationsample/— the folder the dev host opens