rooklang 0.3.3 → 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 +34 -7
- package/README.ru.md +34 -7
- package/dist/ast/declarations.d.ts +39 -4
- package/dist/ast/expressions.d.ts +34 -8
- package/dist/ast/markup.d.ts +6 -1
- package/dist/ast/statements.d.ts +28 -2
- package/dist/auto-import.d.ts +11 -0
- package/dist/auto-import.js +81 -0
- package/dist/breaks.d.ts +6 -0
- package/dist/breaks.js +36 -0
- package/dist/build.d.ts +7 -0
- package/dist/build.js +23 -2
- package/dist/checker/array-literals.d.ts +6 -0
- package/dist/checker/array-literals.js +59 -0
- package/dist/checker/assignments.d.ts +4 -0
- package/dist/checker/assignments.js +23 -3
- package/dist/checker/base.d.ts +8 -2
- package/dist/checker/base.js +7 -7
- package/dist/checker/builtins.js +22 -8
- package/dist/checker/bytes.d.ts +2 -2
- package/dist/checker/bytes.js +2 -2
- package/dist/checker/calls.js +3 -0
- package/dist/checker/channel-objects.d.ts +3 -0
- package/dist/checker/channel-objects.js +40 -0
- package/dist/checker/channels.d.ts +1 -0
- package/dist/checker/channels.js +17 -4
- package/dist/checker/checker.d.ts +1 -1
- package/dist/checker/checker.js +19 -0
- package/dist/checker/class-members.d.ts +22 -0
- package/dist/checker/class-members.js +105 -0
- package/dist/checker/classes.d.ts +5 -4
- package/dist/checker/classes.js +48 -13
- package/dist/checker/closing.d.ts +21 -0
- package/dist/checker/closing.js +164 -0
- package/dist/checker/completion.d.ts +3 -3
- package/dist/checker/completion.js +4 -3
- 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 +41 -5
- package/dist/checker/context.js +23 -0
- package/dist/checker/control-flow.d.ts +11 -5
- package/dist/checker/control-flow.js +65 -40
- package/dist/checker/declarations.d.ts +2 -2
- package/dist/checker/declarations.js +4 -2
- package/dist/checker/decode.d.ts +8 -0
- package/dist/checker/decode.js +32 -0
- 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 +6 -2
- package/dist/checker/expressions.js +20 -40
- package/dist/checker/flow.d.ts +25 -3
- package/dist/checker/flow.js +56 -12
- package/dist/checker/functions.d.ts +2 -2
- package/dist/checker/functions.js +9 -5
- package/dist/checker/helpers.d.ts +5 -2
- package/dist/checker/helpers.js +16 -4
- package/dist/checker/imports.js +5 -2
- package/dist/checker/iterables.d.ts +3 -1
- package/dist/checker/iterables.js +13 -0
- package/dist/checker/iterators.d.ts +4 -1
- package/dist/checker/iterators.js +12 -4
- package/dist/checker/json.d.ts +5 -0
- package/dist/checker/json.js +92 -0
- package/dist/checker/loops.d.ts +2 -0
- package/dist/checker/loops.js +50 -5
- package/dist/checker/markup.js +14 -2
- package/dist/checker/members.js +10 -1
- package/dist/checker/operators.d.ts +2 -2
- package/dist/checker/operators.js +7 -5
- package/dist/checker/queues.d.ts +2 -2
- package/dist/checker/queues.js +2 -2
- package/dist/checker/resolution.js +5 -3
- package/dist/checker/shared-state.d.ts +11 -0
- package/dist/checker/shared-state.js +37 -0
- package/dist/checker/signals.d.ts +8 -0
- package/dist/checker/signals.js +48 -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/topics.d.ts +6 -0
- package/dist/checker/topics.js +17 -0
- package/dist/checker/transfer.d.ts +3 -1
- package/dist/checker/transfer.js +25 -5
- package/dist/checker/try.d.ts +6 -0
- package/dist/checker/try.js +19 -0
- package/dist/checker/types/constructors.d.ts +1 -1
- package/dist/checker/types/constructors.js +2 -2
- package/dist/checker/types/generics.js +1 -1
- package/dist/checker/types/iterables.d.ts +13 -0
- package/dist/checker/types/iterables.js +52 -0
- package/dist/checker/types/model.d.ts +25 -0
- package/dist/checker/types/params.js +22 -3
- package/dist/checker/types/printing.js +4 -3
- package/dist/checker/types/relations.js +19 -25
- package/dist/checker/types/topic.d.ts +6 -0
- package/dist/checker/types/topic.js +23 -0
- package/dist/checker/types.d.ts +2 -0
- package/dist/checker/types.js +2 -0
- package/dist/checker/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +3 -1
- package/dist/cli.js +18 -36
- package/dist/codegen/attributes.d.ts +4 -0
- package/dist/codegen/attributes.js +24 -2
- package/dist/codegen/calls.d.ts +23 -0
- package/dist/codegen/calls.js +88 -7
- 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/control-statements.d.ts +10 -2
- package/dist/codegen/control-statements.js +56 -19
- package/dist/codegen/creation.js +5 -2
- package/dist/codegen/decode.d.ts +14 -0
- package/dist/codegen/decode.js +93 -0
- package/dist/codegen/early-returns.d.ts +4 -3
- package/dist/codegen/early-returns.js +17 -5
- package/dist/codegen/emitter.d.ts +13 -41
- package/dist/codegen/emitter.js +27 -8
- package/dist/codegen/expressions.d.ts +9 -0
- package/dist/codegen/expressions.js +15 -26
- package/dist/codegen/functions.d.ts +11 -1
- package/dist/codegen/functions.js +31 -3
- package/dist/codegen/js.d.ts +2 -2
- package/dist/codegen/js.js +5 -2
- 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 +42 -0
- package/dist/codegen/options.js +1 -0
- package/dist/codegen/reactive.d.ts +4 -59
- package/dist/codegen/reactive.js +34 -72
- package/dist/codegen/runtime.d.ts +7 -3
- package/dist/codegen/runtime.js +33 -14
- package/dist/codegen/shared.d.ts +22 -0
- package/dist/codegen/shared.js +93 -0
- package/dist/codegen/simple-statements.d.ts +11 -5
- package/dist/codegen/simple-statements.js +40 -15
- package/dist/codegen/statements.d.ts +5 -0
- package/dist/codegen/statements.js +99 -4
- package/dist/codegen/try.d.ts +5 -0
- package/dist/codegen/try.js +40 -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.d.ts +9 -0
- package/dist/compile.js +25 -8
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +10 -5
- package/dist/declarations.js +32 -6
- package/dist/decorator-copy.d.ts +1 -1
- package/dist/decorator-copy.js +6 -2
- 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/goroutines.d.ts +1 -1
- package/dist/goroutines.js +5 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +14 -5
- package/dist/inline-packages.d.ts +2 -1
- package/dist/inline-packages.js +9 -4
- package/dist/lexer/base.d.ts +2 -0
- package/dist/lexer/base.js +6 -1
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/literals.d.ts +5 -0
- package/dist/lexer/literals.js +51 -1
- package/dist/lexer/markup.js +3 -4
- package/dist/lexer/tables.js +2 -0
- package/dist/lexer/token.d.ts +4 -3
- package/dist/lexer/token.js +0 -2
- package/dist/loader.js +30 -8
- package/dist/names.d.ts +4 -0
- package/dist/names.js +29 -0
- package/dist/parser/base.d.ts +5 -0
- package/dist/parser/base.js +2 -1
- package/dist/parser/classes.d.ts +2 -0
- package/dist/parser/classes.js +88 -23
- package/dist/parser/control-flow.d.ts +8 -1
- package/dist/parser/control-flow.js +49 -26
- package/dist/parser/declarations.d.ts +5 -12
- package/dist/parser/declarations.js +14 -79
- package/dist/parser/decorators.d.ts +6 -2
- package/dist/parser/decorators.js +43 -19
- package/dist/parser/expressions.js +27 -3
- package/dist/parser/jumps.d.ts +11 -0
- package/dist/parser/jumps.js +77 -0
- package/dist/parser/literals.d.ts +9 -0
- package/dist/parser/literals.js +45 -6
- package/dist/parser/markup.js +1 -1
- package/dist/parser/params.d.ts +21 -0
- package/dist/parser/params.js +143 -0
- package/dist/parser/simple-statements.d.ts +2 -1
- package/dist/parser/simple-statements.js +38 -15
- package/dist/parser/statements.d.ts +5 -5
- package/dist/parser/statements.js +39 -35
- package/dist/parser/syntax.d.ts +11 -1
- package/dist/parser/syntax.js +16 -2
- package/dist/parser/types.js +8 -3
- package/dist/project-modules.d.ts +12 -0
- package/dist/project-modules.js +106 -0
- package/dist/run-file.d.ts +6 -0
- package/dist/run-file.js +111 -0
- package/dist/runtime/sources.js +13 -6
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +75 -0
- package/dist/ts-types.js +4 -1
- package/dist/typescript/convert-objects.d.ts +2 -1
- package/dist/typescript/convert-objects.js +18 -9
- package/dist/vite.js +12 -4
- package/dist/walk-types.js +1 -0
- package/dist/walk.d.ts +0 -2
- package/dist/walk.js +14 -31
- package/package.json +2 -2
- package/src/runtime/chan.js +15 -5
- package/src/runtime/channel.js +28 -0
- package/src/runtime/classes.js +32 -0
- package/src/runtime/core.js +10 -1
- package/src/runtime/decode.js +221 -0
- package/src/runtime/go.js +3 -0
- package/src/runtime/index.js +7 -0
- package/src/runtime/markup.js +27 -17
- package/src/runtime/select.js +14 -9
- package/src/runtime/shared.js +17 -0
- package/src/runtime/signals.js +59 -0
- package/src/runtime/threads.js +32 -5
- package/src/runtime/topic.js +118 -0
- package/src/runtime/try.js +45 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Scope } from "./context.js";
|
|
2
|
-
import { ControlFlowChecker } from "./control-flow.js";
|
|
3
2
|
import { isTerminating } from "./flow.js";
|
|
4
3
|
import { isUntyped, callSignature } from "./helpers.js";
|
|
5
4
|
import { awaitedResults, isPromise } from "./promise.js";
|
|
5
|
+
import { TaggedUnionChecker } from "./tagged-unions.js";
|
|
6
6
|
import { commonType, containsTypeParam, func, nonNull, UNKNOWN, } from "./types.js";
|
|
7
7
|
// Bodies of functions, arrow functions and func literals, and the results they return.
|
|
8
|
-
export class FunctionChecker extends
|
|
8
|
+
export class FunctionChecker extends TaggedUnionChecker {
|
|
9
9
|
/**
|
|
10
10
|
* Checks a function body and returns its result types: the declared ones, or those inferred from
|
|
11
11
|
* the `return` statements when `results` is `null`.
|
|
@@ -30,7 +30,9 @@ export class FunctionChecker extends ControlFlowChecker {
|
|
|
30
30
|
// A generic function: its type parameters are types in its body.
|
|
31
31
|
for (const typeParam of type.typeParams)
|
|
32
32
|
this.scope.types.set(typeParam.name, typeParam);
|
|
33
|
-
params.forEach((param, i) =>
|
|
33
|
+
params.forEach((param, i) =>
|
|
34
|
+
// A bound property of a component, `bind active string`, changes as its state does.
|
|
35
|
+
this.declareValue(param.name, param.bind ? 'state' : (options.paramKind ?? 'param'), type.params[i] ?? UNKNOWN));
|
|
34
36
|
if (body.kind === 'BlockStatement') {
|
|
35
37
|
this.checkStatementList(body.body, false);
|
|
36
38
|
options.inspect?.(this.scope);
|
|
@@ -87,9 +89,11 @@ export class FunctionChecker extends ControlFlowChecker {
|
|
|
87
89
|
}
|
|
88
90
|
stableFlow() {
|
|
89
91
|
const flow = new Map();
|
|
90
|
-
|
|
92
|
+
// A path keeps its narrowing when its variable does, like `user.boss` of a constant `user`.
|
|
93
|
+
for (const [place, type] of this.flow) {
|
|
94
|
+
const { binding } = place;
|
|
91
95
|
if (binding.kind === 'const' || !this.assigned.has(binding.name))
|
|
92
|
-
flow.set(
|
|
96
|
+
flow.set(place, type);
|
|
93
97
|
}
|
|
94
98
|
return flow;
|
|
95
99
|
}
|
|
@@ -11,7 +11,10 @@ export declare function ownStatements(body: ast.BlockStatement): ast.Statement[]
|
|
|
11
11
|
* the type of a DOM node), lists of them, or null.
|
|
12
12
|
*/
|
|
13
13
|
export declare function isContent(given: Type, node: Type): boolean;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Values that setAttribute accepts: text, numbers, bools, or null to leave it out. A literal is
|
|
16
|
+
* one of them, and so is a union of such, `type Status "shown" | "aborted"`.
|
|
17
|
+
*/
|
|
15
18
|
export declare function isAttributeValue(type: Type): boolean;
|
|
16
19
|
export declare function isUntyped(type: Type): boolean;
|
|
17
20
|
export declare function isNumeric(type: Type): boolean;
|
|
@@ -26,7 +29,7 @@ export declare function callSignature(type: Type): FunctionType | null;
|
|
|
26
29
|
/** Whether the expected type has literal members: then a literal gets a literal type. */
|
|
27
30
|
export declare function expectsLiteral(expected: Type | null): boolean;
|
|
28
31
|
export declare function countValues(count: number): string;
|
|
29
|
-
/** A call, or `await` of a call or of `go`: it may give several values. */
|
|
32
|
+
/** A call, `decode<T>(x)`, `try`, or `await` of a call or of `go`: it may give several values. */
|
|
30
33
|
export declare function isCall(node: ast.Expression): boolean;
|
|
31
34
|
/** How to name an expression in messages: `divide()`, `stats.minMax()`, or `this expression`. */
|
|
32
35
|
export declare function callName(node: ast.Expression): string;
|
package/dist/checker/helpers.js
CHANGED
|
@@ -61,7 +61,10 @@ export function isContent(given, node) {
|
|
|
61
61
|
return false;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Values that setAttribute accepts: text, numbers, bools, or null to leave it out. A literal is
|
|
66
|
+
* one of them, and so is a union of such, `type Status "shown" | "aborted"`.
|
|
67
|
+
*/
|
|
65
68
|
export function isAttributeValue(type) {
|
|
66
69
|
const value = nonNull(type);
|
|
67
70
|
return (isUntyped(value) ||
|
|
@@ -69,8 +72,10 @@ export function isAttributeValue(type) {
|
|
|
69
72
|
value.kind === 'number' ||
|
|
70
73
|
value.kind === 'bigint' ||
|
|
71
74
|
value.kind === 'bool' ||
|
|
75
|
+
value.kind === 'literal' ||
|
|
72
76
|
value.kind === 'null' ||
|
|
73
|
-
value.kind === 'never'
|
|
77
|
+
value.kind === 'never' ||
|
|
78
|
+
(value.kind === 'union' && value.types.every(isAttributeValue)));
|
|
74
79
|
}
|
|
75
80
|
export function isUntyped(type) {
|
|
76
81
|
return type.kind === 'any' || type.kind === 'unknown';
|
|
@@ -123,12 +128,15 @@ export function countValues(count) {
|
|
|
123
128
|
return 'no values';
|
|
124
129
|
return count === 1 ? '1 value' : `${count} values`;
|
|
125
130
|
}
|
|
126
|
-
/** A call, or `await` of a call or of `go`: it may give several values. */
|
|
131
|
+
/** A call, `decode<T>(x)`, `try`, or `await` of a call or of `go`: it may give several values. */
|
|
127
132
|
export function isCall(node) {
|
|
128
133
|
if (node.kind === 'UnaryExpression' && node.operator === 'await') {
|
|
129
134
|
return node.argument.kind === 'CallExpression' || node.argument.kind === 'GoExpression';
|
|
130
135
|
}
|
|
131
|
-
return
|
|
136
|
+
// `return decode<User>(body)` and `return try f()` pass on the value and the error.
|
|
137
|
+
return (node.kind === 'CallExpression' ||
|
|
138
|
+
node.kind === 'DecodeExpression' ||
|
|
139
|
+
node.kind === 'TryExpression');
|
|
132
140
|
}
|
|
133
141
|
/** How to name an expression in messages: `divide()`, `stats.minMax()`, or `this expression`. */
|
|
134
142
|
export function callName(node) {
|
|
@@ -139,6 +147,10 @@ export function callName(node) {
|
|
|
139
147
|
return callName(node.call);
|
|
140
148
|
if (node.kind === 'StreamChanExpression')
|
|
141
149
|
return 'chan(stream)';
|
|
150
|
+
if (node.kind === 'DecodeExpression')
|
|
151
|
+
return 'decode';
|
|
152
|
+
if (node.kind === 'TryExpression')
|
|
153
|
+
return 'try';
|
|
142
154
|
if (node.kind !== 'CallExpression')
|
|
143
155
|
return 'this expression';
|
|
144
156
|
const { callee } = node;
|
package/dist/checker/imports.js
CHANGED
|
@@ -54,8 +54,11 @@ export class ImportChecker extends CheckerBase {
|
|
|
54
54
|
if (!found) {
|
|
55
55
|
this.error(`"${name}" is not exported by "${specifier}"`, specifierNode.imported);
|
|
56
56
|
}
|
|
57
|
-
if (value !== undefined)
|
|
58
|
-
this.declareValue(specifierNode.local, 'import', value);
|
|
57
|
+
if (value !== undefined) {
|
|
58
|
+
const binding = this.declareValue(specifierNode.local, 'import', value);
|
|
59
|
+
if (exports.shared?.has(name))
|
|
60
|
+
binding.shared = specifier;
|
|
61
|
+
}
|
|
59
62
|
if (type !== undefined)
|
|
60
63
|
this.declareType(specifierNode.local, type);
|
|
61
64
|
if (decorator !== undefined)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ObjectType, type Type } from './types.ts';
|
|
2
2
|
/** `Map<K, V>` or `ReadonlyMap<K, V>`: `for key, value in m`, and `for value in m` for the values. */
|
|
3
3
|
export declare function isMap(type: Type): boolean;
|
|
4
4
|
/** The key and the value of `for key, value in x`: of a Map, or of an array-like with positions. */
|
|
5
5
|
export declare function loopEntry(type: ObjectType): [Type, Type] | null;
|
|
6
|
+
/** What `for v in x` waits for, of an async iterable that cannot be gone over at once, or null. */
|
|
7
|
+
export declare function asyncIterated(type: Type): Type | null;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { bindParams, substitute } from "./types.js";
|
|
1
2
|
// What `for ... in` goes over besides arrays, strings and channels: the iterables of the lib files,
|
|
2
3
|
// whose values come from `[Symbol.iterator]()`. A Map gives its keys and values, as a map in Go.
|
|
4
|
+
// An async iterable, such as a stream of Node, gives the values that the Promises of
|
|
5
|
+
// `[Symbol.asyncIterator]()` give, and the loop waits for them, as `for await` of JS.
|
|
3
6
|
/** `Map<K, V>` or `ReadonlyMap<K, V>`: `for key, value in m`, and `for value in m` for the values. */
|
|
4
7
|
export function isMap(type) {
|
|
5
8
|
const name = type.kind === 'object' ? (type.instanceOf?.template.name ?? type.name) : null;
|
|
@@ -15,3 +18,13 @@ export function loopEntry(type) {
|
|
|
15
18
|
// A typed array or a NodeList has positions, and `entries()`, as an array has.
|
|
16
19
|
return numberIndex && type.members.has('entries') ? [{ kind: 'number' }, iterates] : null;
|
|
17
20
|
}
|
|
21
|
+
/** What `for v in x` waits for, of an async iterable that cannot be gone over at once, or null. */
|
|
22
|
+
export function asyncIterated(type) {
|
|
23
|
+
if (type.kind === 'object')
|
|
24
|
+
return type.iterates ? null : (type.asyncIterates ?? null);
|
|
25
|
+
if (type.kind !== 'class' || !type.info.asyncIterates)
|
|
26
|
+
return null;
|
|
27
|
+
// TypeScript lists inherited members with the type arguments of the base class already.
|
|
28
|
+
const { info } = type;
|
|
29
|
+
return substitute(info.asyncIterates, bindParams(info.typeParams, type.args ?? info.typeParams));
|
|
30
|
+
}
|
|
@@ -8,6 +8,9 @@ export declare abstract class IteratorChecker extends GoForChecker {
|
|
|
8
8
|
/** The rules of iterators in the whole program; Checker.run calls it. */
|
|
9
9
|
protected checkIterators(): void;
|
|
10
10
|
private checkIterator;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Where `yield` is used in the body of an iterator: `nested` inside a function in it, or in `try`,
|
|
13
|
+
* which compiles to a function that runs its expression (codegen/try.ts).
|
|
14
|
+
*/
|
|
12
15
|
private checkYields;
|
|
13
16
|
}
|
|
@@ -68,14 +68,21 @@ export class IteratorChecker extends GoForChecker {
|
|
|
68
68
|
if (!yieldsBool) {
|
|
69
69
|
this.error('the yield of an iterator is func(T) bool or func(K, V) bool, e.g. func(yield func(number) bool)', type ?? param);
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
if (node.body)
|
|
72
|
+
this.checkYields(node.body, false);
|
|
72
73
|
}
|
|
73
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Where `yield` is used in the body of an iterator: `nested` inside a function in it, or in `try`,
|
|
76
|
+
* which compiles to a function that runs its expression (codegen/try.ts).
|
|
77
|
+
*/
|
|
74
78
|
checkYields(node, nested) {
|
|
75
79
|
if (isYieldCall(node)) {
|
|
76
|
-
if (nested) {
|
|
80
|
+
if (nested === 'function') {
|
|
77
81
|
this.error('yield is called in a function inside the iterator: a JS generator yields only in its own body, so loop there, e.g. for x in items { if !yield(x) { return } }', node.callee);
|
|
78
82
|
}
|
|
83
|
+
else if (nested === 'try') {
|
|
84
|
+
this.error('yield cannot be in try, which compiles to a function: a JS generator yields only in its own body, so call yield outside of it', node.callee);
|
|
85
|
+
}
|
|
79
86
|
for (const argument of node.arguments)
|
|
80
87
|
this.checkYields(argument, nested);
|
|
81
88
|
return;
|
|
@@ -89,7 +96,8 @@ export class IteratorChecker extends GoForChecker {
|
|
|
89
96
|
isIterator(node.params)) {
|
|
90
97
|
return;
|
|
91
98
|
}
|
|
92
|
-
const inner = nested ||
|
|
99
|
+
const inner = nested ||
|
|
100
|
+
(NESTED.has(node.kind) ? 'function' : node.kind === 'TryExpression' ? 'try' : false);
|
|
93
101
|
forEachChild(node, (child) => this.checkYields(child, inner));
|
|
94
102
|
}
|
|
95
103
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Type } from './types.ts';
|
|
2
|
+
/** Why a value of the type cannot come from JSON, or null if it can. */
|
|
3
|
+
export declare function notDecoded(type: Type, seen?: Set<Type>): string | null;
|
|
4
|
+
/** Whether a value of the type is always a string: then decode reads it as JSON text. */
|
|
5
|
+
export declare function isText(type: Type): boolean;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { isQueue, isTopic, memberNames, typedArrayOf, typeToString } from "./types.js";
|
|
2
|
+
// What JSON can hold, for `decode<T>(x)` (decode.ts): numbers, strings, bool, null, literals,
|
|
3
|
+
// arrays, unions and objects without methods. A string is JSON text to decode.
|
|
4
|
+
/** Types that JSON has no way to hold, by name, with what to decode instead. */
|
|
5
|
+
const NOT_IN_JSON = new Map([
|
|
6
|
+
['Date', 'JSON has no Date: decode a string and make the Date from it, e.g. new Date(s)'],
|
|
7
|
+
['Map', 'JSON has no Map: decode an object or an array of pairs and make the Map from it'],
|
|
8
|
+
['Set', 'JSON has no Set: decode an array and make the Set from it, e.g. new Set(items)'],
|
|
9
|
+
['RegExp', 'JSON has no RegExp: decode a string and make it with new RegExp(s)'],
|
|
10
|
+
['Promise', 'a Promise cannot come from JSON'],
|
|
11
|
+
['chan', 'a channel cannot come from JSON'],
|
|
12
|
+
['chan<-', 'a channel cannot come from JSON'],
|
|
13
|
+
['<-chan', 'a channel cannot come from JSON'],
|
|
14
|
+
]);
|
|
15
|
+
/** Why a value of the type cannot come from JSON, or null if it can. */
|
|
16
|
+
export function notDecoded(type, seen = new Set()) {
|
|
17
|
+
if (seen.has(type))
|
|
18
|
+
return null;
|
|
19
|
+
seen.add(type);
|
|
20
|
+
const inner = (types) => {
|
|
21
|
+
for (const each of types) {
|
|
22
|
+
const reason = notDecoded(each, seen);
|
|
23
|
+
if (reason !== null)
|
|
24
|
+
return reason;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
};
|
|
28
|
+
switch (type.kind) {
|
|
29
|
+
case 'number':
|
|
30
|
+
case 'string':
|
|
31
|
+
case 'bool':
|
|
32
|
+
case 'null':
|
|
33
|
+
case 'literal':
|
|
34
|
+
case 'any':
|
|
35
|
+
case 'unknown':
|
|
36
|
+
return null;
|
|
37
|
+
case 'bigint':
|
|
38
|
+
return 'JSON has no bigint: decode a string and make it with BigInt(s)';
|
|
39
|
+
case 'nullable':
|
|
40
|
+
return inner([type.type]);
|
|
41
|
+
case 'array':
|
|
42
|
+
return inner([type.element]);
|
|
43
|
+
case 'union':
|
|
44
|
+
return inner(type.types);
|
|
45
|
+
case 'param':
|
|
46
|
+
return `${type.name} is a type parameter: decode needs a type known when the program compiles`;
|
|
47
|
+
case 'function':
|
|
48
|
+
return 'a function cannot come from JSON';
|
|
49
|
+
case 'component':
|
|
50
|
+
return 'a component cannot come from JSON';
|
|
51
|
+
case 'classValue':
|
|
52
|
+
return `the class ${type.info.name} cannot come from JSON`;
|
|
53
|
+
case 'class':
|
|
54
|
+
return (NOT_IN_JSON.get(type.info.name) ??
|
|
55
|
+
`decode gives plain objects, without the methods of the class ${type.info.name}: decode an interface with its fields`);
|
|
56
|
+
case 'object': {
|
|
57
|
+
const name = type.instanceOf?.template.name ?? type.name;
|
|
58
|
+
const known = name === null ? undefined : NOT_IN_JSON.get(name);
|
|
59
|
+
if (known !== undefined)
|
|
60
|
+
return known;
|
|
61
|
+
if (isQueue(type))
|
|
62
|
+
return 'JSON has no queue: decode an array and make the queue from it';
|
|
63
|
+
if (isTopic(type))
|
|
64
|
+
return 'a topic cannot come from JSON';
|
|
65
|
+
if (typedArrayOf(type) !== null)
|
|
66
|
+
return `JSON has no ${typeToString(type)}`;
|
|
67
|
+
if (type.call || type.construct) {
|
|
68
|
+
return `${typeToString(type)} is a function and cannot come from JSON`;
|
|
69
|
+
}
|
|
70
|
+
const members = [...memberNames(type.members)].flatMap((name) => type.members.get(name) ?? []);
|
|
71
|
+
if (members.some((member) => member.method)) {
|
|
72
|
+
return `${typeToString(type)} has methods, and JSON gives only data: decode an interface with its fields`;
|
|
73
|
+
}
|
|
74
|
+
return inner([...members.map((member) => member.type), ...(type.index ? [type.index] : [])]);
|
|
75
|
+
}
|
|
76
|
+
default:
|
|
77
|
+
return `${typeToString(type)} cannot come from JSON`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Whether a value of the type is always a string: then decode reads it as JSON text. */
|
|
81
|
+
export function isText(type) {
|
|
82
|
+
switch (type.kind) {
|
|
83
|
+
case 'string':
|
|
84
|
+
return true;
|
|
85
|
+
case 'literal':
|
|
86
|
+
return typeof type.value === 'string';
|
|
87
|
+
case 'union':
|
|
88
|
+
return type.types.every(isText);
|
|
89
|
+
default:
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
package/dist/checker/loops.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type * as ast from '../ast.ts';
|
|
|
2
2
|
import { BytesChecker } from './bytes.ts';
|
|
3
3
|
export declare abstract class LoopChecker extends BytesChecker {
|
|
4
4
|
protected checkForIn(node: ast.ForInStatement): void;
|
|
5
|
+
/** `with key todo.id`: a string or a number, by which the blocks of the list are found again. */
|
|
6
|
+
private checkListKey;
|
|
5
7
|
/**
|
|
6
8
|
* The key and the value of a loop over an iterator: what its `yield` takes. Of two, one variable
|
|
7
9
|
* gets the second, as with a Map; of one, there is no key.
|
package/dist/checker/loops.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { assignedNames } from "../names.js";
|
|
2
1
|
import { BytesChecker } from "./bytes.js";
|
|
3
2
|
import { chanDirection, elementOf, isChan } from "./channel.js";
|
|
4
|
-
import { isMap, loopEntry } from "./iterables.js";
|
|
5
|
-
import { ANY, isNullable, NUMBER, STRING, typeToString, UNKNOWN, } from "./types.js";
|
|
3
|
+
import { asyncIterated, isMap, loopEntry } from "./iterables.js";
|
|
4
|
+
import { ANY, isNullable, isTopic, NUMBER, STRING, topicElement, typeToString, UNKNOWN, } from "./types.js";
|
|
6
5
|
// `for ... in`: over arrays, strings, the iterables of the lib files (a Map gives its keys and
|
|
7
|
-
// values), channels, which wait for their values,
|
|
6
|
+
// values), channels and async iterables, such as the streams of Node, which wait for their values,
|
|
7
|
+
// and iterators, `func(yield func(T) bool)`.
|
|
8
8
|
export class LoopChecker extends BytesChecker {
|
|
9
9
|
checkForIn(node) {
|
|
10
10
|
const iterable = this.checkValue(node.iterable);
|
|
@@ -26,6 +26,16 @@ export class LoopChecker extends BytesChecker {
|
|
|
26
26
|
this.error(`${typeToString(iterable)} has no keys: loop over its values, e.g. "for value in items"`, node.key);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
else if (asyncIterated(iterable) !== null) {
|
|
30
|
+
// `for await` of JS: each value waits for the Promise of next().
|
|
31
|
+
value = asyncIterated(iterable);
|
|
32
|
+
if (this.fn && !this.fn.isAsync) {
|
|
33
|
+
this.error(`"for ... in" ${typeToString(iterable)} waits for its values, so it is allowed only in async functions and at the top level of a module`, node.iterable);
|
|
34
|
+
}
|
|
35
|
+
if (node.key) {
|
|
36
|
+
this.error(`${typeToString(iterable)} has no keys: loop over its values, e.g. "for value in items"`, node.key);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
29
39
|
else if (iterable.kind === 'function' && iterable.iterator) {
|
|
30
40
|
[key, value] = this.yielded(iterable, node);
|
|
31
41
|
}
|
|
@@ -41,6 +51,15 @@ export class LoopChecker extends BytesChecker {
|
|
|
41
51
|
if (node.key)
|
|
42
52
|
this.error('a channel has no keys: write "for value in ch"', node.key);
|
|
43
53
|
}
|
|
54
|
+
else if (isTopic(iterable)) {
|
|
55
|
+
// Each value waits for another tab to send one, until the topic is closed.
|
|
56
|
+
value = topicElement(iterable);
|
|
57
|
+
if (this.fn && !this.fn.isAsync) {
|
|
58
|
+
this.error('"for ... in" a topic waits for its values, so it is allowed only in async functions and at the top level of a module', node.iterable);
|
|
59
|
+
}
|
|
60
|
+
if (node.key)
|
|
61
|
+
this.error('a topic has no keys: write "for value in t"', node.key);
|
|
62
|
+
}
|
|
44
63
|
else if (iterable.kind === 'string') {
|
|
45
64
|
value = STRING;
|
|
46
65
|
}
|
|
@@ -58,13 +77,22 @@ export class LoopChecker extends BytesChecker {
|
|
|
58
77
|
if (node.key)
|
|
59
78
|
this.declareValue(node.key, 'loop', key);
|
|
60
79
|
this.declareValue(node.value, 'loop', value);
|
|
61
|
-
|
|
80
|
+
if (node.withKey)
|
|
81
|
+
this.checkListKey(node.withKey);
|
|
82
|
+
this.dropNarrowing(node.body);
|
|
62
83
|
const entry = this.flow;
|
|
63
84
|
this.flow = new Map(entry);
|
|
64
85
|
this.checkBlock(node.body.body);
|
|
65
86
|
this.flow = entry;
|
|
66
87
|
});
|
|
67
88
|
}
|
|
89
|
+
/** `with key todo.id`: a string or a number, by which the blocks of the list are found again. */
|
|
90
|
+
checkListKey(key) {
|
|
91
|
+
const type = this.checkValue(key);
|
|
92
|
+
if (!isListKey(type)) {
|
|
93
|
+
this.error(`the key of a list must be a string or a number, not ${typeToString(type)}: e.g. "with key todo.id"`, key);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
68
96
|
/**
|
|
69
97
|
* The key and the value of a loop over an iterator: what its `yield` takes. Of two, one variable
|
|
70
98
|
* gets the second, as with a Map; of one, there is no key.
|
|
@@ -80,3 +108,20 @@ export class LoopChecker extends BytesChecker {
|
|
|
80
108
|
return [UNKNOWN, taken[0] ?? UNKNOWN];
|
|
81
109
|
}
|
|
82
110
|
}
|
|
111
|
+
/** A key of a list: a string, a number, a literal of one or a union of them. */
|
|
112
|
+
function isListKey(type) {
|
|
113
|
+
switch (type.kind) {
|
|
114
|
+
case 'string':
|
|
115
|
+
case 'number':
|
|
116
|
+
case 'bigint':
|
|
117
|
+
case 'any':
|
|
118
|
+
case 'unknown':
|
|
119
|
+
return true;
|
|
120
|
+
case 'literal':
|
|
121
|
+
return typeof type.value !== 'boolean';
|
|
122
|
+
case 'union':
|
|
123
|
+
return type.types.every(isListKey);
|
|
124
|
+
default:
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
package/dist/checker/markup.js
CHANGED
|
@@ -4,8 +4,10 @@ import { domProperty, elementType } from "./dom.js";
|
|
|
4
4
|
import { svgElementType } from "./dom-svg.js";
|
|
5
5
|
import { isContent, isAttributeValue, isUntyped } from "./helpers.js";
|
|
6
6
|
import { OperatorChecker } from "./operators.js";
|
|
7
|
-
import { BOOL, func, STRING, typeToString } from "./types.js";
|
|
7
|
+
import { arrayOf, BOOL, func, nullable, STRING, typeToString } from "./types.js";
|
|
8
8
|
// Markup: elements, their children and attributes, `bind:` and event handlers.
|
|
9
|
+
/** The type of a list of classes, `class={[styles.tab, active ? styles.active : null]}`. */
|
|
10
|
+
const CLASS_LIST = arrayOf(nullable(STRING));
|
|
9
11
|
export class MarkupChecker extends OperatorChecker {
|
|
10
12
|
/** Whether the markup being checked is inside `<svg>`: its elements are SVG then. */
|
|
11
13
|
inSvg = false;
|
|
@@ -98,7 +100,17 @@ export class MarkupChecker extends OperatorChecker {
|
|
|
98
100
|
}
|
|
99
101
|
if (value?.kind === 'EventHandler')
|
|
100
102
|
return;
|
|
101
|
-
const
|
|
103
|
+
const list = name === 'class' && value?.kind === 'ArrayLiteral' ? CLASS_LIST : null;
|
|
104
|
+
const valueType = value === null
|
|
105
|
+
? BOOL
|
|
106
|
+
: value.kind === 'StringLiteral'
|
|
107
|
+
? STRING
|
|
108
|
+
: this.checkValue(value, list);
|
|
109
|
+
// `class={[styles.tab, active ? styles.active : null]}`: the classes that are not null.
|
|
110
|
+
if (name === 'class' && valueType.kind === 'array') {
|
|
111
|
+
this.expectAssignable(valueType, CLASS_LIST, value, ' for attribute "class"');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
102
114
|
if (name === 'style') {
|
|
103
115
|
if (!isUntyped(valueType) && valueType.kind !== 'string' && valueType.kind !== 'object') {
|
|
104
116
|
this.error(`style must be a string or an object, not ${typeToString(valueType)}`, attribute);
|
package/dist/checker/members.js
CHANGED
|
@@ -16,6 +16,7 @@ export class MemberChecker extends DecoratorMemberChecker {
|
|
|
16
16
|
return [this.checkSuperCall(node), false];
|
|
17
17
|
let [callee, shortCircuits] = this.chainPart(node.callee);
|
|
18
18
|
this.checkDecoratorMutation(node);
|
|
19
|
+
this.checkSharedMutation(node);
|
|
19
20
|
if (isNullable(callee)) {
|
|
20
21
|
if (node.optional)
|
|
21
22
|
shortCircuits = true;
|
|
@@ -39,7 +40,15 @@ export class MemberChecker extends DecoratorMemberChecker {
|
|
|
39
40
|
}
|
|
40
41
|
if (node.kind === 'MemberExpression') {
|
|
41
42
|
const member = this.findMember(object, node.property.name, node.property);
|
|
42
|
-
|
|
43
|
+
const { name } = node.property;
|
|
44
|
+
if (member !== 'any' && member?.accessor === 'set') {
|
|
45
|
+
this.error(`cannot read "${name}": it has a setter and no getter`, node.property);
|
|
46
|
+
}
|
|
47
|
+
else if (member !== 'any' && member?.abstract && node.object.kind === 'SuperExpression') {
|
|
48
|
+
this.error(`super.${name} is abstract in ${member.owner?.name ?? 'the base class'}: it has no code to run`, node.property);
|
|
49
|
+
}
|
|
50
|
+
const type = member === 'any' ? ANY : (member?.type ?? UNKNOWN);
|
|
51
|
+
return [this.pathType(node, object, type), shortCircuits];
|
|
43
52
|
}
|
|
44
53
|
const keyed = this.keyedIndex(object, node.index);
|
|
45
54
|
if (keyed)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { ArrayLiteralChecker } from './array-literals.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
|
-
export declare abstract class OperatorChecker extends
|
|
4
|
+
export declare abstract class OperatorChecker extends ArrayLiteralChecker {
|
|
5
5
|
/** In the operation of a `select` case, a channel may be null: that case never goes on. */
|
|
6
6
|
protected inSelectCase: boolean;
|
|
7
7
|
protected checkUnary(node: ast.UnaryExpression): Type;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { bigintBinary, bigintUnary } from "./bigint.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ArrayLiteralChecker } from "./array-literals.js";
|
|
3
3
|
import { withNarrowing } from "./flow.js";
|
|
4
4
|
import { isUntyped, isNumeric, instanceTypeOf } from "./helpers.js";
|
|
5
5
|
import { chanDirection, elementOf, isChan, received } from "./channel.js";
|
|
6
6
|
import { awaited } from "./promise.js";
|
|
7
|
-
import { ANY, BOOL, commonType, isAssignable, isComparable, isNullable, nonNull, nullable, NUMBER, STRING, typeToString, UNKNOWN, widenLiterals, } from "./types.js";
|
|
7
|
+
import { ANY, BOOL, commonType, isAssignable, isComparable, isNullable, isTopic, nonNull, nullable, NUMBER, STRING, topicElement, typeToString, UNKNOWN, widenLiterals, } from "./types.js";
|
|
8
8
|
// Unary, binary and conditional operators.
|
|
9
|
-
export class OperatorChecker extends
|
|
9
|
+
export class OperatorChecker extends ArrayLiteralChecker {
|
|
10
10
|
/** In the operation of a `select` case, a channel may be null: that case never goes on. */
|
|
11
11
|
inSelectCase = false;
|
|
12
12
|
checkUnary(node) {
|
|
@@ -19,8 +19,8 @@ export class OperatorChecker extends ExpressionChecker {
|
|
|
19
19
|
return STRING;
|
|
20
20
|
// What the Promise gives; the results of an async function are unpacked from it.
|
|
21
21
|
case 'await':
|
|
22
|
-
if (isChan(type)) {
|
|
23
|
-
this.error(
|
|
22
|
+
if (isChan(type) || isTopic(type)) {
|
|
23
|
+
this.error(`"await" does not wait for a ${isChan(type) ? 'channel' : 'topic'}: receive from it, e.g. "<-ch"`, node);
|
|
24
24
|
}
|
|
25
25
|
return awaited(type);
|
|
26
26
|
case '<-': {
|
|
@@ -31,6 +31,8 @@ export class OperatorChecker extends ExpressionChecker {
|
|
|
31
31
|
}
|
|
32
32
|
return received(elementOf(channel));
|
|
33
33
|
}
|
|
34
|
+
if (isTopic(channel))
|
|
35
|
+
return received(topicElement(channel));
|
|
34
36
|
if (!isUntyped(type)) {
|
|
35
37
|
this.error(`"<-" receives from a channel, not from ${typeToString(type)}`, node.argument);
|
|
36
38
|
}
|
package/dist/checker/queues.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { SignalChecker } from './signals.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
|
-
export declare abstract class QueueChecker extends
|
|
4
|
+
export declare abstract class QueueChecker extends SignalChecker {
|
|
5
5
|
protected checkQueue(node: ast.QueueExpression): Type;
|
|
6
6
|
}
|
package/dist/checker/queues.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SignalChecker } from "./signals.js";
|
|
2
2
|
import { isUntyped } from "./helpers.js";
|
|
3
3
|
import { arrayOf, queueOf, typeToString } from "./types.js";
|
|
4
4
|
// Queues: `queue<number>()` makes an empty one, `queue<number>(items)` one with the values of an
|
|
5
5
|
// array or of another iterable. The members of `queue T` are in types/queue.ts.
|
|
6
|
-
export class QueueChecker extends
|
|
6
|
+
export class QueueChecker extends SignalChecker {
|
|
7
7
|
checkQueue(node) {
|
|
8
8
|
const element = this.resolveType(node.element);
|
|
9
9
|
if (node.items) {
|
|
@@ -2,7 +2,7 @@ import { holdsBigInts, TYPED_ELEMENTS } from "../typed-arrays.js";
|
|
|
2
2
|
import { chanOf } from "./channel.js";
|
|
3
3
|
import { ClassChecker } from "./classes.js";
|
|
4
4
|
import { CONTENT } from "./dom.js";
|
|
5
|
-
import { ANY, arrayOf, BIGINT, bindParams, builtInTypedArray, classInstance, func, instantiate, isAssignable, isNullable, literal, nullable, NUMBER, queueOf, substitute, typeToString, union, UNKNOWN, } from "./types.js";
|
|
5
|
+
import { ANY, arrayOf, BIGINT, bindParams, builtInTypedArray, classInstance, func, instantiate, isAssignable, isNullable, literal, nullable, NUMBER, queueOf, topicOf, substitute, typeToString, union, UNKNOWN, } from "./types.js";
|
|
6
6
|
// Type annotations as types: names, aliases, generics with their arguments; and the type a variable gets from its value.
|
|
7
7
|
export class TypeResolver extends ClassChecker {
|
|
8
8
|
resolveType(node) {
|
|
@@ -17,6 +17,8 @@ export class TypeResolver extends ClassChecker {
|
|
|
17
17
|
return chanOf(this.resolveType(node.element), node.direction);
|
|
18
18
|
case 'QueueType':
|
|
19
19
|
return queueOf(this.resolveType(node.element));
|
|
20
|
+
case 'TopicType':
|
|
21
|
+
return topicOf(this.resolveType(node.element));
|
|
20
22
|
case 'NullableType':
|
|
21
23
|
return nullable(this.resolveType(node.type));
|
|
22
24
|
case 'FuncType':
|
|
@@ -81,8 +83,8 @@ export class TypeResolver extends ClassChecker {
|
|
|
81
83
|
if (entry === undefined) {
|
|
82
84
|
this.error(this.lookupValue(name.name)
|
|
83
85
|
? `"${name.name}" is a value, not a type`
|
|
84
|
-
: name.name === 'queue'
|
|
85
|
-
?
|
|
86
|
+
: name.name === 'queue' || name.name === 'topic'
|
|
87
|
+
? `a ${name.name} type is written with the type of its values after it, e.g. "${name.name} number"`
|
|
86
88
|
: `unknown type "${name.name}"`, name);
|
|
87
89
|
return UNKNOWN;
|
|
88
90
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { ImportChecker } from './imports.ts';
|
|
3
|
+
export declare abstract class SharedStateChecker extends ImportChecker {
|
|
4
|
+
/** `items = []` or `items[0].done = true` in a module that imported `items`. */
|
|
5
|
+
protected checkSharedWrite(target: ast.Expression): boolean;
|
|
6
|
+
/** `items.push(x)`: a method that changes state that another module shares. */
|
|
7
|
+
protected checkSharedMutation(node: ast.CallExpression): void;
|
|
8
|
+
/** The import that a target writes into, `items` of `items[0].done`, if it is shared state. */
|
|
9
|
+
private importedShared;
|
|
10
|
+
private sharedError;
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mayChange } from "../methods.js";
|
|
2
|
+
import { ImportChecker } from "./imports.js";
|
|
3
|
+
// State shared by components: `state items []Item = []` at the top level of a module. Its module
|
|
4
|
+
// changes it anywhere; other modules import it to read, and change it only by calling a function
|
|
5
|
+
// of its module, so that the module keeps its data consistent, as a decorator keeps its state.
|
|
6
|
+
export class SharedStateChecker extends ImportChecker {
|
|
7
|
+
/** `items = []` or `items[0].done = true` in a module that imported `items`. */
|
|
8
|
+
checkSharedWrite(target) {
|
|
9
|
+
const shared = this.importedShared(target);
|
|
10
|
+
if (shared)
|
|
11
|
+
this.sharedError(shared, target);
|
|
12
|
+
return shared !== null;
|
|
13
|
+
}
|
|
14
|
+
/** `items.push(x)`: a method that changes state that another module shares. */
|
|
15
|
+
checkSharedMutation(node) {
|
|
16
|
+
const { callee } = node;
|
|
17
|
+
if (callee.kind !== 'MemberExpression')
|
|
18
|
+
return;
|
|
19
|
+
const shared = this.importedShared(callee.object);
|
|
20
|
+
if (shared && mayChange(this.typeOfChecked(callee.object), callee.property.name)) {
|
|
21
|
+
this.sharedError(shared, node);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The import that a target writes into, `items` of `items[0].done`, if it is shared state. */
|
|
25
|
+
importedShared(target) {
|
|
26
|
+
let root = target;
|
|
27
|
+
while (root.kind === 'MemberExpression' || root.kind === 'IndexExpression')
|
|
28
|
+
root = root.object;
|
|
29
|
+
if (root.kind !== 'Identifier')
|
|
30
|
+
return null;
|
|
31
|
+
const binding = this.lookupValue(root.name);
|
|
32
|
+
return binding?.shared ? binding : null;
|
|
33
|
+
}
|
|
34
|
+
sharedError(binding, node) {
|
|
35
|
+
this.error(`"${binding.name}" is state of "${binding.shared}", which is read-only here; change it with a function that "${binding.shared}" exports`, node);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { ChannelChecker } from './channels.ts';
|
|
3
|
+
import { type Type } from './types.ts';
|
|
4
|
+
export declare abstract class SignalChecker extends ChannelChecker {
|
|
5
|
+
protected checkCall(node: ast.CallExpression, callee: Type, expected?: Type | null): Type;
|
|
6
|
+
/** `signal("SIGINT", "SIGTERM")`: at least one name, and the names are those of signals. */
|
|
7
|
+
private checkSignalNames;
|
|
8
|
+
}
|