rooklang 0.4.2 → 0.5.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/CHANGELOG.md +235 -0
- package/CHANGELOG.ru.md +235 -0
- package/dist/aliases.d.ts +2 -0
- package/dist/aliases.js +83 -0
- package/dist/ast/declarations.d.ts +18 -0
- package/dist/ast/expressions.d.ts +14 -1
- package/dist/ast/markup.d.ts +2 -0
- package/dist/ast/statements.d.ts +2 -2
- package/dist/checker/assignments.js +2 -1
- package/dist/checker/base.d.ts +3 -0
- package/dist/checker/base.js +1 -1
- package/dist/checker/builtins.js +5 -1
- package/dist/checker/calls.d.ts +2 -0
- package/dist/checker/calls.js +18 -3
- package/dist/checker/classes.js +8 -0
- package/dist/checker/component-bindings.d.ts +16 -0
- package/dist/checker/component-bindings.js +65 -2
- package/dist/checker/context.d.ts +12 -0
- package/dist/checker/declarations.d.ts +2 -0
- package/dist/checker/declarations.js +38 -3
- package/dist/checker/expressions.d.ts +1 -0
- package/dist/checker/expressions.js +17 -1
- package/dist/checker/flow.d.ts +1 -1
- package/dist/checker/flow.js +10 -0
- package/dist/checker/functions.d.ts +2 -0
- package/dist/checker/functions.js +33 -7
- package/dist/checker/goroutines.d.ts +2 -2
- package/dist/checker/goroutines.js +2 -2
- package/dist/checker/imports.d.ts +10 -0
- package/dist/checker/imports.js +73 -6
- package/dist/checker/initializers.d.ts +12 -0
- package/dist/checker/initializers.js +80 -0
- package/dist/checker/loops.js +3 -1
- package/dist/checker/members.js +1 -0
- package/dist/checker/resolution.d.ts +2 -0
- package/dist/checker/resolution.js +13 -2
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +26 -3
- package/dist/checker/tagged-unions.d.ts +6 -0
- package/dist/checker/tagged-unions.js +31 -1
- package/dist/checker/tags.d.ts +6 -0
- package/dist/checker/tags.js +44 -0
- package/dist/checker/types/constructors.js +10 -1
- package/dist/checker/types/params.js +33 -7
- package/dist/checker/types/printing.js +5 -1
- package/dist/checker/types/relations.js +2 -1
- package/dist/codegen/calls.d.ts +2 -0
- package/dist/codegen/calls.js +23 -1
- package/dist/codegen/component-props.js +8 -1
- package/dist/codegen/control-flow.d.ts +3 -0
- package/dist/codegen/control-flow.js +17 -0
- package/dist/codegen/expressions.d.ts +4 -1
- package/dist/codegen/expressions.js +4 -15
- package/dist/codegen/functions.d.ts +2 -0
- package/dist/codegen/functions.js +13 -0
- package/dist/codegen/mutations.d.ts +6 -0
- package/dist/codegen/mutations.js +36 -0
- package/dist/codegen/options.d.ts +2 -0
- package/dist/codegen/reactive.js +9 -5
- package/dist/codegen/simple-statements.js +3 -1
- package/dist/codegen/statements.d.ts +4 -0
- package/dist/codegen/statements.js +37 -1
- package/dist/compile.js +2 -0
- package/dist/completion.js +2 -1
- package/dist/declarations.js +18 -0
- package/dist/lexer/base.d.ts +3 -2
- package/dist/lexer/base.js +14 -5
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/token.d.ts +6 -1
- package/dist/lexer/token.js +0 -1
- package/dist/modules.js +4 -3
- package/dist/parser/base.d.ts +5 -0
- package/dist/parser/base.js +9 -1
- package/dist/parser/classes.js +4 -3
- package/dist/parser/declarations.d.ts +2 -0
- package/dist/parser/declarations.js +25 -2
- package/dist/parser/decorators.js +1 -1
- package/dist/parser/expressions.js +6 -0
- package/dist/parser/literals.d.ts +2 -4
- package/dist/parser/literals.js +12 -24
- package/dist/parser/objects.d.ts +8 -0
- package/dist/parser/objects.js +51 -0
- package/dist/parser/params.d.ts +37 -4
- package/dist/parser/params.js +143 -9
- package/dist/parser/postfix.d.ts +5 -0
- package/dist/parser/postfix.js +36 -0
- package/dist/parser/simple-statements.d.ts +2 -0
- package/dist/parser/simple-statements.js +50 -0
- package/dist/parser/statements.js +8 -6
- package/dist/parser/syntax.js +0 -1
- package/dist/parser/types.d.ts +0 -4
- package/dist/parser/types.js +17 -32
- package/dist/project-modules.js +21 -0
- package/dist/reexports.d.ts +4 -0
- package/dist/reexports.js +56 -0
- package/dist/typescript/importer.d.ts +4 -0
- package/dist/typescript/importer.js +27 -3
- package/dist/typescript/library.js +5 -2
- package/dist/walk.js +12 -0
- package/package.json +4 -2
|
@@ -58,10 +58,17 @@ export class ComponentPropsEmitter extends ControlFlowEmitter {
|
|
|
58
58
|
*/
|
|
59
59
|
binder(target) {
|
|
60
60
|
const name = `$$bind${this.nextId()}`;
|
|
61
|
+
const list = target.kind === 'Identifier' ? this.loopLists.get(target.name) : undefined;
|
|
61
62
|
const text = this.liveExpression(target);
|
|
62
63
|
// `todos` of `bind:todos={todos}`, or the state that `bind:open={menu.open}` writes into.
|
|
63
|
-
const written = [...(this.reactive?.rootSources(target) ?? [])].filter((source) => source.kind === 'state');
|
|
64
|
+
const written = [...(this.reactive?.rootSources(list ?? target) ?? [])].filter((source) => source.kind === 'state');
|
|
64
65
|
const body = this.block(() => {
|
|
66
|
+
// The item of `{for row in rows}`: a new one replaces it in the list, wherever it is now.
|
|
67
|
+
if (list) {
|
|
68
|
+
const items = this.liveExpression(list, POSTFIX);
|
|
69
|
+
this.line(`const $$at = ${items}.indexOf(${text});`);
|
|
70
|
+
this.line(`if ($$at !== -1) ${items}[$$at] = $$value;`);
|
|
71
|
+
}
|
|
65
72
|
this.line(`${text} = $$value;`);
|
|
66
73
|
for (const name of rootsOf(target, this.shared)) {
|
|
67
74
|
this.line(`$$changed(${this.shared.listeners.get(name)});`);
|
|
@@ -8,6 +8,8 @@ import { type Live, type Source } from './reactive.ts';
|
|
|
8
8
|
* returns its update.
|
|
9
9
|
*/
|
|
10
10
|
export declare abstract class ControlFlowEmitter extends ElementEmitter {
|
|
11
|
+
/** The lists of the `{for}` loops around the code, by the names of their items. */
|
|
12
|
+
protected readonly loopLists: Map<string, ast.Expression>;
|
|
11
13
|
/** Writes the code that creates the content and returns what to append; see the class. */
|
|
12
14
|
protected liveControl(statement: ControlStatement, live: Live, sources: ReadonlySet<Source>): string;
|
|
13
15
|
/** The choice of an if/else chain, `() => a ? 0 : b ? 1 : -1`, and its blocks. */
|
|
@@ -20,6 +22,7 @@ export declare abstract class ControlFlowEmitter extends ElementEmitter {
|
|
|
20
22
|
* state go into its update. The block of `<Page />` gets the component, `($$component3) => ...`.
|
|
21
23
|
*/
|
|
22
24
|
protected blockCreator(statements: readonly ast.Statement[], parent: Live, loop?: ast.ForInStatement, component?: ast.Identifier): string;
|
|
25
|
+
private blockFunction;
|
|
23
26
|
/** The key of a list, `with key todo.id`: `(todo) => todo.id`, which its blocks are found by. */
|
|
24
27
|
private listKey;
|
|
25
28
|
/** A classic `for` loop in live markup: a function that runs it and returns the content. */
|
|
@@ -9,6 +9,8 @@ import { CONDITIONAL, indentMore, upperFirst } from "./syntax.js";
|
|
|
9
9
|
* returns its update.
|
|
10
10
|
*/
|
|
11
11
|
export class ControlFlowEmitter extends ElementEmitter {
|
|
12
|
+
/** The lists of the `{for}` loops around the code, by the names of their items. */
|
|
13
|
+
loopLists = new Map();
|
|
12
14
|
/** Writes the code that creates the content and returns what to append; see the class. */
|
|
13
15
|
liveControl(statement, live, sources) {
|
|
14
16
|
const id = this.nextId();
|
|
@@ -88,6 +90,21 @@ export class ControlFlowEmitter extends ElementEmitter {
|
|
|
88
90
|
blockCreator(statements, parent, loop, component) {
|
|
89
91
|
const block = new Block(parent);
|
|
90
92
|
const params = loop ? loopParameters(loop) : component ? [parameter(component)] : [];
|
|
93
|
+
const saved = loop && this.loopLists.get(loop.value.name);
|
|
94
|
+
// `bind:row={row}` in the block writes into the list.
|
|
95
|
+
if (loop)
|
|
96
|
+
this.loopLists.set(loop.value.name, loop.iterable);
|
|
97
|
+
try {
|
|
98
|
+
return this.blockFunction(block, statements, params, loop);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
if (loop && saved)
|
|
102
|
+
this.loopLists.set(loop.value.name, saved);
|
|
103
|
+
else if (loop)
|
|
104
|
+
this.loopLists.delete(loop.value.name);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
blockFunction(block, statements, params, loop) {
|
|
91
108
|
return this.withFunction('none', params, () => this.withRendering(this.rendering + 1, () => {
|
|
92
109
|
const paramList = this.params(params);
|
|
93
110
|
const fragment = `$$block${this.nextId()}`;
|
|
@@ -22,6 +22,10 @@ export declare abstract class ExpressionEmitter extends Emitter {
|
|
|
22
22
|
protected abstract streamChan(node: ast.StreamChanExpression): string;
|
|
23
23
|
protected abstract queue(node: ast.QueueExpression): string;
|
|
24
24
|
protected abstract topic(node: ast.TopicExpression): string;
|
|
25
|
+
/** `{ a: 1, ...b, f() { ... } }`; implemented by CallEmitter. */
|
|
26
|
+
protected abstract objectLiteral(node: ast.ObjectLiteral): string;
|
|
27
|
+
/** sql`select ... ${id}`; implemented by CallEmitter. */
|
|
28
|
+
protected abstract taggedTemplate(node: ast.TaggedTemplateExpression): string;
|
|
25
29
|
/** `go f(x)`; implemented by CallEmitter. */
|
|
26
30
|
protected abstract go(node: ast.GoExpression): string;
|
|
27
31
|
/** `decode<T>(x)`; implemented by DecodeEmitter. */
|
|
@@ -54,7 +58,6 @@ export declare abstract class ExpressionEmitter extends Emitter {
|
|
|
54
58
|
private expressionWithPrecedence;
|
|
55
59
|
protected elements(elements: readonly (ast.Expression | ast.SpreadElement)[]): string;
|
|
56
60
|
private template;
|
|
57
|
-
private objectLiteral;
|
|
58
61
|
private arrowFunction;
|
|
59
62
|
private unary;
|
|
60
63
|
private binary;
|
|
@@ -82,9 +82,12 @@ export class ExpressionEmitter extends Emitter {
|
|
|
82
82
|
return [decoratedName(node.decorator.name, node.member.name), PRIMARY];
|
|
83
83
|
case 'ThisExpression':
|
|
84
84
|
return ['this', PRIMARY];
|
|
85
|
-
// `as`
|
|
85
|
+
// `as` and `user!` are only for the checker.
|
|
86
86
|
case 'AsExpression':
|
|
87
|
+
case 'NonNullExpression':
|
|
87
88
|
return this.expressionWithPrecedence(node.expression);
|
|
89
|
+
case 'TaggedTemplateExpression':
|
|
90
|
+
return [this.taggedTemplate(node), POSTFIX];
|
|
88
91
|
case 'SuperExpression':
|
|
89
92
|
return ['super', PRIMARY];
|
|
90
93
|
case 'ArrayLiteral': {
|
|
@@ -171,20 +174,6 @@ export class ExpressionEmitter extends Emitter {
|
|
|
171
174
|
});
|
|
172
175
|
return `${text}\``;
|
|
173
176
|
}
|
|
174
|
-
objectLiteral(node) {
|
|
175
|
-
if (node.properties.length === 0)
|
|
176
|
-
return '{}';
|
|
177
|
-
const properties = node.properties.map((property) => {
|
|
178
|
-
if (property.kind === 'SpreadElement') {
|
|
179
|
-
return `...${this.expression(property.argument, ARROW)}`;
|
|
180
|
-
}
|
|
181
|
-
const key = property.key.kind === 'StringLiteral' ? property.key.raw : property.key.name;
|
|
182
|
-
const value = this.expression(property.value, ARROW);
|
|
183
|
-
// A renamed shorthand (`{ delete }` → `delete$`) must keep its key.
|
|
184
|
-
return property.shorthand && value === key ? key : `${key}: ${value}`;
|
|
185
|
-
});
|
|
186
|
-
return `{ ${properties.join(', ')} }`;
|
|
187
|
-
}
|
|
188
177
|
arrowFunction(node) {
|
|
189
178
|
const { body } = node;
|
|
190
179
|
const async = node.async ? 'async ' : '';
|
|
@@ -2,6 +2,8 @@ import type * as ast from '../ast.ts';
|
|
|
2
2
|
import { WebComponentEmitter } from './web-components.ts';
|
|
3
3
|
/** Function bodies and `defer`. */
|
|
4
4
|
export declare abstract class FunctionEmitter extends WebComponentEmitter {
|
|
5
|
+
/** `a, times = 1, ...parts`: default values and the rest parameter are those of JS. */
|
|
6
|
+
protected params(params: ast.Parameter[]): string;
|
|
5
7
|
/** Parameters and body of a function, method or func literal. */
|
|
6
8
|
protected func(params: ast.Parameter[], body: ast.BlockStatement): [string, string];
|
|
7
9
|
/**
|
|
@@ -4,6 +4,19 @@ import { WebComponentEmitter } from "./web-components.js";
|
|
|
4
4
|
import { ARROW, POSTFIX } from "./syntax.js";
|
|
5
5
|
/** Function bodies and `defer`. */
|
|
6
6
|
export class FunctionEmitter extends WebComponentEmitter {
|
|
7
|
+
/** `a, times = 1, ...parts`: default values and the rest parameter are those of JS. */
|
|
8
|
+
params(params) {
|
|
9
|
+
const names = super.params(params).split(', ');
|
|
10
|
+
return params
|
|
11
|
+
.map((param, i) => {
|
|
12
|
+
if (param.rest)
|
|
13
|
+
return `...${names[i]}`;
|
|
14
|
+
return param.defaultValue
|
|
15
|
+
? `${names[i]} = ${this.expression(param.defaultValue, ARROW)}`
|
|
16
|
+
: names[i];
|
|
17
|
+
})
|
|
18
|
+
.join(', ');
|
|
19
|
+
}
|
|
7
20
|
/** Parameters and body of a function, method or func literal. */
|
|
8
21
|
func(params, body) {
|
|
9
22
|
const defers = findDefers(body);
|
|
@@ -4,6 +4,12 @@ import type { Type } from '../checker/types.ts';
|
|
|
4
4
|
export declare function rootName(node: ast.Expression): string | null;
|
|
5
5
|
/** Names whose values a node reads, not counting code that runs on events. */
|
|
6
6
|
export declare function readNames(node: ast.Node, names: Set<string>): void;
|
|
7
|
+
/**
|
|
8
|
+
* Names of the functions that code may run while it runs: those it calls and those it gives to a
|
|
9
|
+
* call, `items.map(format)`. A function it only gives as a value, `act={copy}` or
|
|
10
|
+
* `act={() => show("x")}`, runs later, when whoever gets it calls it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function calledNames(node: ast.Node, names: Set<string>): void;
|
|
7
13
|
/**
|
|
8
14
|
* Names an effect reads while it runs: not in the functions it creates, which run later, nor in
|
|
9
15
|
* its `defer`, nor the variables it writes, or `count++` would run it again.
|
|
@@ -57,6 +57,42 @@ export function readNames(node, names) {
|
|
|
57
57
|
forEachChild(node, (child) => readNames(child, names));
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Names of the functions that code may run while it runs: those it calls and those it gives to a
|
|
62
|
+
* call, `items.map(format)`. A function it only gives as a value, `act={copy}` or
|
|
63
|
+
* `act={() => show("x")}`, runs later, when whoever gets it calls it.
|
|
64
|
+
*/
|
|
65
|
+
export function calledNames(node, names) {
|
|
66
|
+
switch (node.kind) {
|
|
67
|
+
case 'CallExpression':
|
|
68
|
+
if (node.callee.kind === 'Identifier')
|
|
69
|
+
names.add(node.callee.name);
|
|
70
|
+
else
|
|
71
|
+
calledNames(node.callee, names);
|
|
72
|
+
for (const arg of node.arguments) {
|
|
73
|
+
if (arg.kind === 'Identifier')
|
|
74
|
+
names.add(arg.name);
|
|
75
|
+
else if (arg.kind === 'ArrowFunction' || arg.kind === 'FuncExpression') {
|
|
76
|
+
forEachChild(arg, (child) => calledNames(child, names));
|
|
77
|
+
}
|
|
78
|
+
else
|
|
79
|
+
calledNames(arg, names);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
case 'ArrowFunction':
|
|
83
|
+
case 'FuncExpression':
|
|
84
|
+
case 'EventHandler':
|
|
85
|
+
case 'MountStatement':
|
|
86
|
+
case 'EffectStatement':
|
|
87
|
+
return;
|
|
88
|
+
case 'JsxAttribute':
|
|
89
|
+
if (node.value !== null && !/^on[A-Z]/.test(node.name.name))
|
|
90
|
+
calledNames(node.value, names);
|
|
91
|
+
return;
|
|
92
|
+
default:
|
|
93
|
+
forEachChild(node, (child) => calledNames(child, names));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
60
96
|
/**
|
|
61
97
|
* Names an effect reads while it runs: not in the functions it creates, which run later, nor in
|
|
62
98
|
* its `defer`, nor the variables it writes, or `count++` would run it again.
|
|
@@ -33,6 +33,8 @@ export interface JsOptions {
|
|
|
33
33
|
dataClasses?: ReadonlySet<string> | undefined;
|
|
34
34
|
/** State of other modules that this one imports, `local → name there`: it only reads it. */
|
|
35
35
|
shared?: ReadonlyMap<string, string> | undefined;
|
|
36
|
+
/** What each `export { ... } from` passes on in JS, with hidden names (reexports.ts). */
|
|
37
|
+
reexports?: ReadonlyMap<ast.ExportFromDeclaration, readonly string[]> | undefined;
|
|
36
38
|
}
|
|
37
39
|
/** A decorator of another module, with the types of the expressions of that module. */
|
|
38
40
|
export interface ImportedDecorator extends DecoratorSource {
|
package/dist/codegen/reactive.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ownStatements } from "../checker/helpers.js";
|
|
|
2
2
|
import { wrappedMarkup } from "../decorators.js";
|
|
3
3
|
import { containsReturn, returnedMarkup } from "../recursion.js";
|
|
4
4
|
import { forEachChild } from "../walk.js";
|
|
5
|
-
import { readNames, rootName, writeTargets } from "./mutations.js";
|
|
5
|
+
import { calledNames, readNames, rootName, writeTargets } from "./mutations.js";
|
|
6
6
|
// Reactivity of components: which markup depends on which state, and which code changes it.
|
|
7
7
|
export { Block, updatesOf } from "./live.js";
|
|
8
8
|
const EMPTY = new Set();
|
|
@@ -69,19 +69,23 @@ export class Reactive {
|
|
|
69
69
|
...wrappedMarkup(component),
|
|
70
70
|
];
|
|
71
71
|
const seen = new Set();
|
|
72
|
-
|
|
72
|
+
// A function the markup only gives away, `act={copy}`, runs later and keeps its updates.
|
|
73
|
+
const visit = (node, body = false) => {
|
|
73
74
|
const names = new Set();
|
|
74
|
-
|
|
75
|
+
if (body)
|
|
76
|
+
forEachChild(node, (child) => calledNames(child, names));
|
|
77
|
+
else
|
|
78
|
+
calledNames(node, names);
|
|
75
79
|
for (const name of names) {
|
|
76
80
|
const fn = this.functions.get(name);
|
|
77
81
|
if (!fn || seen.has(name))
|
|
78
82
|
continue;
|
|
79
83
|
seen.add(name);
|
|
80
84
|
this.renderFunctions.add(fn);
|
|
81
|
-
visit(fn);
|
|
85
|
+
visit(fn, true);
|
|
82
86
|
}
|
|
83
87
|
};
|
|
84
|
-
markup.forEach(visit);
|
|
88
|
+
markup.forEach((node) => visit(node));
|
|
85
89
|
}
|
|
86
90
|
/** Whether a statement of the body declares a function that the live markup calls. */
|
|
87
91
|
declaresRenderFunction(node) {
|
|
@@ -170,8 +170,10 @@ export class SimpleStatementEmitter extends TryEmitter {
|
|
|
170
170
|
case 'EffectStatement':
|
|
171
171
|
this.effectStatement(node);
|
|
172
172
|
break;
|
|
173
|
+
// Components write their styles themselves (ComponentEmitter), and StatementEmitter the
|
|
174
|
+
// exports of other modules.
|
|
173
175
|
case 'StyleStatement':
|
|
174
|
-
|
|
176
|
+
case 'ExportFromDeclaration':
|
|
175
177
|
break;
|
|
176
178
|
}
|
|
177
179
|
}
|
|
@@ -20,6 +20,10 @@ export declare abstract class StatementEmitter extends ParallelLoopEmitter {
|
|
|
20
20
|
* decorators uses, and that of exported components when other modules inline them.
|
|
21
21
|
*/
|
|
22
22
|
protected hiddenExports(): void;
|
|
23
|
+
/** `const { name, size: bytes, ...rest } = file`, as in JS. */
|
|
24
|
+
protected variableDeclaration(node: ast.VariableDeclaration): string;
|
|
25
|
+
/** `export { Rows } from "./rows.js"` with the hidden names that go with them (reexports.ts). */
|
|
26
|
+
private exportFrom;
|
|
23
27
|
protected importPath(source: ast.StringLiteral): string;
|
|
24
28
|
protected classDeclaration(node: ast.ClassDeclaration): void;
|
|
25
29
|
/** Visibility modifiers are checked by the compiler and do not exist in the output. */
|
|
@@ -17,6 +17,10 @@ export class StatementEmitter extends ParallelLoopEmitter {
|
|
|
17
17
|
sharedReads = null;
|
|
18
18
|
/** State of the module: the updates of the components that read it are kept beside it. */
|
|
19
19
|
emitStatement(node) {
|
|
20
|
+
if (node.kind === 'ExportFromDeclaration') {
|
|
21
|
+
this.exportFrom(node);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
20
24
|
super.emitStatement(node);
|
|
21
25
|
if (node.kind !== 'VariableDeclaration' || node.keyword !== 'state')
|
|
22
26
|
return;
|
|
@@ -124,9 +128,34 @@ export class StatementEmitter extends ParallelLoopEmitter {
|
|
|
124
128
|
this.blankLine();
|
|
125
129
|
this.line(`export { ${names.join(', ')} };`);
|
|
126
130
|
}
|
|
131
|
+
/** `const { name, size: bytes, ...rest } = file`, as in JS. */
|
|
132
|
+
variableDeclaration(node) {
|
|
133
|
+
if (!node.pattern)
|
|
134
|
+
return super.variableDeclaration(node);
|
|
135
|
+
const { keys, rest } = node.pattern;
|
|
136
|
+
const fields = keys.map((key, i) => {
|
|
137
|
+
const name = this.name(node.names[i].name);
|
|
138
|
+
const field = key.kind === 'StringLiteral' ? key.raw : key.name;
|
|
139
|
+
return field === name ? name : `${field}: ${name}`;
|
|
140
|
+
});
|
|
141
|
+
if (rest)
|
|
142
|
+
fields.push(`...${this.name(node.names.at(-1).name)}`);
|
|
143
|
+
return `${node.keyword} { ${fields.join(', ')} } = ${this.expression(node.values[0], ARROW)}`;
|
|
144
|
+
}
|
|
145
|
+
/** `export { Rows } from "./rows.js"` with the hidden names that go with them (reexports.ts). */
|
|
146
|
+
exportFrom(node) {
|
|
147
|
+
const source = this.importPath(node.source);
|
|
148
|
+
const names = node.specifiers ? (this.options.reexports?.get(node) ?? []) : null;
|
|
149
|
+
if (names === null)
|
|
150
|
+
this.line(`export * from ${source};`);
|
|
151
|
+
else if (names.length > 0)
|
|
152
|
+
this.line(`export { ${names.join(', ')} } from ${source};`);
|
|
153
|
+
}
|
|
127
154
|
importPath(source) {
|
|
128
155
|
const path = source.value;
|
|
129
|
-
|
|
156
|
+
// `./button.rk` and the aliases `#ui/button.rk` and `@/ui/button.rk` (aliases.ts): the built
|
|
157
|
+
// module is next to its source.
|
|
158
|
+
if (this.options.rewriteImports && !isBare(path) && path.endsWith('.rk')) {
|
|
130
159
|
return JSON.stringify(`${path.slice(0, -'.rk'.length)}.js`);
|
|
131
160
|
}
|
|
132
161
|
return source.raw;
|
|
@@ -235,3 +264,10 @@ function hash(text) {
|
|
|
235
264
|
}
|
|
236
265
|
return value.toString(16).padStart(8, '0');
|
|
237
266
|
}
|
|
267
|
+
/** `pkg/file.rk` of another package, which is not built with this one. */
|
|
268
|
+
function isBare(path) {
|
|
269
|
+
return (!/^\.{1,2}\//.test(path) &&
|
|
270
|
+
!path.startsWith('#') &&
|
|
271
|
+
!path.startsWith('@/') &&
|
|
272
|
+
!path.startsWith('~/'));
|
|
273
|
+
}
|
package/dist/compile.js
CHANGED
|
@@ -6,6 +6,7 @@ import { goroutines, goTarget, workerProgram } from "./goroutines.js";
|
|
|
6
6
|
import { withoutMarks } from "./source-map.js";
|
|
7
7
|
import { parse } from "./parser/parser.js";
|
|
8
8
|
import { isStyleModule, styleModule, unusedClasses } from "./style-modules.js";
|
|
9
|
+
import { reexportedNames } from "./reexports.js";
|
|
9
10
|
/**
|
|
10
11
|
* Where the worker module of a module is: a `.worker.js` next to its `.js`, or, where `.rk`
|
|
11
12
|
* files are imported as they are (`rook run`), the module itself with `?goroutines`. Vite drops
|
|
@@ -48,6 +49,7 @@ export function compileModule(source, options = {}) {
|
|
|
48
49
|
runtime: options.runtime,
|
|
49
50
|
dataClasses,
|
|
50
51
|
...importedCode(program, options.importModule),
|
|
52
|
+
reexports: reexportedNames(program, options.importModule),
|
|
51
53
|
};
|
|
52
54
|
const sourceName = options.sourceName ?? 'main.rk';
|
|
53
55
|
const main = withoutMarks(generateJs(program, {
|
package/dist/completion.js
CHANGED
|
@@ -16,7 +16,8 @@ const STRINGS = new Set([
|
|
|
16
16
|
'CssText',
|
|
17
17
|
'RegExp',
|
|
18
18
|
]);
|
|
19
|
-
|
|
19
|
+
// `type` is a keyword only at the start of a statement, where it is offered too.
|
|
20
|
+
const VALUE_KEYWORDS = [...KEYWORDS, 'type'];
|
|
20
21
|
// `queue` is a type of its own only in a type: elsewhere it is a name.
|
|
21
22
|
const TYPE_KEYWORDS = ['func', 'comp', 'chan', 'queue', 'topic'];
|
|
22
23
|
/** Keywords only at the start of a statement at the top level of a component or a decorator. */
|
package/dist/declarations.js
CHANGED
|
@@ -29,6 +29,10 @@ class DeclarationPrinter {
|
|
|
29
29
|
case 'ImportDeclaration':
|
|
30
30
|
this.importDeclaration(statement);
|
|
31
31
|
break;
|
|
32
|
+
case 'ExportFromDeclaration':
|
|
33
|
+
exports = true;
|
|
34
|
+
this.exportFrom(statement);
|
|
35
|
+
break;
|
|
32
36
|
case 'FuncDeclaration':
|
|
33
37
|
if (statement.exported)
|
|
34
38
|
this.functionDeclaration(statement);
|
|
@@ -73,6 +77,20 @@ class DeclarationPrinter {
|
|
|
73
77
|
const source = node.source.value.replace(/\.rk$/, '.js');
|
|
74
78
|
this.lines.push(`import ${parts.join(', ')} from ${JSON.stringify(source)};`);
|
|
75
79
|
}
|
|
80
|
+
/** `export { Row } from "./rows.js"`: the values and types it passes on; components have none. */
|
|
81
|
+
exportFrom(node) {
|
|
82
|
+
const source = JSON.stringify(node.source.value.replace(/\.rk$/, '.js'));
|
|
83
|
+
if (!node.specifiers) {
|
|
84
|
+
this.lines.push(`export * from ${source};`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const { exports } = this.checked;
|
|
88
|
+
const names = node.specifiers
|
|
89
|
+
.filter(({ local }) => exports.values.has(local.name) || exports.types.has(local.name))
|
|
90
|
+
.map(({ imported, local }) => imported.name === local.name ? local.name : `${imported.name} as ${local.name}`);
|
|
91
|
+
if (names.length > 0)
|
|
92
|
+
this.lines.push(`export { ${names.join(', ')} } from ${source};`);
|
|
93
|
+
}
|
|
76
94
|
functionDeclaration(node) {
|
|
77
95
|
const type = this.checked.declarations.values.get(node.name.name);
|
|
78
96
|
if (type?.kind !== 'function')
|
package/dist/lexer/base.d.ts
CHANGED
|
@@ -14,8 +14,9 @@ export declare abstract class LexerBase {
|
|
|
14
14
|
protected lastEndsValue(): boolean;
|
|
15
15
|
protected lastEndsStatement(): boolean;
|
|
16
16
|
/**
|
|
17
|
-
* Unlike Go, a line starting with `.` / `?.` continues a method chain,
|
|
18
|
-
* a
|
|
17
|
+
* Unlike Go, a line starting with `.` / `?.` continues a method chain, a line starting with `?`
|
|
18
|
+
* or `:` continues a conditional expression, and a line starting with a closing bracket ends a
|
|
19
|
+
* list, so no semicolon is needed before it.
|
|
19
20
|
*/
|
|
20
21
|
protected nextLineContinues(): boolean;
|
|
21
22
|
protected insertSemicolon(): void;
|
package/dist/lexer/base.js
CHANGED
|
@@ -55,21 +55,30 @@ export class LexerBase {
|
|
|
55
55
|
}
|
|
56
56
|
lastEndsStatement() {
|
|
57
57
|
const last = this.tokens.at(-1);
|
|
58
|
-
|
|
58
|
+
if (last === undefined)
|
|
59
|
+
return false;
|
|
60
|
+
// `env.PORT!` at the end of a line: the `!` right after a value, which takes it not to be null.
|
|
61
|
+
if (last.kind === '!') {
|
|
62
|
+
const before = this.tokens.at(-2);
|
|
63
|
+
return before !== undefined && before.end === last.start && ENDS_VALUE.has(before.kind);
|
|
64
|
+
}
|
|
65
|
+
return ENDS_STATEMENT.has(last.kind);
|
|
59
66
|
}
|
|
60
67
|
/**
|
|
61
|
-
* Unlike Go, a line starting with `.` / `?.` continues a method chain,
|
|
62
|
-
* a
|
|
68
|
+
* Unlike Go, a line starting with `.` / `?.` continues a method chain, a line starting with `?`
|
|
69
|
+
* or `:` continues a conditional expression, and a line starting with a closing bracket ends a
|
|
70
|
+
* list, so no semicolon is needed before it.
|
|
63
71
|
*/
|
|
64
72
|
nextLineContinues() {
|
|
65
73
|
const t = this.text;
|
|
66
74
|
const c = t[this.pos];
|
|
67
75
|
const next = t[this.pos + 1];
|
|
68
|
-
if (c === ')' || c === ']' || c === '}')
|
|
76
|
+
if (c === ')' || c === ']' || c === '}' || c === ':')
|
|
69
77
|
return true;
|
|
70
78
|
if (c === '.')
|
|
71
79
|
return next !== '.' && !isDigit(next);
|
|
72
|
-
|
|
80
|
+
// `? "yes"` and `?.name`, but not `??`, which stays at the end of the line as other operators.
|
|
81
|
+
return c === '?' && next !== '?';
|
|
73
82
|
}
|
|
74
83
|
insertSemicolon() {
|
|
75
84
|
const end = this.tokens.at(-1)?.end ?? 0;
|
package/dist/lexer/lexer.js
CHANGED
|
@@ -30,7 +30,10 @@ class Lexer extends StyleLexer {
|
|
|
30
30
|
}
|
|
31
31
|
if (this.pos >= this.text.length)
|
|
32
32
|
break;
|
|
33
|
+
const count = this.tokens.length;
|
|
33
34
|
this.scanToken();
|
|
35
|
+
if (sawNewline && this.tokens.length > count)
|
|
36
|
+
this.tokens[count].newlineBefore = true;
|
|
34
37
|
}
|
|
35
38
|
if (this.lastEndsStatement())
|
|
36
39
|
this.insertSemicolon();
|
package/dist/lexer/token.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const KEYWORDS: readonly ["func", "comp", "go", "chan", "return", "let", "const", "
|
|
1
|
+
export declare const KEYWORDS: readonly ["func", "comp", "go", "chan", "return", "let", "const", "interface", "class", "extends", "new", "this", "super", "if", "else", "for", "in", "break", "continue", "switch", "case", "default", "defer", "try", "catch", "finally", "throw", "import", "export", "null", "true", "false", "typeof", "instanceof", "async", "await"];
|
|
2
2
|
export declare const PUNCTUATORS: readonly ["{", "}", "(", ")", "[", "]", ";", ",", ":", ".", "...", "?", "?.", "??", "=>", "=", "+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>=", "&&=", "||=", "??=", "==", "!=", "<", "<-", ">", "<=", ">=", "+", "-", "*", "/", "%", "**", "++", "--", "&", "|", "^", "~", "<<", ">>", ">>>", "&&", "||", "!", "@"];
|
|
3
3
|
export type Keyword = (typeof KEYWORDS)[number];
|
|
4
4
|
export type Punctuator = (typeof PUNCTUATORS)[number];
|
|
@@ -24,6 +24,11 @@ interface TokenBase {
|
|
|
24
24
|
text: string;
|
|
25
25
|
start: number;
|
|
26
26
|
end: number;
|
|
27
|
+
/**
|
|
28
|
+
* A line break comes before the token. No semicolon is inserted after `>`, which may be an
|
|
29
|
+
* operator; the parser ends a declaration after `Map<string, number>` by it.
|
|
30
|
+
*/
|
|
31
|
+
newlineBefore?: boolean;
|
|
27
32
|
}
|
|
28
33
|
export interface NumberToken extends TokenBase {
|
|
29
34
|
kind: 'Number';
|
package/dist/lexer/token.js
CHANGED
package/dist/modules.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveRookModule } from "./aliases.js";
|
|
3
3
|
import { check } from "./checker/checker.js";
|
|
4
4
|
import { markupFor } from "./config.js";
|
|
5
5
|
import { parse } from "./parser/parser.js";
|
|
@@ -9,9 +9,10 @@ const cache = new Map();
|
|
|
9
9
|
const loading = new Set();
|
|
10
10
|
/** Finds the types exported by a `.rk` module imported from the file `fromFile`. */
|
|
11
11
|
export function loadModuleExports(fromFile, specifier, markup) {
|
|
12
|
-
|
|
12
|
+
// A relative path, or an alias of package.json or tsconfig.json (aliases.ts).
|
|
13
|
+
const path = specifier.endsWith('.rk') ? resolveRookModule(fromFile, specifier) : null;
|
|
14
|
+
if (path === null)
|
|
13
15
|
return undefined;
|
|
14
|
-
const path = resolve(dirname(fromFile), specifier);
|
|
15
16
|
let version;
|
|
16
17
|
try {
|
|
17
18
|
const stats = statSync(path);
|
package/dist/parser/base.d.ts
CHANGED
|
@@ -66,6 +66,11 @@ export declare abstract class ParserBase {
|
|
|
66
66
|
protected requireMarkup(what: string): void;
|
|
67
67
|
protected peek(offset?: number): Token;
|
|
68
68
|
protected check(kind: TokenKind): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The line ended with the `>` of type arguments, `votes Map<string, number>`: the lexer inserts
|
|
71
|
+
* no semicolon after `>`, which may be an operator, so the line break ends the declaration here.
|
|
72
|
+
*/
|
|
73
|
+
protected afterTypeArguments(): boolean;
|
|
69
74
|
/** Whether the current token is the word `word`, e.g. the contextual keyword `from`. */
|
|
70
75
|
protected checkWord(word: string): boolean;
|
|
71
76
|
protected next(): Token;
|
package/dist/parser/base.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 → params → declarations → decorators → classes → types → expressions →
|
|
3
|
+
// flow → jumps → params → declarations → decorators → classes → types → expressions → postfix →
|
|
4
|
+
// objects → literals → markup → Parser
|
|
4
5
|
// (parser.ts). Statements, expressions and markup contain each other, so methods of later layers
|
|
5
6
|
// that earlier ones call are declared here as abstract.
|
|
6
7
|
//
|
|
@@ -43,6 +44,13 @@ export class ParserBase {
|
|
|
43
44
|
check(kind) {
|
|
44
45
|
return this.peek().kind === kind;
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The line ended with the `>` of type arguments, `votes Map<string, number>`: the lexer inserts
|
|
49
|
+
* no semicolon after `>`, which may be an operator, so the line break ends the declaration here.
|
|
50
|
+
*/
|
|
51
|
+
afterTypeArguments() {
|
|
52
|
+
return this.pos > 0 && this.peek(-1).kind === '>' && this.peek().newlineBefore === true;
|
|
53
|
+
}
|
|
46
54
|
/** Whether the current token is the word `word`, e.g. the contextual keyword `from`. */
|
|
47
55
|
checkWord(word) {
|
|
48
56
|
const token = this.peek();
|
package/dist/parser/classes.js
CHANGED
|
@@ -83,7 +83,7 @@ export class ClassParser extends DecoratorParser {
|
|
|
83
83
|
if (modifier)
|
|
84
84
|
this.error(`a constructor cannot be ${word}`, modifier.start, modifier.end);
|
|
85
85
|
}
|
|
86
|
-
const params = this.parseParams(false,
|
|
86
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed', properties: true });
|
|
87
87
|
const resultsStart = this.peek().start;
|
|
88
88
|
if (this.parseResults().length > 0) {
|
|
89
89
|
this.error('constructors cannot have result types', resultsStart, this.span(start).end);
|
|
@@ -161,7 +161,7 @@ export class ClassParser extends DecoratorParser {
|
|
|
161
161
|
if (accessor && asyncToken) {
|
|
162
162
|
this.error(`a ${accessor}ter cannot be async`, asyncToken.start, asyncToken.end);
|
|
163
163
|
}
|
|
164
|
-
const params = this.parseParams(false);
|
|
164
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed' });
|
|
165
165
|
const results = this.parseResults();
|
|
166
166
|
const header = this.span(start);
|
|
167
167
|
if (accessor === 'get' && (params.length > 0 || results.length !== 1)) {
|
|
@@ -209,7 +209,8 @@ export class ClassParser extends DecoratorParser {
|
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
211
|
parseTypeAlias(start, exported) {
|
|
212
|
-
|
|
212
|
+
// The word `type`, which the caller has seen.
|
|
213
|
+
this.next();
|
|
213
214
|
const name = this.parseIdentifier('type name');
|
|
214
215
|
const typeParams = this.parseTypeParams();
|
|
215
216
|
const type = this.parseType();
|
|
@@ -6,6 +6,8 @@ export declare abstract class DeclarationParser extends ParamParser {
|
|
|
6
6
|
/** `state items []Item = []` at the top level of a module: state its components share. */
|
|
7
7
|
protected parseDeclaration(start: number, exported: boolean): ast.Statement | undefined;
|
|
8
8
|
protected parseExport(): ast.Statement;
|
|
9
|
+
/** `export { Row, Rows as Table } from "./rows.rk"` or `export * from "./rows.rk"`. */
|
|
10
|
+
private parseExportFrom;
|
|
9
11
|
protected parseFuncDeclaration(start: number, exported: boolean): ast.FuncDeclaration;
|
|
10
12
|
/**
|
|
11
13
|
* `<T any, U Shape>` after a name; `<K, V any>` gives both the constraint, as in Go.
|