rooklang 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -2
- package/README.ru.md +33 -2
- package/dist/ast/declarations.d.ts +19 -3
- package/dist/ast/expressions.d.ts +4 -8
- package/dist/ast/statements.d.ts +3 -1
- package/dist/bun-register.d.ts +1 -0
- package/dist/bun-register.js +9 -0
- package/dist/bun.d.ts +34 -0
- package/dist/bun.js +99 -0
- package/dist/checker/assignments.d.ts +2 -0
- package/dist/checker/assignments.js +9 -3
- package/dist/checker/base.d.ts +8 -2
- package/dist/checker/base.js +3 -3
- package/dist/checker/builtins.js +7 -3
- package/dist/checker/channels.js +1 -2
- package/dist/checker/classes.js +7 -1
- package/dist/checker/closing.js +1 -1
- package/dist/checker/completion.d.ts +1 -1
- package/dist/checker/completion.js +2 -1
- package/dist/checker/component-bindings.d.ts +7 -0
- package/dist/checker/component-bindings.js +45 -0
- package/dist/checker/component-values.js +10 -7
- package/dist/checker/components.d.ts +2 -2
- package/dist/checker/components.js +8 -3
- package/dist/checker/context.d.ts +28 -5
- package/dist/checker/context.js +23 -0
- package/dist/checker/control-flow.d.ts +6 -5
- package/dist/checker/control-flow.js +48 -40
- package/dist/checker/declarations.d.ts +2 -2
- package/dist/checker/declarations.js +3 -2
- package/dist/checker/decorator-exports.js +4 -0
- package/dist/checker/decorator-includes.d.ts +9 -0
- package/dist/checker/decorator-includes.js +71 -0
- package/dist/checker/decorator-members.d.ts +8 -10
- package/dist/checker/decorator-members.js +28 -31
- package/dist/checker/decorator-wrappers.d.ts +1 -1
- package/dist/checker/decorator-wrappers.js +3 -2
- package/dist/checker/decorators.d.ts +14 -5
- package/dist/checker/decorators.js +52 -25
- package/dist/checker/expressions.d.ts +1 -1
- package/dist/checker/expressions.js +7 -6
- package/dist/checker/flow.d.ts +25 -3
- package/dist/checker/flow.js +48 -8
- package/dist/checker/functions.d.ts +2 -2
- package/dist/checker/functions.js +9 -5
- package/dist/checker/helpers.d.ts +4 -1
- package/dist/checker/helpers.js +7 -2
- package/dist/checker/imports.js +5 -2
- package/dist/checker/loops.d.ts +2 -0
- package/dist/checker/loops.js +27 -2
- package/dist/checker/markup.js +14 -2
- package/dist/checker/members.js +3 -1
- package/dist/checker/shared-state.d.ts +11 -0
- package/dist/checker/shared-state.js +37 -0
- package/dist/checker/statements.d.ts +5 -2
- package/dist/checker/statements.js +51 -22
- package/dist/checker/tagged-unions.d.ts +37 -0
- package/dist/checker/tagged-unions.js +132 -0
- package/dist/checker/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +3 -1
- package/dist/codegen/attributes.d.ts +4 -0
- package/dist/codegen/attributes.js +24 -2
- package/dist/codegen/component-helpers.d.ts +6 -1
- package/dist/codegen/component-helpers.js +6 -1
- package/dist/codegen/component-props.d.ts +11 -2
- package/dist/codegen/component-props.js +50 -5
- package/dist/codegen/components.d.ts +6 -5
- package/dist/codegen/components.js +44 -21
- package/dist/codegen/control-flow.d.ts +2 -0
- package/dist/codegen/control-flow.js +23 -9
- package/dist/codegen/creation.js +5 -2
- package/dist/codegen/early-returns.d.ts +4 -3
- package/dist/codegen/early-returns.js +17 -5
- package/dist/codegen/emitter.d.ts +3 -0
- package/dist/codegen/emitter.js +18 -4
- package/dist/codegen/expressions.js +0 -2
- package/dist/codegen/functions.d.ts +11 -1
- package/dist/codegen/functions.js +31 -3
- package/dist/codegen/live.d.ts +60 -0
- package/dist/codegen/live.js +29 -0
- package/dist/codegen/mutations.d.ts +7 -0
- package/dist/codegen/mutations.js +43 -2
- package/dist/codegen/options.d.ts +2 -0
- package/dist/codegen/reactive.d.ts +4 -59
- package/dist/codegen/reactive.js +34 -72
- package/dist/codegen/runtime.js +24 -7
- package/dist/codegen/shared.d.ts +22 -0
- package/dist/codegen/shared.js +93 -0
- package/dist/codegen/simple-statements.d.ts +1 -1
- package/dist/codegen/statements.d.ts +5 -0
- package/dist/codegen/statements.js +35 -0
- package/dist/codegen/updates.d.ts +11 -3
- package/dist/codegen/updates.js +52 -4
- package/dist/codegen/web-components.d.ts +2 -2
- package/dist/codegen/web-components.js +2 -2
- package/dist/compile-file.d.ts +4 -1
- package/dist/compile.d.ts +1 -1
- package/dist/compile.js +6 -2
- package/dist/decorator-copy.d.ts +1 -1
- package/dist/decorator-copy.js +1 -0
- package/dist/decorator-names.d.ts +14 -0
- package/dist/decorator-names.js +56 -0
- package/dist/decorators.d.ts +2 -3
- package/dist/decorators.js +36 -14
- package/dist/index.d.ts +1 -1
- package/dist/inline-packages.d.ts +2 -1
- package/dist/inline-packages.js +9 -4
- package/dist/loader.js +6 -19
- package/dist/names.d.ts +4 -0
- package/dist/names.js +29 -0
- package/dist/parser/base.d.ts +1 -0
- package/dist/parser/base.js +1 -1
- package/dist/parser/control-flow.d.ts +5 -0
- package/dist/parser/control-flow.js +28 -2
- package/dist/parser/declarations.d.ts +5 -15
- package/dist/parser/declarations.js +15 -119
- package/dist/parser/decorators.d.ts +6 -2
- package/dist/parser/decorators.js +43 -19
- package/dist/parser/params.d.ts +21 -0
- package/dist/parser/params.js +143 -0
- package/dist/parser/simple-statements.d.ts +1 -0
- package/dist/parser/simple-statements.js +22 -6
- package/dist/parser/statements.d.ts +5 -2
- package/dist/parser/statements.js +18 -11
- package/dist/run-cache.d.ts +7 -0
- package/dist/run-cache.js +24 -0
- package/dist/run-file.js +6 -1
- package/dist/runtime/sources.js +4 -3
- package/dist/walk.js +3 -3
- package/package.json +10 -2
- package/src/runtime/decode.js +41 -10
- package/src/runtime/go.js +10 -4
- package/src/runtime/index.js +1 -0
- package/src/runtime/markup.js +27 -17
- package/src/runtime/shared.js +17 -0
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
import { ARRAY_MUTATORS } from "../methods.js";
|
|
2
|
-
import {
|
|
2
|
+
import { withIncluded } from "./context.js";
|
|
3
|
+
import { DecoratorIncludeChecker } from "./decorator-includes.js";
|
|
3
4
|
import { nonNull, UNKNOWN } from "./types.js";
|
|
4
5
|
// The public members of decorators, in the components they are applied to: `@visible.show()` calls
|
|
5
|
-
// a function,
|
|
6
|
-
//
|
|
7
|
-
export class DecoratorMemberChecker extends
|
|
8
|
-
/** `@visible.show`:
|
|
6
|
+
// a function, `@visible.shown` reads state or a constant, and none of them can be changed outside
|
|
7
|
+
// the decorator.
|
|
8
|
+
export class DecoratorMemberChecker extends DecoratorIncludeChecker {
|
|
9
|
+
/** `@visible.shown` or `@visible.show`: public state, a constant or a function. */
|
|
9
10
|
checkDecoratorMember(node) {
|
|
10
|
-
|
|
11
|
-
if (member && member.kind !== 'func') {
|
|
12
|
-
const { decorator, member: name } = node;
|
|
13
|
-
this.error(`read "${name.name}" of @${decorator.name} with get(@${decorator.name}.${name.name})`, node);
|
|
14
|
-
}
|
|
15
|
-
return member?.type ?? UNKNOWN;
|
|
16
|
-
}
|
|
17
|
-
/** `get(@visible.shown)`: public state or a constant. */
|
|
18
|
-
checkDecoratorGet(node) {
|
|
19
|
-
const member = this.findDecoratorMember(node.target);
|
|
20
|
-
if (member?.kind === 'func') {
|
|
21
|
-
const { decorator, member: name } = node.target;
|
|
22
|
-
this.error(`"${name.name}" of @${decorator.name} is a function: call it, @${decorator.name}.${name.name}()`, node);
|
|
23
|
-
}
|
|
24
|
-
return member?.type ?? UNKNOWN;
|
|
11
|
+
return this.findDecoratorMember(node)?.type ?? UNKNOWN;
|
|
25
12
|
}
|
|
26
13
|
/**
|
|
27
14
|
* A public member of a decorator applied to the component being checked, or of one that the
|
|
@@ -38,6 +25,13 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
38
25
|
return null;
|
|
39
26
|
}
|
|
40
27
|
const info = component.decorators.find((decorator) => decorator.node.name.name === name);
|
|
28
|
+
// `@cart.count` of the `cart` that `@shop` includes is `@shop.count`.
|
|
29
|
+
const via = component.decorators.find((decorator) => withIncluded(decorator.included).some((each) => each.node.name.name === name));
|
|
30
|
+
if (!info && via) {
|
|
31
|
+
const combined = via.node.name.name;
|
|
32
|
+
this.error(`"${member}" of @${name} comes through @${combined}: write @${combined}.${member}`, node);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
41
35
|
if (!info) {
|
|
42
36
|
this.error(this.decorators.has(name)
|
|
43
37
|
? `@${name} is not applied to ${component.node.name.name}: write @${name} before comp`
|
|
@@ -46,7 +40,7 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
46
40
|
}
|
|
47
41
|
return this.publicMember(node, info);
|
|
48
42
|
}
|
|
49
|
-
/**
|
|
43
|
+
/** `@cart.count` in `dec cartBadge() needs cart`. */
|
|
50
44
|
neededMember(node, decorator) {
|
|
51
45
|
const name = node.decorator.name;
|
|
52
46
|
const current = decorator.name.name;
|
|
@@ -54,8 +48,9 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
54
48
|
this.error(`inside @${name} its members are used by their names: ${node.member.name}`, node);
|
|
55
49
|
return null;
|
|
56
50
|
}
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
const uses = [...decorator.needs, ...decorator.includes.map((use) => use.name)];
|
|
52
|
+
if (!uses.some((used) => used.name === name)) {
|
|
53
|
+
this.error(`@${current} can use @${name} only if it needs or includes it: write "needs ${name}" after its parameters`, node.decorator);
|
|
59
54
|
return null;
|
|
60
55
|
}
|
|
61
56
|
const info = this.decorators.get(name);
|
|
@@ -74,18 +69,22 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
74
69
|
return found ?? null;
|
|
75
70
|
}
|
|
76
71
|
/**
|
|
77
|
-
* Members of a decorator are read-only outside it:
|
|
78
|
-
*
|
|
72
|
+
* Members of a decorator are read-only outside it: `@cart.count = 0`, `@cart.items[0] = x`.
|
|
73
|
+
* Returns whether `target` is one of them.
|
|
79
74
|
*/
|
|
80
75
|
checkDecoratorWrite(target) {
|
|
81
76
|
const root = decoratorRoot(target);
|
|
82
77
|
if (!root)
|
|
83
78
|
return false;
|
|
84
|
-
|
|
79
|
+
const member = this.findDecoratorMember(root);
|
|
80
|
+
// A ref always points to its element, but the element is the page's: `@framed.box.title = x`.
|
|
81
|
+
if (member?.kind === 'ref' && target !== root)
|
|
82
|
+
return false;
|
|
83
|
+
if (member)
|
|
85
84
|
this.readOnlyError(root, target);
|
|
86
85
|
return true;
|
|
87
86
|
}
|
|
88
|
-
/**
|
|
87
|
+
/** `@cart.items.push(x)`: a method that changes the array. */
|
|
89
88
|
checkDecoratorMutation(node) {
|
|
90
89
|
const { callee } = node;
|
|
91
90
|
if (callee.kind !== 'MemberExpression')
|
|
@@ -118,17 +117,15 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
118
117
|
const member = node.property.name;
|
|
119
118
|
this.error(info.members.get(member)?.kind === 'func'
|
|
120
119
|
? `"${name}" is not defined; to call a function of @${name} write @${name}.${member}()`
|
|
121
|
-
: `"${name}" is not defined; to read public state of @${name} write
|
|
120
|
+
: `"${name}" is not defined; to read public state of @${name} write @${name}.${member}`, object);
|
|
122
121
|
return true;
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
|
-
/** The member of a decorator that
|
|
124
|
+
/** The member of a decorator that `@cart.items[0].done` starts with, if any. */
|
|
126
125
|
function decoratorRoot(node) {
|
|
127
126
|
switch (node.kind) {
|
|
128
127
|
case 'DecoratorMember':
|
|
129
128
|
return node;
|
|
130
|
-
case 'DecoratorGet':
|
|
131
|
-
return node.target;
|
|
132
129
|
case 'MemberExpression':
|
|
133
130
|
case 'IndexExpression':
|
|
134
131
|
return decoratorRoot(node.object);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentInfo, type DecoratorInfo } from './context.ts';
|
|
2
2
|
import { StyleChecker } from './styles.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
4
|
type WrapperInfo = NonNullable<DecoratorInfo['wrapper']>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { decoratorNames, wrapperBody, wrapperOf } from "../decorators.js";
|
|
2
|
+
import { withIncluded } from "./context.js";
|
|
2
3
|
import { isUntyped, ownStatements } from "./helpers.js";
|
|
3
4
|
import { StyleChecker } from "./styles.js";
|
|
4
5
|
import { isAssignable, typeToString, UNKNOWN } from "./types.js";
|
|
@@ -78,7 +79,7 @@ export class DecoratorWrapperChecker extends StyleChecker {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
wrappedType(info, own, seen) {
|
|
81
|
-
return info.decorators.reduceRight((type, decorator) => (decorator.wrapper ? this.wrapperResult(decorator.wrapper, seen) : type), own);
|
|
82
|
+
return withIncluded(info.decorators).reduceRight((type, decorator) => (decorator.wrapper ? this.wrapperResult(decorator.wrapper, seen) : type), own);
|
|
82
83
|
}
|
|
83
84
|
/** What a wrapper gives, found without checking it again, like the markup of a component. */
|
|
84
85
|
wrapperResult(wrapper, seen) {
|
|
@@ -96,7 +97,7 @@ export class DecoratorWrapperChecker extends StyleChecker {
|
|
|
96
97
|
checkWrappedTypes(info) {
|
|
97
98
|
let type = this.ownResult(info, new Set());
|
|
98
99
|
let source = null;
|
|
99
|
-
for (const decorator of
|
|
100
|
+
for (const decorator of withIncluded(info.decorators).reverse()) {
|
|
100
101
|
const { wrapper } = decorator;
|
|
101
102
|
if (!wrapper)
|
|
102
103
|
continue;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import type
|
|
2
|
+
import { type ComponentInfo, type DecoratorInfo, type Scope } from './context.ts';
|
|
3
3
|
import { DecoratorExportChecker } from './decorator-exports.ts';
|
|
4
|
+
import { type Type } from './types.ts';
|
|
4
5
|
export declare abstract class DecoratorChecker extends DecoratorExportChecker {
|
|
5
6
|
protected resolveDecoratorParams(info: DecoratorInfo): void;
|
|
6
7
|
/** `needs cart, auth`: other decorators, each once. */
|
|
7
8
|
protected checkNeedsDeclaration(info: DecoratorInfo): void;
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Decorators after the ones they need or include. A cycle is an error: none can be first.
|
|
11
|
+
*/
|
|
9
12
|
protected orderByNeeds(decorators: readonly DecoratorInfo[]): DecoratorInfo[];
|
|
10
13
|
/**
|
|
11
14
|
* The body is checked once, by itself: it does not see the components it is applied to. Its
|
|
@@ -15,11 +18,17 @@ export declare abstract class DecoratorChecker extends DecoratorExportChecker {
|
|
|
15
18
|
protected collectMembers(info: DecoratorInfo, scope: Scope): void;
|
|
16
19
|
/**
|
|
17
20
|
* The decorators before `comp`: they exist, are applied once and below the ones they need, get
|
|
18
|
-
* valid arguments, and their wrappers get what they can take.
|
|
21
|
+
* valid arguments, and their wrappers get what they can take. Those that a decorator includes
|
|
22
|
+
* are applied with it, above it.
|
|
19
23
|
*/
|
|
20
24
|
protected checkDecoratorUses(info: ComponentInfo): void;
|
|
21
|
-
/**
|
|
22
|
-
|
|
25
|
+
/** Applied once, below the ones it needs; `via` is the decorator that includes it. */
|
|
26
|
+
private checkApplied;
|
|
27
|
+
/**
|
|
28
|
+
* Arguments are positional and see `params`: the properties of the component, as its body does,
|
|
29
|
+
* or the parameters of the decorator that includes this one.
|
|
30
|
+
*/
|
|
31
|
+
protected checkDecoratorArguments(use: ast.DecoratorUse, decorator: DecoratorInfo, scope: readonly [ast.Identifier, Type][]): void;
|
|
23
32
|
/**
|
|
24
33
|
* The code of the decorator and its arguments go into the component's body, so the names of the
|
|
25
34
|
* module they use must not be hidden there by the component's declarations.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { freeNames } from "../names.js";
|
|
2
2
|
import { forEachChild } from "../walk.js";
|
|
3
|
+
import { withIncluded, } from "./context.js";
|
|
3
4
|
import { DecoratorExportChecker } from "./decorator-exports.js";
|
|
4
5
|
import { lookupIn, ownStatements } from "./helpers.js";
|
|
5
6
|
import { func, UNKNOWN } from "./types.js";
|
|
@@ -42,7 +43,9 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
42
43
|
needed.add(name);
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Decorators after the ones they need or include. A cycle is an error: none can be first.
|
|
48
|
+
*/
|
|
46
49
|
orderByNeeds(decorators) {
|
|
47
50
|
const order = [];
|
|
48
51
|
const path = [];
|
|
@@ -51,13 +54,17 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
51
54
|
return;
|
|
52
55
|
const at = path.indexOf(info);
|
|
53
56
|
if (at >= 0) {
|
|
54
|
-
const cycle = path.slice(at)
|
|
55
|
-
const steps = cycle.map((
|
|
56
|
-
|
|
57
|
+
const cycle = path.slice(at);
|
|
58
|
+
const steps = cycle.map((each, i) => {
|
|
59
|
+
const next = cycle[(i + 1) % cycle.length];
|
|
60
|
+
const how = each.included.includes(next) ? 'includes' : 'needs';
|
|
61
|
+
return `@${each.node.name.name} ${how} @${next.node.name.name}`;
|
|
62
|
+
});
|
|
63
|
+
this.error(`decorators cannot need or include each other: ${steps.join(', ')}; remove one of them`, info.node.name);
|
|
57
64
|
return;
|
|
58
65
|
}
|
|
59
66
|
path.push(info);
|
|
60
|
-
for (const needed of info.needed)
|
|
67
|
+
for (const needed of [...info.needed, ...info.included])
|
|
61
68
|
visit(needed);
|
|
62
69
|
path.pop();
|
|
63
70
|
order.push(info);
|
|
@@ -112,8 +119,7 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
112
119
|
}
|
|
113
120
|
else if (statement.kind === 'VariableDeclaration' &&
|
|
114
121
|
statement.isPublic &&
|
|
115
|
-
statement.keyword !== 'let'
|
|
116
|
-
statement.keyword !== 'ref') {
|
|
122
|
+
statement.keyword !== 'let') {
|
|
117
123
|
for (const name of statement.names)
|
|
118
124
|
add(name, statement.keyword);
|
|
119
125
|
}
|
|
@@ -121,10 +127,16 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
121
127
|
}
|
|
122
128
|
/**
|
|
123
129
|
* The decorators before `comp`: they exist, are applied once and below the ones they need, get
|
|
124
|
-
* valid arguments, and their wrappers get what they can take.
|
|
130
|
+
* valid arguments, and their wrappers get what they can take. Those that a decorator includes
|
|
131
|
+
* are applied with it, above it.
|
|
125
132
|
*/
|
|
126
133
|
checkDecoratorUses(info) {
|
|
127
|
-
|
|
134
|
+
// The decorator that each one is applied with, for those that another includes.
|
|
135
|
+
const applied = new Map();
|
|
136
|
+
const props = info.node.params.map((param) => [
|
|
137
|
+
param.name,
|
|
138
|
+
info.props.get(param.name.name)?.type ?? UNKNOWN,
|
|
139
|
+
]);
|
|
128
140
|
for (const use of info.node.decorators) {
|
|
129
141
|
const name = use.name.name;
|
|
130
142
|
const decorator = this.decorators.get(name);
|
|
@@ -132,23 +144,39 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
132
144
|
this.error(`unknown decorator @${name}`, use.name);
|
|
133
145
|
continue;
|
|
134
146
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
continue;
|
|
141
|
-
const other = needed.node.name.name;
|
|
142
|
-
this.error(`@${name} needs @${other} above it: @${other} @${name} comp ...`, use);
|
|
147
|
+
for (const each of withIncluded([decorator])) {
|
|
148
|
+
const via = each === decorator ? null : name;
|
|
149
|
+
this.checkApplied(each, via, applied, use);
|
|
150
|
+
applied.set(each, via);
|
|
151
|
+
this.checkInlinedNames(use, each, info);
|
|
143
152
|
}
|
|
144
|
-
|
|
145
|
-
this.checkDecoratorArguments(use, decorator, info);
|
|
146
|
-
this.checkInlinedNames(use, decorator, info);
|
|
153
|
+
this.checkDecoratorArguments(use, decorator, props);
|
|
147
154
|
}
|
|
148
155
|
this.checkWrappedTypes(info);
|
|
149
156
|
}
|
|
150
|
-
/**
|
|
151
|
-
|
|
157
|
+
/** Applied once, below the ones it needs; `via` is the decorator that includes it. */
|
|
158
|
+
checkApplied(decorator, via, applied, use) {
|
|
159
|
+
const name = decorator.node.name.name;
|
|
160
|
+
const where = (other) => (other === null ? '' : ` (it is in @${other})`);
|
|
161
|
+
if (applied.has(decorator)) {
|
|
162
|
+
const before = applied.get(decorator) ?? null;
|
|
163
|
+
this.error(before === null && via === null
|
|
164
|
+
? `@${name} is applied twice: apply it once`
|
|
165
|
+
: `@${name} is applied twice${where(via ?? before)}: apply it once`, use);
|
|
166
|
+
}
|
|
167
|
+
// The body of a decorator may read the state of those it needs right away.
|
|
168
|
+
for (const needed of decorator.needed) {
|
|
169
|
+
if (applied.has(needed))
|
|
170
|
+
continue;
|
|
171
|
+
const other = needed.node.name.name;
|
|
172
|
+
this.error(`@${name}${where(via)} needs @${other} above it: @${other} @${via ?? name} comp ...`, use);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Arguments are positional and see `params`: the properties of the component, as its body does,
|
|
177
|
+
* or the parameters of the decorator that includes this one.
|
|
178
|
+
*/
|
|
179
|
+
checkDecoratorArguments(use, decorator, scope) {
|
|
152
180
|
const name = use.name.name;
|
|
153
181
|
const { params } = decorator.node;
|
|
154
182
|
const extra = use.arguments[params.length];
|
|
@@ -157,9 +185,8 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
157
185
|
this.error(params.length === 0 ? `@${name} takes no arguments` : `@${name} takes ${count}`, extra);
|
|
158
186
|
}
|
|
159
187
|
this.withScope(() => {
|
|
160
|
-
for (const param of
|
|
161
|
-
this.declareValue(param
|
|
162
|
-
}
|
|
188
|
+
for (const [param, type] of scope)
|
|
189
|
+
this.declareValue(param, 'prop', type);
|
|
163
190
|
params.forEach((param, i) => {
|
|
164
191
|
const type = decorator.params[i] ?? UNKNOWN;
|
|
165
192
|
const arg = use.arguments[i];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import type
|
|
2
|
+
import { type ComponentInfo } from './context.ts';
|
|
3
3
|
import { FunctionChecker } from './functions.ts';
|
|
4
4
|
import { type ComponentType, type Type } from './types.ts';
|
|
5
5
|
export declare abstract class ExpressionChecker extends FunctionChecker {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { placeOf } from "./context.js";
|
|
1
2
|
import { FunctionChecker } from "./functions.js";
|
|
2
3
|
import { expectsLiteral, callName, isUntyped } from "./helpers.js";
|
|
3
4
|
import { ANY, BIGINT, BOOL, hasUntypedBase, instanceMembers, isAssignable, literal, nonNull, NULL, nullable, NUMBER, STRING, typeToString, UNKNOWN, } from "./types.js";
|
|
@@ -90,8 +91,6 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
90
91
|
return this.checkElement(node);
|
|
91
92
|
case 'DecoratorMember':
|
|
92
93
|
return this.checkDecoratorMember(node);
|
|
93
|
-
case 'DecoratorGet':
|
|
94
|
-
return this.checkDecoratorGet(node);
|
|
95
94
|
case 'MemberExpression':
|
|
96
95
|
case 'IndexExpression':
|
|
97
96
|
case 'CallExpression': {
|
|
@@ -130,7 +129,7 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
130
129
|
this.error(`"${node.name}" is used before its declaration`, node);
|
|
131
130
|
return UNKNOWN;
|
|
132
131
|
}
|
|
133
|
-
return this.flow.get(binding) ?? binding.type;
|
|
132
|
+
return this.flow.get(placeOf(binding)) ?? binding.type;
|
|
134
133
|
}
|
|
135
134
|
thisType(node) {
|
|
136
135
|
if (!this.cls) {
|
|
@@ -155,10 +154,11 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
155
154
|
}
|
|
156
155
|
checkObjectLiteral(node, expected) {
|
|
157
156
|
let target = expected ? nonNull(expected) : null;
|
|
158
|
-
// `?(ScrollIntoViewOptions | bool)`: an object literal can only be the object type in it
|
|
157
|
+
// `?(ScrollIntoViewOptions | bool)`: an object literal can only be the object type in it, or
|
|
158
|
+
// the one that its tag chooses, `{ kind: "pickup" }` of `Pickup | Courier`.
|
|
159
159
|
if (target?.kind === 'union') {
|
|
160
160
|
const objects = target.types.filter((type) => type.kind === 'object' || type.kind === 'class');
|
|
161
|
-
target = objects.length === 1 ? objects[0] :
|
|
161
|
+
target = objects.length === 1 ? objects[0] : this.taggedMember(node, objects, target);
|
|
162
162
|
}
|
|
163
163
|
const index = target?.kind === 'object' ? target.index : undefined;
|
|
164
164
|
const targetMembers = target?.kind === 'object'
|
|
@@ -168,7 +168,8 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
168
168
|
: null;
|
|
169
169
|
const members = new Map();
|
|
170
170
|
const written = new Set();
|
|
171
|
-
|
|
171
|
+
// A tag of no member is reported once, not again where the literal is used.
|
|
172
|
+
let untyped = target?.kind === 'unknown';
|
|
172
173
|
for (const property of node.properties) {
|
|
173
174
|
if (property.kind === 'SpreadElement') {
|
|
174
175
|
const spread = this.checkValue(property.argument);
|
package/dist/checker/flow.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import type { Flow, Narrowing } from './context.ts';
|
|
3
|
-
import { type Type } from './types.ts';
|
|
2
|
+
import type { Binding, Flow, Narrowing, Place } from './context.ts';
|
|
3
|
+
import { type LiteralType, type Type } from './types.ts';
|
|
4
4
|
/** What `typeof` gives for every value of a type, or `null` when it may give several things. */
|
|
5
5
|
export declare function typeofTag(type: Type): string | null;
|
|
6
6
|
/** The type after `typeof x == tag` is found true (`matches`) or false. */
|
|
@@ -9,8 +9,30 @@ export declare function narrowByTypeof(type: Type, tag: string, matches: boolean
|
|
|
9
9
|
export declare function literalValue(node: ast.Expression): string | number | boolean | undefined;
|
|
10
10
|
/** A type without one literal value: what is left after `x != "all"`. */
|
|
11
11
|
export declare function withoutLiteral(type: Type, value: string | number | boolean): Type;
|
|
12
|
+
/**
|
|
13
|
+
* A union after a comparison of its tag, a field with literal types in each member: `tags` has the
|
|
14
|
+
* values the tag may have in each member, `values` those it is compared with. When `equal`, the
|
|
15
|
+
* members whose tag may be one of the values remain; otherwise those whose tag may be another one.
|
|
16
|
+
*/
|
|
17
|
+
export declare function narrowByTag(type: Type, tags: readonly (readonly LiteralType['value'][])[], values: readonly LiteralType['value'][], equal: boolean): Type;
|
|
18
|
+
/**
|
|
19
|
+
* Whether `field` is a tag of what `place` was declared as: then an assignment to it may change
|
|
20
|
+
* the member of the union that the place was narrowed to.
|
|
21
|
+
*/
|
|
22
|
+
export type IsTag = (place: Place, field: string) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether an assignment to the fields `written` of a variable ends the narrowing of `place`, a
|
|
25
|
+
* path of the same variable or the variable itself (the empty path): it writes the path, its
|
|
26
|
+
* start or a tag of it. `"delivery.kind"` ends the narrowing of `"delivery"`, which may come from
|
|
27
|
+
* that tag; `"delivery.floor"` does not.
|
|
28
|
+
*/
|
|
29
|
+
export declare function overwrites(written: string, place: Place, isTag: IsTag): boolean;
|
|
30
|
+
/** Ends the narrowing that an assignment to `binding` or to its fields `written` changes. */
|
|
31
|
+
export declare function forgetWritten(flow: Flow, binding: Binding, written: string, isTag: IsTag): void;
|
|
32
|
+
/** The flow without what assignments to the variables `names` and the paths `paths` change. */
|
|
33
|
+
export declare function withoutAssigned(flow: Flow, names: ReadonlySet<string>, paths: ReadonlySet<string>, isTag: IsTag): Flow;
|
|
12
34
|
export declare function withNarrowing(flow: Flow, narrowing: Narrowing): Flow;
|
|
13
|
-
/** Narrowing that holds after both branches: the
|
|
35
|
+
/** Narrowing that holds after both branches: the places narrowed the same way in both. */
|
|
14
36
|
export declare function mergeFlows(a: Flow, b: Flow): Flow;
|
|
15
37
|
/** Go's terminating statements: execution never continues after them. */
|
|
16
38
|
export declare function isTerminating(node: ast.Statement): boolean;
|
package/dist/checker/flow.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { containsBreak } from "../breaks.js";
|
|
2
2
|
import { BIGINT, BOOL, isNullable, nonNull, NULL, NUMBER, STRING, typesEqual, union, unionMembers, } from "./types.js";
|
|
3
|
-
// Control flow for the checker: how conditions narrow the types of variables, and which
|
|
4
|
-
// execution never continues after.
|
|
3
|
+
// Control flow for the checker: how conditions narrow the types of variables and paths, and which
|
|
4
|
+
// statements execution never continues after.
|
|
5
5
|
/** What a value of unknown type is after `typeof x == tag`. */
|
|
6
6
|
const PRIMITIVE_TAGS = new Map([
|
|
7
7
|
['string', STRING],
|
|
@@ -67,19 +67,59 @@ export function withoutLiteral(type, value) {
|
|
|
67
67
|
const kept = unionMembers(nonNull(type)).filter((member) => !(member.kind === 'literal' && member.value === value));
|
|
68
68
|
return union(isNullable(type) ? [...kept, NULL] : kept);
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* A union after a comparison of its tag, a field with literal types in each member: `tags` has the
|
|
72
|
+
* values the tag may have in each member, `values` those it is compared with. When `equal`, the
|
|
73
|
+
* members whose tag may be one of the values remain; otherwise those whose tag may be another one.
|
|
74
|
+
*/
|
|
75
|
+
export function narrowByTag(type, tags, values, equal) {
|
|
76
|
+
const kept = unionMembers(nonNull(type)).filter((_, i) => {
|
|
77
|
+
const tag = tags[i] ?? [];
|
|
78
|
+
return equal
|
|
79
|
+
? tag.some((value) => values.includes(value))
|
|
80
|
+
: !tag.every((value) => values.includes(value));
|
|
81
|
+
});
|
|
82
|
+
return union(!equal && isNullable(type) ? [...kept, NULL] : kept);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Whether an assignment to the fields `written` of a variable ends the narrowing of `place`, a
|
|
86
|
+
* path of the same variable or the variable itself (the empty path): it writes the path, its
|
|
87
|
+
* start or a tag of it. `"delivery.kind"` ends the narrowing of `"delivery"`, which may come from
|
|
88
|
+
* that tag; `"delivery.floor"` does not.
|
|
89
|
+
*/
|
|
90
|
+
export function overwrites(written, place, isTag) {
|
|
91
|
+
const { path } = place;
|
|
92
|
+
if (written === '' || written === path || path.startsWith(`${written}.`))
|
|
93
|
+
return true;
|
|
94
|
+
const field = path === '' ? written : written.startsWith(`${path}.`) ? written.slice(path.length + 1) : null;
|
|
95
|
+
return field !== null && !field.includes('.') && isTag(place, field);
|
|
96
|
+
}
|
|
97
|
+
/** Ends the narrowing that an assignment to `binding` or to its fields `written` changes. */
|
|
98
|
+
export function forgetWritten(flow, binding, written, isTag) {
|
|
99
|
+
for (const place of flow.keys()) {
|
|
100
|
+
if (place.binding === binding && overwrites(written, place, isTag))
|
|
101
|
+
flow.delete(place);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** The flow without what assignments to the variables `names` and the paths `paths` change. */
|
|
105
|
+
export function withoutAssigned(flow, names, paths, isTag) {
|
|
106
|
+
const written = [...paths].map((path) => path.split('.'));
|
|
107
|
+
return new Map([...flow].filter(([place]) => !names.has(place.binding.name) &&
|
|
108
|
+
!written.some(([root, ...fields]) => root === place.binding.name && overwrites(fields.join('.'), place, isTag))));
|
|
109
|
+
}
|
|
70
110
|
export function withNarrowing(flow, narrowing) {
|
|
71
111
|
const result = new Map(flow);
|
|
72
|
-
for (const [
|
|
73
|
-
result.set(
|
|
112
|
+
for (const [place, type] of narrowing)
|
|
113
|
+
result.set(place, type);
|
|
74
114
|
return result;
|
|
75
115
|
}
|
|
76
|
-
/** Narrowing that holds after both branches: the
|
|
116
|
+
/** Narrowing that holds after both branches: the places narrowed the same way in both. */
|
|
77
117
|
export function mergeFlows(a, b) {
|
|
78
118
|
const result = new Map();
|
|
79
|
-
for (const [
|
|
80
|
-
const other = b.get(
|
|
119
|
+
for (const [place, type] of a) {
|
|
120
|
+
const other = b.get(place);
|
|
81
121
|
if (other && typesEqual(type, other))
|
|
82
|
-
result.set(
|
|
122
|
+
result.set(place, type);
|
|
83
123
|
}
|
|
84
124
|
return result;
|
|
85
125
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { type BindingKind, Scope, type Flow } from './context.ts';
|
|
3
|
-
import {
|
|
3
|
+
import { TaggedUnionChecker } from './tagged-unions.ts';
|
|
4
4
|
import { type FunctionType, type Type } from './types.ts';
|
|
5
|
-
export declare abstract class FunctionChecker extends
|
|
5
|
+
export declare abstract class FunctionChecker extends TaggedUnionChecker {
|
|
6
6
|
/**
|
|
7
7
|
* Checks a function body and returns its result types: the declared ones, or those inferred from
|
|
8
8
|
* the `return` statements when `results` is `null`.
|
|
@@ -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;
|
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';
|
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)
|
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.
|