luaut-parser 1.1.0 → 2.0.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,10 @@ 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
+ **Calls** — every argument is checked against its parameter, and a generic
121
+ parameter against its constraint (`GetService<K extends keyof Services>`
122
+ rejects `""`).
123
+
85
124
  **Narrowing** follows TypeScript's model: references (`x`, `x.a.b`, `x["k"]`)
86
125
  rather than just variables, discriminated unions at any depth, `and`/`or`,
87
126
  early return, `break`/`continue`, `error()` (declared `-> never`), user type
@@ -103,6 +142,8 @@ written in luaut on top of those, not built in.
103
142
  `typeof(expr)` spelling works too. It is compile-time only, unrelated to the
104
143
  `typeof(v)` function that returns a string at runtime.
105
144
 
145
+ **Modules** — `import` / `export`, export lists, re-exports and `export *`.
146
+
106
147
  ## Options
107
148
 
108
149
  ```ts
@@ -111,10 +152,10 @@ analyzeScopes(program, {
111
152
  })
112
153
 
113
154
  analyzeTypes(program, scopes, {
114
- libs: defaultLibs, // parsed `.d.luaut` definitions
155
+ libs, // parsed `.d.luaut` definitions
115
156
  globalTypes: { … }, // types for specific globals; wins over `libs`
116
157
  libTypes: { … }, // extra named types for annotations
117
- diagnostics: true, // emit assignability errors (default)
158
+ diagnostics: true, // emit type errors (default)
118
159
  resolveModule: specifier => exportsOfThatFile,
119
160
  // what an `import` sees; without it imports are `any`
120
161
  })
@@ -123,22 +164,22 @@ analyzeTypes(program, scopes, {
123
164
  ## Known limitations
124
165
 
125
166
  - `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.
167
+ supported.
128
168
  - `setmetatable` and metatables are not modelled.
129
169
  - Accessing a property a type does not have yields `unknown` rather than an
130
- error; assigning to a `readonly` property is not reported; generic
131
- constraints are not checked at call sites.
170
+ error; assigning to a `readonly` property is not reported.
171
+ - A sourcemap child whose name is not an identifier (`"My Part"`) is not typed.
132
172
 
133
173
  ## Development
134
174
 
135
175
  ```bash
136
176
  npm install
137
- npm test # parses smoketest/*.luaut, writes AST + inferred types to generated/
177
+ npm test # smoketests (types via smoketest/luaut.config.json), then project tests
138
178
  npm run build
139
179
  npm run typecheck
140
180
  ```
141
181
 
142
182
  `smoketest/` is the test suite. Each file annotates its bindings with the type
143
183
  they should infer to, so a wrong result surfaces as a diagnostic rather than
144
- something to eyeball.
184
+ something to eyeball. `scripts/project.test.ts` covers configs, type
185
+ libraries, import paths and sourcemaps against an in-memory file system.