luaut-parser 1.2.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -28
- package/dist/index.cjs +769 -59
- package/dist/index.d.cts +127 -25
- package/dist/index.d.ts +127 -25
- package/dist/index.js +760 -49
- package/package.json +2 -2
- package/dist/luau.d.luaut +0 -245
- package/dist/roblox.d.luaut +0 -503
package/README.md
CHANGED
|
@@ -11,13 +11,20 @@ npm install luaut-parser
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
+
import { readFileSync } from "node:fs"
|
|
14
15
|
import {
|
|
15
|
-
parse, analyzeScopes, analyzeTypes,
|
|
16
|
+
parse, analyzeScopes, analyzeTypes,
|
|
17
|
+
findConfig, resolveTypeLibraries, resolveModulePath,
|
|
16
18
|
} from "luaut-parser"
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
20
|
+
// The project the file belongs to, and the type libraries it names.
|
|
21
|
+
const { config } = findConfig(file)
|
|
22
|
+
const libs = config ? resolveTypeLibraries(config).files.map(f => parse(readFileSync(f, "utf8"))) : []
|
|
23
|
+
const globals = libs.flatMap(lib => lib.body.statements.flatMap(s => s.type === "DeclareStatement" ? [s.name] : []))
|
|
24
|
+
|
|
25
|
+
const program = parse(readFileSync(file, "utf8"))
|
|
26
|
+
const scopes = analyzeScopes(program, { builtinGlobals: globals })
|
|
27
|
+
const types = analyzeTypes(program, scopes, { libs })
|
|
21
28
|
|
|
22
29
|
for (const d of [...scopes.diagnostics, ...types.diagnostics]) console.log(d.message)
|
|
23
30
|
```
|
|
@@ -28,41 +35,69 @@ for (const d of [...scopes.diagnostics, ...types.diagnostics]) console.log(d.mes
|
|
|
28
35
|
|---|---|---|
|
|
29
36
|
| `parse(source)` | `Program` — every node carries `line`/`column` spans | everything |
|
|
30
37
|
| `analyzeScopes(program, opts)` | `bindingOf`, `bindings`, `references`, `diagnostics` | go-to-definition, find-references, rename |
|
|
31
|
-
| `analyzeTypes(program, scopes, opts)` | `typeOf`, `narrowedTypeOf`, `bindingType`, `typeOfTypeNode`, `aliases`, `diagnostics` | hover,
|
|
38
|
+
| `analyzeTypes(program, scopes, opts)` | `typeOf`, `narrowedTypeOf`, `bindingType`, `typeOfTypeNode`, `expectedTypeOf`, `aliases`, `diagnostics` | hover, completion, type errors |
|
|
32
39
|
|
|
33
40
|
Every name in the AST has a node with its own span — including the ones that
|
|
34
41
|
used to be bare strings: `DeclareStatement.id`, `TableTypeProperty.key`,
|
|
35
42
|
`FunctionTypeParameter.id`, `GenericTypeParameter.id`, `InferTypeNode.id`,
|
|
36
43
|
`MappedTypeNode.parameterId`. `typeOfTypeNode` gives what each type
|
|
37
|
-
annotation resolves to,
|
|
44
|
+
annotation resolves to, and `expectedTypeOf` what each call argument should
|
|
45
|
+
be, so a tool never has to re-derive a type from text.
|
|
38
46
|
|
|
39
47
|
`parseWithRecovery(source)` returns `{ program, errors }` instead of throwing —
|
|
40
48
|
use it for editors, where the text is usually mid-edit.
|
|
41
49
|
|
|
42
50
|
Neither analysis mutates the AST; both return side tables.
|
|
43
51
|
|
|
44
|
-
##
|
|
52
|
+
## Projects
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
**No types are built in** — not `print`, not `string`, not `game`. A project
|
|
55
|
+
lists the type libraries it uses in `luaut.config.json`, the way TypeScript
|
|
56
|
+
uses `@types/*`:
|
|
49
57
|
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
analyzeTypes(program, scopes, { libs: [luauLib] }) // core Luau only
|
|
58
|
+
```bash
|
|
59
|
+
npm i -D @luaut/roblox # or just @luaut/luau
|
|
53
60
|
```
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
```jsonc
|
|
63
|
+
// luaut.config.json
|
|
64
|
+
{
|
|
65
|
+
"types": ["roblox"], // @luaut/roblox, which brings @luaut/luau
|
|
66
|
+
"paths": { "@shared/*": ["src/shared/*"] }, // import aliases, as in tsconfig
|
|
67
|
+
"sourceMap": "sourcemap.json" // a Rojo sourcemap, or null
|
|
68
|
+
}
|
|
62
69
|
```
|
|
63
70
|
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
- **Which config applies** — the nearest one in the file's folder or above.
|
|
72
|
+
`luaut.config.json` and `luaut.config.jsonc` in the same folder is an error.
|
|
73
|
+
Both forms accept comments and trailing commas.
|
|
74
|
+
- **`types`** — `"luau"` is looked up as `@luaut/luau`, then as a package named
|
|
75
|
+
`luau`, in `node_modules` from the config upward. A full package name or a
|
|
76
|
+
relative path (`"./types"`, `"./defs.d.luaut"`) works too. A type library's
|
|
77
|
+
own type-library dependencies load first.
|
|
78
|
+
- **`paths`** — tsconfig rules: an exact pattern wins, then the `*` pattern
|
|
79
|
+
with the longest prefix; targets resolve from `baseUrl` (default: the
|
|
80
|
+
config's folder).
|
|
81
|
+
- **`sourceMap`** — the instance tree becomes types: `game` and `workspace`
|
|
82
|
+
follow it, and a file the tree maps gets its own `script`, so
|
|
83
|
+
`script.Parent.Remotes` is typed. A `.luaut` file matches the Luau file of
|
|
84
|
+
the same path.
|
|
85
|
+
|
|
86
|
+
| function | does |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `findConfig(file, host?)` | the config that applies, problems with it, and every path searched |
|
|
89
|
+
| `loadConfig(path, host?)` | read and check one config |
|
|
90
|
+
| `resolveTypeLibraries(config, host?)` | the `.d.luaut` files to load, in order |
|
|
91
|
+
| `moduleCandidates(from, specifier, config?)` / `resolveModulePath(...)` | what an `import` means |
|
|
92
|
+
| `sourceMapTypes(text, path, { classes })` | the tree's types, and `scriptFor(file)` |
|
|
93
|
+
|
|
94
|
+
Every problem comes back as `{ file, message, line, column }`, pointing into
|
|
95
|
+
the config (or sourcemap) it is about. `host` reads files — pass your own to
|
|
96
|
+
read unsaved editor buffers or to record what was read.
|
|
97
|
+
|
|
98
|
+
`type` / `typeof` are **not** special-cased in the analyzer either: they are
|
|
99
|
+
overload sets in `@luaut/luau`, and narrowing is derived from them. Without a
|
|
100
|
+
library that declares them, they narrow nothing.
|
|
66
101
|
|
|
67
102
|
## The language, in brief
|
|
68
103
|
|
|
@@ -82,6 +117,43 @@ name: T | nil -- must be written, but may be nil
|
|
|
82
117
|
Omitting an argument requires `?` (or a default), as in TypeScript — a
|
|
83
118
|
parameter typed `T | nil` still has to be passed something.
|
|
84
119
|
|
|
120
|
+
**Classes** — types are structural, except for classes. A definitions file
|
|
121
|
+
declares one with `declare class`, and it is nominal, as Roblox's classes are:
|
|
122
|
+
|
|
123
|
+
```luau
|
|
124
|
+
declare class BasePart extends PVInstance { Size: Vector3 }
|
|
125
|
+
declare class Part extends BasePart { Shape: EnumItem }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
A `Part` is a `BasePart` and an `Instance` because it extends them. A
|
|
129
|
+
`ReplicatedStorage` is not a `Part`, and no table literal is an `Instance`,
|
|
130
|
+
however alike their members. A class still fits a shape that names members it
|
|
131
|
+
has (`{ Name: string }`). It is not a table, though, so `typeof(part)` picks
|
|
132
|
+
the `"Instance"` overload, not `"table"`. Members are inherited, and a subclass
|
|
133
|
+
may narrow one (`Parent: SomeFolder`).
|
|
134
|
+
|
|
135
|
+
**Callbacks** — a function written where a function type is expected takes
|
|
136
|
+
its parameter types from it: in `signal:Connect(function(player) ... end)`,
|
|
137
|
+
`player` is typed from `Connect`. The same applies to an annotated `const`
|
|
138
|
+
and to an assignment such as `remote.OnServerInvoke = function(player) ...`.
|
|
139
|
+
|
|
140
|
+
**Type packs** — `type Signal<T... = ...any> = { Connect: (self, cb: (T...) -> ()) -> () }`.
|
|
141
|
+
A pack parameter takes every type argument from its position on:
|
|
142
|
+
`Signal<Player, string>`, `Signal<()>` for none.
|
|
143
|
+
|
|
144
|
+
**Operators** — on a type that declares metamethods (`__add`, `__mul`,
|
|
145
|
+
`__unm`, ...), an operator has the metamethod's result, tried on the left
|
|
146
|
+
operand and then the right one, as Luau does. So `Vector3 + Vector3` and
|
|
147
|
+
`2 * vector` are both `Vector3`.
|
|
148
|
+
|
|
149
|
+
**Qualified type names** — a definitions file may declare `Enum.Material`
|
|
150
|
+
(`declare class Enum.Material extends EnumItem {}`), and code writes it the
|
|
151
|
+
same way.
|
|
152
|
+
|
|
153
|
+
**Calls** — every argument is checked against its parameter, and a generic
|
|
154
|
+
parameter against its constraint (`GetService<K extends keyof Services>`
|
|
155
|
+
rejects `""`).
|
|
156
|
+
|
|
85
157
|
**Narrowing** follows TypeScript's model: references (`x`, `x.a.b`, `x["k"]`)
|
|
86
158
|
rather than just variables, discriminated unions at any depth, `and`/`or`,
|
|
87
159
|
early return, `break`/`continue`, `error()` (declared `-> never`), user type
|
|
@@ -103,6 +175,8 @@ written in luaut on top of those, not built in.
|
|
|
103
175
|
`typeof(expr)` spelling works too. It is compile-time only, unrelated to the
|
|
104
176
|
`typeof(v)` function that returns a string at runtime.
|
|
105
177
|
|
|
178
|
+
**Modules** — `import` / `export`, export lists, re-exports and `export *`.
|
|
179
|
+
|
|
106
180
|
## Options
|
|
107
181
|
|
|
108
182
|
```ts
|
|
@@ -111,10 +185,10 @@ analyzeScopes(program, {
|
|
|
111
185
|
})
|
|
112
186
|
|
|
113
187
|
analyzeTypes(program, scopes, {
|
|
114
|
-
libs
|
|
188
|
+
libs, // parsed `.d.luaut` definitions
|
|
115
189
|
globalTypes: { … }, // types for specific globals; wins over `libs`
|
|
116
190
|
libTypes: { … }, // extra named types for annotations
|
|
117
|
-
diagnostics: true, // emit
|
|
191
|
+
diagnostics: true, // emit type errors (default)
|
|
118
192
|
resolveModule: specifier => exportsOfThatFile,
|
|
119
193
|
// what an `import` sees; without it imports are `any`
|
|
120
194
|
})
|
|
@@ -123,21 +197,22 @@ analyzeTypes(program, scopes, {
|
|
|
123
197
|
## Known limitations
|
|
124
198
|
|
|
125
199
|
- `export * as ns from` and namespace imports (`import * as ns`) are not
|
|
126
|
-
supported.
|
|
127
|
-
(`export { a } from`) and `export * from` are.
|
|
200
|
+
supported.
|
|
128
201
|
- `setmetatable` and metatables are not modelled.
|
|
129
202
|
- Accessing a property a type does not have yields `unknown` rather than an
|
|
130
203
|
error; assigning to a `readonly` property is not reported.
|
|
204
|
+
- A sourcemap child whose name is not an identifier (`"My Part"`) is not typed.
|
|
131
205
|
|
|
132
206
|
## Development
|
|
133
207
|
|
|
134
208
|
```bash
|
|
135
209
|
npm install
|
|
136
|
-
npm test #
|
|
210
|
+
npm test # smoketests (types via smoketest/luaut.config.json), then project tests
|
|
137
211
|
npm run build
|
|
138
212
|
npm run typecheck
|
|
139
213
|
```
|
|
140
214
|
|
|
141
215
|
`smoketest/` is the test suite. Each file annotates its bindings with the type
|
|
142
216
|
they should infer to, so a wrong result surfaces as a diagnostic rather than
|
|
143
|
-
something to eyeball.
|
|
217
|
+
something to eyeball. `scripts/project.test.ts` covers configs, type
|
|
218
|
+
libraries, import paths and sourcemaps against an in-memory file system.
|