rooklang 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/README.ru.md +2 -0
- 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/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 +6 -1
- 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.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/inline-packages.d.ts +2 -1
- package/dist/inline-packages.js +9 -4
- 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/runtime/sources.js +3 -2
- package/dist/walk.js +3 -3
- package/package.json +2 -2
- package/src/runtime/decode.js +41 -10
- package/src/runtime/index.js +1 -0
- package/src/runtime/markup.js +27 -17
- package/src/runtime/shared.js +17 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type * as ast from './ast.ts';
|
|
2
|
+
import type { DecoratorSource } from './decorators.ts';
|
|
3
|
+
/** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
|
|
4
|
+
export declare function decoratedName(decorator: string, member: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* The public members that a decorator gives the components it is applied to: its own and those of
|
|
7
|
+
* the decorators it includes, each with whether it changes (state and derived values do).
|
|
8
|
+
*/
|
|
9
|
+
export declare function publicMembers(decorator: ast.DecoratorDeclaration, decorators: ReadonlyMap<string, DecoratorSource>, seen?: ReadonlySet<ast.DecoratorDeclaration>): Map<string, boolean>;
|
|
10
|
+
/**
|
|
11
|
+
* `@shop.count` of a member that `shop` gets from the `cart` it includes: a variable of `shop`
|
|
12
|
+
* that is that of `cart`, `derived` when it changes, so that what reads it is updated.
|
|
13
|
+
*/
|
|
14
|
+
export declare function aliases(included: DecoratorSource, use: ast.DecoratorUse, prefix: string, decorators: ReadonlyMap<string, DecoratorSource>, prefixes: ReadonlyMap<string, string> | undefined): ast.VariableDeclaration[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// The names of the members of decorators in the components they are applied to, and the members
|
|
2
|
+
// that a decorator gives with those of the decorators it includes.
|
|
3
|
+
/** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
|
|
4
|
+
export function decoratedName(decorator, member) {
|
|
5
|
+
return `$$${decorator}$${member}`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The public members that a decorator gives the components it is applied to: its own and those of
|
|
9
|
+
* the decorators it includes, each with whether it changes (state and derived values do).
|
|
10
|
+
*/
|
|
11
|
+
export function publicMembers(decorator, decorators, seen = new Set()) {
|
|
12
|
+
const members = new Map();
|
|
13
|
+
const inner = new Set([...seen, decorator]);
|
|
14
|
+
for (const use of decorator.includes) {
|
|
15
|
+
const included = decorators.get(use.name.name);
|
|
16
|
+
if (!included || inner.has(included.node))
|
|
17
|
+
continue;
|
|
18
|
+
for (const [name, changes] of publicMembers(included.node, decorators, inner)) {
|
|
19
|
+
members.set(name, changes);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (const statement of decorator.body.body) {
|
|
23
|
+
if (statement.kind === 'FuncDeclaration' && statement.isPublic) {
|
|
24
|
+
members.set(statement.name.name, false);
|
|
25
|
+
}
|
|
26
|
+
else if (statement.kind === 'VariableDeclaration' && statement.isPublic) {
|
|
27
|
+
const changes = statement.keyword === 'state' || statement.keyword === 'derived';
|
|
28
|
+
for (const name of statement.names)
|
|
29
|
+
members.set(name.name, changes);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return members;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `@shop.count` of a member that `shop` gets from the `cart` it includes: a variable of `shop`
|
|
36
|
+
* that is that of `cart`, `derived` when it changes, so that what reads it is updated.
|
|
37
|
+
*/
|
|
38
|
+
export function aliases(included, use, prefix, decorators, prefixes) {
|
|
39
|
+
const from = prefixes?.get(included.node.name.name) ?? included.node.name.name;
|
|
40
|
+
return [...publicMembers(included.node, decorators)].map(([member, changes]) => ({
|
|
41
|
+
kind: 'VariableDeclaration',
|
|
42
|
+
exported: false,
|
|
43
|
+
isPublic: false,
|
|
44
|
+
keyword: changes ? 'derived' : 'const',
|
|
45
|
+
names: [
|
|
46
|
+
{ kind: 'Identifier', name: decoratedName(prefix, member), start: use.start, end: use.end },
|
|
47
|
+
],
|
|
48
|
+
type: null,
|
|
49
|
+
values: [
|
|
50
|
+
{ kind: 'Identifier', name: decoratedName(from, member), start: use.start, end: use.end },
|
|
51
|
+
],
|
|
52
|
+
body: null,
|
|
53
|
+
start: use.start,
|
|
54
|
+
end: use.end,
|
|
55
|
+
}));
|
|
56
|
+
}
|
package/dist/decorators.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type * as ast from './ast.ts';
|
|
2
2
|
import { type CopyListener } from './decorator-copy.ts';
|
|
3
|
-
|
|
4
|
-
export declare function decoratedName(decorator: string, member: string): string;
|
|
3
|
+
export { decoratedName, publicMembers } from './decorator-names.ts';
|
|
5
4
|
/**
|
|
6
5
|
* Whether a declaration of an applied component gets an argument of a decorator. When the
|
|
7
6
|
* argument reads the component's properties, the declaration is a `state` that the properties
|
|
@@ -45,7 +44,7 @@ export declare function moduleNamesOf(decorator: ast.DecoratorDeclaration, progr
|
|
|
45
44
|
* The component with its decorators applied. Its body first declares the arguments of each
|
|
46
45
|
* decorator and runs its body, from the top decorator down, then runs the component's own body.
|
|
47
46
|
* The names of a decorator get its prefix, `$$visible$shown`, so they clash neither with each other
|
|
48
|
-
* nor with the component's names, and
|
|
47
|
+
* nor with the component's names, and `@visible.shown` reads that variable. The `mount` of
|
|
49
48
|
* the decorators runs after the component's own, from the inside out, so it goes before every
|
|
50
49
|
* `return` of the component's body.
|
|
51
50
|
*
|
package/dist/decorators.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { renamer } from "./decorator-copy.js";
|
|
2
|
+
import { aliases, decoratedName } from "./decorator-names.js";
|
|
2
3
|
import { declaredIn, freeNames, mentionsName, topLevelNames } from "./names.js";
|
|
3
4
|
import { containsReturn } from "./recursion.js";
|
|
4
5
|
import { forEachChild } from "./walk.js";
|
|
5
6
|
// Applying decorators: a component with decorators becomes one component whose body runs the
|
|
6
7
|
// bodies of its decorators first. The checker uses it to see what the inlined code uses, the
|
|
7
8
|
// generator to write it.
|
|
8
|
-
|
|
9
|
-
export function decoratedName(decorator, member) {
|
|
10
|
-
return `$$${decorator}$${member}`;
|
|
11
|
-
}
|
|
9
|
+
export { decoratedName, publicMembers } from "./decorator-names.js";
|
|
12
10
|
const argumentDeclarations = new WeakSet();
|
|
13
11
|
const wrappedContents = new WeakSet();
|
|
14
12
|
/**
|
|
@@ -69,7 +67,7 @@ export function moduleNamesOf(decorator, program) {
|
|
|
69
67
|
* The component with its decorators applied. Its body first declares the arguments of each
|
|
70
68
|
* decorator and runs its body, from the top decorator down, then runs the component's own body.
|
|
71
69
|
* The names of a decorator get its prefix, `$$visible$shown`, so they clash neither with each other
|
|
72
|
-
* nor with the component's names, and
|
|
70
|
+
* nor with the component's names, and `@visible.shown` reads that variable. The `mount` of
|
|
73
71
|
* the decorators runs after the component's own, from the inside out, so it goes before every
|
|
74
72
|
* `return` of the component's body.
|
|
75
73
|
*
|
|
@@ -83,37 +81,55 @@ export function moduleNamesOf(decorator, program) {
|
|
|
83
81
|
export function decorate(component, decorators, copied, prefixes) {
|
|
84
82
|
if (component.decorators.length === 0)
|
|
85
83
|
return component;
|
|
86
|
-
|
|
84
|
+
// Arguments that read the properties, or such arguments of a decorator that includes another,
|
|
85
|
+
// are state: they change with them.
|
|
86
|
+
const reactive = new Set(component.params.map((param) => param.name.name));
|
|
87
87
|
const setup = [];
|
|
88
88
|
const mounts = [];
|
|
89
89
|
const wrappers = [];
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
continue;
|
|
90
|
+
// `argument` copies an argument into the component: one of the component stays as it is, one of
|
|
91
|
+
// a decorator it includes is that decorator's code.
|
|
92
|
+
const apply = (source, args, argument, applying) => {
|
|
94
93
|
const decorator = source.node;
|
|
94
|
+
// A decorator that includes itself, through others too, is reported by the checker.
|
|
95
|
+
const inner = new Set([...applying, decorator]);
|
|
95
96
|
const prefix = prefixes?.get(decorator.name.name) ?? decorator.name.name;
|
|
96
97
|
const names = [...decoratorNames(decorator), ...source.moduleNames];
|
|
97
98
|
const renames = membersOf(prefix, names);
|
|
98
99
|
const copy = renamer(renames, copied, { decorators: prefixes, tags: source.tags });
|
|
99
100
|
decorator.params.forEach((param, i) => {
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
const given = args[i];
|
|
102
|
+
// A default value is the decorator's code.
|
|
103
|
+
const value = given
|
|
104
|
+
? argument(given)
|
|
105
|
+
: param.defaultValue && copy(param.defaultValue, new Set());
|
|
106
|
+
const name = renames.get(param.name.name);
|
|
107
|
+
const changes = value !== null && value !== undefined && mentionsName(value, reactive);
|
|
102
108
|
const declaration = {
|
|
103
109
|
kind: 'VariableDeclaration',
|
|
104
110
|
exported: false,
|
|
105
111
|
isPublic: false,
|
|
106
|
-
keyword:
|
|
107
|
-
names: [{ ...param.name, name
|
|
112
|
+
keyword: changes ? 'state' : 'const',
|
|
113
|
+
names: [{ ...param.name, name }],
|
|
108
114
|
type: param.type,
|
|
109
115
|
values: value ? [value] : [],
|
|
116
|
+
body: null,
|
|
110
117
|
start: param.start,
|
|
111
118
|
end: param.end,
|
|
112
119
|
};
|
|
120
|
+
if (changes)
|
|
121
|
+
reactive.add(name);
|
|
113
122
|
argumentDeclarations.add(declaration);
|
|
114
123
|
copied?.(declaration, param);
|
|
115
124
|
setup.push(declaration);
|
|
116
125
|
});
|
|
126
|
+
for (const use of decorator.includes) {
|
|
127
|
+
const included = decorators.get(use.name.name);
|
|
128
|
+
if (!included || inner.has(included.node))
|
|
129
|
+
continue;
|
|
130
|
+
apply(included, use.arguments, (value) => copy(value, new Set()), inner);
|
|
131
|
+
setup.push(...aliases(included, use, prefix, decorators, prefixes));
|
|
132
|
+
}
|
|
117
133
|
const wrapper = wrapperOf(decorator);
|
|
118
134
|
const own = [];
|
|
119
135
|
for (const statement of decorator.body.body) {
|
|
@@ -128,6 +144,11 @@ export function decorate(component, decorators, copied, prefixes) {
|
|
|
128
144
|
mounts.push(own);
|
|
129
145
|
if (wrapper)
|
|
130
146
|
wrappers.push(applyWrapper(source, wrapper, copied, prefix, prefixes));
|
|
147
|
+
};
|
|
148
|
+
for (const use of component.decorators) {
|
|
149
|
+
const source = decorators.get(use.name.name);
|
|
150
|
+
if (source)
|
|
151
|
+
apply(source, use.arguments, (value) => value, new Set());
|
|
131
152
|
}
|
|
132
153
|
const inside = mounts.reverse().flat();
|
|
133
154
|
const chain = wrappers.reverse();
|
|
@@ -186,6 +207,7 @@ function wrapped(node, chain) {
|
|
|
186
207
|
names: [{ ...wrapper.content }],
|
|
187
208
|
type: wrapper.type,
|
|
188
209
|
values: [value],
|
|
210
|
+
body: null,
|
|
189
211
|
start: node.start,
|
|
190
212
|
end: node.end,
|
|
191
213
|
};
|
|
@@ -27,6 +27,7 @@ export interface ModulePackages {
|
|
|
27
27
|
* recursive or a web component cannot be inlined, and neither can one that reaches components or
|
|
28
28
|
* decorators the module imports: those would bring the names of a third module. Nor can code that
|
|
29
29
|
* uses a component of the module as a value, which is the function in the module, or that assigns
|
|
30
|
-
* a variable of the module: another module could only read it.
|
|
30
|
+
* a variable of the module: another module could only read it. Nor can code that reads state of
|
|
31
|
+
* the module, `state items` at its top level: it listens to the changes there.
|
|
31
32
|
*/
|
|
32
33
|
export declare function inlinePackages(program: ast.Program): ModulePackages;
|
package/dist/inline-packages.js
CHANGED
|
@@ -7,7 +7,8 @@ import { forEachChild } from "./walk.js";
|
|
|
7
7
|
* recursive or a web component cannot be inlined, and neither can one that reaches components or
|
|
8
8
|
* decorators the module imports: those would bring the names of a third module. Nor can code that
|
|
9
9
|
* uses a component of the module as a value, which is the function in the module, or that assigns
|
|
10
|
-
* a variable of the module: another module could only read it.
|
|
10
|
+
* a variable of the module: another module could only read it. Nor can code that reads state of
|
|
11
|
+
* the module, `state items` at its top level: it listens to the changes there.
|
|
11
12
|
*/
|
|
12
13
|
export function inlinePackages(program) {
|
|
13
14
|
const components = new Map();
|
|
@@ -30,9 +31,13 @@ export function inlinePackages(program) {
|
|
|
30
31
|
};
|
|
31
32
|
const topLevel = topLevelNames(program);
|
|
32
33
|
const usesComponentValues = (node) => componentValues(node, (name) => components.has(name)).length > 0;
|
|
33
|
-
const
|
|
34
|
+
const states = new Set(program.body.flatMap((statement) => statement.kind === 'VariableDeclaration' && statement.keyword === 'state'
|
|
35
|
+
? statement.names.map((name) => name.name)
|
|
36
|
+
: []));
|
|
37
|
+
// It assigns a variable of the module or reads its state, which only the module can do.
|
|
38
|
+
const needsItsModule = (node) => {
|
|
34
39
|
const assigned = assignedNames(node);
|
|
35
|
-
return [...freeNames(node)].some((name) => topLevel.has(name) && assigned.has(name));
|
|
40
|
+
return [...freeNames(node)].some((name) => topLevel.has(name) && (assigned.has(name) || states.has(name)));
|
|
36
41
|
};
|
|
37
42
|
/** The package of a component, or of the tags in the code of a decorator. */
|
|
38
43
|
const build = (root, tags) => {
|
|
@@ -69,7 +74,7 @@ export function inlinePackages(program) {
|
|
|
69
74
|
found.components.set(current, component);
|
|
70
75
|
if (usesComponentValues(expanded.get(current)))
|
|
71
76
|
return false;
|
|
72
|
-
if (
|
|
77
|
+
if (needsItsModule(expanded.get(current)))
|
|
73
78
|
return false;
|
|
74
79
|
for (const use of component.decorators) {
|
|
75
80
|
const source = sources.get(use.name.name);
|
package/dist/names.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type * as ast from './ast.ts';
|
|
2
2
|
/** Names assigned anywhere inside `node`: `x = ...`, `x += ...`, `x++`. */
|
|
3
3
|
export declare function assignedNames(node: ast.Node, names?: Set<string>): Set<string>;
|
|
4
|
+
/** `order.delivery.kind`: the variable and the fields of a path, or `null` for other expressions. */
|
|
5
|
+
export declare function pathOf(node: ast.Expression): string[] | null;
|
|
6
|
+
/** Paths of fields assigned anywhere inside `node`, e.g. `order.delivery` of `order.delivery = d`. */
|
|
7
|
+
export declare function assignedPaths(node: ast.Node, paths?: Set<string>): Set<string>;
|
|
4
8
|
/** Whether an identifier with one of `names` occurs in `node`, whatever it refers to. */
|
|
5
9
|
export declare function mentionsName(node: ast.Node, names: ReadonlySet<string>): boolean;
|
|
6
10
|
/** Every name declared inside `node`: parameters and all local declarations, nested ones too. */
|
package/dist/names.js
CHANGED
|
@@ -19,6 +19,30 @@ export function assignedNames(node, names = new Set()) {
|
|
|
19
19
|
forEachChild(node, (child) => assignedNames(child, names));
|
|
20
20
|
return names;
|
|
21
21
|
}
|
|
22
|
+
/** `order.delivery.kind`: the variable and the fields of a path, or `null` for other expressions. */
|
|
23
|
+
export function pathOf(node) {
|
|
24
|
+
if (node.kind === 'Identifier')
|
|
25
|
+
return [node.name];
|
|
26
|
+
if (node.kind !== 'MemberExpression' || node.optional)
|
|
27
|
+
return null;
|
|
28
|
+
const object = pathOf(node.object);
|
|
29
|
+
return object && [...object, node.property.name];
|
|
30
|
+
}
|
|
31
|
+
/** Paths of fields assigned anywhere inside `node`, e.g. `order.delivery` of `order.delivery = d`. */
|
|
32
|
+
export function assignedPaths(node, paths = new Set()) {
|
|
33
|
+
const targets = node.kind === 'AssignmentStatement'
|
|
34
|
+
? node.targets
|
|
35
|
+
: node.kind === 'IncDecStatement'
|
|
36
|
+
? [node.target]
|
|
37
|
+
: [];
|
|
38
|
+
for (const target of targets) {
|
|
39
|
+
const path = pathOf(target);
|
|
40
|
+
if (path && path.length > 1)
|
|
41
|
+
paths.add(path.join('.'));
|
|
42
|
+
}
|
|
43
|
+
forEachChild(node, (child) => assignedPaths(child, paths));
|
|
44
|
+
return paths;
|
|
45
|
+
}
|
|
22
46
|
/** Whether an identifier with one of `names` occurs in `node`, whatever it refers to. */
|
|
23
47
|
export function mentionsName(node, names) {
|
|
24
48
|
if (node.kind === 'Identifier')
|
|
@@ -99,6 +123,7 @@ export function freeNames(component) {
|
|
|
99
123
|
for (const name of node.names)
|
|
100
124
|
declared.add(name.name);
|
|
101
125
|
node.values.forEach(visit);
|
|
126
|
+
visit(node.body);
|
|
102
127
|
return;
|
|
103
128
|
case 'SelectCase':
|
|
104
129
|
for (const name of node.names)
|
|
@@ -120,6 +145,8 @@ export function freeNames(component) {
|
|
|
120
145
|
if (node.key)
|
|
121
146
|
declared.add(node.key.name);
|
|
122
147
|
visit(node.iterable);
|
|
148
|
+
if (node.withKey)
|
|
149
|
+
visit(node.withKey);
|
|
123
150
|
visit(node.body);
|
|
124
151
|
return;
|
|
125
152
|
case 'CatchClause':
|
|
@@ -195,6 +222,7 @@ export function collectValueNames(node, names, isComponent = () => true) {
|
|
|
195
222
|
return;
|
|
196
223
|
case 'VariableDeclaration':
|
|
197
224
|
node.values.forEach(visit);
|
|
225
|
+
visit(node.body);
|
|
198
226
|
return;
|
|
199
227
|
case 'FuncDeclaration':
|
|
200
228
|
case 'MethodDeclaration':
|
|
@@ -237,6 +265,7 @@ export function collectValueNames(node, names, isComponent = () => true) {
|
|
|
237
265
|
return;
|
|
238
266
|
case 'ForInStatement':
|
|
239
267
|
visit(node.iterable);
|
|
268
|
+
visit(node.withKey ?? null);
|
|
240
269
|
visit(node.body);
|
|
241
270
|
return;
|
|
242
271
|
case 'CatchClause':
|
package/dist/parser/base.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare abstract class ParserBase {
|
|
|
25
25
|
protected abstract parseClass(start: number, exported: boolean): ast.ClassDeclaration;
|
|
26
26
|
protected abstract parseInterface(start: number, exported: boolean): ast.InterfaceDeclaration;
|
|
27
27
|
protected abstract parseTypeAlias(start: number, exported: boolean): ast.TypeAliasDeclaration;
|
|
28
|
+
protected abstract parseFunctionBody(async?: boolean): ast.BlockStatement;
|
|
28
29
|
protected abstract canStartType(offset?: number): boolean;
|
|
29
30
|
protected abstract parseType(): ast.TypeNode;
|
|
30
31
|
protected abstract parseTypeArgs(): ast.TypeNode[];
|
package/dist/parser/base.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { STATEMENT_KEYWORDS, BRACKETS, ParseError, describe } from "./syntax.js";
|
|
2
2
|
// The parser is a chain of layers, one per area: base → simple statements → statements → control
|
|
3
|
-
// flow → jumps → declarations → decorators → classes → types → expressions → literals → markup → Parser
|
|
3
|
+
// flow → jumps → params → declarations → decorators → classes → types → expressions → literals → markup → Parser
|
|
4
4
|
// (parser.ts). Statements, expressions and markup contain each other, so methods of later layers
|
|
5
5
|
// that earlier ones call are declared here as abstract.
|
|
6
6
|
//
|
|
@@ -17,6 +17,11 @@ export declare abstract class ControlFlowParser extends StatementParser {
|
|
|
17
17
|
ast.SimpleStatement | null
|
|
18
18
|
];
|
|
19
19
|
protected parseForIn(start: number, go?: boolean): ast.ForInStatement;
|
|
20
|
+
/**
|
|
21
|
+
* `with key todo.id` after the items of a `{for}` in markup: its blocks are found again by the
|
|
22
|
+
* key instead of the item. `with` and `key` are words only here, not keywords.
|
|
23
|
+
*/
|
|
24
|
+
private parseWithKey;
|
|
20
25
|
/** `go for i, v in items { }`: the parts of the array run as goroutines, and the loop waits. */
|
|
21
26
|
protected parseGoFor(): ast.ForStatement | ast.ForInStatement;
|
|
22
27
|
protected parseLoopBody(): ast.BlockStatement;
|
|
@@ -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
|
}
|