rooklang 0.3.3 → 0.3.4
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 +32 -7
- package/README.ru.md +32 -7
- package/dist/ast/declarations.d.ts +20 -1
- package/dist/ast/expressions.d.ts +31 -1
- package/dist/ast/markup.d.ts +6 -1
- package/dist/ast/statements.d.ts +25 -1
- package/dist/auto-import.d.ts +11 -0
- package/dist/auto-import.js +81 -0
- package/dist/breaks.d.ts +6 -0
- package/dist/breaks.js +36 -0
- package/dist/build.d.ts +7 -0
- package/dist/build.js +23 -2
- package/dist/checker/array-literals.d.ts +6 -0
- package/dist/checker/array-literals.js +59 -0
- package/dist/checker/assignments.d.ts +2 -0
- package/dist/checker/assignments.js +14 -0
- package/dist/checker/base.js +6 -6
- package/dist/checker/builtins.js +15 -5
- package/dist/checker/bytes.d.ts +2 -2
- package/dist/checker/bytes.js +2 -2
- package/dist/checker/calls.js +3 -0
- package/dist/checker/channel-objects.d.ts +3 -0
- package/dist/checker/channel-objects.js +40 -0
- package/dist/checker/channels.d.ts +1 -0
- package/dist/checker/channels.js +16 -2
- package/dist/checker/checker.d.ts +1 -1
- package/dist/checker/checker.js +19 -0
- package/dist/checker/class-members.d.ts +22 -0
- package/dist/checker/class-members.js +105 -0
- package/dist/checker/classes.d.ts +5 -4
- package/dist/checker/classes.js +41 -12
- package/dist/checker/closing.d.ts +21 -0
- package/dist/checker/closing.js +164 -0
- package/dist/checker/completion.d.ts +2 -2
- package/dist/checker/completion.js +2 -2
- package/dist/checker/context.d.ts +13 -0
- package/dist/checker/control-flow.d.ts +5 -0
- package/dist/checker/control-flow.js +17 -0
- package/dist/checker/declarations.js +1 -0
- package/dist/checker/decode.d.ts +8 -0
- package/dist/checker/decode.js +32 -0
- package/dist/checker/expressions.d.ts +5 -1
- package/dist/checker/expressions.js +13 -34
- package/dist/checker/flow.js +8 -4
- package/dist/checker/helpers.d.ts +1 -1
- package/dist/checker/helpers.js +9 -2
- package/dist/checker/iterables.d.ts +3 -1
- package/dist/checker/iterables.js +13 -0
- package/dist/checker/iterators.d.ts +4 -1
- package/dist/checker/iterators.js +12 -4
- package/dist/checker/json.d.ts +5 -0
- package/dist/checker/json.js +92 -0
- package/dist/checker/loops.js +23 -3
- package/dist/checker/members.js +7 -0
- package/dist/checker/operators.d.ts +2 -2
- package/dist/checker/operators.js +7 -5
- package/dist/checker/queues.d.ts +2 -2
- package/dist/checker/queues.js +2 -2
- package/dist/checker/resolution.js +5 -3
- package/dist/checker/signals.d.ts +8 -0
- package/dist/checker/signals.js +48 -0
- package/dist/checker/topics.d.ts +6 -0
- package/dist/checker/topics.js +17 -0
- package/dist/checker/transfer.d.ts +3 -1
- package/dist/checker/transfer.js +25 -5
- package/dist/checker/try.d.ts +6 -0
- package/dist/checker/try.js +19 -0
- package/dist/checker/types/constructors.d.ts +1 -1
- package/dist/checker/types/constructors.js +2 -2
- package/dist/checker/types/generics.js +1 -1
- package/dist/checker/types/iterables.d.ts +13 -0
- package/dist/checker/types/iterables.js +52 -0
- package/dist/checker/types/model.d.ts +25 -0
- package/dist/checker/types/params.js +22 -3
- package/dist/checker/types/printing.js +4 -3
- package/dist/checker/types/relations.js +19 -25
- package/dist/checker/types/topic.d.ts +6 -0
- package/dist/checker/types/topic.js +23 -0
- package/dist/checker/types.d.ts +2 -0
- package/dist/checker/types.js +2 -0
- package/dist/cli.js +18 -36
- package/dist/codegen/calls.d.ts +23 -0
- package/dist/codegen/calls.js +88 -7
- package/dist/codegen/control-statements.d.ts +10 -2
- package/dist/codegen/control-statements.js +56 -19
- package/dist/codegen/decode.d.ts +14 -0
- package/dist/codegen/decode.js +93 -0
- package/dist/codegen/emitter.d.ts +10 -41
- package/dist/codegen/emitter.js +9 -4
- package/dist/codegen/expressions.d.ts +9 -0
- package/dist/codegen/expressions.js +15 -24
- package/dist/codegen/js.d.ts +2 -2
- package/dist/codegen/js.js +5 -2
- package/dist/codegen/options.d.ts +40 -0
- package/dist/codegen/options.js +1 -0
- package/dist/codegen/runtime.d.ts +7 -3
- package/dist/codegen/runtime.js +27 -13
- package/dist/codegen/simple-statements.d.ts +10 -4
- package/dist/codegen/simple-statements.js +40 -15
- package/dist/codegen/statements.js +64 -4
- package/dist/codegen/try.d.ts +5 -0
- package/dist/codegen/try.js +40 -0
- package/dist/compile.d.ts +9 -0
- package/dist/compile.js +19 -6
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +10 -5
- package/dist/declarations.js +32 -6
- package/dist/decorator-copy.js +5 -2
- package/dist/goroutines.d.ts +1 -1
- package/dist/goroutines.js +5 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +14 -5
- package/dist/lexer/base.d.ts +2 -0
- package/dist/lexer/base.js +6 -1
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/literals.d.ts +5 -0
- package/dist/lexer/literals.js +51 -1
- package/dist/lexer/markup.js +3 -4
- package/dist/lexer/tables.js +2 -0
- package/dist/lexer/token.d.ts +4 -3
- package/dist/lexer/token.js +0 -2
- package/dist/loader.js +30 -8
- package/dist/parser/base.d.ts +4 -0
- package/dist/parser/base.js +2 -1
- package/dist/parser/classes.d.ts +2 -0
- package/dist/parser/classes.js +88 -23
- package/dist/parser/control-flow.d.ts +3 -1
- package/dist/parser/control-flow.js +21 -24
- package/dist/parser/declarations.d.ts +7 -4
- package/dist/parser/declarations.js +45 -6
- package/dist/parser/expressions.js +27 -3
- package/dist/parser/jumps.d.ts +11 -0
- package/dist/parser/jumps.js +77 -0
- package/dist/parser/literals.d.ts +9 -0
- package/dist/parser/literals.js +45 -6
- package/dist/parser/markup.js +1 -1
- package/dist/parser/simple-statements.d.ts +1 -1
- package/dist/parser/simple-statements.js +16 -9
- package/dist/parser/statements.d.ts +0 -3
- package/dist/parser/statements.js +22 -25
- package/dist/parser/syntax.d.ts +11 -1
- package/dist/parser/syntax.js +16 -2
- package/dist/parser/types.js +8 -3
- package/dist/project-modules.d.ts +12 -0
- package/dist/project-modules.js +106 -0
- package/dist/run-file.d.ts +6 -0
- package/dist/run-file.js +111 -0
- package/dist/runtime/sources.js +11 -5
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +75 -0
- package/dist/ts-types.js +4 -1
- package/dist/typescript/convert-objects.d.ts +2 -1
- package/dist/typescript/convert-objects.js +18 -9
- package/dist/vite.js +12 -4
- package/dist/walk-types.js +1 -0
- package/dist/walk.d.ts +0 -2
- package/dist/walk.js +11 -28
- package/package.json +1 -1
- package/src/runtime/chan.js +15 -5
- package/src/runtime/channel.js +28 -0
- package/src/runtime/classes.js +32 -0
- package/src/runtime/core.js +10 -1
- package/src/runtime/decode.js +190 -0
- package/src/runtime/go.js +3 -0
- package/src/runtime/index.js +6 -0
- package/src/runtime/select.js +14 -9
- package/src/runtime/signals.js +59 -0
- package/src/runtime/threads.js +32 -5
- package/src/runtime/topic.js +118 -0
- package/src/runtime/try.js +45 -0
package/README.md
CHANGED
|
@@ -25,14 +25,21 @@ close(jobs)
|
|
|
25
25
|
for let i = 0; i < 4; i++ {
|
|
26
26
|
console.log(<-results)
|
|
27
27
|
}
|
|
28
|
+
close(results)
|
|
28
29
|
```
|
|
29
30
|
|
|
30
31
|
- **Goroutines and channels.** `go f(x)` runs a function as a goroutine, on another processor, and
|
|
31
|
-
channels, `select`, `after`, `tick` and `cancel` work as in Go
|
|
32
|
+
channels, `select`, `after`, `tick` and `cancel` work as in Go, and signals and `AbortSignal`
|
|
33
|
+
are channels too, `signal("SIGTERM")` and `done(request.signal)`; `go for i, v in pixels { ... }`
|
|
32
34
|
splits an array among the processors. The compiler checks that what goes to another thread can be
|
|
33
|
-
copied there
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
copied there, and warns of a channel that is never closed; an instance of a `data class` goes
|
|
36
|
+
with its class and methods.
|
|
37
|
+
- **Topics between tabs.** `topic<CartEvent>("cart")` passes values between the tabs of a site
|
|
38
|
+
over a `BroadcastChannel`: `cart <- e` sends and waits for nobody, `for e in cart` receives what
|
|
39
|
+
the other tabs send. `Channel` and `Topic` have what channels and topics compile to, by name, and
|
|
40
|
+
their closed zero values, `Channel.EMPTY` and `Topic.EMPTY`.
|
|
41
|
+
- **Go syntax.** `func`, the type after the name, several results, errors as a second value
|
|
42
|
+
(`try JSON.parse(text)` makes what JS throws one), `defer`, a single `for` loop:
|
|
36
43
|
|
|
37
44
|
```go
|
|
38
45
|
func divide(a, b number) (number, error) {
|
|
@@ -43,7 +50,8 @@ for let i = 0; i < 4; i++ {
|
|
|
43
50
|
}
|
|
44
51
|
```
|
|
45
52
|
|
|
46
|
-
- **Static types with null safety.** A `?User` cannot be used without a check
|
|
53
|
+
- **Static types with null safety.** A `?User` cannot be used without a check, and
|
|
54
|
+
`decode<User>(body)` checks JSON from outside against the type when the program runs.
|
|
47
55
|
- **JavaScript inside.** The same values, classes and modules, any JS library; markup creates real
|
|
48
56
|
DOM elements, and components are compiled away.
|
|
49
57
|
|
|
@@ -60,6 +68,7 @@ npm install rooklang
|
|
|
60
68
|
npx rook build app.rk # app.js next to the source
|
|
61
69
|
npx rook build src --out-dir dist # every module of a folder
|
|
62
70
|
npx rook run app.rk # compile and run
|
|
71
|
+
npx rook run --watch app.rk # and again when a file of it changes
|
|
63
72
|
```
|
|
64
73
|
|
|
65
74
|
The compiled code imports what channels, goroutines and markup need from `rooklang/runtime`, so
|
|
@@ -97,9 +106,11 @@ staged version is formatted, so edits that are not staged stay out of the commit
|
|
|
97
106
|
| `npm run vscode:package` | Pack the VS Code extension into `rooklang.vsix` |
|
|
98
107
|
|
|
99
108
|
Options of `build`: `--out-dir dir` writes the `.js` files to a separate folder, `--stdout` prints
|
|
100
|
-
the JS of one file, `--no-check` skips type checking
|
|
109
|
+
the JS of one file, `--no-check` skips type checking, `--no-source-map` writes no source maps
|
|
110
|
+
(`app.js.map`, by default next to each `.js`). `--markup` and `--no-markup` turn markup and
|
|
101
111
|
components on or off for all files (with `run` too); by default they are off, and a package turns
|
|
102
|
-
them on in its `package.json`: `"rook": { "markup": true }`.
|
|
112
|
+
them on in its `package.json`: `"rook": { "markup": true }`. `run --watch` runs the program again
|
|
113
|
+
when a `.rk` file or a CSS module of it changes, after stopping it with `SIGTERM`.
|
|
103
114
|
|
|
104
115
|
## Releases
|
|
105
116
|
|
|
@@ -183,6 +194,20 @@ build, and in development they show up over the page. Markup and components are
|
|
|
183
194
|
so `.rk` modules fit a React, Svelte or Vue app as they are; turn them on with
|
|
184
195
|
`"rook": { "markup": true }` in `package.json`, or `rook({ markup: true })`.
|
|
185
196
|
|
|
197
|
+
### Tests
|
|
198
|
+
|
|
199
|
+
Rook has no test runner of its own: Vitest, with the same plugin in `vitest.config.ts`, runs tests
|
|
200
|
+
of `.rk` modules, written in JS or TS. Goroutines work there too: a worker thread of Vitest loads
|
|
201
|
+
the `.rk` modules with the loader of `rook run`.
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// math.test.ts
|
|
205
|
+
import { expect, it } from 'vitest';
|
|
206
|
+
import { add } from './math.rk';
|
|
207
|
+
|
|
208
|
+
it('adds', () => expect(add(1, 2)).toBe(3));
|
|
209
|
+
```
|
|
210
|
+
|
|
186
211
|
## Conventions
|
|
187
212
|
|
|
188
213
|
- Imports are written with the `.ts` extension (`import { x } from './lexer.ts'`).
|
package/README.ru.md
CHANGED
|
@@ -25,14 +25,21 @@ close(jobs)
|
|
|
25
25
|
for let i = 0; i < 4; i++ {
|
|
26
26
|
console.log(<-results)
|
|
27
27
|
}
|
|
28
|
+
close(results)
|
|
28
29
|
```
|
|
29
30
|
|
|
30
31
|
- **Горутины и каналы.** `go f(x)` запускает функцию горутиной, на другом процессоре, а каналы,
|
|
31
|
-
`select`, `after`, `tick` и `cancel` работают как в Go
|
|
32
|
+
`select`, `after`, `tick` и `cancel` работают как в Go, а сигналы и `AbortSignal` тоже становятся
|
|
33
|
+
каналами, `signal("SIGTERM")` и `done(request.signal)`; `go for i, v in pixels { ... }` делит
|
|
32
34
|
массив между процессорами. Компилятор проверяет, что уходящее в другой поток можно туда
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
скопировать, и предупреждает о канале, который никогда не закрывают; экземпляр `data class`
|
|
36
|
+
уходит вместе с классом и методами.
|
|
37
|
+
- **Топики между вкладками.** `topic<CartEvent>("cart")` передаёт значения между вкладками сайта
|
|
38
|
+
поверх `BroadcastChannel`: `cart <- e` отправляет и никого не ждёт, `for e in cart` принимает то,
|
|
39
|
+
что шлют другие вкладки. В `Channel` и `Topic` лежит то, во что компилируются каналы и топики, по
|
|
40
|
+
именам, и их закрытые нулевые значения `Channel.EMPTY` и `Topic.EMPTY`.
|
|
41
|
+
- **Синтаксис Go.** `func`, тип после имени, несколько результатов, ошибки вторым значением
|
|
42
|
+
(`try JSON.parse(text)` делает им то, что бросил JS), `defer`, один цикл `for`:
|
|
36
43
|
|
|
37
44
|
```go
|
|
38
45
|
func divide(a, b number) (number, error) {
|
|
@@ -43,7 +50,8 @@ for let i = 0; i < 4; i++ {
|
|
|
43
50
|
}
|
|
44
51
|
```
|
|
45
52
|
|
|
46
|
-
- **Статическая типизация с null-safety.** Значение типа `?User` нельзя использовать без
|
|
53
|
+
- **Статическая типизация с null-safety.** Значение типа `?User` нельзя использовать без проверки,
|
|
54
|
+
а `decode<User>(body)` во время работы сверяет с типом JSON, пришедший извне.
|
|
47
55
|
- **Внутри — JavaScript.** Те же значения, классы и модули, любые JS-библиотеки; разметка создаёт
|
|
48
56
|
настоящие DOM-элементы, а компоненты исчезают при компиляции.
|
|
49
57
|
|
|
@@ -60,6 +68,7 @@ npm install rooklang
|
|
|
60
68
|
npx rook build app.rk # app.js рядом с исходником
|
|
61
69
|
npx rook build src --out-dir dist # все модули папки
|
|
62
70
|
npx rook run app.rk # скомпилировать и запустить
|
|
71
|
+
npx rook run --watch app.rk # и запускать снова, когда меняется его файл
|
|
63
72
|
```
|
|
64
73
|
|
|
65
74
|
Скомпилированный код импортирует то, что нужно каналам, горутинам и разметке, из
|
|
@@ -98,9 +107,11 @@ Node.js ≥ 24 (`nvm use`). Компилятор написан на TypeScript,
|
|
|
98
107
|
| `npm run vscode:package` | Упаковать расширение для VS Code в `rooklang.vsix` |
|
|
99
108
|
|
|
100
109
|
Опции `build`: `--out-dir dir` — писать `.js` в отдельную папку, `--stdout` — вывести JS одного
|
|
101
|
-
файла в консоль, `--no-check` — без проверки
|
|
110
|
+
файла в консоль, `--no-check` — без проверки типов, `--no-source-map` — без source maps
|
|
111
|
+
(`app.js.map`, по умолчанию рядом с каждым `.js`). `--markup` и `--no-markup` включают или
|
|
102
112
|
выключают разметку и компоненты для всех файлов (и в `run`); по умолчанию они выключены, а пакет
|
|
103
|
-
включает их в своём `package.json`: `"rook": { "markup": true }`.
|
|
113
|
+
включает их в своём `package.json`: `"rook": { "markup": true }`. `run --watch` запускает программу
|
|
114
|
+
заново, когда меняется её `.rk`-файл или CSS-модуль, сначала остановив её через `SIGTERM`.
|
|
104
115
|
|
|
105
116
|
## Релизы
|
|
106
117
|
|
|
@@ -183,6 +194,20 @@ export default defineConfig({ plugins: [rook()] });
|
|
|
183
194
|
есть; включить их можно через `"rook": { "markup": true }` в `package.json` или
|
|
184
195
|
`rook({ markup: true })`.
|
|
185
196
|
|
|
197
|
+
### Тесты
|
|
198
|
+
|
|
199
|
+
Своего запускателя тестов у Rook нет: Vitest с тем же плагином в `vitest.config.ts` запускает тесты
|
|
200
|
+
`.rk`-модулей, написанные на JS или TS. Горутины там тоже работают: поток воркера Vitest загружает
|
|
201
|
+
`.rk`-модули загрузчиком `rook run`.
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// math.test.ts
|
|
205
|
+
import { expect, it } from 'vitest';
|
|
206
|
+
import { add } from './math.rk';
|
|
207
|
+
|
|
208
|
+
it('adds', () => expect(add(1, 2)).toBe(3));
|
|
209
|
+
```
|
|
210
|
+
|
|
186
211
|
## Соглашения
|
|
187
212
|
|
|
188
213
|
- Импорты пишутся с расширением `.ts` (`import { x } from './lexer.ts'`).
|
|
@@ -57,6 +57,14 @@ export interface Parameter extends NodeBase {
|
|
|
57
57
|
* properties, which `{...attrs}` puts on one of its elements.
|
|
58
58
|
*/
|
|
59
59
|
rest?: true;
|
|
60
|
+
/**
|
|
61
|
+
* `constructor(private name string)`: the parameter is a property too, which the constructor
|
|
62
|
+
* assigns, as in TypeScript.
|
|
63
|
+
*/
|
|
64
|
+
property?: {
|
|
65
|
+
visibility: Visibility;
|
|
66
|
+
readonly?: true;
|
|
67
|
+
};
|
|
60
68
|
}
|
|
61
69
|
/**
|
|
62
70
|
* `comp Card(title string, children Content) { ...; return <section>...</section> }`. Components
|
|
@@ -123,6 +131,10 @@ export interface VariableDeclaration extends NodeBase {
|
|
|
123
131
|
export interface ClassDeclaration extends NodeBase {
|
|
124
132
|
kind: 'ClassDeclaration';
|
|
125
133
|
exported: boolean;
|
|
134
|
+
/** `data class Point`: its fields are data, and its instances go to goroutines with the class. */
|
|
135
|
+
data: boolean;
|
|
136
|
+
/** `abstract class Shape`: it has no instances of its own, only those of its subclasses. */
|
|
137
|
+
abstract?: true;
|
|
126
138
|
name: Identifier;
|
|
127
139
|
typeParams: TypeParameter[];
|
|
128
140
|
/** An expression, as in JS, so that classes from JS modules can be extended. */
|
|
@@ -138,6 +150,8 @@ export interface FieldDeclaration extends NodeBase {
|
|
|
138
150
|
/** `public` when no modifier is written. */
|
|
139
151
|
visibility: Visibility;
|
|
140
152
|
isStatic: boolean;
|
|
153
|
+
/** `readonly id string`: assigned only by its value or in the constructor of its class. */
|
|
154
|
+
readonly?: true;
|
|
141
155
|
name: Identifier;
|
|
142
156
|
type: TypeNode | null;
|
|
143
157
|
value: Expression | null;
|
|
@@ -156,10 +170,15 @@ export interface MethodDeclaration extends NodeBase {
|
|
|
156
170
|
isStatic: boolean;
|
|
157
171
|
/** `async load() User`, like `async func`. */
|
|
158
172
|
async?: true;
|
|
173
|
+
/** `get area() number { ... }` and `set area(v number) { ... }`, as in JS. */
|
|
174
|
+
accessor?: 'get' | 'set';
|
|
175
|
+
/** `abstract area() number`: no body, each subclass that is not abstract implements it. */
|
|
176
|
+
abstract?: true;
|
|
159
177
|
name: Identifier;
|
|
160
178
|
params: Parameter[];
|
|
161
179
|
results: TypeNode[];
|
|
162
|
-
|
|
180
|
+
/** `null` for an abstract method. */
|
|
181
|
+
body: BlockStatement | null;
|
|
163
182
|
}
|
|
164
183
|
/** `interface Shape { name string; area() number }` */
|
|
165
184
|
export interface InterfaceDeclaration extends NodeBase {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NodeBase, Parameter } from './declarations.ts';
|
|
2
2
|
import type { ElementExpression, TypeNode } from './markup.ts';
|
|
3
3
|
import type { BlockStatement } from './statements.ts';
|
|
4
|
-
export type Expression = Identifier | NumberLiteral | BigIntLiteral | StringLiteral | TemplateLiteral | BooleanLiteral | NullLiteral | ThisExpression | SuperExpression | ArrayLiteral | ObjectLiteral | FuncExpression | ArrowFunction | UnaryExpression | BinaryExpression | ConditionalExpression | CallExpression | NewExpression | MemberExpression | IndexExpression | ElementExpression | DecoratorMember | DecoratorGet | AsExpression | GoExpression | ChanExpression | StreamChanExpression | QueueExpression | ConversionExpression | MakeExpression;
|
|
4
|
+
export type Expression = Identifier | NumberLiteral | BigIntLiteral | StringLiteral | RegExpLiteral | TemplateLiteral | BooleanLiteral | NullLiteral | ThisExpression | SuperExpression | ArrayLiteral | ObjectLiteral | FuncExpression | ArrowFunction | UnaryExpression | BinaryExpression | ConditionalExpression | CallExpression | NewExpression | MemberExpression | IndexExpression | ElementExpression | DecoratorMember | DecoratorGet | AsExpression | GoExpression | ChanExpression | StreamChanExpression | QueueExpression | TopicExpression | DecodeExpression | TryExpression | ConversionExpression | MakeExpression;
|
|
5
5
|
/**
|
|
6
6
|
* `[]byte("rook")`: the UTF-8 bytes of a string. `string(data)`, the way back, is a call, since
|
|
7
7
|
* `string` may be the name of a value.
|
|
@@ -33,6 +33,29 @@ export interface QueueExpression extends NodeBase {
|
|
|
33
33
|
element: TypeNode;
|
|
34
34
|
items: Expression | null;
|
|
35
35
|
}
|
|
36
|
+
/** `topic<CartEvent>("cart")`: values of the tabs of a site, over a BroadcastChannel of that name. */
|
|
37
|
+
export interface TopicExpression extends NodeBase {
|
|
38
|
+
kind: 'TopicExpression';
|
|
39
|
+
element: TypeNode;
|
|
40
|
+
name: Expression;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `decode<User>(body)`: JSON text, or a value from JS, checked against a type when the program
|
|
44
|
+
* runs; it gives `(User, error)`.
|
|
45
|
+
*/
|
|
46
|
+
export interface DecodeExpression extends NodeBase {
|
|
47
|
+
kind: 'DecodeExpression';
|
|
48
|
+
type: TypeNode;
|
|
49
|
+
argument: Expression;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `try JSON.parse(text)`: an exception in the expression, or a rejected Promise that an `await` in
|
|
53
|
+
* it waits for, becomes the error of `(T, error)`.
|
|
54
|
+
*/
|
|
55
|
+
export interface TryExpression extends NodeBase {
|
|
56
|
+
kind: 'TryExpression';
|
|
57
|
+
argument: Expression;
|
|
58
|
+
}
|
|
36
59
|
/**
|
|
37
60
|
* `chan(res.body, 4)`: a channel that receives the chunks of a ReadableStream, with room for 4,
|
|
38
61
|
* and a channel that gets the error of the stream, or null at its end.
|
|
@@ -82,6 +105,13 @@ export interface StringLiteral extends NodeBase {
|
|
|
82
105
|
/** Source text including quotes. */
|
|
83
106
|
raw: string;
|
|
84
107
|
}
|
|
108
|
+
/** `/ab+c/gi`, which JS writes the same way. */
|
|
109
|
+
export interface RegExpLiteral extends NodeBase {
|
|
110
|
+
kind: 'RegExpLiteral';
|
|
111
|
+
/** As it is written, between the slashes. */
|
|
112
|
+
pattern: string;
|
|
113
|
+
flags: string;
|
|
114
|
+
}
|
|
85
115
|
/** `` `Hello, ${name}!` ``: `quasis` always has one element more than `expressions`. */
|
|
86
116
|
export interface TemplateLiteral extends NodeBase {
|
|
87
117
|
kind: 'TemplateLiteral';
|
package/dist/ast/markup.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface EventHandler extends NodeBase {
|
|
|
60
60
|
kind: 'EventHandler';
|
|
61
61
|
body: SimpleStatement[];
|
|
62
62
|
}
|
|
63
|
-
export type TypeNode = TypeReference | ArrayType | NullableType | FuncType | ComponentType | ObjectType | UnionType | LiteralType | ChanType | QueueType;
|
|
63
|
+
export type TypeNode = TypeReference | ArrayType | NullableType | FuncType | ComponentType | ObjectType | UnionType | LiteralType | ChanType | QueueType | TopicType;
|
|
64
64
|
/**
|
|
65
65
|
* `chan number`: a channel of numbers, made by `chan<number>(size)`; `chan<- number` only sends
|
|
66
66
|
* and `<-chan number` only receives.
|
|
@@ -75,6 +75,11 @@ export interface QueueType extends NodeBase {
|
|
|
75
75
|
kind: 'QueueType';
|
|
76
76
|
element: TypeNode;
|
|
77
77
|
}
|
|
78
|
+
/** `topic CartEvent`: values that go to the other tabs of a site, made by `topic<T>(name)`. */
|
|
79
|
+
export interface TopicType extends NodeBase {
|
|
80
|
+
kind: 'TopicType';
|
|
81
|
+
element: TypeNode;
|
|
82
|
+
}
|
|
78
83
|
/** A named type: `number`, `User`, `Point`, `Box<number>`. */
|
|
79
84
|
export interface TypeReference extends NodeBase {
|
|
80
85
|
kind: 'TypeReference';
|
package/dist/ast/statements.d.ts
CHANGED
|
@@ -4,11 +4,20 @@ import type { JsxElementStatement } from './markup.ts';
|
|
|
4
4
|
export type Statement = ImportDeclaration | FuncDeclaration | ComponentDeclaration | DecoratorDeclaration | VariableDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | BlockStatement | ExpressionStatement | AssignmentStatement | IncDecStatement | ReturnStatement | IfStatement | ForStatement | ForInStatement | SwitchStatement | BreakStatement | ContinueStatement | ThrowStatement | TryStatement | DeferStatement | MountStatement | EffectStatement | StyleStatement | DeleteStatement | JsxElementStatement | SendStatement | SelectStatement;
|
|
5
5
|
/** Statements allowed in the `init` and `update` parts of a `for` header. */
|
|
6
6
|
export type SimpleStatement = VariableDeclaration | ExpressionStatement | AssignmentStatement | IncDecStatement | DeleteStatement | SendStatement;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* `ch <- value`: waits until the channel takes the value, so only in async functions. A send to a
|
|
9
|
+
* topic waits for nobody, so the parser, which does not know the type, leaves the error to the
|
|
10
|
+
* checker: `cannotWait` is the `<-` of a send where waiting is not allowed, and why.
|
|
11
|
+
*/
|
|
8
12
|
export interface SendStatement extends NodeBase {
|
|
9
13
|
kind: 'SendStatement';
|
|
10
14
|
channel: Expression;
|
|
11
15
|
value: Expression;
|
|
16
|
+
cannotWait?: {
|
|
17
|
+
inDefer: boolean;
|
|
18
|
+
start: number;
|
|
19
|
+
end: number;
|
|
20
|
+
};
|
|
12
21
|
}
|
|
13
22
|
export interface BlockStatement extends NodeBase {
|
|
14
23
|
kind: 'BlockStatement';
|
|
@@ -44,6 +53,8 @@ export interface ReturnStatement extends NodeBase {
|
|
|
44
53
|
}
|
|
45
54
|
export interface IfStatement extends NodeBase {
|
|
46
55
|
kind: 'IfStatement';
|
|
56
|
+
/** `if const v, err = f(); err != null { }`: its variables are seen in every branch, as in Go. */
|
|
57
|
+
init: VariableDeclaration | null;
|
|
47
58
|
condition: Expression;
|
|
48
59
|
consequent: BlockStatement;
|
|
49
60
|
/** `else { ... }` or `else if ...`. */
|
|
@@ -58,6 +69,8 @@ export interface IfStatement extends NodeBase {
|
|
|
58
69
|
*/
|
|
59
70
|
export interface ForStatement extends NodeBase {
|
|
60
71
|
kind: 'ForStatement';
|
|
72
|
+
/** `outer: for { ... }`, for `break outer` and `continue outer` in a nested loop. */
|
|
73
|
+
label?: Identifier;
|
|
61
74
|
init: SimpleStatement | null;
|
|
62
75
|
condition: Expression | null;
|
|
63
76
|
update: SimpleStatement | null;
|
|
@@ -69,6 +82,7 @@ export interface ForStatement extends NodeBase {
|
|
|
69
82
|
*/
|
|
70
83
|
export interface ForInStatement extends NodeBase {
|
|
71
84
|
kind: 'ForInStatement';
|
|
85
|
+
label?: Identifier;
|
|
72
86
|
key: Identifier | null;
|
|
73
87
|
value: Identifier;
|
|
74
88
|
iterable: Expression;
|
|
@@ -78,6 +92,9 @@ export interface ForInStatement extends NodeBase {
|
|
|
78
92
|
/** `switch cmd { ... }`, or `switch { ... }` without a discriminant as an if/else chain. */
|
|
79
93
|
export interface SwitchStatement extends NodeBase {
|
|
80
94
|
kind: 'SwitchStatement';
|
|
95
|
+
label?: Identifier;
|
|
96
|
+
/** `switch const kind = node.kind; kind { }`, as the initializer of `if`. */
|
|
97
|
+
init: VariableDeclaration | null;
|
|
81
98
|
discriminant: Expression | null;
|
|
82
99
|
cases: SwitchCase[];
|
|
83
100
|
}
|
|
@@ -94,6 +111,7 @@ export interface SwitchCase extends NodeBase {
|
|
|
94
111
|
*/
|
|
95
112
|
export interface SelectStatement extends NodeBase {
|
|
96
113
|
kind: 'SelectStatement';
|
|
114
|
+
label?: Identifier;
|
|
97
115
|
cases: SelectCase[];
|
|
98
116
|
}
|
|
99
117
|
/** A case of `select`: a receive, `<-ch`, a send, `ch <- v`, or `default` when it is null. */
|
|
@@ -104,12 +122,18 @@ export interface SelectCase extends NodeBase {
|
|
|
104
122
|
operation: UnaryExpression | SendStatement | null;
|
|
105
123
|
body: Statement[];
|
|
106
124
|
}
|
|
125
|
+
/** `break`, or `break outer`: the label of an enclosing loop, switch or select. */
|
|
107
126
|
export interface BreakStatement extends NodeBase {
|
|
108
127
|
kind: 'BreakStatement';
|
|
128
|
+
label?: Identifier;
|
|
109
129
|
}
|
|
130
|
+
/** `continue`, or `continue outer`: the label of an enclosing loop. */
|
|
110
131
|
export interface ContinueStatement extends NodeBase {
|
|
111
132
|
kind: 'ContinueStatement';
|
|
133
|
+
label?: Identifier;
|
|
112
134
|
}
|
|
135
|
+
/** The statements that can have a label. */
|
|
136
|
+
export type LabeledStatement = ForStatement | ForInStatement | SwitchStatement | SelectStatement;
|
|
113
137
|
export interface ThrowStatement extends NodeBase {
|
|
114
138
|
kind: 'ThrowStatement';
|
|
115
139
|
argument: Expression;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Completion, TextEdit } from './checker/checker.ts';
|
|
2
|
+
import type { CompletionList } from './completion.ts';
|
|
3
|
+
/** A fix of a diagnostic: the import of the name from one module. */
|
|
4
|
+
export interface ImportFix {
|
|
5
|
+
title: string;
|
|
6
|
+
edit: TextEdit;
|
|
7
|
+
}
|
|
8
|
+
/** The names of other modules that fit where the cursor is and that are not in scope there. */
|
|
9
|
+
export declare function importCompletions(source: string, filename: string, list: CompletionList): Completion[];
|
|
10
|
+
/** For an error about a name that is not defined, the imports of the modules that export it. */
|
|
11
|
+
export declare function importFixes(source: string, filename: string, message: string): ImportFix[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { dirname, relative, resolve, sep } from 'node:path';
|
|
2
|
+
import { markupFor } from "./config.js";
|
|
3
|
+
import { parse } from "./parser/parser.js";
|
|
4
|
+
import { projectExports } from "./project-modules.js";
|
|
5
|
+
const SPACES = {
|
|
6
|
+
value: 'value',
|
|
7
|
+
type: 'type',
|
|
8
|
+
tag: 'tag',
|
|
9
|
+
};
|
|
10
|
+
/** The names of other modules that fit where the cursor is and that are not in scope there. */
|
|
11
|
+
export function importCompletions(source, filename, list) {
|
|
12
|
+
const space = list.context && SPACES[list.context];
|
|
13
|
+
if (!space)
|
|
14
|
+
return [];
|
|
15
|
+
const known = new Set(list.items.map((item) => item.name));
|
|
16
|
+
const candidates = projectExports(filename).filter((each) => each.spaces.includes(space) && !known.has(each.name));
|
|
17
|
+
if (candidates.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
const imports = importsOf(source, filename);
|
|
20
|
+
return candidates.map((each) => {
|
|
21
|
+
const specifier = specifierOf(filename, each.path);
|
|
22
|
+
return {
|
|
23
|
+
name: each.name,
|
|
24
|
+
kind: each.kind,
|
|
25
|
+
import: { specifier, edit: importEdit(source, imports, each.name, specifier, each.path) },
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const UNDEFINED = [
|
|
30
|
+
[/^"(.+)" is not defined$/, 'value'],
|
|
31
|
+
[/^unknown type "(.+)"$/, 'type'],
|
|
32
|
+
[/^unknown component <(.+)>$/, 'tag'],
|
|
33
|
+
[/^unknown decorator @(.+)$/, 'decorator'],
|
|
34
|
+
];
|
|
35
|
+
/** For an error about a name that is not defined, the imports of the modules that export it. */
|
|
36
|
+
export function importFixes(source, filename, message) {
|
|
37
|
+
for (const [pattern, space] of UNDEFINED) {
|
|
38
|
+
const name = pattern.exec(message)?.[1];
|
|
39
|
+
if (name === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
const imports = importsOf(source, filename);
|
|
42
|
+
return projectExports(filename)
|
|
43
|
+
.filter((each) => each.name === name && each.spaces.includes(space))
|
|
44
|
+
.map((each) => {
|
|
45
|
+
const specifier = specifierOf(filename, each.path);
|
|
46
|
+
return {
|
|
47
|
+
title: `Add import { ${name} } from "${specifier}"`,
|
|
48
|
+
edit: importEdit(source, imports, name, specifier, each.path),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
/** The imports of the module, with the absolute paths of the modules they import. */
|
|
55
|
+
function importsOf(source, filename) {
|
|
56
|
+
const { program } = parse(source, { markup: markupFor(filename) });
|
|
57
|
+
return program.body.flatMap((statement) => statement.kind === 'ImportDeclaration'
|
|
58
|
+
? [[statement, resolve(dirname(filename), statement.source.value)]]
|
|
59
|
+
: []);
|
|
60
|
+
}
|
|
61
|
+
/** The path of a module as an import of the file `filename` writes it: "./card.rk". */
|
|
62
|
+
function specifierOf(filename, path) {
|
|
63
|
+
const specifier = relative(dirname(resolve(filename)), path)
|
|
64
|
+
.split(sep)
|
|
65
|
+
.join('/');
|
|
66
|
+
return specifier.startsWith('../') ? specifier : `./${specifier}`;
|
|
67
|
+
}
|
|
68
|
+
function importEdit(source, imports, name, specifier, path) {
|
|
69
|
+
const same = imports.find(([node, from]) => from === path && node.namedImports.length > 0);
|
|
70
|
+
const last = same?.[0].namedImports.at(-1);
|
|
71
|
+
if (last)
|
|
72
|
+
return { start: last.end, end: last.end, text: `, ${name}` };
|
|
73
|
+
const line = `import { ${name} } from "${specifier}"`;
|
|
74
|
+
const after = imports.at(-1)?.[0];
|
|
75
|
+
if (!after)
|
|
76
|
+
return { start: 0, end: 0, text: `${line}\n\n` };
|
|
77
|
+
const end = source.indexOf('\n', after.end);
|
|
78
|
+
return end === -1
|
|
79
|
+
? { start: source.length, end: source.length, text: `\n${line}` }
|
|
80
|
+
: { start: end + 1, end: end + 1, text: `${line}\n` };
|
|
81
|
+
}
|
package/dist/breaks.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type * as ast from './ast.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a `break` in these statements leaves the enclosing loop or switch: an unlabeled one
|
|
4
|
+
* outside of nested loops and switches, or `break label` anywhere when `label` is the statement's.
|
|
5
|
+
*/
|
|
6
|
+
export declare function containsBreak(statements: readonly ast.Statement[], label?: ast.Identifier | null): boolean;
|
package/dist/breaks.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { forEachChild } from "./walk.js";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a `break` in these statements leaves the enclosing loop or switch: an unlabeled one
|
|
4
|
+
* outside of nested loops and switches, or `break label` anywhere when `label` is the statement's.
|
|
5
|
+
*/
|
|
6
|
+
export function containsBreak(statements, label = null) {
|
|
7
|
+
let found = false;
|
|
8
|
+
const visit = (node, nested) => {
|
|
9
|
+
switch (node.kind) {
|
|
10
|
+
case 'BreakStatement':
|
|
11
|
+
if (node.label ? node.label.name === label?.name : !nested)
|
|
12
|
+
found = true;
|
|
13
|
+
return;
|
|
14
|
+
// An unlabeled `break` inside these belongs to them.
|
|
15
|
+
case 'ForStatement':
|
|
16
|
+
case 'ForInStatement':
|
|
17
|
+
case 'SwitchStatement':
|
|
18
|
+
case 'SelectStatement':
|
|
19
|
+
if (label !== null)
|
|
20
|
+
forEachChild(node, (child) => visit(child, true));
|
|
21
|
+
return;
|
|
22
|
+
case 'FuncDeclaration':
|
|
23
|
+
case 'FuncExpression':
|
|
24
|
+
case 'ArrowFunction':
|
|
25
|
+
case 'ClassDeclaration':
|
|
26
|
+
case 'MountStatement':
|
|
27
|
+
case 'EffectStatement':
|
|
28
|
+
return;
|
|
29
|
+
default:
|
|
30
|
+
forEachChild(node, (child) => visit(child, nested));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
for (const statement of statements)
|
|
34
|
+
visit(statement, false);
|
|
35
|
+
return found;
|
|
36
|
+
}
|
package/dist/build.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface BuildOptions {
|
|
|
9
9
|
declarations?: boolean;
|
|
10
10
|
/** Markup and components for every file; by default as its package.json says (config.ts). */
|
|
11
11
|
markup?: boolean;
|
|
12
|
+
/** Write a source map next to each `.js`, `app.js.map`. Defaults to `true`. */
|
|
13
|
+
sourceMap?: boolean;
|
|
12
14
|
}
|
|
13
15
|
export interface BuildOutput {
|
|
14
16
|
/** Absolute path of the `.rk` file. */
|
|
@@ -26,6 +28,11 @@ export interface BuildOutput {
|
|
|
26
28
|
output: string;
|
|
27
29
|
code: string;
|
|
28
30
|
};
|
|
31
|
+
/** The source maps of the module and of its worker module, `.js.map`, unless turned off. */
|
|
32
|
+
maps?: {
|
|
33
|
+
output: string;
|
|
34
|
+
code: string;
|
|
35
|
+
}[];
|
|
29
36
|
}
|
|
30
37
|
/** The errors or the warnings of a file. */
|
|
31
38
|
export interface BuildError {
|
package/dist/build.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
2
|
+
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { compile } from "./index.js";
|
|
4
|
+
import { sourceMapComment } from "./source-map.js";
|
|
4
5
|
import { SourceFile } from "./source.js";
|
|
5
6
|
/**
|
|
6
7
|
* Compiles `.rk` files and directories (recursively), together with every `.rk` file they
|
|
@@ -47,6 +48,8 @@ export function build(entries, options = {}) {
|
|
|
47
48
|
code: result.code,
|
|
48
49
|
declarations: result.declarations,
|
|
49
50
|
worker: result.worker,
|
|
51
|
+
map: result.map,
|
|
52
|
+
workerMap: result.workerMap,
|
|
50
53
|
});
|
|
51
54
|
}
|
|
52
55
|
for (const specifier of result.dependencies) {
|
|
@@ -62,15 +65,33 @@ export function build(entries, options = {}) {
|
|
|
62
65
|
return { outputs: [], errors, warnings };
|
|
63
66
|
// The output keeps the folder structure below the given directories and all the files.
|
|
64
67
|
const root = commonDirectory(queue, directories);
|
|
65
|
-
const outputs = compiled.map((
|
|
68
|
+
const outputs = compiled.map((each) => {
|
|
69
|
+
const { source, declarations: types } = each;
|
|
66
70
|
const target = options.outDir ? join(resolve(options.outDir), relative(root, source)) : source;
|
|
67
71
|
const base = target.replace(/\.rk$/, '');
|
|
72
|
+
const maps = [];
|
|
73
|
+
// The code ends with where its map is, and the map says where the source is, from there.
|
|
74
|
+
const mapped = (code, output, map) => {
|
|
75
|
+
if (options.sourceMap === false || map === undefined)
|
|
76
|
+
return code;
|
|
77
|
+
const sources = [relative(dirname(output), source).split(sep).join('/')];
|
|
78
|
+
maps.push({
|
|
79
|
+
output: `${output}.map`,
|
|
80
|
+
code: JSON.stringify({ ...map, file: basename(output), sources }),
|
|
81
|
+
});
|
|
82
|
+
return `${code}${sourceMapComment(`${basename(output)}.map`)}`;
|
|
83
|
+
};
|
|
84
|
+
const code = mapped(each.code, `${base}.js`, each.map);
|
|
85
|
+
const worker = each.worker === undefined
|
|
86
|
+
? undefined
|
|
87
|
+
: mapped(each.worker, `${base}.worker.js`, each.workerMap);
|
|
68
88
|
return {
|
|
69
89
|
source,
|
|
70
90
|
output: `${base}.js`,
|
|
71
91
|
code,
|
|
72
92
|
...(types === undefined ? {} : { declarations: { output: `${base}.d.ts`, code: types } }),
|
|
73
93
|
...(worker === undefined ? {} : { worker: { output: `${base}.worker.js`, code: worker } }),
|
|
94
|
+
...(maps.length > 0 ? { maps } : {}),
|
|
74
95
|
};
|
|
75
96
|
});
|
|
76
97
|
return { outputs, errors, warnings };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { ExpressionChecker } from './expressions.ts';
|
|
3
|
+
import { type Type } from './types.ts';
|
|
4
|
+
export declare abstract class ArrayLiteralChecker extends ExpressionChecker {
|
|
5
|
+
protected checkArrayLiteral(node: ast.ArrayLiteral, expected: Type | null): Type;
|
|
6
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ExpressionChecker } from "./expressions.js";
|
|
2
|
+
import { ANY, arrayOf, commonType, NEVER, nonNull, typeToString, UNKNOWN, } from "./types.js";
|
|
3
|
+
// Array literals: the type of their elements is the expected one, or the common type of all of
|
|
4
|
+
// them, `[1, null]` is a `[]?number`. A literal without spreads also keeps the types of its
|
|
5
|
+
// elements by position, so that a call infers `K` and `V` of a tuple `[K, V]` of a `.d.ts` from
|
|
6
|
+
// `["a", 1]`: `new Map([["a", 1]])` is a `Map<string, number>` (types/params.ts).
|
|
7
|
+
export class ArrayLiteralChecker extends ExpressionChecker {
|
|
8
|
+
checkArrayLiteral(node, expected) {
|
|
9
|
+
const context = expected ? nonNull(expected) : null;
|
|
10
|
+
const expectedElement = context?.kind === 'array' ? context.element : context?.kind === 'any' ? ANY : null;
|
|
11
|
+
let element = expectedElement ?? NEVER;
|
|
12
|
+
const positions = [];
|
|
13
|
+
for (const item of node.elements) {
|
|
14
|
+
let type;
|
|
15
|
+
if (item.kind === 'SpreadElement') {
|
|
16
|
+
const spread = this.checkValue(item.argument, expectedElement && arrayOf(expectedElement));
|
|
17
|
+
if (spread.kind === 'array')
|
|
18
|
+
type = spread.element;
|
|
19
|
+
else if (spread.kind === 'any' || spread.kind === 'unknown')
|
|
20
|
+
type = spread;
|
|
21
|
+
// JS spreads any iterable: a Set, a typed array, a `[]byte`.
|
|
22
|
+
else if (spread.kind === 'object' && spread.iterates)
|
|
23
|
+
type = spread.iterates;
|
|
24
|
+
else {
|
|
25
|
+
this.error(`cannot spread ${typeToString(spread)}: it is not an array or an iterable`, item.argument);
|
|
26
|
+
type = UNKNOWN;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
type = this.checkValue(item, expectedElement);
|
|
31
|
+
positions.push(type);
|
|
32
|
+
}
|
|
33
|
+
if (expectedElement) {
|
|
34
|
+
this.expectAssignable(type, expectedElement, item.kind === 'SpreadElement' ? item.argument : item);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
element = commonElement(element, type);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return positions.length === node.elements.length && positions.length > 0
|
|
41
|
+
? arrayOf(element, positions)
|
|
42
|
+
: arrayOf(element);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The common type of the elements so far and the next one; of arrays with positions, by position,
|
|
47
|
+
* so that `[["a", 1], ["b", "x"]]` gives a `V` of `number | string`.
|
|
48
|
+
*/
|
|
49
|
+
function commonElement(element, next) {
|
|
50
|
+
const common = commonType(element, next);
|
|
51
|
+
if (common.kind !== 'array' ||
|
|
52
|
+
element.kind !== 'array' ||
|
|
53
|
+
next.kind !== 'array' ||
|
|
54
|
+
element.positions?.length !== next.positions?.length ||
|
|
55
|
+
!element.positions) {
|
|
56
|
+
return common;
|
|
57
|
+
}
|
|
58
|
+
return arrayOf(common.element, element.positions.map((each, i) => commonType(each, next.positions[i])));
|
|
59
|
+
}
|
|
@@ -10,6 +10,8 @@ export declare abstract class AssignmentChecker extends InitializerChecker {
|
|
|
10
10
|
protected checkAssignment(node: ast.AssignmentStatement): void;
|
|
11
11
|
/** The type a value assigned to `target` must have. */
|
|
12
12
|
protected checkTarget(target: ast.Expression): Type;
|
|
13
|
+
/** `this.id = id` in the constructor of the class that has the readonly field `id`. */
|
|
14
|
+
private initializes;
|
|
13
15
|
protected checkReturn(node: ast.ReturnStatement): void;
|
|
14
16
|
/**
|
|
15
17
|
* `return null, err` in a function of `(User, error)`: null takes the place of a value only
|