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
|
@@ -69,13 +69,39 @@ export class ControlFlowParser extends StatementParser {
|
|
|
69
69
|
}
|
|
70
70
|
this.expect('in');
|
|
71
71
|
const iterable = this.parseHeaderExpression();
|
|
72
|
+
const withKey = this.parseWithKey(go);
|
|
73
|
+
const keyed = withKey ? { withKey } : {};
|
|
72
74
|
if (!go) {
|
|
73
75
|
const body = this.parseLoopBody();
|
|
74
|
-
return { kind: 'ForInStatement', key, value, iterable, body, ...this.span(start) };
|
|
76
|
+
return { kind: 'ForInStatement', key, value, iterable, ...keyed, body, ...this.span(start) };
|
|
75
77
|
}
|
|
76
78
|
// The parts run at once in workers: nothing can leave the loop but `continue`.
|
|
77
79
|
const body = this.withContext({ inLoop: true, inBreakable: false, inGoFor: true, labels: [] }, () => this.parseBlock());
|
|
78
|
-
return {
|
|
80
|
+
return {
|
|
81
|
+
kind: 'ForInStatement',
|
|
82
|
+
key,
|
|
83
|
+
value,
|
|
84
|
+
iterable,
|
|
85
|
+
...keyed,
|
|
86
|
+
body,
|
|
87
|
+
go,
|
|
88
|
+
...this.span(start),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* `with key todo.id` after the items of a `{for}` in markup: its blocks are found again by the
|
|
93
|
+
* key instead of the item. `with` and `key` are words only here, not keywords.
|
|
94
|
+
*/
|
|
95
|
+
parseWithKey(go) {
|
|
96
|
+
if (!this.checkWord('with'))
|
|
97
|
+
return null;
|
|
98
|
+
const start = this.next().start;
|
|
99
|
+
this.expectWord('key');
|
|
100
|
+
const key = this.parseHeaderExpression();
|
|
101
|
+
if (go || !this.context.inMarkup) {
|
|
102
|
+
this.error('"with key" finds again the blocks of a {for} in markup, and this loop has none: remove it', start, key.end);
|
|
103
|
+
}
|
|
104
|
+
return key;
|
|
79
105
|
}
|
|
80
106
|
/** `go for i, v in items { }`: the parts of the array run as goroutines, and the loop waits. */
|
|
81
107
|
parseGoFor() {
|
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import {
|
|
3
|
-
export declare abstract class DeclarationParser extends
|
|
2
|
+
import { ParamParser } from './params.ts';
|
|
3
|
+
export declare abstract class DeclarationParser extends ParamParser {
|
|
4
4
|
protected parseImport(): ast.ImportDeclaration;
|
|
5
5
|
protected parseImportSpecifier(): ast.ImportSpecifier;
|
|
6
|
+
/** `state items []Item = []` at the top level of a module: state its components share. */
|
|
7
|
+
protected parseDeclaration(start: number, exported: boolean): ast.Statement | undefined;
|
|
6
8
|
protected parseExport(): ast.Statement;
|
|
7
9
|
protected parseFuncDeclaration(start: number, exported: boolean): ast.FuncDeclaration;
|
|
8
10
|
/**
|
|
9
11
|
* `<T any, U Shape>` after a name; `<K, V any>` gives both the constraint, as in Go.
|
|
10
12
|
*/
|
|
11
13
|
protected parseTypeParams(): ast.TypeParameter[];
|
|
12
|
-
/**
|
|
13
|
-
* `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
|
|
14
|
-
* Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
|
|
15
|
-
* properties: `(private name string)`.
|
|
16
|
-
*/
|
|
17
|
-
protected parseParams(typesOptional: boolean, allowDefaults?: boolean, allowRest?: boolean, allowProperties?: boolean): ast.Parameter[];
|
|
18
|
-
/** `private`, `readonly` or both before a parameter that is a property of its class. */
|
|
19
|
-
private parsePropertyModifiers;
|
|
20
|
-
/** `...attrs`: the attributes that a component passes on to one of its elements. */
|
|
21
|
-
private parseRestParam;
|
|
22
|
-
/** Result types after a parameter list: nothing, `T`, or `(T1, T2)`. */
|
|
23
|
-
protected parseResults(): ast.TypeNode[];
|
|
24
14
|
/** `comp Name(properties) { ...; return <markup> }`, maybe after decorators. */
|
|
25
15
|
protected parseComponent(start: number, exported: boolean, htmlTag?: ast.HtmlTag | null, decorators?: ast.DecoratorUse[]): ast.ComponentDeclaration;
|
|
26
|
-
/** Like a function body, but its own statements may also declare `state`, `ref` and `mount
|
|
16
|
+
/** Like a function body, but its own statements may also declare `state`, `ref` and `mount`. */
|
|
27
17
|
protected parseComponentBody(): ast.BlockStatement;
|
|
28
18
|
/** A statement at the top level of a component or a decorator. */
|
|
29
19
|
protected parseComponentStatement(): ast.Statement;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { describe, FUNCTION_BODY
|
|
3
|
-
// Imports and exports, functions and components
|
|
4
|
-
export class DeclarationParser extends
|
|
1
|
+
import { ParamParser } from "./params.js";
|
|
2
|
+
import { describe, FUNCTION_BODY } from "./syntax.js";
|
|
3
|
+
// Imports and exports, functions and components.
|
|
4
|
+
export class DeclarationParser extends ParamParser {
|
|
5
5
|
parseImport() {
|
|
6
6
|
const start = this.next().start;
|
|
7
7
|
if (!this.context.topLevel) {
|
|
@@ -50,6 +50,16 @@ export class DeclarationParser extends JumpParser {
|
|
|
50
50
|
}
|
|
51
51
|
return { kind: 'ImportSpecifier', imported, local, ...this.span(token.start) };
|
|
52
52
|
}
|
|
53
|
+
/** `state items []Item = []` at the top level of a module: state its components share. */
|
|
54
|
+
parseDeclaration(start, exported) {
|
|
55
|
+
if (!this.context.topLevel || !this.checkWord('state') || this.peek(1).kind !== 'Identifier') {
|
|
56
|
+
return super.parseDeclaration(start, exported);
|
|
57
|
+
}
|
|
58
|
+
this.requireMarkup('state of a module is');
|
|
59
|
+
const declaration = this.parseVariableDeclaration(start, exported);
|
|
60
|
+
this.endStatement();
|
|
61
|
+
return declaration;
|
|
62
|
+
}
|
|
53
63
|
parseExport() {
|
|
54
64
|
const start = this.next().start;
|
|
55
65
|
if (!this.context.topLevel) {
|
|
@@ -109,120 +119,6 @@ export class DeclarationParser extends JumpParser {
|
|
|
109
119
|
this.expectTypeClose('">" after the type parameters');
|
|
110
120
|
return params;
|
|
111
121
|
}
|
|
112
|
-
/**
|
|
113
|
-
* `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
|
|
114
|
-
* Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
|
|
115
|
-
* properties: `(private name string)`.
|
|
116
|
-
*/
|
|
117
|
-
parseParams(typesOptional, allowDefaults = false, allowRest = false, allowProperties = false) {
|
|
118
|
-
this.expect('(');
|
|
119
|
-
const params = [];
|
|
120
|
-
let untyped = 0;
|
|
121
|
-
while (!this.check(')')) {
|
|
122
|
-
if (this.check('...')) {
|
|
123
|
-
params.push(this.parseRestParam(allowRest));
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
const paramStart = this.peek().start;
|
|
127
|
-
const property = this.parsePropertyModifiers(allowProperties);
|
|
128
|
-
const name = this.parseIdentifier('parameter name');
|
|
129
|
-
const type = this.canStartType() ? this.parseType() : null;
|
|
130
|
-
const defaultValue = allowDefaults && this.accept('=') ? this.parseExpression() : null;
|
|
131
|
-
params.push({
|
|
132
|
-
kind: 'Parameter',
|
|
133
|
-
name,
|
|
134
|
-
type,
|
|
135
|
-
defaultValue,
|
|
136
|
-
...(property ? { property } : {}),
|
|
137
|
-
...this.span(paramStart),
|
|
138
|
-
});
|
|
139
|
-
if (type === null) {
|
|
140
|
-
untyped++;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
for (const param of params.slice(params.length - 1 - untyped))
|
|
144
|
-
param.type = type;
|
|
145
|
-
untyped = 0;
|
|
146
|
-
}
|
|
147
|
-
if (!this.accept(','))
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
this.expect(')');
|
|
151
|
-
if (untyped > 0 && !typesOptional) {
|
|
152
|
-
const param = params[params.length - untyped];
|
|
153
|
-
this.error(`missing type for parameter "${param.name.name}"`, param.start, param.end);
|
|
154
|
-
}
|
|
155
|
-
return params;
|
|
156
|
-
}
|
|
157
|
-
/** `private`, `readonly` or both before a parameter that is a property of its class. */
|
|
158
|
-
parsePropertyModifiers(allowed) {
|
|
159
|
-
let visibility = null;
|
|
160
|
-
let readonly = false;
|
|
161
|
-
const start = this.peek().start;
|
|
162
|
-
// Elsewhere `readonly number` is a parameter named readonly, and `private x number` an error.
|
|
163
|
-
while (this.check('Identifier') &&
|
|
164
|
-
(VISIBILITIES.has(this.peek().text) || this.peek().text === 'readonly') &&
|
|
165
|
-
this.peek(1).kind === 'Identifier' &&
|
|
166
|
-
(allowed || this.peek(2).kind === 'Identifier')) {
|
|
167
|
-
const modifier = this.next();
|
|
168
|
-
if (modifier.text === 'readonly') {
|
|
169
|
-
if (readonly)
|
|
170
|
-
this.error('duplicate "readonly" modifier', modifier.start, modifier.end);
|
|
171
|
-
readonly = true;
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
if (visibility)
|
|
175
|
-
this.error('duplicate visibility modifier', modifier.start, modifier.end);
|
|
176
|
-
visibility = modifier.text;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (visibility === null && !readonly)
|
|
180
|
-
return null;
|
|
181
|
-
if (!allowed) {
|
|
182
|
-
this.error('only the parameters of a constructor can be properties, e.g. constructor(private name string)', start, this.span(start).end);
|
|
183
|
-
}
|
|
184
|
-
return { visibility: visibility ?? 'public', ...(readonly ? { readonly: true } : {}) };
|
|
185
|
-
}
|
|
186
|
-
/** `...attrs`: the attributes that a component passes on to one of its elements. */
|
|
187
|
-
parseRestParam(allowed) {
|
|
188
|
-
const start = this.next().start;
|
|
189
|
-
const name = this.parseIdentifier('parameter name');
|
|
190
|
-
if (!allowed) {
|
|
191
|
-
this.error(`...${name.name} is only for components: it takes the attributes of an element`, start, name.end);
|
|
192
|
-
}
|
|
193
|
-
if (this.canStartType() || this.check('=')) {
|
|
194
|
-
const at = this.peek().start;
|
|
195
|
-
// After the error, the type and the default are read, so they are not reported again.
|
|
196
|
-
if (this.canStartType())
|
|
197
|
-
this.parseType();
|
|
198
|
-
if (this.accept('='))
|
|
199
|
-
this.parseExpression();
|
|
200
|
-
this.error(`...${name.name} has no type and no default: it gets the type of the element it is spread on`, at, this.span(at).end);
|
|
201
|
-
}
|
|
202
|
-
// After the error, the rest of the list is skipped up to the `)`.
|
|
203
|
-
if (this.check(',')) {
|
|
204
|
-
this.error(`...${name.name} is the last property of a component`, start, name.end);
|
|
205
|
-
while (!this.check(')') && !this.check('EOF'))
|
|
206
|
-
this.next();
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
kind: 'Parameter',
|
|
210
|
-
name,
|
|
211
|
-
type: null,
|
|
212
|
-
defaultValue: null,
|
|
213
|
-
rest: true,
|
|
214
|
-
...this.span(start),
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
/** Result types after a parameter list: nothing, `T`, or `(T1, T2)`. */
|
|
218
|
-
parseResults() {
|
|
219
|
-
if (this.accept('('))
|
|
220
|
-
return this.parseList(')', () => this.parseType());
|
|
221
|
-
// `{` after the parameters starts the body, so an object result type needs parentheses.
|
|
222
|
-
if (this.canStartType() && !this.check('{'))
|
|
223
|
-
return [this.parseType()];
|
|
224
|
-
return [];
|
|
225
|
-
}
|
|
226
122
|
/** `comp Name(properties) { ...; return <markup> }`, maybe after decorators. */
|
|
227
123
|
parseComponent(start, exported, htmlTag = null, decorators = []) {
|
|
228
124
|
this.requireMarkup('components are');
|
|
@@ -248,7 +144,7 @@ export class DeclarationParser extends JumpParser {
|
|
|
248
144
|
...this.span(start),
|
|
249
145
|
};
|
|
250
146
|
}
|
|
251
|
-
/** Like a function body, but its own statements may also declare `state`, `ref` and `mount
|
|
147
|
+
/** Like a function body, but its own statements may also declare `state`, `ref` and `mount`. */
|
|
252
148
|
parseComponentBody() {
|
|
253
149
|
const start = this.expect('{').start;
|
|
254
150
|
const body = [];
|
|
@@ -6,6 +6,10 @@ export declare abstract class DecoratorParser extends DeclarationParser {
|
|
|
6
6
|
protected parseDecoratorDeclaration(start: number, exported: boolean): ast.DecoratorDeclaration;
|
|
7
7
|
/** `needs cart, auth` after the parameters: `needs` is a keyword only here. */
|
|
8
8
|
protected parseNeeds(): ast.Identifier[];
|
|
9
|
+
/** `includes visible(margin), cart` after `needs`: `includes` is a keyword only here. */
|
|
10
|
+
protected parseIncludes(): ast.DecoratorUse[];
|
|
11
|
+
/** `visible(margin)` or `cart`: like a decorator before `comp`, without the `@`. */
|
|
12
|
+
protected parseIncluded(): ast.DecoratorUse;
|
|
9
13
|
/** Like the body of a component, but `state`, `const` and `func` may be `public`. */
|
|
10
14
|
protected parseDecoratorBody(): ast.BlockStatement;
|
|
11
15
|
/** `public` followed by a declaration; `public = 1` assigns a variable named `public`. */
|
|
@@ -24,7 +28,7 @@ export declare abstract class DecoratorParser extends DeclarationParser {
|
|
|
24
28
|
/** `@visible.show`: a member of a decorator; a statement may start with it. */
|
|
25
29
|
protected isDecoratorMember(): boolean;
|
|
26
30
|
protected parseDecoratorMember(): ast.DecoratorMember;
|
|
27
|
-
/** `get(@visible.shown)
|
|
31
|
+
/** `get(@visible.shown)`, the old way to read a member: reported, and read as the member. */
|
|
28
32
|
protected isDecoratorGet(): boolean;
|
|
29
|
-
protected parseDecoratorGet(): ast.
|
|
33
|
+
protected parseDecoratorGet(): ast.DecoratorMember;
|
|
30
34
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DeclarationParser } from "./declarations.js";
|
|
2
2
|
import { describe, FUNCTION_BODY } from "./syntax.js";
|
|
3
3
|
// Decorators: their declarations `dec name(...) { ... }`, the decorators written before `comp`
|
|
4
|
-
// (`@html-tag` and `@visible("300px")`), and the access to their members,
|
|
5
|
-
//
|
|
4
|
+
// (`@html-tag` and `@visible("300px")`), and the access to their members, `@visible.shown` and
|
|
5
|
+
// `@visible.show()`.
|
|
6
6
|
export class DecoratorParser extends DeclarationParser {
|
|
7
7
|
/** `dec visible(margin string = "0px") { ... }`: `dec` is a keyword only here. */
|
|
8
8
|
isDecoratorDeclaration() {
|
|
@@ -17,6 +17,7 @@ export class DecoratorParser extends DeclarationParser {
|
|
|
17
17
|
const name = this.parseIdentifier('decorator name');
|
|
18
18
|
const params = this.parseParams(false, true);
|
|
19
19
|
const needs = this.parseNeeds();
|
|
20
|
+
const includes = this.parseIncludes();
|
|
20
21
|
const body = this.withContext({ ...FUNCTION_BODY, inComponent: true }, () => this.parseDecoratorBody());
|
|
21
22
|
return {
|
|
22
23
|
kind: 'DecoratorDeclaration',
|
|
@@ -24,6 +25,7 @@ export class DecoratorParser extends DeclarationParser {
|
|
|
24
25
|
name,
|
|
25
26
|
params,
|
|
26
27
|
needs,
|
|
28
|
+
includes,
|
|
27
29
|
body,
|
|
28
30
|
...this.span(start),
|
|
29
31
|
};
|
|
@@ -41,6 +43,34 @@ export class DecoratorParser extends DeclarationParser {
|
|
|
41
43
|
needs.push(this.parseIdentifier('decorator name'));
|
|
42
44
|
return needs;
|
|
43
45
|
}
|
|
46
|
+
/** `includes visible(margin), cart` after `needs`: `includes` is a keyword only here. */
|
|
47
|
+
parseIncludes() {
|
|
48
|
+
if (!this.checkWord('includes'))
|
|
49
|
+
return [];
|
|
50
|
+
if (this.peek(1).kind !== 'Identifier') {
|
|
51
|
+
this.fail('"includes" is followed by the decorators this one applies, e.g. includes cart');
|
|
52
|
+
}
|
|
53
|
+
this.next();
|
|
54
|
+
const includes = [this.parseIncluded()];
|
|
55
|
+
while (this.accept(','))
|
|
56
|
+
includes.push(this.parseIncluded());
|
|
57
|
+
return includes;
|
|
58
|
+
}
|
|
59
|
+
/** `visible(margin)` or `cart`: like a decorator before `comp`, without the `@`. */
|
|
60
|
+
parseIncluded() {
|
|
61
|
+
const name = this.parseIdentifier('decorator name');
|
|
62
|
+
const args = this.check('(') ? this.parseArguments() : [];
|
|
63
|
+
const spread = args.find((arg) => arg.kind === 'SpreadElement');
|
|
64
|
+
if (spread) {
|
|
65
|
+
this.error('arguments of a decorator cannot be spread', spread.start, spread.end);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
kind: 'DecoratorUse',
|
|
69
|
+
name,
|
|
70
|
+
arguments: args.filter((arg) => arg.kind !== 'SpreadElement'),
|
|
71
|
+
...this.span(name.start),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
44
74
|
/** Like the body of a component, but `state`, `const` and `func` may be `public`. */
|
|
45
75
|
parseDecoratorBody() {
|
|
46
76
|
const start = this.expect('{').start;
|
|
@@ -71,8 +101,11 @@ export class DecoratorParser extends DeclarationParser {
|
|
|
71
101
|
declaration.isPublic = true;
|
|
72
102
|
return declaration;
|
|
73
103
|
}
|
|
74
|
-
if (!this.check('const') &&
|
|
75
|
-
this.
|
|
104
|
+
if (!this.check('const') &&
|
|
105
|
+
!this.check('let') &&
|
|
106
|
+
!this.isStateDeclaration() &&
|
|
107
|
+
!this.isRefDeclaration()) {
|
|
108
|
+
this.fail(`only state, derived, const, ref and func can be public, not ${describe(this.peek())}`);
|
|
76
109
|
}
|
|
77
110
|
const declaration = this.parseVariableDeclaration(modifier.start, false);
|
|
78
111
|
this.endStatement();
|
|
@@ -137,18 +170,7 @@ export class DecoratorParser extends DeclarationParser {
|
|
|
137
170
|
/** `@visible("300px")` or `@visible`: the arguments are positional, as in a call. */
|
|
138
171
|
parseDecoratorUse() {
|
|
139
172
|
const at = this.expect('@');
|
|
140
|
-
|
|
141
|
-
const args = this.check('(') ? this.parseArguments() : [];
|
|
142
|
-
const spread = args.find((arg) => arg.kind === 'SpreadElement');
|
|
143
|
-
if (spread) {
|
|
144
|
-
this.error('arguments of a decorator cannot be spread', spread.start, spread.end);
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
kind: 'DecoratorUse',
|
|
148
|
-
name,
|
|
149
|
-
arguments: args.filter((arg) => arg.kind !== 'SpreadElement'),
|
|
150
|
-
...this.span(at.start),
|
|
151
|
-
};
|
|
173
|
+
return { ...this.parseIncluded(), ...this.span(at.start) };
|
|
152
174
|
}
|
|
153
175
|
/** `@visible.show`: a member of a decorator; a statement may start with it. */
|
|
154
176
|
isDecoratorMember() {
|
|
@@ -162,7 +184,7 @@ export class DecoratorParser extends DeclarationParser {
|
|
|
162
184
|
const member = this.parsePropertyName('member name');
|
|
163
185
|
return { kind: 'DecoratorMember', decorator, member, ...this.span(at.start) };
|
|
164
186
|
}
|
|
165
|
-
/** `get(@visible.shown)
|
|
187
|
+
/** `get(@visible.shown)`, the old way to read a member: reported, and read as the member. */
|
|
166
188
|
isDecoratorGet() {
|
|
167
189
|
return this.checkWord('get') && this.peek(1).kind === '(' && this.peek(2).kind === '@';
|
|
168
190
|
}
|
|
@@ -170,7 +192,9 @@ export class DecoratorParser extends DeclarationParser {
|
|
|
170
192
|
const start = this.next().start;
|
|
171
193
|
this.expect('(');
|
|
172
194
|
const target = this.parseDecoratorMember();
|
|
173
|
-
|
|
174
|
-
|
|
195
|
+
const { decorator, member } = target;
|
|
196
|
+
this.expect(')');
|
|
197
|
+
this.error(`read a member of a decorator without get: @${decorator.name}.${member.name}`, start, this.span(start).end);
|
|
198
|
+
return target;
|
|
175
199
|
}
|
|
176
200
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { JumpParser } from './jumps.ts';
|
|
3
|
+
export declare abstract class ParamParser extends JumpParser {
|
|
4
|
+
/**
|
|
5
|
+
* `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
|
|
6
|
+
* Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
|
|
7
|
+
* properties: `(private name string)`.
|
|
8
|
+
*/
|
|
9
|
+
protected parseParams(typesOptional: boolean, allowDefaults?: boolean, allowRest?: boolean, allowProperties?: boolean): ast.Parameter[];
|
|
10
|
+
/** `private`, `readonly` or both before a parameter that is a property of its class. */
|
|
11
|
+
private parsePropertyModifiers;
|
|
12
|
+
/**
|
|
13
|
+
* `bind` before a property of a component, `bind active string`. Followed by a name and then
|
|
14
|
+
* not by `,` or `)`, so a parameter named bind, `(bind Mode)`, stays one.
|
|
15
|
+
*/
|
|
16
|
+
private parseBindModifier;
|
|
17
|
+
/** `...attrs`: the attributes that a component passes on to one of its elements. */
|
|
18
|
+
private parseRestParam;
|
|
19
|
+
/** Result types after a parameter list: nothing, `T`, or `(T1, T2)`. */
|
|
20
|
+
protected parseResults(): ast.TypeNode[];
|
|
21
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { JumpParser } from "./jumps.js";
|
|
2
|
+
import { VISIBILITIES } from "./syntax.js";
|
|
3
|
+
// The parameters of functions, constructors and components, with their modifiers, and the results
|
|
4
|
+
// of functions.
|
|
5
|
+
export class ParamParser extends JumpParser {
|
|
6
|
+
/**
|
|
7
|
+
* `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
|
|
8
|
+
* Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
|
|
9
|
+
* properties: `(private name string)`.
|
|
10
|
+
*/
|
|
11
|
+
parseParams(typesOptional, allowDefaults = false, allowRest = false, allowProperties = false) {
|
|
12
|
+
this.expect('(');
|
|
13
|
+
const params = [];
|
|
14
|
+
let untyped = 0;
|
|
15
|
+
while (!this.check(')')) {
|
|
16
|
+
if (this.check('...')) {
|
|
17
|
+
params.push(this.parseRestParam(allowRest));
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
const paramStart = this.peek().start;
|
|
21
|
+
const property = this.parsePropertyModifiers(allowProperties);
|
|
22
|
+
const bind = this.parseBindModifier(allowRest);
|
|
23
|
+
const name = this.parseIdentifier('parameter name');
|
|
24
|
+
const type = this.canStartType() ? this.parseType() : null;
|
|
25
|
+
const defaultValue = allowDefaults && this.accept('=') ? this.parseExpression() : null;
|
|
26
|
+
if (bind && name.name === 'children') {
|
|
27
|
+
this.error('children cannot be bound: it is the content between the tags', paramStart, name.end);
|
|
28
|
+
}
|
|
29
|
+
params.push({
|
|
30
|
+
kind: 'Parameter',
|
|
31
|
+
name,
|
|
32
|
+
type,
|
|
33
|
+
defaultValue,
|
|
34
|
+
...(property ? { property } : {}),
|
|
35
|
+
...(bind ? { bind: true } : {}),
|
|
36
|
+
...this.span(paramStart),
|
|
37
|
+
});
|
|
38
|
+
if (type === null) {
|
|
39
|
+
untyped++;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
for (const param of params.slice(params.length - 1 - untyped))
|
|
43
|
+
param.type = type;
|
|
44
|
+
untyped = 0;
|
|
45
|
+
}
|
|
46
|
+
if (!this.accept(','))
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
this.expect(')');
|
|
50
|
+
if (untyped > 0 && !typesOptional) {
|
|
51
|
+
const param = params[params.length - untyped];
|
|
52
|
+
this.error(`missing type for parameter "${param.name.name}"`, param.start, param.end);
|
|
53
|
+
}
|
|
54
|
+
return params;
|
|
55
|
+
}
|
|
56
|
+
/** `private`, `readonly` or both before a parameter that is a property of its class. */
|
|
57
|
+
parsePropertyModifiers(allowed) {
|
|
58
|
+
let visibility = null;
|
|
59
|
+
let readonly = false;
|
|
60
|
+
const start = this.peek().start;
|
|
61
|
+
// Elsewhere `readonly number` is a parameter named readonly, and `private x number` an error.
|
|
62
|
+
while (this.check('Identifier') &&
|
|
63
|
+
(VISIBILITIES.has(this.peek().text) || this.peek().text === 'readonly') &&
|
|
64
|
+
this.peek(1).kind === 'Identifier' &&
|
|
65
|
+
(allowed || this.peek(2).kind === 'Identifier')) {
|
|
66
|
+
const modifier = this.next();
|
|
67
|
+
if (modifier.text === 'readonly') {
|
|
68
|
+
if (readonly)
|
|
69
|
+
this.error('duplicate "readonly" modifier', modifier.start, modifier.end);
|
|
70
|
+
readonly = true;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
if (visibility)
|
|
74
|
+
this.error('duplicate visibility modifier', modifier.start, modifier.end);
|
|
75
|
+
visibility = modifier.text;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (visibility === null && !readonly)
|
|
79
|
+
return null;
|
|
80
|
+
if (!allowed) {
|
|
81
|
+
this.error('only the parameters of a constructor can be properties, e.g. constructor(private name string)', start, this.span(start).end);
|
|
82
|
+
}
|
|
83
|
+
return { visibility: visibility ?? 'public', ...(readonly ? { readonly: true } : {}) };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* `bind` before a property of a component, `bind active string`. Followed by a name and then
|
|
87
|
+
* not by `,` or `)`, so a parameter named bind, `(bind Mode)`, stays one.
|
|
88
|
+
*/
|
|
89
|
+
parseBindModifier(allowed) {
|
|
90
|
+
const after = this.peek(2).kind;
|
|
91
|
+
if (!this.checkWord('bind') ||
|
|
92
|
+
this.peek(1).kind !== 'Identifier' ||
|
|
93
|
+
after === ',' ||
|
|
94
|
+
after === ')') {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const modifier = this.next();
|
|
98
|
+
if (!allowed) {
|
|
99
|
+
this.error('only the properties of components can be bound, e.g. comp Tabs(bind active string)', modifier.start, modifier.end);
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
/** `...attrs`: the attributes that a component passes on to one of its elements. */
|
|
104
|
+
parseRestParam(allowed) {
|
|
105
|
+
const start = this.next().start;
|
|
106
|
+
const name = this.parseIdentifier('parameter name');
|
|
107
|
+
if (!allowed) {
|
|
108
|
+
this.error(`...${name.name} is only for components: it takes the attributes of an element`, start, name.end);
|
|
109
|
+
}
|
|
110
|
+
if (this.canStartType() || this.check('=')) {
|
|
111
|
+
const at = this.peek().start;
|
|
112
|
+
// After the error, the type and the default are read, so they are not reported again.
|
|
113
|
+
if (this.canStartType())
|
|
114
|
+
this.parseType();
|
|
115
|
+
if (this.accept('='))
|
|
116
|
+
this.parseExpression();
|
|
117
|
+
this.error(`...${name.name} has no type and no default: it gets the type of the element it is spread on`, at, this.span(at).end);
|
|
118
|
+
}
|
|
119
|
+
// After the error, the rest of the list is skipped up to the `)`.
|
|
120
|
+
if (this.check(',')) {
|
|
121
|
+
this.error(`...${name.name} is the last property of a component`, start, name.end);
|
|
122
|
+
while (!this.check(')') && !this.check('EOF'))
|
|
123
|
+
this.next();
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
kind: 'Parameter',
|
|
127
|
+
name,
|
|
128
|
+
type: null,
|
|
129
|
+
defaultValue: null,
|
|
130
|
+
rest: true,
|
|
131
|
+
...this.span(start),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Result types after a parameter list: nothing, `T`, or `(T1, T2)`. */
|
|
135
|
+
parseResults() {
|
|
136
|
+
if (this.accept('('))
|
|
137
|
+
return this.parseList(')', () => this.parseType());
|
|
138
|
+
// `{` after the parameters starts the body, so an object result type needs parentheses.
|
|
139
|
+
if (this.canStartType() && !this.check('{'))
|
|
140
|
+
return [this.parseType()];
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -18,5 +18,6 @@ export declare abstract class SimpleStatementParser extends ParserBase {
|
|
|
18
18
|
protected parseExpressionList(): ast.Expression[];
|
|
19
19
|
/** `ref input` is one name, and the markup gives its element. */
|
|
20
20
|
private checkRef;
|
|
21
|
+
private parseDerivedBlock;
|
|
21
22
|
protected parseVariableDeclaration(start: number, exported: boolean): ast.VariableDeclaration;
|
|
22
23
|
}
|
|
@@ -67,7 +67,6 @@ export class SimpleStatementParser extends ParserBase {
|
|
|
67
67
|
checkAssignable(target) {
|
|
68
68
|
// Members of decorators are read-only outside them: the checker explains it.
|
|
69
69
|
const assignable = target.kind === 'Identifier' ||
|
|
70
|
-
target.kind === 'DecoratorGet' ||
|
|
71
70
|
target.kind === 'DecoratorMember' ||
|
|
72
71
|
((target.kind === 'MemberExpression' || target.kind === 'IndexExpression') &&
|
|
73
72
|
!target.optional);
|
|
@@ -125,6 +124,17 @@ export class SimpleStatementParser extends ParserBase {
|
|
|
125
124
|
this.error(`ref declares one name, e.g. ref ${name}`, start, end);
|
|
126
125
|
}
|
|
127
126
|
}
|
|
127
|
+
parseDerivedBlock() {
|
|
128
|
+
const body = this.parseFunctionBody();
|
|
129
|
+
return {
|
|
130
|
+
kind: 'FuncExpression',
|
|
131
|
+
params: [],
|
|
132
|
+
results: [],
|
|
133
|
+
body,
|
|
134
|
+
start: body.start,
|
|
135
|
+
end: body.end,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
128
138
|
parseVariableDeclaration(start, exported) {
|
|
129
139
|
const token = this.next();
|
|
130
140
|
const keyword = token.kind === 'const'
|
|
@@ -135,16 +145,21 @@ export class SimpleStatementParser extends ParserBase {
|
|
|
135
145
|
const names = [this.parseIdentifier('variable name')];
|
|
136
146
|
while (this.accept(','))
|
|
137
147
|
names.push(this.parseIdentifier('variable name'));
|
|
138
|
-
|
|
139
|
-
|
|
148
|
+
// `derived summary {` starts the block that computes it, as `{` after the parameters of a
|
|
149
|
+
// function starts its body: an object type goes in parentheses.
|
|
150
|
+
const block = () => keyword === 'derived' && this.check('{');
|
|
151
|
+
const type = !block() && this.canStartType() ? this.parseType() : null;
|
|
152
|
+
const body = block() ? this.parseDerivedBlock() : null;
|
|
153
|
+
const values = body === null && this.accept('=') ? this.parseExpressionList() : [];
|
|
140
154
|
const { end } = this.span(start);
|
|
141
155
|
if (keyword === 'ref') {
|
|
142
156
|
this.checkRef(names, values.length > 0, start, end);
|
|
143
157
|
}
|
|
144
|
-
else if (keyword === 'derived' &&
|
|
145
|
-
|
|
158
|
+
else if (keyword === 'derived' &&
|
|
159
|
+
(names.length !== 1 || values.length + Number(!!body) !== 1)) {
|
|
160
|
+
this.error('derived is one name and the expression it is computed from, e.g. derived double = count * 2, or a block that returns it: derived total number { ... return sum }', start, end);
|
|
146
161
|
}
|
|
147
|
-
else if (values.length === 0 && type === null) {
|
|
162
|
+
else if (values.length === 0 && type === null && body === null) {
|
|
148
163
|
this.error(`"${names[0].name}" needs a type or a value`, start, end);
|
|
149
164
|
}
|
|
150
165
|
else if (values.length === 0 && keyword === 'const') {
|
|
@@ -159,6 +174,7 @@ export class SimpleStatementParser extends ParserBase {
|
|
|
159
174
|
names,
|
|
160
175
|
type,
|
|
161
176
|
values,
|
|
177
|
+
body,
|
|
162
178
|
start,
|
|
163
179
|
end,
|
|
164
180
|
};
|
|
@@ -21,9 +21,12 @@ export declare abstract class StatementParser extends SimpleStatementParser {
|
|
|
21
21
|
protected parseStyle(): ast.StyleStatement;
|
|
22
22
|
/** `effect {`: like `state`, a keyword only at the start of a statement in a component. */
|
|
23
23
|
protected isEffectDeclaration(): boolean;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* `mount {`: like `state`, a keyword only at the start of a statement in a component. The old
|
|
26
|
+
* form `mount() {` is recognized too, so that parseMount can say how to write it now.
|
|
27
|
+
*/
|
|
25
28
|
protected isMountDeclaration(): boolean;
|
|
26
|
-
/** `mount
|
|
29
|
+
/** `mount { ... }`; its body is a function that runs later, so `return` is its own. */
|
|
27
30
|
protected parseMount(): ast.MountStatement;
|
|
28
31
|
/** `effect { ... }`: its body runs again later, so `return` and `defer` are its own. */
|
|
29
32
|
protected parseEffect(): ast.EffectStatement;
|