rooklang 0.3.4 → 0.4.1
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 +33 -2
- package/README.ru.md +33 -2
- package/dist/ast/declarations.d.ts +19 -3
- package/dist/ast/expressions.d.ts +4 -8
- package/dist/ast/statements.d.ts +3 -1
- package/dist/bun-register.d.ts +1 -0
- package/dist/bun-register.js +9 -0
- package/dist/bun.d.ts +34 -0
- package/dist/bun.js +99 -0
- package/dist/checker/assignments.d.ts +2 -0
- package/dist/checker/assignments.js +9 -3
- package/dist/checker/base.d.ts +8 -2
- package/dist/checker/base.js +3 -3
- package/dist/checker/builtins.js +7 -3
- package/dist/checker/channels.js +1 -2
- package/dist/checker/classes.js +7 -1
- package/dist/checker/closing.js +1 -1
- package/dist/checker/completion.d.ts +1 -1
- package/dist/checker/completion.js +2 -1
- package/dist/checker/component-bindings.d.ts +7 -0
- package/dist/checker/component-bindings.js +45 -0
- package/dist/checker/component-values.js +10 -7
- package/dist/checker/components.d.ts +2 -2
- package/dist/checker/components.js +8 -3
- package/dist/checker/context.d.ts +28 -5
- package/dist/checker/context.js +23 -0
- package/dist/checker/control-flow.d.ts +6 -5
- package/dist/checker/control-flow.js +48 -40
- package/dist/checker/declarations.d.ts +2 -2
- package/dist/checker/declarations.js +3 -2
- package/dist/checker/decorator-exports.js +4 -0
- package/dist/checker/decorator-includes.d.ts +9 -0
- package/dist/checker/decorator-includes.js +71 -0
- package/dist/checker/decorator-members.d.ts +8 -10
- package/dist/checker/decorator-members.js +28 -31
- package/dist/checker/decorator-wrappers.d.ts +1 -1
- package/dist/checker/decorator-wrappers.js +3 -2
- package/dist/checker/decorators.d.ts +14 -5
- package/dist/checker/decorators.js +52 -25
- package/dist/checker/expressions.d.ts +1 -1
- package/dist/checker/expressions.js +7 -6
- package/dist/checker/flow.d.ts +25 -3
- package/dist/checker/flow.js +48 -8
- package/dist/checker/functions.d.ts +2 -2
- package/dist/checker/functions.js +9 -5
- package/dist/checker/helpers.d.ts +4 -1
- package/dist/checker/helpers.js +7 -2
- package/dist/checker/imports.js +5 -2
- package/dist/checker/loops.d.ts +2 -0
- package/dist/checker/loops.js +27 -2
- package/dist/checker/markup.js +14 -2
- package/dist/checker/members.js +3 -1
- package/dist/checker/shared-state.d.ts +11 -0
- package/dist/checker/shared-state.js +37 -0
- package/dist/checker/statements.d.ts +5 -2
- package/dist/checker/statements.js +51 -22
- package/dist/checker/tagged-unions.d.ts +37 -0
- package/dist/checker/tagged-unions.js +132 -0
- package/dist/checker/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +3 -1
- package/dist/codegen/attributes.d.ts +4 -0
- package/dist/codegen/attributes.js +24 -2
- package/dist/codegen/component-helpers.d.ts +6 -1
- package/dist/codegen/component-helpers.js +6 -1
- package/dist/codegen/component-props.d.ts +11 -2
- package/dist/codegen/component-props.js +50 -5
- package/dist/codegen/components.d.ts +6 -5
- package/dist/codegen/components.js +44 -21
- package/dist/codegen/control-flow.d.ts +2 -0
- package/dist/codegen/control-flow.js +23 -9
- package/dist/codegen/creation.js +5 -2
- package/dist/codegen/early-returns.d.ts +4 -3
- package/dist/codegen/early-returns.js +17 -5
- package/dist/codegen/emitter.d.ts +3 -0
- package/dist/codegen/emitter.js +18 -4
- package/dist/codegen/expressions.js +0 -2
- package/dist/codegen/functions.d.ts +11 -1
- package/dist/codegen/functions.js +31 -3
- package/dist/codegen/live.d.ts +60 -0
- package/dist/codegen/live.js +29 -0
- package/dist/codegen/mutations.d.ts +7 -0
- package/dist/codegen/mutations.js +43 -2
- package/dist/codegen/options.d.ts +2 -0
- package/dist/codegen/reactive.d.ts +4 -59
- package/dist/codegen/reactive.js +34 -72
- package/dist/codegen/runtime.js +24 -7
- package/dist/codegen/shared.d.ts +22 -0
- package/dist/codegen/shared.js +93 -0
- package/dist/codegen/simple-statements.d.ts +1 -1
- package/dist/codegen/statements.d.ts +5 -0
- package/dist/codegen/statements.js +35 -0
- package/dist/codegen/updates.d.ts +11 -3
- package/dist/codegen/updates.js +52 -4
- package/dist/codegen/web-components.d.ts +2 -2
- package/dist/codegen/web-components.js +2 -2
- package/dist/compile-file.d.ts +4 -1
- package/dist/compile.d.ts +1 -1
- package/dist/compile.js +6 -2
- package/dist/decorator-copy.d.ts +1 -1
- package/dist/decorator-copy.js +1 -0
- package/dist/decorator-names.d.ts +14 -0
- package/dist/decorator-names.js +56 -0
- package/dist/decorators.d.ts +2 -3
- package/dist/decorators.js +36 -14
- package/dist/index.d.ts +1 -1
- package/dist/inline-packages.d.ts +2 -1
- package/dist/inline-packages.js +9 -4
- package/dist/loader.js +6 -19
- package/dist/names.d.ts +4 -0
- package/dist/names.js +29 -0
- package/dist/parser/base.d.ts +1 -0
- package/dist/parser/base.js +1 -1
- package/dist/parser/control-flow.d.ts +5 -0
- package/dist/parser/control-flow.js +28 -2
- package/dist/parser/declarations.d.ts +5 -15
- package/dist/parser/declarations.js +15 -119
- package/dist/parser/decorators.d.ts +6 -2
- package/dist/parser/decorators.js +43 -19
- package/dist/parser/params.d.ts +21 -0
- package/dist/parser/params.js +143 -0
- package/dist/parser/simple-statements.d.ts +1 -0
- package/dist/parser/simple-statements.js +22 -6
- package/dist/parser/statements.d.ts +5 -2
- package/dist/parser/statements.js +18 -11
- package/dist/run-cache.d.ts +7 -0
- package/dist/run-cache.js +24 -0
- package/dist/run-file.js +6 -1
- package/dist/runtime/sources.js +4 -3
- package/dist/walk.js +3 -3
- package/package.json +10 -2
- package/src/runtime/decode.js +41 -10
- package/src/runtime/go.js +10 -4
- package/src/runtime/index.js +1 -0
- package/src/runtime/markup.js +27 -17
- package/src/runtime/shared.js +17 -0
package/README.md
CHANGED
|
@@ -58,10 +58,12 @@ close(results)
|
|
|
58
58
|
The guide with examples and a playground: <https://qwern-9876r.github.io/RookLang/>.
|
|
59
59
|
Specification: [docs/spec.md](docs/spec.md), design of components:
|
|
60
60
|
[docs/components.md](docs/components.md), an example: [examples/hello.rk](examples/hello.rk).
|
|
61
|
+
Where Rook stands and the road to 1.0: [docs/roadmap.md](docs/roadmap.md); what changed in each
|
|
62
|
+
version: [CHANGELOG.md](CHANGELOG.md).
|
|
61
63
|
|
|
62
64
|
## Installation
|
|
63
65
|
|
|
64
|
-
Rook needs Node.js ≥ 24.
|
|
66
|
+
Rook needs Node.js ≥ 24 or [Bun](#bun).
|
|
65
67
|
|
|
66
68
|
```sh
|
|
67
69
|
npm install rooklang
|
|
@@ -75,7 +77,8 @@ The compiled code imports what channels, goroutines and markup need from `rookla
|
|
|
75
77
|
the package is a dependency of the project, not only of its build.
|
|
76
78
|
|
|
77
79
|
From code, the compiler is `import { compile } from 'rooklang'`. The package also has the
|
|
78
|
-
[Vite](#vite) plugin, `rooklang/vite`,
|
|
80
|
+
[Vite](#vite) plugin, `rooklang/vite`, the [Bun](#bun) plugin, `rooklang/bun`, and
|
|
81
|
+
`rooklang/browser`, the compiler without the
|
|
79
82
|
TypeScript compiler for running in a browser, as in the site's playground: there imports are not
|
|
80
83
|
type checked.
|
|
81
84
|
|
|
@@ -208,6 +211,34 @@ import { add } from './math.rk';
|
|
|
208
211
|
it('adds', () => expect(add(1, 2)).toBe(3));
|
|
209
212
|
```
|
|
210
213
|
|
|
214
|
+
## Bun
|
|
215
|
+
|
|
216
|
+
Rook works in Bun too: `bun add rooklang`, then `bunx rook build app.rk` or `bunx rook run
|
|
217
|
+
app.rk`. Under the runtime of Bun, `bun --bun rook run app.rk`, the program runs in Bun.
|
|
218
|
+
|
|
219
|
+
To let Bun load `.rk` modules itself, preload the plugin `rooklang/bun` in `bunfig.toml`:
|
|
220
|
+
|
|
221
|
+
```toml
|
|
222
|
+
preload = ["rooklang/bun/register"]
|
|
223
|
+
|
|
224
|
+
[test]
|
|
225
|
+
preload = ["rooklang/bun/register"]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Then `bun app.rk` runs a module, TypeScript imports `.rk` modules, `bun --watch` and `bun --hot`
|
|
229
|
+
follow them, and `bun test` runs tests of them; goroutines work there too. The plugin itself is
|
|
230
|
+
`import { rook } from 'rooklang/bun'`, for `Bun.plugin(rook())` or `Bun.build({ plugins: [rook()] })`;
|
|
231
|
+
a bundle of `Bun.build` cannot have the workers of `go` yet, so build those with `rook build` or
|
|
232
|
+
Vite.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// math.test.ts
|
|
236
|
+
import { expect, test } from 'bun:test';
|
|
237
|
+
import { add } from './math.rk';
|
|
238
|
+
|
|
239
|
+
test('adds', () => expect(add(1, 2)).toBe(3));
|
|
240
|
+
```
|
|
241
|
+
|
|
211
242
|
## Conventions
|
|
212
243
|
|
|
213
244
|
- Imports are written with the `.ts` extension (`import { x } from './lexer.ts'`).
|
package/README.ru.md
CHANGED
|
@@ -58,10 +58,12 @@ close(results)
|
|
|
58
58
|
Руководство с примерами и песочницей: <https://qwern-9876r.github.io/RookLang/ru/>.
|
|
59
59
|
Спецификация: [docs/spec.ru.md](docs/spec.ru.md), концепция компонентов:
|
|
60
60
|
[docs/components.ru.md](docs/components.ru.md), пример: [examples/hello.rk](examples/hello.rk).
|
|
61
|
+
Где Rook сейчас и путь к 1.0: [docs/roadmap.ru.md](docs/roadmap.ru.md); что изменилось в каждой
|
|
62
|
+
версии: [CHANGELOG.ru.md](CHANGELOG.ru.md).
|
|
61
63
|
|
|
62
64
|
## Установка
|
|
63
65
|
|
|
64
|
-
Нужен Node.js ≥ 24.
|
|
66
|
+
Нужен Node.js ≥ 24 или [Bun](#bun).
|
|
65
67
|
|
|
66
68
|
```sh
|
|
67
69
|
npm install rooklang
|
|
@@ -75,7 +77,8 @@ npx rook run --watch app.rk # и запускать снова, ког
|
|
|
75
77
|
`rooklang/runtime`, поэтому пакет — зависимость проекта, а не только его сборки.
|
|
76
78
|
|
|
77
79
|
Из кода компилятор подключается как `import { compile } from 'rooklang'`. Ещё в пакете есть
|
|
78
|
-
плагин для [Vite](#vite) — `rooklang/vite
|
|
80
|
+
плагин для [Vite](#vite) — `rooklang/vite`, — плагин для [Bun](#bun) — `rooklang/bun` — и
|
|
81
|
+
`rooklang/browser`: компилятор без
|
|
79
82
|
компилятора TypeScript, для работы в браузере, как в песочнице сайта; импорты там не проверяются
|
|
80
83
|
по типам.
|
|
81
84
|
|
|
@@ -208,6 +211,34 @@ import { add } from './math.rk';
|
|
|
208
211
|
it('adds', () => expect(add(1, 2)).toBe(3));
|
|
209
212
|
```
|
|
210
213
|
|
|
214
|
+
## Bun
|
|
215
|
+
|
|
216
|
+
Rook работает и в Bun: `bun add rooklang`, потом `bunx rook build app.rk` или `bunx rook run
|
|
217
|
+
app.rk`. Под рантаймом Bun, `bun --bun rook run app.rk`, программа выполняется в Bun.
|
|
218
|
+
|
|
219
|
+
Чтобы Bun сам загружал `.rk`-модули, плагин `rooklang/bun` подключают заранее в `bunfig.toml`:
|
|
220
|
+
|
|
221
|
+
```toml
|
|
222
|
+
preload = ["rooklang/bun/register"]
|
|
223
|
+
|
|
224
|
+
[test]
|
|
225
|
+
preload = ["rooklang/bun/register"]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Тогда `bun app.rk` запускает модуль, TypeScript импортирует `.rk`-модули, `bun --watch` и
|
|
229
|
+
`bun --hot` следят за ними, а `bun test` запускает их тесты; горутины там тоже работают. Сам плагин —
|
|
230
|
+
`import { rook } from 'rooklang/bun'`, для `Bun.plugin(rook())` или
|
|
231
|
+
`Bun.build({ plugins: [rook()] })`; в бандле `Bun.build` воркеров `go` пока быть не может, поэтому
|
|
232
|
+
такой код собирают `rook build` или Vite.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// math.test.ts
|
|
236
|
+
import { expect, test } from 'bun:test';
|
|
237
|
+
import { add } from './math.rk';
|
|
238
|
+
|
|
239
|
+
test('adds', () => expect(add(1, 2)).toBe(3));
|
|
240
|
+
```
|
|
241
|
+
|
|
211
242
|
## Соглашения
|
|
212
243
|
|
|
213
244
|
- Импорты пишутся с расширением `.ts` (`import { x } from './lexer.ts'`).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Expression, Identifier, StringLiteral } from './expressions.ts';
|
|
1
|
+
import type { Expression, FuncExpression, Identifier, StringLiteral } from './expressions.ts';
|
|
2
2
|
import type { TypeMember, TypeNode, TypeReference } from './markup.ts';
|
|
3
3
|
import type { BlockStatement, Statement } from './statements.ts';
|
|
4
4
|
export interface NodeBase {
|
|
@@ -57,6 +57,11 @@ export interface Parameter extends NodeBase {
|
|
|
57
57
|
* properties, which `{...attrs}` puts on one of its elements.
|
|
58
58
|
*/
|
|
59
59
|
rest?: true;
|
|
60
|
+
/**
|
|
61
|
+
* `bind active string`: a property of a component that it may change. `bind:active={tab}`
|
|
62
|
+
* writes the changes into the parent's `tab`; without it the property is the component's state.
|
|
63
|
+
*/
|
|
64
|
+
bind?: true;
|
|
60
65
|
/**
|
|
61
66
|
* `constructor(private name string)`: the parameter is a property too, which the constructor
|
|
62
67
|
* assigns, as in TypeScript.
|
|
@@ -97,6 +102,11 @@ export interface DecoratorDeclaration extends NodeBase {
|
|
|
97
102
|
params: Parameter[];
|
|
98
103
|
/** `needs cart, auth`: the decorators whose public members it uses; they are applied above it. */
|
|
99
104
|
needs: Identifier[];
|
|
105
|
+
/**
|
|
106
|
+
* `includes visible(margin), cart`: decorators applied with it, above its own body, whose
|
|
107
|
+
* arguments see its parameters. Their public members are its own for the component, `@shop.count`.
|
|
108
|
+
*/
|
|
109
|
+
includes: DecoratorUse[];
|
|
100
110
|
body: BlockStatement;
|
|
101
111
|
}
|
|
102
112
|
/** `@visible("300px")` or `@visible` before `comp`; the arguments are positional. */
|
|
@@ -118,14 +128,20 @@ export interface VariableDeclaration extends NodeBase {
|
|
|
118
128
|
exported: boolean;
|
|
119
129
|
keyword: 'let' | 'const' | 'state' | 'derived' | 'ref';
|
|
120
130
|
/**
|
|
121
|
-
* `public state`, `public derived` or `public const` in a decorator: the component reads it
|
|
122
|
-
*
|
|
131
|
+
* `public state`, `public derived` or `public const` in a decorator: the component reads it as
|
|
132
|
+
* `@name.x`.
|
|
123
133
|
*/
|
|
124
134
|
isPublic: boolean;
|
|
125
135
|
/** `_` is an ordinary identifier here; the checker treats it as "skip this value". */
|
|
126
136
|
names: Identifier[];
|
|
127
137
|
type: TypeNode | null;
|
|
128
138
|
values: Expression[];
|
|
139
|
+
/**
|
|
140
|
+
* `derived summary string { ...; return text }`: the block that computes a derived value, a
|
|
141
|
+
* function without parameters, so that what looks for the `return`s, `defer`s and `break`s of a
|
|
142
|
+
* component stops at it, as at other functions. Its result type is `type`; `values` is empty.
|
|
143
|
+
*/
|
|
144
|
+
body: FuncExpression | null;
|
|
129
145
|
}
|
|
130
146
|
/** `class Admin extends User implements Shape { ... }` */
|
|
131
147
|
export interface ClassDeclaration 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 | RegExpLiteral | TemplateLiteral | BooleanLiteral | NullLiteral | ThisExpression | SuperExpression | ArrayLiteral | ObjectLiteral | FuncExpression | ArrowFunction | UnaryExpression | BinaryExpression | ConditionalExpression | CallExpression | NewExpression | MemberExpression | IndexExpression | ElementExpression | DecoratorMember |
|
|
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 | 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.
|
|
@@ -231,16 +231,12 @@ export interface IndexExpression extends NodeBase {
|
|
|
231
231
|
optional: boolean;
|
|
232
232
|
}
|
|
233
233
|
/**
|
|
234
|
-
* `@visible.shown`: a public member of a decorator applied to the component
|
|
235
|
-
* the decorator, not to any scope, so walkers do not visit them
|
|
234
|
+
* `@visible.shown` or `@visible.show()`: a public member of a decorator applied to the component,
|
|
235
|
+
* read or called. The names belong to the decorator, not to any scope, so walkers do not visit them
|
|
236
|
+
* as identifiers.
|
|
236
237
|
*/
|
|
237
238
|
export interface DecoratorMember extends NodeBase {
|
|
238
239
|
kind: 'DecoratorMember';
|
|
239
240
|
decorator: Identifier;
|
|
240
241
|
member: Identifier;
|
|
241
242
|
}
|
|
242
|
-
/** `get(@visible.shown)`: reads public state or a constant of a decorator. */
|
|
243
|
-
export interface DecoratorGet extends NodeBase {
|
|
244
|
-
kind: 'DecoratorGet';
|
|
245
|
-
target: DecoratorMember;
|
|
246
|
-
}
|
package/dist/ast/statements.d.ts
CHANGED
|
@@ -86,6 +86,8 @@ export interface ForInStatement extends NodeBase {
|
|
|
86
86
|
key: Identifier | null;
|
|
87
87
|
value: Identifier;
|
|
88
88
|
iterable: Expression;
|
|
89
|
+
/** `with key todo.id` of a `{for}` in markup: what its blocks are found again by. */
|
|
90
|
+
withKey?: Expression;
|
|
89
91
|
body: BlockStatement;
|
|
90
92
|
go?: boolean;
|
|
91
93
|
}
|
|
@@ -156,7 +158,7 @@ export interface DeferStatement extends NodeBase {
|
|
|
156
158
|
body: CallExpression | BlockStatement;
|
|
157
159
|
}
|
|
158
160
|
/**
|
|
159
|
-
* `mount
|
|
161
|
+
* `mount { ... }` at the top level of a component: runs once the component's markup is in the
|
|
160
162
|
* document. It may return a function, which runs when `{if}`, `{switch}` or `{for}` removes the
|
|
161
163
|
* markup.
|
|
162
164
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { rook } from "./bun.js";
|
|
2
|
+
// `preload = ["rooklang/bun/register"]` in bunfig.toml: Bun then compiles `.rk` modules when they
|
|
3
|
+
// load, in `bun app.rk`, in imports from TypeScript and in `bun test` (bun.ts). `rook run` under
|
|
4
|
+
// Bun preloads it too, and the workers of `go` import it before their module.
|
|
5
|
+
const { Bun } = globalThis;
|
|
6
|
+
if (Bun === undefined) {
|
|
7
|
+
throw new Error('rooklang/bun/register is for Bun; in Node, rook run compiles .rk files');
|
|
8
|
+
}
|
|
9
|
+
Bun.plugin(rook());
|
package/dist/bun.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** What a plugin of Bun is given, of what Rook uses: the package needs no types of Bun. */
|
|
2
|
+
interface PluginBuilder {
|
|
3
|
+
onResolve(constraints: {
|
|
4
|
+
filter: RegExp;
|
|
5
|
+
namespace?: string;
|
|
6
|
+
}, callback: (args: {
|
|
7
|
+
path: string;
|
|
8
|
+
importer: string;
|
|
9
|
+
}) => {
|
|
10
|
+
path: string;
|
|
11
|
+
namespace?: string;
|
|
12
|
+
}): void;
|
|
13
|
+
onLoad(constraints: {
|
|
14
|
+
filter: RegExp;
|
|
15
|
+
namespace?: string;
|
|
16
|
+
}, callback: (args: {
|
|
17
|
+
path: string;
|
|
18
|
+
}) => {
|
|
19
|
+
contents: string;
|
|
20
|
+
loader: 'js';
|
|
21
|
+
}): void;
|
|
22
|
+
}
|
|
23
|
+
export interface BunPlugin {
|
|
24
|
+
name: string;
|
|
25
|
+
setup(build: PluginBuilder): void;
|
|
26
|
+
}
|
|
27
|
+
export interface RookBunOptions {
|
|
28
|
+
/** Check types before generating code. Defaults to `true`. */
|
|
29
|
+
typeCheck?: boolean;
|
|
30
|
+
/** Markup and components; by default as the package.json of the file says. */
|
|
31
|
+
markup?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function rook(options?: RookBunOptions): BunPlugin;
|
|
34
|
+
export {};
|
package/dist/bun.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import { isMainThread } from 'node:worker_threads';
|
|
6
|
+
import { displayPath } from "./build.js";
|
|
7
|
+
import { compileFile } from "./compile-file.js";
|
|
8
|
+
import { cachedFile, listFiles, withMap } from "./run-cache.js";
|
|
9
|
+
import { RUNTIME_MODULE } from "./runtime-module.js";
|
|
10
|
+
// The plugin of Bun: `bun app.rk`, `import "./cart.rk"` from TypeScript and `bun test` compile the
|
|
11
|
+
// `.rk` modules when they load, as `rook run` does in Node (loader.ts). `preload =
|
|
12
|
+
// ["rooklang/bun/register"]` in bunfig.toml registers it (bun-register.ts), and so does
|
|
13
|
+
// `rook run` under Bun.
|
|
14
|
+
//
|
|
15
|
+
// `app.rk.goroutines.js` is the worker module of `go` in app.rk: Bun drops the query of
|
|
16
|
+
// `app.rk?goroutines` before a plugin sees it, as Vite does (vite.ts). A Web Worker of Bun has no
|
|
17
|
+
// preload and loads its module without the plugins, so it starts with a module that registers the
|
|
18
|
+
// plugin first (startWorker in codegen/runtime.ts). The main thread compiles a module once and
|
|
19
|
+
// leaves its code and that of its worker module in ROOK_RUN_CACHE, a directory of the run, which
|
|
20
|
+
// the workers take instead of compiling again; without a run of `rook run`, the plugin makes one
|
|
21
|
+
// and removes it when the process ends.
|
|
22
|
+
/** The worker module of `go` in a module, `app.rk.goroutines.js`, next to it. */
|
|
23
|
+
const GOROUTINES = '.goroutines.js';
|
|
24
|
+
// The files next to this one: .ts in src, .js in the dist of the package.
|
|
25
|
+
const extension = extname(fileURLToPath(import.meta.url));
|
|
26
|
+
// The worker of `go` registers the plugin with this module before it imports its module.
|
|
27
|
+
const register = new URL(`./bun-register${extension}`, import.meta.url).href;
|
|
28
|
+
// The compiled code imports the runtime of this compiler, wherever the program is, by its URL: a
|
|
29
|
+
// worker of Bun does not give a plugin the bare `rooklang/runtime`, and looks for the package.
|
|
30
|
+
const runtimeImport = `from "${RUNTIME_MODULE}";`;
|
|
31
|
+
const runtime = `from ${JSON.stringify(import.meta.resolve(RUNTIME_MODULE))};`;
|
|
32
|
+
export function rook(options = {}) {
|
|
33
|
+
return {
|
|
34
|
+
name: 'rook',
|
|
35
|
+
setup(build) {
|
|
36
|
+
build.onResolve({ filter: /\.rk\.goroutines\.js$/ }, (args) => ({
|
|
37
|
+
path: args.path.startsWith('file:')
|
|
38
|
+
? fileURLToPath(args.path)
|
|
39
|
+
: resolve(dirname(args.importer), args.path),
|
|
40
|
+
namespace: 'rook-goroutines',
|
|
41
|
+
}));
|
|
42
|
+
build.onLoad({ filter: /.*/, namespace: 'rook-goroutines' }, (args) => ({
|
|
43
|
+
contents: load(args.path.slice(0, -GOROUTINES.length), true, options),
|
|
44
|
+
loader: 'js',
|
|
45
|
+
}));
|
|
46
|
+
build.onLoad({ filter: /\.rk$/ }, (args) => ({
|
|
47
|
+
contents: load(args.path, false, options),
|
|
48
|
+
loader: 'js',
|
|
49
|
+
}));
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The code of a module or of its worker module, from the cache of the run when it is there. The
|
|
55
|
+
* key has the time the file was changed, since `bun --hot` loads a changed module again.
|
|
56
|
+
*/
|
|
57
|
+
function load(path, worker, options) {
|
|
58
|
+
const url = `${pathToFileURL(path).href}#${statSync(path).mtimeMs}`;
|
|
59
|
+
const cache = runCache();
|
|
60
|
+
const file = cachedFile(cache, worker ? `${url}${GOROUTINES}` : url);
|
|
61
|
+
if (existsSync(file))
|
|
62
|
+
return readFileSync(file, 'utf8');
|
|
63
|
+
const result = compile(path, options);
|
|
64
|
+
const code = withMap(result.code.replaceAll(runtimeImport, runtime), result.map);
|
|
65
|
+
const workerCode = withMap((result.worker ?? '').replaceAll(runtimeImport, runtime), result.workerMap);
|
|
66
|
+
if (isMainThread) {
|
|
67
|
+
// The CSS modules it imports; the .rk modules are listed when they are loaded.
|
|
68
|
+
const read = result.dependencies.map((each) => resolve(dirname(path), each));
|
|
69
|
+
listFiles(cache, [path, ...read.filter((each) => existsSync(each))]);
|
|
70
|
+
writeFileSync(cachedFile(cache, url), code);
|
|
71
|
+
writeFileSync(cachedFile(cache, `${url}${GOROUTINES}`), workerCode);
|
|
72
|
+
}
|
|
73
|
+
return worker ? workerCode : code;
|
|
74
|
+
}
|
|
75
|
+
function compile(path, options) {
|
|
76
|
+
return compileFile(displayPath(path), {
|
|
77
|
+
rewriteImports: false,
|
|
78
|
+
typeCheck: options.typeCheck ?? process.env['ROOK_NO_CHECK'] === undefined,
|
|
79
|
+
markup: options.markup ?? markupSetting(),
|
|
80
|
+
quiet: !isMainThread,
|
|
81
|
+
workerUrl: `./${basename(path)}${GOROUTINES}`,
|
|
82
|
+
nodeLoader: register,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** ROOK_MARKUP of `rook run`, 1 or 0, turns markup on or off for every file. */
|
|
86
|
+
function markupSetting() {
|
|
87
|
+
const setting = process.env['ROOK_MARKUP'];
|
|
88
|
+
return setting === undefined ? undefined : setting === '1';
|
|
89
|
+
}
|
|
90
|
+
/** The directory of the run: that of `rook run`, or one of this process, which its workers share. */
|
|
91
|
+
function runCache() {
|
|
92
|
+
const existing = process.env['ROOK_RUN_CACHE'];
|
|
93
|
+
if (existing !== undefined)
|
|
94
|
+
return existing;
|
|
95
|
+
const dir = mkdtempSync(join(tmpdir(), 'rook-bun-'));
|
|
96
|
+
process.env['ROOK_RUN_CACHE'] = dir;
|
|
97
|
+
process.on('exit', () => rmSync(dir, { recursive: true, force: true }));
|
|
98
|
+
return dir;
|
|
99
|
+
}
|
|
@@ -2,6 +2,8 @@ import type * as ast from '../ast.ts';
|
|
|
2
2
|
import { InitializerChecker } from './initializers.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
4
|
export declare abstract class AssignmentChecker extends InitializerChecker {
|
|
5
|
+
/** The name of the `derived` whose block is being checked: it computes, so it changes no state. */
|
|
6
|
+
protected derivedBlock: string | null;
|
|
5
7
|
/**
|
|
6
8
|
* `a, b = f()` or `return f()`: the call must return exactly `count` values. Returns their
|
|
7
9
|
* types.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { placeOf } from "./context.js";
|
|
1
2
|
import { guardedByError, unpackedResults } from "./error-results.js";
|
|
2
3
|
import { isUntyped, countValues, callName, isCall } from "./helpers.js";
|
|
3
4
|
import { InitializerChecker } from "./initializers.js";
|
|
@@ -5,6 +6,8 @@ import { BOOL, ANY, isAssignable, isNullable, nonNull, nullable, typeToString, U
|
|
|
5
6
|
// Where values go: assignments to variables, fields and elements, and `return`. Both unpack a call
|
|
6
7
|
// that returns several values.
|
|
7
8
|
export class AssignmentChecker extends InitializerChecker {
|
|
9
|
+
/** The name of the `derived` whose block is being checked: it computes, so it changes no state. */
|
|
10
|
+
derivedBlock = null;
|
|
8
11
|
/**
|
|
9
12
|
* `a, b = f()` or `return f()`: the call must return exactly `count` values. Returns their
|
|
10
13
|
* types.
|
|
@@ -66,13 +69,13 @@ export class AssignmentChecker extends InitializerChecker {
|
|
|
66
69
|
const binding = this.narrowableBinding(target);
|
|
67
70
|
const declared = binding?.type;
|
|
68
71
|
if (binding && declared && isNullable(declared) && !isNullable(valueTypes[i] ?? UNKNOWN)) {
|
|
69
|
-
this.flow.set(binding, nonNull(declared));
|
|
72
|
+
this.flow.set(placeOf(binding), nonNull(declared));
|
|
70
73
|
}
|
|
71
74
|
});
|
|
72
75
|
}
|
|
73
76
|
/** The type a value assigned to `target` must have. */
|
|
74
77
|
checkTarget(target) {
|
|
75
|
-
if (this.checkDecoratorWrite(target))
|
|
78
|
+
if (this.checkDecoratorWrite(target) || this.checkSharedWrite(target))
|
|
76
79
|
return UNKNOWN;
|
|
77
80
|
switch (target.kind) {
|
|
78
81
|
case 'Identifier': {
|
|
@@ -92,13 +95,16 @@ export class AssignmentChecker extends InitializerChecker {
|
|
|
92
95
|
else if (binding.kind === 'ref') {
|
|
93
96
|
this.error(`cannot assign to "${target.name}": a ref always points to its element`, target);
|
|
94
97
|
}
|
|
98
|
+
else if (binding.kind === 'state' && this.derivedBlock !== null) {
|
|
99
|
+
this.error(`derived "${this.derivedBlock}" cannot change the state "${target.name}": change it in an event handler or an effect`, target);
|
|
100
|
+
}
|
|
95
101
|
else if (binding.kind === 'loop') {
|
|
96
102
|
this.error(`cannot assign to loop variable "${target.name}"`, target);
|
|
97
103
|
}
|
|
98
104
|
else if (binding.kind === 'prop') {
|
|
99
105
|
this.error(this.decorator
|
|
100
106
|
? `cannot assign to "${target.name}": parameters of a decorator are read-only`
|
|
101
|
-
: `cannot assign to "${target.name}": component properties are read-only; to change
|
|
107
|
+
: `cannot assign to "${target.name}": component properties are read-only; to change it, declare it with bind, e.g. comp ${this.component?.node.name.name ?? 'Tabs'}(bind ${target.name} ...)`, target);
|
|
102
108
|
}
|
|
103
109
|
else if (binding.kind !== 'let' &&
|
|
104
110
|
binding.kind !== 'state' &&
|
package/dist/checker/base.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import type { Diagnostic } from '../diagnostics.ts';
|
|
3
|
-
import { type CheckOptions, type BindingKind, type Binding, type ComponentInfo, type DecoratorInfo, type AliasEntry, Scope, type FunctionContext, type ClassContext, type Flow } from './context.ts';
|
|
3
|
+
import { type CheckOptions, type BindingKind, type Binding, type ComponentInfo, type DecoratorInfo, type AliasEntry, Scope, type FunctionContext, type ClassContext, type Flow, type Place } from './context.ts';
|
|
4
4
|
import { type ClassInfo, type FunctionType, type Member, type Type, type TypeParam } from './types.ts';
|
|
5
5
|
export declare abstract class CheckerBase {
|
|
6
6
|
protected abstract checkClassBodies(node: ast.ClassDeclaration, info: ClassInfo): void;
|
|
@@ -18,6 +18,13 @@ export declare abstract class CheckerBase {
|
|
|
18
18
|
protected abstract checkTry(node: ast.TryStatement): void;
|
|
19
19
|
protected abstract forget(target: ast.Expression): void;
|
|
20
20
|
protected abstract narrowableBinding(node: ast.Expression): Binding | undefined;
|
|
21
|
+
/** A variable or a path of data fields that conditions narrow, with its type at this point. */
|
|
22
|
+
protected abstract narrowable(node: ast.Expression): {
|
|
23
|
+
place: Place;
|
|
24
|
+
type: Type;
|
|
25
|
+
} | undefined;
|
|
26
|
+
/** Whether an assignment to `field` of `place` may change the member of a union it is. */
|
|
27
|
+
protected abstract isTagOf(place: Place, field: string): boolean;
|
|
21
28
|
/** The error for a value that may be null where it must not be. */
|
|
22
29
|
protected abstract nullError(node: ast.Expression): void;
|
|
23
30
|
protected abstract checkFunction(params: readonly ast.Parameter[], type: FunctionType, results: Type[] | null, body: ast.BlockStatement | ast.Expression, options?: {
|
|
@@ -44,7 +51,6 @@ export declare abstract class CheckerBase {
|
|
|
44
51
|
protected abstract orderByNeeds(decorators: readonly DecoratorInfo[]): DecoratorInfo[];
|
|
45
52
|
protected abstract wrappedType(info: ComponentInfo, own: Type, seen: Set<ComponentInfo>): Type;
|
|
46
53
|
protected abstract checkDecoratorMember(node: ast.DecoratorMember): Type;
|
|
47
|
-
protected abstract checkDecoratorGet(node: ast.DecoratorGet): Type;
|
|
48
54
|
protected abstract checkDecoratorWrite(target: ast.Expression): boolean;
|
|
49
55
|
protected abstract checkComponentUse(node: ast.ElementExpression, tag: ast.Identifier): Type;
|
|
50
56
|
protected abstract checkRefDeclaration(node: ast.VariableDeclaration): void;
|
package/dist/checker/base.js
CHANGED
|
@@ -4,9 +4,9 @@ import { Scope, globalScope, } from "./context.js";
|
|
|
4
4
|
import { DOCUMENT_FRAGMENT, eventType, HTML_ELEMENT, NODE } from "./dom.js";
|
|
5
5
|
import { explainMismatch, isAssignable, LazyMap, memberNames, nonNull, property, typeToString, UNKNOWN, } from "./types.js";
|
|
6
6
|
// The type checker is a chain of layers, one per area, like the code generator: base → imports →
|
|
7
|
-
// declarations → class members → classes → resolution → initializers → assignments → statements → control flow →
|
|
8
|
-
// functions → expressions → array literals → operators → markup → forwarding → components → component values →
|
|
9
|
-
// refs → styles → decorator wrappers → decorator exports → decorators → decorator members →
|
|
7
|
+
// shared state → declarations → class members → classes → resolution → initializers → assignments → statements → control flow →
|
|
8
|
+
// tagged unions → functions → expressions → array literals → operators → markup → forwarding → component bindings → components → component values →
|
|
9
|
+
// refs → styles → decorator wrappers → decorator exports → decorators → decorator includes → decorator members →
|
|
10
10
|
// members → calls → goroutines → channels → signals → queues → topics → decode → try → bytes → loops → go for →
|
|
11
11
|
// iterators → closing → completion → Checker (checker.ts). Statements, expressions and markup
|
|
12
12
|
// check each other recursively, so methods of later layers that earlier ones call are declared
|
package/dist/checker/builtins.js
CHANGED
|
@@ -6,7 +6,7 @@ import { chanOf } from "./channel.js";
|
|
|
6
6
|
import { CHANNEL_OBJECT, TOPIC_OBJECT } from "./channel-objects.js";
|
|
7
7
|
import { PROMISE } from "./promise.js";
|
|
8
8
|
import { BUFFERS } from "./transfer.js";
|
|
9
|
-
import { UNTYPED_GLOBALS } from "./untyped-globals.js";
|
|
9
|
+
import { UNTYPED_CLASSES, UNTYPED_GLOBALS } from "./untyped-globals.js";
|
|
10
10
|
import { ANY, arrayOf, BIGINT, BOOL, ERROR, ERROR_CLASS, func, method, NUMBER, nullable, property, STRING, topicOf, union, } from "./types.js";
|
|
11
11
|
// Types of the JS standard library that Rook programs use most. Anything not listed here
|
|
12
12
|
// can still be reached through the untyped globals below.
|
|
@@ -249,7 +249,10 @@ export const GLOBAL_VALUES = new Map([
|
|
|
249
249
|
['cancel', func([ANY], [])],
|
|
250
250
|
['Channel', CHANNEL_OBJECT],
|
|
251
251
|
['Topic', TOPIC_OBJECT],
|
|
252
|
-
...[...UNTYPED_GLOBALS, ...BUFFERS].map((name) => [
|
|
252
|
+
...[...UNTYPED_GLOBALS, ...UNTYPED_CLASSES, ...BUFFERS].map((name) => [
|
|
253
|
+
name,
|
|
254
|
+
ANY,
|
|
255
|
+
]),
|
|
253
256
|
]);
|
|
254
257
|
/** Built-in types that a library from TypeScript's lib files replaces: untyped ones and the DOM. */
|
|
255
258
|
export const LIBRARY_TYPES = new Set([
|
|
@@ -260,6 +263,7 @@ export const LIBRARY_TYPES = new Set([
|
|
|
260
263
|
'Promise',
|
|
261
264
|
'URL',
|
|
262
265
|
'Event',
|
|
266
|
+
...UNTYPED_CLASSES,
|
|
263
267
|
...BUFFERS,
|
|
264
268
|
...[...DOM_TYPES.keys()].filter((name) => name !== 'Content'),
|
|
265
269
|
...SVG_TYPES.keys(),
|
|
@@ -278,7 +282,7 @@ export const GLOBAL_TYPES = new Map([
|
|
|
278
282
|
['any', ANY],
|
|
279
283
|
['error', ERROR],
|
|
280
284
|
['Error', ERROR_CLASS.instance],
|
|
281
|
-
...['Date', 'RegExp', 'Map', 'Set', 'URL', ...BUFFERS].map((name) => [name, ANY]),
|
|
285
|
+
...['Date', 'RegExp', 'Map', 'Set', 'URL', ...UNTYPED_CLASSES, ...BUFFERS].map((name) => [name, ANY]),
|
|
282
286
|
['Promise', PROMISE],
|
|
283
287
|
...DOM_TYPES,
|
|
284
288
|
...SVG_TYPES,
|
package/dist/checker/channels.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { assignedNames } from "../names.js";
|
|
2
1
|
import { cannotWait } from "../parser/syntax.js";
|
|
3
2
|
import { chanDirection, chanOf, elementOf, isChan } from "./channel.js";
|
|
4
3
|
import { GoroutineChecker } from "./goroutines.js";
|
|
@@ -115,7 +114,7 @@ export class ChannelChecker extends GoroutineChecker {
|
|
|
115
114
|
: ANY;
|
|
116
115
|
}
|
|
117
116
|
checkSelect(node) {
|
|
118
|
-
this.dropNarrowing(
|
|
117
|
+
this.dropNarrowing(node);
|
|
119
118
|
const entry = this.flow;
|
|
120
119
|
for (const { names, operation, body } of node.cases) {
|
|
121
120
|
this.flow = new Map(entry);
|
package/dist/checker/classes.js
CHANGED
|
@@ -195,6 +195,7 @@ export class ClassChecker extends ClassMemberChecker {
|
|
|
195
195
|
types: new Map(),
|
|
196
196
|
decorators: new Map(),
|
|
197
197
|
components: new Map(),
|
|
198
|
+
shared: new Set(),
|
|
198
199
|
};
|
|
199
200
|
const declarations = { values: new Map(), types: new Map() };
|
|
200
201
|
const valueOf = (name) => this.moduleScope.values.get(name)?.type ?? UNKNOWN;
|
|
@@ -217,6 +218,8 @@ export class ClassChecker extends ClassMemberChecker {
|
|
|
217
218
|
case 'VariableDeclaration':
|
|
218
219
|
for (const name of statement.names) {
|
|
219
220
|
add(statement.exported, 'values', name.name, valueOf(name.name));
|
|
221
|
+
if (statement.exported && statement.keyword === 'state')
|
|
222
|
+
exports.shared.add(name.name);
|
|
220
223
|
}
|
|
221
224
|
break;
|
|
222
225
|
case 'ClassDeclaration':
|
|
@@ -230,7 +233,10 @@ export class ClassChecker extends ClassMemberChecker {
|
|
|
230
233
|
case 'ComponentDeclaration': {
|
|
231
234
|
const info = this.moduleScope.values.get(statement.name.name)?.component;
|
|
232
235
|
if (statement.exported && info?.node === statement) {
|
|
233
|
-
|
|
236
|
+
const found = this.modulePackages().components.get(info.node.name.name) ?? null;
|
|
237
|
+
// Code that reads state another module shares listens to it here, so it stays here.
|
|
238
|
+
const shared = found?.moduleNames.some((name) => this.moduleScope.values.get(name)?.shared !== undefined);
|
|
239
|
+
info.inline = shared ? null : found;
|
|
234
240
|
info.expressionTypes = this.checkedTypes;
|
|
235
241
|
exports.components.set(statement.name.name, info);
|
|
236
242
|
}
|
package/dist/checker/closing.js
CHANGED
|
@@ -4,7 +4,7 @@ import { IteratorChecker } from "./iterators.js";
|
|
|
4
4
|
const OPERATIONS = new Set(['send', 'receive', 'range', 'stream', 'publish', 'listen', 'try']);
|
|
5
5
|
const MESSAGES = {
|
|
6
6
|
chan: (name) => `"${name}" is never closed: close it when nothing more is sent, e.g. close(${name})`,
|
|
7
|
-
topic: (name) => `"${name}" is never closed, and a topic listens until it is closed: close it when it is no longer needed, e.g. close(${name})
|
|
7
|
+
topic: (name) => `"${name}" is never closed, and a topic listens until it is closed: close it when it is no longer needed, e.g. close(${name}); a component closes it with defer close(${name}) in mount`,
|
|
8
8
|
tick: (name) => `"${name}" is never closed, and a tick runs until it is closed: e.g. close(${name})`,
|
|
9
9
|
signal: (name) => `"${name}" is never closed, and it keeps the program running until it is: close it once the signal has come, e.g. close(${name})`,
|
|
10
10
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { ClosingChecker } from './closing.ts';
|
|
3
|
-
import type
|
|
3
|
+
import { type CompletionResult } from './context.ts';
|
|
4
4
|
import { type Type } from './types.ts';
|
|
5
5
|
export declare abstract class CompletionChecker extends ClosingChecker {
|
|
6
6
|
protected completion: CompletionResult | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ClosingChecker } from "./closing.js";
|
|
2
2
|
import { ARRAY_MEMBERS, ATTRIBUTE_NAMES, BIGINT_MEMBERS, BOOL_MEMBERS, EVENT_ATTRIBUTES, HTML_TAGS, NUMBER_MEMBERS, STRING_MEMBERS, } from "./completion-names.js";
|
|
3
|
+
import { placeOf, } from "./context.js";
|
|
3
4
|
import { isSubclass, literalBase, memberNames, memberTypeOf, nonNull, typeToString, union, UNKNOWN, } from "./types.js";
|
|
4
5
|
// Completion in editors (src/completion.ts): the editor writes the name `options.completion` where
|
|
5
6
|
// the cursor is, and this layer lists what can be written there when the check reaches that name:
|
|
@@ -79,7 +80,7 @@ export class CompletionChecker extends ClosingChecker {
|
|
|
79
80
|
if (component) {
|
|
80
81
|
return { name, kind: 'component', detail: typeToString(this.componentValue(component)) };
|
|
81
82
|
}
|
|
82
|
-
const type = binding.type && (this.flow.get(binding) ?? binding.type);
|
|
83
|
+
const type = binding.type && (this.flow.get(placeOf(binding)) ?? binding.type);
|
|
83
84
|
const detail = type ? { detail: typeToString(type) } : {};
|
|
84
85
|
if (kind === 'function' || type?.kind === 'function')
|
|
85
86
|
return { name, kind: 'function', ...detail };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import type { ComponentInfo } from './context.ts';
|
|
3
|
+
import { ForwardingChecker } from './forwarding.ts';
|
|
4
|
+
export declare abstract class ComponentBindingChecker extends ForwardingChecker {
|
|
5
|
+
/** `bind:active={tab}` on a component; adds the name of the property to `given`. */
|
|
6
|
+
protected checkPropBinding(attribute: ast.JsxAttribute, info: ComponentInfo, component: string, given: Set<string>): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { attrsParam } from "../attrs.js";
|
|
2
|
+
import { ForwardingChecker } from "./forwarding.js";
|
|
3
|
+
import { isUntyped } from "./helpers.js";
|
|
4
|
+
import { isAssignable, typeToString } from "./types.js";
|
|
5
|
+
// Bound properties: `comp Tabs(bind active string)` may change `active`, and
|
|
6
|
+
// `<Tabs bind:active={tab} />` writes those changes into the parent's `tab`, as `bind:value` of an
|
|
7
|
+
// input does. The value goes both ways, so the variable and the property have the same type.
|
|
8
|
+
export class ComponentBindingChecker extends ForwardingChecker {
|
|
9
|
+
/** `bind:active={tab}` on a component; adds the name of the property to `given`. */
|
|
10
|
+
checkPropBinding(attribute, info, component, given) {
|
|
11
|
+
const name = attribute.name.name.slice('bind:'.length);
|
|
12
|
+
const prop = info.props.get(name);
|
|
13
|
+
const { value } = attribute;
|
|
14
|
+
if (given.has(name))
|
|
15
|
+
this.error(`duplicate property "${name}"`, attribute.name);
|
|
16
|
+
given.add(name);
|
|
17
|
+
if (!prop) {
|
|
18
|
+
this.error(attrsParam(info.node)
|
|
19
|
+
? `${attribute.name.name} does not go through the attributes of <${component}>`
|
|
20
|
+
: `<${component}> has no property "${name}" to bind`, attribute.name);
|
|
21
|
+
}
|
|
22
|
+
else if (!prop.bind) {
|
|
23
|
+
this.error(`"${name}" of <${component}> is not bound: the component declares it with bind to change it, e.g. comp ${component}(bind ${name} ...)`, attribute.name);
|
|
24
|
+
}
|
|
25
|
+
if (value === null || value.kind === 'StringLiteral' || value.kind === 'EventHandler') {
|
|
26
|
+
this.error(`"bind:${name}" needs a variable in braces, e.g. bind:${name}={${name}}`, attribute);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const bindable = (value.kind === 'Identifier' && value.name !== '_') ||
|
|
30
|
+
((value.kind === 'MemberExpression' || value.kind === 'IndexExpression') && !value.optional);
|
|
31
|
+
if (!bindable) {
|
|
32
|
+
this.checkValue(value);
|
|
33
|
+
this.error(`"bind:${name}" needs a variable or a field to write to`, value);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const type = this.checkTarget(value);
|
|
37
|
+
this.checkedTypes.set(value, type);
|
|
38
|
+
if (!prop?.bind || isUntyped(type) || isUntyped(prop.type))
|
|
39
|
+
return;
|
|
40
|
+
// The parent gives its value, and the component writes its own back.
|
|
41
|
+
if (!isAssignable(type, prop.type) || !isAssignable(prop.type, type)) {
|
|
42
|
+
this.error(`cannot bind ${typeToString(type)} to "${name}" of <${component}>, which is ${typeToString(prop.type)}`, value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|