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 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, formatType, defaultLibs,
16
+ parse, analyzeScopes, analyzeTypes,
17
+ findConfig, resolveTypeLibraries, resolveModulePath,
16
18
  } from "luaut-parser"
17
19
 
18
- const program = parse(source)
19
- const scopes = analyzeScopes(program, {})
20
- const types = analyzeTypes(program, scopes, { libs: defaultLibs })
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, assignability errors |
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, so a tool never has to re-derive a type from text.
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
- ## Definitions
52
+ ## Projects
45
53
 
46
- `type` / `typeof` are **not** special-cased in the analyzer. They are ordinary
47
- overload sets declared in `luau.d.luaut`, and narrowing is derived from them.
48
- Pass the definitions or those built-ins narrow nothing:
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
- ```ts
51
- analyzeTypes(program, scopes, { libs: defaultLibs }) // core Luau + Roblox
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
- The same trick drives the Roblox layer: `IsA`, `GetService` and `Instance.new`
56
- are each one generic signature indexing a map of names to types, so adding a
57
- class is adding a line to `roblox.d.luaut`. Ship your own definitions by
58
- parsing them the same way:
59
-
60
- ```ts
61
- analyzeTypes(program, scopes, { libs: [luauLib, parse(myDefs)] })
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
- `luauDefs` / `robloxDefs` expose the raw text (the loaders read from disk, so a
65
- browser consumer should parse the text itself).
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: defaultLibs, // parsed `.d.luaut` definitions
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 assignability errors (default)
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. Export lists (`export { a, b as c }`), re-exports
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 # parses smoketest/*.luaut, writes AST + inferred types to generated/
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.