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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { DeclarationChecker } from "./declarations.js";
|
|
2
|
+
import { notCopied } from "./transfer.js";
|
|
3
|
+
import { findClassMember, isAssignable, typeToString, UNKNOWN, } from "./types.js";
|
|
4
|
+
// The members of classes that are more than a field or a method: getters and setters, the
|
|
5
|
+
// properties of constructor parameters, abstract methods; ClassChecker declares and checks them.
|
|
6
|
+
export class ClassMemberChecker extends DeclarationChecker {
|
|
7
|
+
/** The signatures of getters and setters: their members have the type of the value. */
|
|
8
|
+
accessorSignatures = new Map();
|
|
9
|
+
declareMember(info, isStatic, name, member) {
|
|
10
|
+
const members = isStatic ? info.statics : info.members;
|
|
11
|
+
if (members.has(name.name))
|
|
12
|
+
this.error(`duplicate member "${name.name}"`, name);
|
|
13
|
+
members.set(name.name, member);
|
|
14
|
+
}
|
|
15
|
+
/** `get area() number` and `set area(v number)` make one member, of the type of the value. */
|
|
16
|
+
declareAccessor(info, node, accessor) {
|
|
17
|
+
const signature = this.signature(node.params, node.results);
|
|
18
|
+
const type = (accessor === 'get' ? signature.results[0] : signature.params[0]) ?? UNKNOWN;
|
|
19
|
+
const members = node.isStatic ? info.statics : info.members;
|
|
20
|
+
const other = members.get(node.name.name);
|
|
21
|
+
this.accessorSignatures.set(node, signature);
|
|
22
|
+
if (other?.owner === info &&
|
|
23
|
+
(other.accessor === 'get' || other.accessor === 'set') &&
|
|
24
|
+
other.accessor !== accessor) {
|
|
25
|
+
const [getter, setter] = accessor === 'get' ? [type, other.type] : [other.type, type];
|
|
26
|
+
if (!isAssignable(getter, setter) || !isAssignable(setter, getter)) {
|
|
27
|
+
this.error(`the getter and the setter of "${node.name.name}" have different types: ` +
|
|
28
|
+
`${typeToString(getter)} and ${typeToString(setter)}`, node.name);
|
|
29
|
+
}
|
|
30
|
+
members.set(node.name.name, { ...other, type: getter, accessor: 'both' });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.declareMember(info, node.isStatic, node.name, {
|
|
34
|
+
type,
|
|
35
|
+
method: false,
|
|
36
|
+
visibility: node.visibility,
|
|
37
|
+
owner: info,
|
|
38
|
+
accessor,
|
|
39
|
+
...(node.abstract ? { abstract: true } : {}),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** `constructor(private name string)`: the parameters that are properties of the class too. */
|
|
43
|
+
declareParamProperties(node, info, ctor) {
|
|
44
|
+
node.params.forEach((param, i) => {
|
|
45
|
+
if (!param.property)
|
|
46
|
+
return;
|
|
47
|
+
const type = ctor.params[i] ?? UNKNOWN;
|
|
48
|
+
const reason = info.data === true ? notCopied(type) : null;
|
|
49
|
+
if (reason !== null) {
|
|
50
|
+
this.error(`the fields of a data class go to other threads as copies: ${reason}`, param.type ?? param.name);
|
|
51
|
+
}
|
|
52
|
+
this.declareMember(info, false, param.name, {
|
|
53
|
+
type,
|
|
54
|
+
method: false,
|
|
55
|
+
visibility: param.property.visibility,
|
|
56
|
+
owner: info,
|
|
57
|
+
...(param.property.readonly ? { readonly: true } : {}),
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Only an abstract class has abstract methods, and a class that is not abstract implements
|
|
63
|
+
* those of its bases.
|
|
64
|
+
*/
|
|
65
|
+
checkAbstract(node, info) {
|
|
66
|
+
if (node.abstract)
|
|
67
|
+
return;
|
|
68
|
+
for (const member of node.members) {
|
|
69
|
+
if (member.kind !== 'MethodDeclaration' || !member.abstract)
|
|
70
|
+
continue;
|
|
71
|
+
this.error(`"${member.name.name}" is abstract, so its class must be too: abstract class ${info.name}`, member.name);
|
|
72
|
+
}
|
|
73
|
+
const missing = new Set();
|
|
74
|
+
for (let base = info.superClass; base; base = base.superClass) {
|
|
75
|
+
for (const [name, member] of base.members) {
|
|
76
|
+
const found = member.abstract ? findClassMember(info, name, false) : undefined;
|
|
77
|
+
if (found?.abstract)
|
|
78
|
+
missing.add(`${found.owner?.name ?? base.name}.${name}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (missing.size === 0)
|
|
82
|
+
return;
|
|
83
|
+
this.error(`class "${info.name}" does not implement ${[...missing].join(', ')}: implement ` +
|
|
84
|
+
`${missing.size === 1 ? 'it' : 'them'} or make the class abstract, e.g. abstract class ${info.name}`, node.name);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* In JS a field is set on each instance and so hides a getter or setter of the prototype: a
|
|
88
|
+
* field and an accessor of the same name in a class and its base would not work together.
|
|
89
|
+
*/
|
|
90
|
+
checkAccessorOverride(info, name, isStatic) {
|
|
91
|
+
const own = (isStatic ? info.statics : info.members).get(name.name);
|
|
92
|
+
const base = info.superClass && findClassMember(info.superClass, name.name, isStatic);
|
|
93
|
+
// Only the classes of Rook have `data` set: lib.dom declares many accessors as fields.
|
|
94
|
+
if (!own || !base || own.method || base.method || base.owner?.data === undefined)
|
|
95
|
+
return;
|
|
96
|
+
if (own.accessor && !base.accessor) {
|
|
97
|
+
this.error(`"${name.name}" is a field in ${base.owner.name}, which would hide a getter or setter ` +
|
|
98
|
+
'here: the field is set on each instance; override it with a field', name);
|
|
99
|
+
}
|
|
100
|
+
else if (!own.accessor && base.accessor) {
|
|
101
|
+
this.error(`"${name.name}" is a getter or setter in ${base.owner.name}, which a field here would ` +
|
|
102
|
+
'hide: override it with a getter or setter', name);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { type ModulePackages } from '../inline-packages.ts';
|
|
3
3
|
import type { ModuleExports } from './context.ts';
|
|
4
|
-
import {
|
|
5
|
-
import { type ClassInfo
|
|
6
|
-
export declare abstract class ClassChecker extends
|
|
4
|
+
import { ClassMemberChecker } from './class-members.ts';
|
|
5
|
+
import { type ClassInfo } from './types.ts';
|
|
6
|
+
export declare abstract class ClassChecker extends ClassMemberChecker {
|
|
7
7
|
protected ensureClassResolved(info: ClassInfo): void;
|
|
8
8
|
/** Resolves the base class and the types of all members (but does not check method bodies). */
|
|
9
9
|
protected resolveClass(node: ast.ClassDeclaration, info: ClassInfo): void;
|
|
10
10
|
protected resolveClassMembers(node: ast.ClassDeclaration, info: ClassInfo): void;
|
|
11
|
-
|
|
11
|
+
/** A data class extends only a data class: its fields go with it, and its class is restored. */
|
|
12
|
+
private checkDataBase;
|
|
12
13
|
/** Method bodies and the checks that need every member of the class. */
|
|
13
14
|
protected checkClassBodies(node: ast.ClassDeclaration, info: ClassInfo): void;
|
|
14
15
|
protected checkClassMembers(node: ast.ClassDeclaration, info: ClassInfo): void;
|
package/dist/checker/classes.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { inlinePackages } from "../inline-packages.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ClassMemberChecker } from "./class-members.js";
|
|
3
3
|
import { isSuperCall, assignsField } from "./helpers.js";
|
|
4
|
+
import { notCopied } from "./transfer.js";
|
|
4
5
|
import { explainMismatch, findClassMember, hasZeroValue, isAssignable, isSubclass, typeToString, UNKNOWN, } from "./types.js";
|
|
5
6
|
// Classes: their base class and members, method bodies, overrides and `implements`; and the declarations a module exports.
|
|
6
|
-
export class ClassChecker extends
|
|
7
|
+
export class ClassChecker extends ClassMemberChecker {
|
|
7
8
|
ensureClassResolved(info) {
|
|
8
9
|
const resolve = this.unresolvedClasses.get(info);
|
|
9
10
|
if (!resolve || this.resolvingClasses.has(info))
|
|
@@ -49,6 +50,9 @@ export class ClassChecker extends DeclarationChecker {
|
|
|
49
50
|
this.error(`cannot extend ${typeToString(base)}: it is not a class`, node.superClass);
|
|
50
51
|
}
|
|
51
52
|
}
|
|
53
|
+
if (node.data)
|
|
54
|
+
this.checkDataBase(node, info);
|
|
55
|
+
info.abstract = node.abstract === true;
|
|
52
56
|
for (const member of node.members) {
|
|
53
57
|
switch (member.kind) {
|
|
54
58
|
case 'FieldDeclaration': {
|
|
@@ -62,20 +66,31 @@ export class ClassChecker extends DeclarationChecker {
|
|
|
62
66
|
else
|
|
63
67
|
type = this.inferredType(valueType, value);
|
|
64
68
|
}
|
|
69
|
+
// The fields of a data class are what goes to another thread.
|
|
70
|
+
const reason = node.data && !member.isStatic ? notCopied(type) : null;
|
|
71
|
+
if (reason !== null) {
|
|
72
|
+
this.error(`the fields of a data class go to other threads as copies: ${reason}`, member.type ?? member.name);
|
|
73
|
+
}
|
|
65
74
|
this.declareMember(info, member.isStatic, member.name, {
|
|
66
75
|
type,
|
|
67
76
|
method: false,
|
|
68
77
|
visibility: member.visibility,
|
|
69
78
|
owner: info,
|
|
79
|
+
...(member.readonly ? { readonly: true } : {}),
|
|
70
80
|
});
|
|
71
81
|
break;
|
|
72
82
|
}
|
|
73
83
|
case 'MethodDeclaration':
|
|
84
|
+
if (member.accessor) {
|
|
85
|
+
this.declareAccessor(info, member, member.accessor);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
74
88
|
this.declareMember(info, member.isStatic, member.name, {
|
|
75
89
|
type: this.signature(member.params, member.results, member.async),
|
|
76
90
|
method: true,
|
|
77
91
|
visibility: member.visibility,
|
|
78
92
|
owner: info,
|
|
93
|
+
...(member.abstract ? { abstract: true } : {}),
|
|
79
94
|
});
|
|
80
95
|
break;
|
|
81
96
|
case 'ConstructorDeclaration':
|
|
@@ -83,15 +98,20 @@ export class ClassChecker extends DeclarationChecker {
|
|
|
83
98
|
this.error('a class can have only one constructor', member);
|
|
84
99
|
info.ctor = this.signature(member.params, []);
|
|
85
100
|
info.ctorVisibility = member.visibility;
|
|
101
|
+
this.declareParamProperties(member, info, info.ctor);
|
|
86
102
|
break;
|
|
87
103
|
}
|
|
88
104
|
}
|
|
89
105
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
/** A data class extends only a data class: its fields go with it, and its class is restored. */
|
|
107
|
+
checkDataBase(node, info) {
|
|
108
|
+
const base = info.superClass;
|
|
109
|
+
if (info.untypedBase) {
|
|
110
|
+
this.error('a data class cannot extend a class of JS: its copy in another thread could not get the class back', node.superClass);
|
|
111
|
+
}
|
|
112
|
+
else if (base && base.data !== true) {
|
|
113
|
+
this.error(`a data class extends only a data class: mark ${base.name} as one, e.g. data class ${base.name}`, node.superClass);
|
|
114
|
+
}
|
|
95
115
|
}
|
|
96
116
|
/** Method bodies and the checks that need every member of the class. */
|
|
97
117
|
checkClassBodies(node, info) {
|
|
@@ -101,13 +121,18 @@ export class ClassChecker extends DeclarationChecker {
|
|
|
101
121
|
let constructorNode = null;
|
|
102
122
|
for (const member of node.members) {
|
|
103
123
|
if (member.kind === 'MethodDeclaration') {
|
|
104
|
-
const
|
|
124
|
+
const { body } = member;
|
|
125
|
+
const type = member.accessor
|
|
126
|
+
? this.accessorSignatures.get(member)
|
|
127
|
+
: (member.isStatic ? info.statics : info.members).get(member.name.name)?.type;
|
|
105
128
|
if (type?.kind !== 'function')
|
|
106
129
|
continue;
|
|
107
|
-
|
|
108
|
-
async: member.async
|
|
109
|
-
}
|
|
130
|
+
if (body) {
|
|
131
|
+
this.withClass({ info, isStatic: member.isStatic }, () => this.checkFunction(member.params, type, type.results, body, { async: member.async }));
|
|
132
|
+
}
|
|
110
133
|
this.checkOverride(info, member);
|
|
134
|
+
if (member.accessor)
|
|
135
|
+
this.checkAccessorOverride(info, member.name, member.isStatic);
|
|
111
136
|
}
|
|
112
137
|
else if (member.kind === 'ConstructorDeclaration' && info.ctor) {
|
|
113
138
|
constructorNode = member;
|
|
@@ -119,9 +144,13 @@ export class ClassChecker extends DeclarationChecker {
|
|
|
119
144
|
}
|
|
120
145
|
}
|
|
121
146
|
}
|
|
147
|
+
this.checkAbstract(node, info);
|
|
122
148
|
// Fields without a zero value must get one at once or in the constructor.
|
|
123
149
|
for (const member of node.members) {
|
|
124
|
-
if (member.kind !== 'FieldDeclaration'
|
|
150
|
+
if (member.kind !== 'FieldDeclaration')
|
|
151
|
+
continue;
|
|
152
|
+
this.checkAccessorOverride(info, member.name, member.isStatic);
|
|
153
|
+
if (member.isStatic || member.value)
|
|
125
154
|
continue;
|
|
126
155
|
const type = info.members.get(member.name.name)?.type;
|
|
127
156
|
if (!type || hasZeroValue(type))
|
|
@@ -166,6 +195,7 @@ export class ClassChecker extends DeclarationChecker {
|
|
|
166
195
|
types: new Map(),
|
|
167
196
|
decorators: new Map(),
|
|
168
197
|
components: new Map(),
|
|
198
|
+
shared: new Set(),
|
|
169
199
|
};
|
|
170
200
|
const declarations = { values: new Map(), types: new Map() };
|
|
171
201
|
const valueOf = (name) => this.moduleScope.values.get(name)?.type ?? UNKNOWN;
|
|
@@ -188,6 +218,8 @@ export class ClassChecker extends DeclarationChecker {
|
|
|
188
218
|
case 'VariableDeclaration':
|
|
189
219
|
for (const name of statement.names) {
|
|
190
220
|
add(statement.exported, 'values', name.name, valueOf(name.name));
|
|
221
|
+
if (statement.exported && statement.keyword === 'state')
|
|
222
|
+
exports.shared.add(name.name);
|
|
191
223
|
}
|
|
192
224
|
break;
|
|
193
225
|
case 'ClassDeclaration':
|
|
@@ -201,7 +233,10 @@ export class ClassChecker extends DeclarationChecker {
|
|
|
201
233
|
case 'ComponentDeclaration': {
|
|
202
234
|
const info = this.moduleScope.values.get(statement.name.name)?.component;
|
|
203
235
|
if (statement.exported && info?.node === statement) {
|
|
204
|
-
|
|
236
|
+
const found = this.modulePackages().components.get(info.node.name.name) ?? null;
|
|
237
|
+
// Code that reads state another module shares listens to it here, so it stays here.
|
|
238
|
+
const shared = found?.moduleNames.some((name) => this.moduleScope.values.get(name)?.shared !== undefined);
|
|
239
|
+
info.inline = shared ? null : found;
|
|
205
240
|
info.expressionTypes = this.checkedTypes;
|
|
206
241
|
exports.components.set(statement.name.name, info);
|
|
207
242
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import type { Binding, BindingKind } from './context.ts';
|
|
3
|
+
import { IteratorChecker } from './iterators.ts';
|
|
4
|
+
import type { Type } from './types.ts';
|
|
5
|
+
export declare abstract class ClosingChecker extends IteratorChecker {
|
|
6
|
+
/** The identifier that declares a binding of this module. */
|
|
7
|
+
private readonly declarations;
|
|
8
|
+
/** What each name refers to: its declaration, or the name of a predeclared value. */
|
|
9
|
+
private readonly references;
|
|
10
|
+
protected declareValue(id: ast.Identifier, kind: BindingKind, type: Type | null): Binding;
|
|
11
|
+
protected checkIdentifier(node: ast.Identifier): Type;
|
|
12
|
+
/** The functions declared in the module, by their names, which `use` looks arguments up in. */
|
|
13
|
+
private functions;
|
|
14
|
+
protected checkClosing(): void;
|
|
15
|
+
/** `chan<T>()`, `topic<T>()`, `tick(ms)`, `signal(name)` and their `Channel.make` and the like. */
|
|
16
|
+
private made;
|
|
17
|
+
/** `Channel.close` and the like, as a name, when `Channel` is the predeclared one. */
|
|
18
|
+
private predeclaredMember;
|
|
19
|
+
/** What the code around a name of a channel does with it. */
|
|
20
|
+
private use;
|
|
21
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { forEachChild } from "../walk.js";
|
|
2
|
+
import { IteratorChecker } from "./iterators.js";
|
|
3
|
+
/** What `Channel.x(ch)` and `Topic.x(t)` do with a channel without keeping it. */
|
|
4
|
+
const OPERATIONS = new Set(['send', 'receive', 'range', 'stream', 'publish', 'listen', 'try']);
|
|
5
|
+
const MESSAGES = {
|
|
6
|
+
chan: (name) => `"${name}" is never closed: close it when nothing more is sent, e.g. close(${name})`,
|
|
7
|
+
topic: (name) => `"${name}" is never closed, and a topic listens until it is closed: close it when it is no longer needed, e.g. close(${name}); a component closes it with defer close(${name}) in mount`,
|
|
8
|
+
tick: (name) => `"${name}" is never closed, and a tick runs until it is closed: e.g. close(${name})`,
|
|
9
|
+
signal: (name) => `"${name}" is never closed, and it keeps the program running until it is: close it once the signal has come, e.g. close(${name})`,
|
|
10
|
+
};
|
|
11
|
+
export class ClosingChecker extends IteratorChecker {
|
|
12
|
+
/** The identifier that declares a binding of this module. */
|
|
13
|
+
declarations = new WeakMap();
|
|
14
|
+
/** What each name refers to: its declaration, or the name of a predeclared value. */
|
|
15
|
+
references = new Map();
|
|
16
|
+
declareValue(id, kind, type) {
|
|
17
|
+
const binding = super.declareValue(id, kind, type);
|
|
18
|
+
this.declarations.set(binding, id);
|
|
19
|
+
return binding;
|
|
20
|
+
}
|
|
21
|
+
checkIdentifier(node) {
|
|
22
|
+
const binding = this.lookupValue(node.name);
|
|
23
|
+
const declaration = binding && this.declarations.get(binding);
|
|
24
|
+
if (declaration)
|
|
25
|
+
this.references.set(node, declaration);
|
|
26
|
+
else if (binding && binding === this.globals.values.get(node.name)) {
|
|
27
|
+
this.references.set(node, node.name);
|
|
28
|
+
}
|
|
29
|
+
return super.checkIdentifier(node);
|
|
30
|
+
}
|
|
31
|
+
/** The functions declared in the module, by their names, which `use` looks arguments up in. */
|
|
32
|
+
functions = new Map();
|
|
33
|
+
checkClosing() {
|
|
34
|
+
const made = [];
|
|
35
|
+
const uses = new Map();
|
|
36
|
+
const collect = (node) => {
|
|
37
|
+
if (node.kind === 'FuncDeclaration')
|
|
38
|
+
this.functions.set(node.name, node);
|
|
39
|
+
forEachChild(node, collect);
|
|
40
|
+
};
|
|
41
|
+
const visit = (node, parent) => {
|
|
42
|
+
const what = node.kind === 'VariableDeclaration' && !node.exported && node.names.length === 1
|
|
43
|
+
? node.values[0] && this.made(node.values[0])
|
|
44
|
+
: null;
|
|
45
|
+
if (node.kind === 'VariableDeclaration' && what)
|
|
46
|
+
made.push([node.names[0], what]);
|
|
47
|
+
const declaration = node.kind === 'Identifier' ? this.references.get(node) : undefined;
|
|
48
|
+
if (node.kind === 'Identifier' && typeof declaration === 'object' && parent) {
|
|
49
|
+
if (!uses.has(declaration)) {
|
|
50
|
+
uses.set(declaration, { closed: false, escapes: false, passed: [] });
|
|
51
|
+
}
|
|
52
|
+
this.use(node, parent, uses.get(declaration));
|
|
53
|
+
}
|
|
54
|
+
forEachChild(node, (child) => visit(child, node));
|
|
55
|
+
};
|
|
56
|
+
collect(this.program);
|
|
57
|
+
visit(this.program, null);
|
|
58
|
+
// Whether a variable or a parameter is closed, goes where it is not seen, or neither.
|
|
59
|
+
const fate = (id, seen) => {
|
|
60
|
+
const found = uses.get(id);
|
|
61
|
+
if (!found || seen.has(id))
|
|
62
|
+
return 'none';
|
|
63
|
+
if (found.closed)
|
|
64
|
+
return 'closed';
|
|
65
|
+
seen.add(id);
|
|
66
|
+
let result = found.escapes ? 'escapes' : 'none';
|
|
67
|
+
for (const [fn, i] of found.passed) {
|
|
68
|
+
const param = fn.params[i];
|
|
69
|
+
const inner = param && !param.rest ? fate(param.name, seen) : 'escapes';
|
|
70
|
+
if (inner === 'closed')
|
|
71
|
+
return 'closed';
|
|
72
|
+
if (inner === 'escapes')
|
|
73
|
+
result = 'escapes';
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
};
|
|
77
|
+
for (const [name, what] of made) {
|
|
78
|
+
if (fate(name, new Set()) === 'none')
|
|
79
|
+
this.warn(MESSAGES[what](name.name), name);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** `chan<T>()`, `topic<T>()`, `tick(ms)`, `signal(name)` and their `Channel.make` and the like. */
|
|
83
|
+
made(value) {
|
|
84
|
+
if (value.kind === 'ChanExpression')
|
|
85
|
+
return 'chan';
|
|
86
|
+
if (value.kind === 'TopicExpression')
|
|
87
|
+
return 'topic';
|
|
88
|
+
if (value.kind !== 'CallExpression')
|
|
89
|
+
return null;
|
|
90
|
+
const { callee } = value;
|
|
91
|
+
if (callee.kind === 'Identifier') {
|
|
92
|
+
const predeclared = this.references.get(callee);
|
|
93
|
+
return predeclared === 'tick' || predeclared === 'signal' ? predeclared : null;
|
|
94
|
+
}
|
|
95
|
+
const member = this.predeclaredMember(callee);
|
|
96
|
+
if (member === 'Channel.make')
|
|
97
|
+
return 'chan';
|
|
98
|
+
if (member === 'Channel.tick')
|
|
99
|
+
return 'tick';
|
|
100
|
+
if (member === 'Channel.signal')
|
|
101
|
+
return 'signal';
|
|
102
|
+
return member === 'Topic.make' ? 'topic' : null;
|
|
103
|
+
}
|
|
104
|
+
/** `Channel.close` and the like, as a name, when `Channel` is the predeclared one. */
|
|
105
|
+
predeclaredMember(node) {
|
|
106
|
+
if (node.kind !== 'MemberExpression' || node.object.kind !== 'Identifier')
|
|
107
|
+
return null;
|
|
108
|
+
const object = this.references.get(node.object);
|
|
109
|
+
return (object === 'Channel' || object === 'Topic') && node.property.kind === 'Identifier'
|
|
110
|
+
? `${object}.${node.property.name}`
|
|
111
|
+
: null;
|
|
112
|
+
}
|
|
113
|
+
/** What the code around a name of a channel does with it. */
|
|
114
|
+
use(node, parent, found) {
|
|
115
|
+
switch (parent.kind) {
|
|
116
|
+
case 'CallExpression': {
|
|
117
|
+
const i = parent.arguments.indexOf(node);
|
|
118
|
+
if (i < 0)
|
|
119
|
+
return;
|
|
120
|
+
const { callee } = parent;
|
|
121
|
+
const ref = callee.kind === 'Identifier' ? this.references.get(callee) : undefined;
|
|
122
|
+
const member = this.predeclaredMember(callee)?.split('.')[1];
|
|
123
|
+
if (ref === 'close' || member === 'close')
|
|
124
|
+
found.closed = true;
|
|
125
|
+
else if (ref === 'stream' || (member !== undefined && OPERATIONS.has(member)))
|
|
126
|
+
return;
|
|
127
|
+
else {
|
|
128
|
+
const fn = callee.kind === 'FuncExpression'
|
|
129
|
+
? callee
|
|
130
|
+
: typeof ref === 'object'
|
|
131
|
+
? this.functions.get(ref)
|
|
132
|
+
: undefined;
|
|
133
|
+
if (fn)
|
|
134
|
+
found.passed.push([fn, i]);
|
|
135
|
+
else
|
|
136
|
+
found.escapes = true;
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Sending to it, receiving from it, a loop over it, comparing and assigning it keep it here;
|
|
141
|
+
// sending it through a channel does not.
|
|
142
|
+
case 'SendStatement':
|
|
143
|
+
if (parent.value === node)
|
|
144
|
+
found.escapes = true;
|
|
145
|
+
return;
|
|
146
|
+
case 'UnaryExpression':
|
|
147
|
+
if (parent.operator !== '<-')
|
|
148
|
+
found.escapes = true;
|
|
149
|
+
return;
|
|
150
|
+
case 'ForInStatement':
|
|
151
|
+
return;
|
|
152
|
+
case 'BinaryExpression':
|
|
153
|
+
if (!['==', '!=', '===', '!=='].includes(parent.operator))
|
|
154
|
+
found.escapes = true;
|
|
155
|
+
return;
|
|
156
|
+
case 'AssignmentStatement':
|
|
157
|
+
if (!parent.targets.includes(node))
|
|
158
|
+
found.escapes = true;
|
|
159
|
+
return;
|
|
160
|
+
default:
|
|
161
|
+
found.escapes = true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import {
|
|
3
|
-
import type
|
|
2
|
+
import { ClosingChecker } from './closing.ts';
|
|
3
|
+
import { type CompletionResult } from './context.ts';
|
|
4
4
|
import { type Type } from './types.ts';
|
|
5
|
-
export declare abstract class CompletionChecker extends
|
|
5
|
+
export declare abstract class CompletionChecker extends ClosingChecker {
|
|
6
6
|
protected completion: CompletionResult | null;
|
|
7
7
|
private atCursor;
|
|
8
8
|
/** The first check of the cursor counts: a component's body, for one, is checked for each use. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ClosingChecker } from "./closing.js";
|
|
2
2
|
import { ARRAY_MEMBERS, ATTRIBUTE_NAMES, BIGINT_MEMBERS, BOOL_MEMBERS, EVENT_ATTRIBUTES, HTML_TAGS, NUMBER_MEMBERS, STRING_MEMBERS, } from "./completion-names.js";
|
|
3
|
+
import { placeOf, } from "./context.js";
|
|
3
4
|
import { isSubclass, literalBase, memberNames, memberTypeOf, nonNull, typeToString, union, UNKNOWN, } from "./types.js";
|
|
4
5
|
// Completion in editors (src/completion.ts): the editor writes the name `options.completion` where
|
|
5
6
|
// the cursor is, and this layer lists what can be written there when the check reaches that name:
|
|
@@ -12,7 +13,7 @@ const PRIMITIVE_MEMBERS = {
|
|
|
12
13
|
bigint: BIGINT_MEMBERS,
|
|
13
14
|
bool: BOOL_MEMBERS,
|
|
14
15
|
};
|
|
15
|
-
export class CompletionChecker extends
|
|
16
|
+
export class CompletionChecker extends ClosingChecker {
|
|
16
17
|
completion = null;
|
|
17
18
|
atCursor(node) {
|
|
18
19
|
return node.name === this.options.completion;
|
|
@@ -79,7 +80,7 @@ export class CompletionChecker extends IteratorChecker {
|
|
|
79
80
|
if (component) {
|
|
80
81
|
return { name, kind: 'component', detail: typeToString(this.componentValue(component)) };
|
|
81
82
|
}
|
|
82
|
-
const type = binding.type && (this.flow.get(binding) ?? binding.type);
|
|
83
|
+
const type = binding.type && (this.flow.get(placeOf(binding)) ?? binding.type);
|
|
83
84
|
const detail = type ? { detail: typeToString(type) } : {};
|
|
84
85
|
if (kind === 'function' || type?.kind === 'function')
|
|
85
86
|
return { name, kind: 'function', ...detail };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import type { ComponentInfo } from './context.ts';
|
|
3
|
+
import { ForwardingChecker } from './forwarding.ts';
|
|
4
|
+
export declare abstract class ComponentBindingChecker extends ForwardingChecker {
|
|
5
|
+
/** `bind:active={tab}` on a component; adds the name of the property to `given`. */
|
|
6
|
+
protected checkPropBinding(attribute: ast.JsxAttribute, info: ComponentInfo, component: string, given: Set<string>): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { attrsParam } from "../attrs.js";
|
|
2
|
+
import { ForwardingChecker } from "./forwarding.js";
|
|
3
|
+
import { isUntyped } from "./helpers.js";
|
|
4
|
+
import { isAssignable, typeToString } from "./types.js";
|
|
5
|
+
// Bound properties: `comp Tabs(bind active string)` may change `active`, and
|
|
6
|
+
// `<Tabs bind:active={tab} />` writes those changes into the parent's `tab`, as `bind:value` of an
|
|
7
|
+
// input does. The value goes both ways, so the variable and the property have the same type.
|
|
8
|
+
export class ComponentBindingChecker extends ForwardingChecker {
|
|
9
|
+
/** `bind:active={tab}` on a component; adds the name of the property to `given`. */
|
|
10
|
+
checkPropBinding(attribute, info, component, given) {
|
|
11
|
+
const name = attribute.name.name.slice('bind:'.length);
|
|
12
|
+
const prop = info.props.get(name);
|
|
13
|
+
const { value } = attribute;
|
|
14
|
+
if (given.has(name))
|
|
15
|
+
this.error(`duplicate property "${name}"`, attribute.name);
|
|
16
|
+
given.add(name);
|
|
17
|
+
if (!prop) {
|
|
18
|
+
this.error(attrsParam(info.node)
|
|
19
|
+
? `${attribute.name.name} does not go through the attributes of <${component}>`
|
|
20
|
+
: `<${component}> has no property "${name}" to bind`, attribute.name);
|
|
21
|
+
}
|
|
22
|
+
else if (!prop.bind) {
|
|
23
|
+
this.error(`"${name}" of <${component}> is not bound: the component declares it with bind to change it, e.g. comp ${component}(bind ${name} ...)`, attribute.name);
|
|
24
|
+
}
|
|
25
|
+
if (value === null || value.kind === 'StringLiteral' || value.kind === 'EventHandler') {
|
|
26
|
+
this.error(`"bind:${name}" needs a variable in braces, e.g. bind:${name}={${name}}`, attribute);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const bindable = (value.kind === 'Identifier' && value.name !== '_') ||
|
|
30
|
+
((value.kind === 'MemberExpression' || value.kind === 'IndexExpression') && !value.optional);
|
|
31
|
+
if (!bindable) {
|
|
32
|
+
this.checkValue(value);
|
|
33
|
+
this.error(`"bind:${name}" needs a variable or a field to write to`, value);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const type = this.checkTarget(value);
|
|
37
|
+
this.checkedTypes.set(value, type);
|
|
38
|
+
if (!prop?.bind || isUntyped(type) || isUntyped(prop.type))
|
|
39
|
+
return;
|
|
40
|
+
// The parent gives its value, and the component writes its own back.
|
|
41
|
+
if (!isAssignable(type, prop.type) || !isAssignable(prop.type, type)) {
|
|
42
|
+
this.error(`cannot bind ${typeToString(type)} to "${name}" of <${component}>, which is ${typeToString(prop.type)}`, value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ComponentChecker } from "./components.js";
|
|
2
2
|
import { isUntyped } from "./helpers.js";
|
|
3
|
-
const upperFirst = (name) => name.charAt(0).toUpperCase() + name.slice(1);
|
|
4
3
|
import { isNullable, typeToString, UNKNOWN, } from "./types.js";
|
|
5
4
|
// Components as values: `content: Intro` has the type `comp()`, and a tag takes the component
|
|
6
5
|
// from a value, `<section.content />`, or `<Page />` when `Page` is a variable. Such a tag passes
|
|
@@ -20,12 +19,8 @@ export class ComponentValueChecker extends ComponentChecker {
|
|
|
20
19
|
const name = tag.name;
|
|
21
20
|
if (isUntyped(type) || type.kind !== 'component') {
|
|
22
21
|
if (isNullable(type)) {
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
const show = `{if ${variable} != null { <${variable} /> }}`;
|
|
26
|
-
this.error(name === variable
|
|
27
|
-
? `<${name}> may be null: show it with {if}, e.g. ${show}`
|
|
28
|
-
: `<${name}> may be null: put it into a variable and show it with {if}, e.g. const ${variable} = ${name}, then ${show}`, tag);
|
|
22
|
+
// A path of fields is narrowed like a variable: `{if page.other != null { <page.other /> }}`.
|
|
23
|
+
this.error(`<${name}> may be null: show it with {if}, e.g. {if ${name} != null { <${name} /> }}`, tag);
|
|
29
24
|
}
|
|
30
25
|
else if (!isUntyped(type)) {
|
|
31
26
|
this.error(`<${name}> is ${typeToString(type)}, not a component: give it a type like comp(title string)`, tag);
|
|
@@ -58,6 +53,14 @@ export class ComponentValueChecker extends ComponentChecker {
|
|
|
58
53
|
this.error(`ref cannot point to <${name}>: its component is a value, so its element is not known in advance`, attribute);
|
|
59
54
|
continue;
|
|
60
55
|
}
|
|
56
|
+
// The type of the value does not say which properties are bound.
|
|
57
|
+
if (attributeName.startsWith('bind:')) {
|
|
58
|
+
const { value } = attribute;
|
|
59
|
+
if (value && value.kind !== 'EventHandler')
|
|
60
|
+
this.checkValue(value);
|
|
61
|
+
this.error(`<${name}> takes its component from a value, so it cannot bind its properties; pass a function, e.g. onChange={...}`, attribute.name);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
61
64
|
if (given.has(attributeName)) {
|
|
62
65
|
this.error(`duplicate property "${attributeName}"`, attribute.name);
|
|
63
66
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import type { ComponentInfo } from './context.ts';
|
|
3
|
-
import {
|
|
3
|
+
import { ComponentBindingChecker } from './component-bindings.ts';
|
|
4
4
|
import { type Type } from './types.ts';
|
|
5
|
-
export declare abstract class ComponentChecker extends
|
|
5
|
+
export declare abstract class ComponentChecker extends ComponentBindingChecker {
|
|
6
6
|
protected resolveProps(info: ComponentInfo): void;
|
|
7
7
|
/** The body runs once where the component is used and must end with `return <markup>`. */
|
|
8
8
|
protected checkComponentBody(info: ComponentInfo): void;
|
|
@@ -3,10 +3,11 @@ import { freeNames } from "../names.js";
|
|
|
3
3
|
import { endlessRecursion } from "../recursion.js";
|
|
4
4
|
import { CONTENT } from "./dom.js";
|
|
5
5
|
import { lookupIn, ownStatements, isUntyped, callSignature } from "./helpers.js";
|
|
6
|
-
import {
|
|
6
|
+
import { ComponentBindingChecker } from "./component-bindings.js";
|
|
7
|
+
import { ATTRIBUTES } from "./forwarding.js";
|
|
7
8
|
import { BOOL, func, hasZeroValue, isAssignable, isNullable, nonNull, spreadFields, typesEqual, typeToString, UNKNOWN, } from "./types.js";
|
|
8
9
|
// Components: their bodies and uses, properties and spread, names they use from the module, recursion.
|
|
9
|
-
export class ComponentChecker extends
|
|
10
|
+
export class ComponentChecker extends ComponentBindingChecker {
|
|
10
11
|
resolveProps(info) {
|
|
11
12
|
for (const param of info.node.params) {
|
|
12
13
|
// `...attrs` takes the attributes that are not properties.
|
|
@@ -20,7 +21,7 @@ export class ComponentChecker extends ForwardingChecker {
|
|
|
20
21
|
if (info.props.has(name))
|
|
21
22
|
this.error(`duplicate property "${name}"`, param.name);
|
|
22
23
|
const optional = param.defaultValue !== null || isNullable(type) || name === 'children';
|
|
23
|
-
info.props.set(name, { type, optional });
|
|
24
|
+
info.props.set(name, { type, optional, ...(param.bind ? { bind: true } : {}) });
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
/** The body runs once where the component is used and must end with `return <markup>`. */
|
|
@@ -96,6 +97,10 @@ export class ComponentChecker extends ForwardingChecker {
|
|
|
96
97
|
this.checkRefAttribute(attribute, node);
|
|
97
98
|
continue;
|
|
98
99
|
}
|
|
100
|
+
if (name.startsWith('bind:')) {
|
|
101
|
+
this.checkPropBinding(attribute, info, tag.name, given);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
99
104
|
if (given.has(name))
|
|
100
105
|
this.error(`duplicate property "${name}"`, attribute.name);
|
|
101
106
|
given.add(name);
|