rooklang 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +177 -2
- package/README.ru.md +178 -0
- package/dist/ast/declarations.d.ts +185 -0
- package/dist/ast/declarations.js +1 -0
- package/dist/ast/expressions.d.ts +201 -0
- package/dist/ast/expressions.js +1 -0
- package/dist/ast/markup.d.ts +141 -0
- package/dist/ast/markup.js +1 -0
- package/dist/ast/statements.d.ts +173 -0
- package/dist/ast/statements.js +1 -0
- package/dist/ast.d.ts +18 -0
- package/dist/ast.js +18 -0
- package/dist/attrs.d.ts +8 -0
- package/dist/attrs.js +23 -0
- package/dist/browser.d.ts +30 -0
- package/dist/browser.js +36 -0
- package/dist/build.d.ts +48 -0
- package/dist/build.js +112 -0
- package/dist/checker/assignments.d.ts +14 -0
- package/dist/checker/assignments.js +197 -0
- package/dist/checker/base.d.ts +124 -0
- package/dist/checker/base.js +180 -0
- package/dist/checker/bigint.d.ts +15 -0
- package/dist/checker/bigint.js +85 -0
- package/dist/checker/builtins.d.ts +9 -0
- package/dist/checker/builtins.js +307 -0
- package/dist/checker/bytes.d.ts +11 -0
- package/dist/checker/bytes.js +72 -0
- package/dist/checker/calls.d.ts +23 -0
- package/dist/checker/calls.js +210 -0
- package/dist/checker/channel.d.ts +14 -0
- package/dist/checker/channel.js +44 -0
- package/dist/checker/channels.d.ts +9 -0
- package/dist/checker/channels.js +95 -0
- package/dist/checker/checker.d.ts +4 -0
- package/dist/checker/checker.js +22 -0
- package/dist/checker/classes.d.ts +24 -0
- package/dist/checker/classes.js +223 -0
- package/dist/checker/completion-names.d.ts +11 -0
- package/dist/checker/completion-names.js +100 -0
- package/dist/checker/completion.d.ts +25 -0
- package/dist/checker/completion.js +222 -0
- package/dist/checker/component-values.d.ts +11 -0
- package/dist/checker/component-values.js +88 -0
- package/dist/checker/components.d.ts +37 -0
- package/dist/checker/components.js +287 -0
- package/dist/checker/context.d.ts +196 -0
- package/dist/checker/context.js +35 -0
- package/dist/checker/control-flow.d.ts +24 -0
- package/dist/checker/control-flow.js +237 -0
- package/dist/checker/declarations.d.ts +26 -0
- package/dist/checker/declarations.js +259 -0
- package/dist/checker/decorator-exports.d.ts +5 -0
- package/dist/checker/decorator-exports.js +40 -0
- package/dist/checker/decorator-members.d.ts +28 -0
- package/dist/checker/decorator-members.js +140 -0
- package/dist/checker/decorator-wrappers.d.ts +20 -0
- package/dist/checker/decorator-wrappers.js +132 -0
- package/dist/checker/decorators.d.ts +28 -0
- package/dist/checker/decorators.js +265 -0
- package/dist/checker/dom-svg.d.ts +7 -0
- package/dist/checker/dom-svg.js +19 -0
- package/dist/checker/dom.d.ts +22 -0
- package/dist/checker/dom.js +361 -0
- package/dist/checker/expressions.d.ts +32 -0
- package/dist/checker/expressions.js +244 -0
- package/dist/checker/flow.d.ts +18 -0
- package/dist/checker/flow.js +134 -0
- package/dist/checker/forwarding.d.ts +15 -0
- package/dist/checker/forwarding.js +78 -0
- package/dist/checker/functions.d.ts +24 -0
- package/dist/checker/functions.js +154 -0
- package/dist/checker/go-for.d.ts +12 -0
- package/dist/checker/go-for.js +93 -0
- package/dist/checker/goroutines.d.ts +19 -0
- package/dist/checker/goroutines.js +110 -0
- package/dist/checker/helpers.d.ts +35 -0
- package/dist/checker/helpers.js +162 -0
- package/dist/checker/imports.d.ts +7 -0
- package/dist/checker/imports.js +83 -0
- package/dist/checker/initializers.d.ts +22 -0
- package/dist/checker/initializers.js +104 -0
- package/dist/checker/iterables.d.ts +5 -0
- package/dist/checker/iterables.js +17 -0
- package/dist/checker/iterators.d.ts +13 -0
- package/dist/checker/iterators.js +95 -0
- package/dist/checker/loops.d.ts +10 -0
- package/dist/checker/loops.js +82 -0
- package/dist/checker/markup.d.ts +28 -0
- package/dist/checker/markup.js +220 -0
- package/dist/checker/members.d.ts +29 -0
- package/dist/checker/members.js +233 -0
- package/dist/checker/operators.d.ts +13 -0
- package/dist/checker/operators.js +177 -0
- package/dist/checker/promise.d.ts +17 -0
- package/dist/checker/promise.js +49 -0
- package/dist/checker/refs.d.ts +26 -0
- package/dist/checker/refs.js +136 -0
- package/dist/checker/resolution.d.ts +23 -0
- package/dist/checker/resolution.js +173 -0
- package/dist/checker/statements.d.ts +28 -0
- package/dist/checker/statements.js +212 -0
- package/dist/checker/styles.d.ts +14 -0
- package/dist/checker/styles.js +49 -0
- package/dist/checker/transfer.d.ts +5 -0
- package/dist/checker/transfer.js +83 -0
- package/dist/checker/types/bytes.d.ts +4 -0
- package/dist/checker/types/bytes.js +51 -0
- package/dist/checker/types/classes.d.ts +22 -0
- package/dist/checker/types/classes.js +93 -0
- package/dist/checker/types/constructors.d.ts +27 -0
- package/dist/checker/types/constructors.js +129 -0
- package/dist/checker/types/generics.d.ts +20 -0
- package/dist/checker/types/generics.js +75 -0
- package/dist/checker/types/lazy.d.ts +30 -0
- package/dist/checker/types/lazy.js +92 -0
- package/dist/checker/types/model.d.ts +149 -0
- package/dist/checker/types/model.js +15 -0
- package/dist/checker/types/params.d.ts +7 -0
- package/dist/checker/types/params.js +220 -0
- package/dist/checker/types/printing.d.ts +6 -0
- package/dist/checker/types/printing.js +109 -0
- package/dist/checker/types/relations.d.ts +16 -0
- package/dist/checker/types/relations.js +294 -0
- package/dist/checker/types.d.ts +9 -0
- package/dist/checker/types.js +10 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +193 -0
- package/dist/codegen/analysis.d.ts +18 -0
- package/dist/codegen/analysis.js +144 -0
- package/dist/codegen/attributes.d.ts +40 -0
- package/dist/codegen/attributes.js +227 -0
- package/dist/codegen/calls.d.ts +22 -0
- package/dist/codegen/calls.js +106 -0
- package/dist/codegen/chan-helpers.d.ts +2 -0
- package/dist/codegen/chan-helpers.js +253 -0
- package/dist/codegen/component-helpers.d.ts +33 -0
- package/dist/codegen/component-helpers.js +18 -0
- package/dist/codegen/component-props.d.ts +42 -0
- package/dist/codegen/component-props.js +173 -0
- package/dist/codegen/component-values.d.ts +27 -0
- package/dist/codegen/component-values.js +146 -0
- package/dist/codegen/components.d.ts +53 -0
- package/dist/codegen/components.js +268 -0
- package/dist/codegen/control-flow.d.ts +25 -0
- package/dist/codegen/control-flow.js +152 -0
- package/dist/codegen/control-statements.d.ts +28 -0
- package/dist/codegen/control-statements.js +226 -0
- package/dist/codegen/creation.d.ts +31 -0
- package/dist/codegen/creation.js +113 -0
- package/dist/codegen/early-returns.d.ts +59 -0
- package/dist/codegen/early-returns.js +244 -0
- package/dist/codegen/element-helpers.d.ts +12 -0
- package/dist/codegen/element-helpers.js +54 -0
- package/dist/codegen/elements.d.ts +71 -0
- package/dist/codegen/elements.js +293 -0
- package/dist/codegen/emitter.d.ts +135 -0
- package/dist/codegen/emitter.js +296 -0
- package/dist/codegen/expressions.d.ts +49 -0
- package/dist/codegen/expressions.js +262 -0
- package/dist/codegen/functions.d.ts +28 -0
- package/dist/codegen/functions.js +170 -0
- package/dist/codegen/go-helpers.d.ts +9 -0
- package/dist/codegen/go-helpers.js +307 -0
- package/dist/codegen/helpers.d.ts +10 -0
- package/dist/codegen/helpers.js +356 -0
- package/dist/codegen/imported.d.ts +38 -0
- package/dist/codegen/imported.js +71 -0
- package/dist/codegen/js.d.ts +20 -0
- package/dist/codegen/js.js +113 -0
- package/dist/codegen/mutations.d.ts +10 -0
- package/dist/codegen/mutations.js +102 -0
- package/dist/codegen/parallel.d.ts +7 -0
- package/dist/codegen/parallel.js +63 -0
- package/dist/codegen/reactive.d.ts +95 -0
- package/dist/codegen/reactive.js +280 -0
- package/dist/codegen/refs.d.ts +62 -0
- package/dist/codegen/refs.js +160 -0
- package/dist/codegen/select-helpers.d.ts +2 -0
- package/dist/codegen/select-helpers.js +135 -0
- package/dist/codegen/simple-statements.d.ts +42 -0
- package/dist/codegen/simple-statements.js +253 -0
- package/dist/codegen/statements.d.ts +22 -0
- package/dist/codegen/statements.js +142 -0
- package/dist/codegen/styles.d.ts +54 -0
- package/dist/codegen/styles.js +157 -0
- package/dist/codegen/syntax.d.ts +18 -0
- package/dist/codegen/syntax.js +70 -0
- package/dist/codegen/thread-helpers.d.ts +2 -0
- package/dist/codegen/thread-helpers.js +198 -0
- package/dist/codegen/updates.d.ts +51 -0
- package/dist/codegen/updates.js +201 -0
- package/dist/codegen/web-components.d.ts +20 -0
- package/dist/codegen/web-components.js +149 -0
- package/dist/compile-file.d.ts +11 -0
- package/dist/compile-file.js +24 -0
- package/dist/compile.d.ts +53 -0
- package/dist/compile.js +187 -0
- package/dist/completion.d.ts +10 -0
- package/dist/completion.js +134 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +44 -0
- package/dist/css/hash.d.ts +1 -0
- package/dist/css/hash.js +10 -0
- package/dist/css/parse.d.ts +42 -0
- package/dist/css/parse.js +138 -0
- package/dist/css/print.d.ts +9 -0
- package/dist/css/print.js +35 -0
- package/dist/css/selectors.d.ts +29 -0
- package/dist/css/selectors.js +168 -0
- package/dist/declarations.d.ts +3 -0
- package/dist/declarations.js +207 -0
- package/dist/decorator-copy.d.ts +18 -0
- package/dist/decorator-copy.js +158 -0
- package/dist/decorators.d.ts +61 -0
- package/dist/decorators.js +241 -0
- package/dist/diagnostics.d.ts +17 -0
- package/dist/diagnostics.js +22 -0
- package/dist/go-for.d.ts +26 -0
- package/dist/go-for.js +155 -0
- package/dist/goroutines.d.ts +37 -0
- package/dist/goroutines.js +253 -0
- package/dist/html-tag.d.ts +21 -0
- package/dist/html-tag.js +103 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +55 -0
- package/dist/inline-packages.d.ts +32 -0
- package/dist/inline-packages.js +129 -0
- package/dist/iterators.d.ts +10 -0
- package/dist/iterators.js +33 -0
- package/dist/lexer/base.d.ts +24 -0
- package/dist/lexer/base.js +87 -0
- package/dist/lexer/lexer.d.ts +8 -0
- package/dist/lexer/lexer.js +134 -0
- package/dist/lexer/literals.d.ts +18 -0
- package/dist/lexer/literals.js +203 -0
- package/dist/lexer/markup.d.ts +10 -0
- package/dist/lexer/markup.js +93 -0
- package/dist/lexer/style.d.ts +17 -0
- package/dist/lexer/style.js +97 -0
- package/dist/lexer/tables.d.ts +53 -0
- package/dist/lexer/tables.js +101 -0
- package/dist/lexer/token.d.ts +42 -0
- package/dist/lexer/token.js +108 -0
- package/dist/loader.d.ts +1 -0
- package/dist/loader.js +50 -0
- package/dist/methods.d.ts +5 -0
- package/dist/methods.js +81 -0
- package/dist/modules.d.ts +3 -0
- package/dist/modules.js +49 -0
- package/dist/names.d.ts +23 -0
- package/dist/names.js +267 -0
- package/dist/parser/base.d.ts +96 -0
- package/dist/parser/base.js +202 -0
- package/dist/parser/classes.d.ts +8 -0
- package/dist/parser/classes.js +164 -0
- package/dist/parser/control-flow.d.ts +29 -0
- package/dist/parser/control-flow.js +232 -0
- package/dist/parser/declarations.d.ts +27 -0
- package/dist/parser/declarations.js +238 -0
- package/dist/parser/decorators.d.ts +30 -0
- package/dist/parser/decorators.js +176 -0
- package/dist/parser/expressions.d.ts +15 -0
- package/dist/parser/expressions.js +255 -0
- package/dist/parser/literals.d.ts +27 -0
- package/dist/parser/literals.js +206 -0
- package/dist/parser/markup.d.ts +22 -0
- package/dist/parser/markup.js +193 -0
- package/dist/parser/parser.d.ts +14 -0
- package/dist/parser/parser.js +22 -0
- package/dist/parser/simple-statements.d.ts +22 -0
- package/dist/parser/simple-statements.js +156 -0
- package/dist/parser/statements.d.ts +37 -0
- package/dist/parser/statements.js +267 -0
- package/dist/parser/syntax.d.ts +51 -0
- package/dist/parser/syntax.js +173 -0
- package/dist/parser/types.d.ts +23 -0
- package/dist/parser/types.js +218 -0
- package/dist/recursion.d.ts +25 -0
- package/dist/recursion.js +170 -0
- package/dist/refs.d.ts +49 -0
- package/dist/refs.js +210 -0
- package/dist/run.d.ts +1 -0
- package/dist/run.js +21 -0
- package/dist/source.d.ts +15 -0
- package/dist/source.js +34 -0
- package/dist/style-modules.d.ts +17 -0
- package/dist/style-modules.js +49 -0
- package/dist/styles.d.ts +36 -0
- package/dist/styles.js +160 -0
- package/dist/svg.d.ts +8 -0
- package/dist/svg.js +77 -0
- package/dist/ts-types.d.ts +11 -0
- package/dist/ts-types.js +133 -0
- package/dist/typed-arrays.d.ts +8 -0
- package/dist/typed-arrays.js +43 -0
- package/dist/typescript/convert-base.d.ts +42 -0
- package/dist/typescript/convert-base.js +135 -0
- package/dist/typescript/convert-objects.d.ts +28 -0
- package/dist/typescript/convert-objects.js +245 -0
- package/dist/typescript/convert.d.ts +14 -0
- package/dist/typescript/convert.js +67 -0
- package/dist/typescript/importer.d.ts +37 -0
- package/dist/typescript/importer.js +152 -0
- package/dist/typescript/library.d.ts +23 -0
- package/dist/typescript/library.js +81 -0
- package/dist/vite.d.ts +11 -0
- package/dist/vite.js +86 -0
- package/dist/walk-types.d.ts +4 -0
- package/dist/walk-types.js +56 -0
- package/dist/walk.d.ts +5 -0
- package/dist/walk.js +315 -0
- package/package.json +104 -4
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { OperatorChecker } from './operators.ts';
|
|
3
|
+
import { type Type } from './types.ts';
|
|
4
|
+
export declare abstract class MarkupChecker extends OperatorChecker {
|
|
5
|
+
/** A tag that takes its component from a value; implemented by ComponentValueChecker. */
|
|
6
|
+
protected abstract checkValueTagUse(node: ast.ElementExpression, tag: ast.Identifier, value: ast.Expression): Type;
|
|
7
|
+
/** Whether the markup being checked is inside `<svg>`: its elements are SVG then. */
|
|
8
|
+
private inSvg;
|
|
9
|
+
/** The type of the element `<tag>` creates; outside markup, SVG only for SVG's own tags. */
|
|
10
|
+
protected elementOf(tag: string, svg?: boolean): Type;
|
|
11
|
+
protected checkElement(node: ast.ElementExpression): Type;
|
|
12
|
+
protected checkChildren(children: readonly ast.JsxChild[]): void;
|
|
13
|
+
protected checkAttribute(attribute: ast.JsxAttribute, tag: string, element: Type, svg?: boolean): void;
|
|
14
|
+
/**
|
|
15
|
+
* `bind:value={name}`: the element shows the variable, and what the user enters is written back
|
|
16
|
+
* to it. So the value must be something that can be assigned.
|
|
17
|
+
*/
|
|
18
|
+
protected checkBinding(attribute: ast.JsxAttribute, tag: string, attributes: readonly (ast.JsxAttribute | ast.JsxSpreadAttribute)[]): void;
|
|
19
|
+
/** `onClick={...}`: code to run on the event, or a function that gets the event. */
|
|
20
|
+
protected checkEventAttribute(attribute: ast.JsxAttribute, event: Type): void;
|
|
21
|
+
/**
|
|
22
|
+
* The statements of `onClick={count++}`, with `event` declared when there is one. They run on
|
|
23
|
+
* the event, so in the initializer of a variable they can use it, as functions there can:
|
|
24
|
+
* `const button = <button onClick={button.remove()}>`.
|
|
25
|
+
*/
|
|
26
|
+
protected checkEventHandler(handler: ast.EventHandler, event: Type | null): void;
|
|
27
|
+
private checkHandlerBody;
|
|
28
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { childrenInSvg, isSvgElement } from "../svg.js";
|
|
2
|
+
import { Scope } from "./context.js";
|
|
3
|
+
import { domProperty, elementType } from "./dom.js";
|
|
4
|
+
import { svgElementType } from "./dom-svg.js";
|
|
5
|
+
import { isContent, isAttributeValue, isUntyped } from "./helpers.js";
|
|
6
|
+
import { OperatorChecker } from "./operators.js";
|
|
7
|
+
import { BOOL, func, STRING, typeToString } from "./types.js";
|
|
8
|
+
// Markup: elements, their children and attributes, `bind:` and event handlers.
|
|
9
|
+
export class MarkupChecker extends OperatorChecker {
|
|
10
|
+
/** Whether the markup being checked is inside `<svg>`: its elements are SVG then. */
|
|
11
|
+
inSvg = false;
|
|
12
|
+
/** The type of the element `<tag>` creates; outside markup, SVG only for SVG's own tags. */
|
|
13
|
+
elementOf(tag, svg = isSvgElement(tag, false)) {
|
|
14
|
+
const type = this.options.library?.element(tag, svg);
|
|
15
|
+
return type ?? (svg ? svgElementType(tag) : elementType(tag));
|
|
16
|
+
}
|
|
17
|
+
checkElement(node) {
|
|
18
|
+
if (node.tag && node.value)
|
|
19
|
+
return this.checkValueTagUse(node, node.tag, node.value);
|
|
20
|
+
if (node.tag && /^[A-Z]/.test(node.tag.name)) {
|
|
21
|
+
const binding = this.lookupValue(node.tag.name);
|
|
22
|
+
// `<Page />` with a variable `Page` takes the component from its value.
|
|
23
|
+
return binding && !binding.component
|
|
24
|
+
? this.checkValueTagUse(node, node.tag, node.tag)
|
|
25
|
+
: this.checkComponentUse(node, node.tag);
|
|
26
|
+
}
|
|
27
|
+
const tag = node.tag?.name ?? null;
|
|
28
|
+
const svg = tag !== null && isSvgElement(tag, this.inSvg);
|
|
29
|
+
const type = tag === null ? this.dom.fragment : this.elementOf(tag, svg);
|
|
30
|
+
for (const attribute of node.attributes) {
|
|
31
|
+
if (attribute.kind === 'JsxSpreadAttribute') {
|
|
32
|
+
const spread = this.checkValue(attribute.argument);
|
|
33
|
+
if (!isUntyped(spread) && spread.kind !== 'object') {
|
|
34
|
+
this.error(`cannot spread ${typeToString(spread)} into attributes`, attribute.argument);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (attribute.name.name === 'ref') {
|
|
38
|
+
this.checkRefAttribute(attribute, node);
|
|
39
|
+
}
|
|
40
|
+
else if (tag !== null && attribute.name.name.startsWith('bind:')) {
|
|
41
|
+
this.checkBinding(attribute, tag, node.attributes);
|
|
42
|
+
}
|
|
43
|
+
else if (tag !== null) {
|
|
44
|
+
this.checkAttribute(attribute, tag, type, svg);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const attribute of node.attributes) {
|
|
48
|
+
if (attribute.kind !== 'JsxAttribute' || !attribute.name.name.startsWith('bind:'))
|
|
49
|
+
continue;
|
|
50
|
+
const property = attribute.name.name.slice('bind:'.length);
|
|
51
|
+
const plain = node.attributes.some((other) => other.kind === 'JsxAttribute' && other.name.name === property);
|
|
52
|
+
if (plain) {
|
|
53
|
+
this.error(`"${property}" and "bind:${property}" set the same property: keep one of them`, attribute.name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const saved = this.inSvg;
|
|
57
|
+
this.inSvg = childrenInSvg(tag, svg, saved);
|
|
58
|
+
try {
|
|
59
|
+
this.checkChildren(node.children);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
this.inSvg = saved;
|
|
63
|
+
}
|
|
64
|
+
return type;
|
|
65
|
+
}
|
|
66
|
+
checkChildren(children) {
|
|
67
|
+
for (const child of children) {
|
|
68
|
+
if (child.kind === 'JsxText')
|
|
69
|
+
continue;
|
|
70
|
+
if (child.kind === 'JsxStatementContainer') {
|
|
71
|
+
// Checked like any if/for/switch, so conditions narrow types in the blocks.
|
|
72
|
+
this.checkStatement(child.statement);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (child.kind === 'ElementExpression') {
|
|
76
|
+
this.checkExpression(child, null);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const content = this.checkValue(child.expression);
|
|
80
|
+
if (!isContent(content, this.dom.node)) {
|
|
81
|
+
this.error(`cannot use ${typeToString(content)} as element content` +
|
|
82
|
+
(content.kind === 'bool' ? ': use a condition, e.g. {ok ? <b>yes</b> : null}' : ''), child.expression);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
checkAttribute(attribute, tag, element, svg = false) {
|
|
87
|
+
const name = attribute.name.name;
|
|
88
|
+
const { value } = attribute;
|
|
89
|
+
// `<app-card count={3} />`: a property of a web component of this module.
|
|
90
|
+
const webProperty = this.webComponentProperty(tag, name);
|
|
91
|
+
if (webProperty) {
|
|
92
|
+
this.checkProp(attribute, webProperty, tag);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (/^on[A-Z]/.test(name)) {
|
|
96
|
+
this.checkEventAttribute(attribute, this.eventOf(name, element));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (value?.kind === 'EventHandler')
|
|
100
|
+
return;
|
|
101
|
+
const valueType = value === null ? BOOL : value.kind === 'StringLiteral' ? STRING : this.checkValue(value);
|
|
102
|
+
if (name === 'style') {
|
|
103
|
+
if (!isUntyped(valueType) && valueType.kind !== 'string' && valueType.kind !== 'object') {
|
|
104
|
+
this.error(`style must be a string or an object, not ${typeToString(valueType)}`, attribute);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// `<a download>` without a value sets an empty attribute, whatever the property type is.
|
|
109
|
+
if (value === null)
|
|
110
|
+
return;
|
|
111
|
+
// SVG attributes are set with setAttribute: their properties are objects.
|
|
112
|
+
const property = svg ? null : domProperty(tag, name);
|
|
113
|
+
if (property) {
|
|
114
|
+
this.expectAssignable(valueType, property.type, value, ` for attribute "${name}"`);
|
|
115
|
+
}
|
|
116
|
+
else if (!isAttributeValue(valueType)) {
|
|
117
|
+
this.error(`attribute "${name}" needs a string, number or bool, not ${typeToString(valueType)}`, value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* `bind:value={name}`: the element shows the variable, and what the user enters is written back
|
|
122
|
+
* to it. So the value must be something that can be assigned.
|
|
123
|
+
*/
|
|
124
|
+
checkBinding(attribute, tag, attributes) {
|
|
125
|
+
const name = attribute.name.name;
|
|
126
|
+
const property = name.slice('bind:'.length);
|
|
127
|
+
const { value } = attribute;
|
|
128
|
+
if (property !== 'value' && property !== 'checked') {
|
|
129
|
+
this.error(`unknown binding "${name}": use bind:value or bind:checked`, attribute.name);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const tags = property === 'value' ? ['input', 'textarea', 'select'] : ['input'];
|
|
133
|
+
if (!tags.includes(tag)) {
|
|
134
|
+
this.error(property === 'value'
|
|
135
|
+
? 'bind:value works with <input>, <textarea> and <select>'
|
|
136
|
+
: 'bind:checked works with <input>', attribute.name);
|
|
137
|
+
}
|
|
138
|
+
if (value === null || value.kind === 'StringLiteral' || value.kind === 'EventHandler') {
|
|
139
|
+
this.error(`"${name}" needs a variable in braces, e.g. ${name}={title}`, attribute);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const bindable = (value.kind === 'Identifier' && value.name !== '_') ||
|
|
143
|
+
((value.kind === 'MemberExpression' || value.kind === 'IndexExpression') && !value.optional);
|
|
144
|
+
if (!bindable) {
|
|
145
|
+
this.checkValue(value);
|
|
146
|
+
this.error(`"${name}" needs a variable or a field to write to`, value);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const type = this.checkTarget(value);
|
|
150
|
+
this.checkedTypes.set(value, type);
|
|
151
|
+
if (isUntyped(type))
|
|
152
|
+
return;
|
|
153
|
+
if (property === 'checked' && type.kind !== 'bool') {
|
|
154
|
+
this.error(`bind:checked needs a bool, not ${typeToString(type)}`, value);
|
|
155
|
+
}
|
|
156
|
+
else if (property === 'value' && type.kind === 'number' && tag === 'input') {
|
|
157
|
+
// A number is read with valueAsNumber, which only number and range inputs have.
|
|
158
|
+
const typeAttribute = attributes.find((other) => other.kind === 'JsxAttribute' && other.name.name === 'type')?.value;
|
|
159
|
+
const inputType = typeAttribute?.kind === 'StringLiteral' ? typeAttribute.value : null;
|
|
160
|
+
if (inputType !== 'number' && inputType !== 'range') {
|
|
161
|
+
this.error('a number can be bound to <input type="number"> or <input type="range">', attribute.name);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (property === 'value' &&
|
|
165
|
+
type.kind !== 'string' &&
|
|
166
|
+
!(type.kind === 'number' && tag === 'input')) {
|
|
167
|
+
const allowed = tag === 'input' ? 'a string or a number' : 'a string';
|
|
168
|
+
this.error(`bind:value needs ${allowed}, not ${typeToString(type)}`, value);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** `onClick={...}`: code to run on the event, or a function that gets the event. */
|
|
172
|
+
checkEventAttribute(attribute, event) {
|
|
173
|
+
const name = attribute.name.name;
|
|
174
|
+
const { value } = attribute;
|
|
175
|
+
if (value === null || value.kind === 'StringLiteral') {
|
|
176
|
+
this.error(`"${name}" needs code or a function in braces, e.g. ${name}={save()}`, attribute);
|
|
177
|
+
}
|
|
178
|
+
else if (value.kind === 'EventHandler') {
|
|
179
|
+
this.checkEventHandler(value, event);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const handler = func([event], []);
|
|
183
|
+
const type = this.checkValue(value, handler);
|
|
184
|
+
this.expectAssignable(type, handler, value, ` as the "${name}" handler`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The statements of `onClick={count++}`, with `event` declared when there is one. They run on
|
|
189
|
+
* the event, so in the initializer of a variable they can use it, as functions there can:
|
|
190
|
+
* `const button = <button onClick={button.remove()}>`.
|
|
191
|
+
*/
|
|
192
|
+
checkEventHandler(handler, event) {
|
|
193
|
+
const checkBody = () => this.checkHandlerBody(handler, event);
|
|
194
|
+
if (!this.deferBody(handler, checkBody))
|
|
195
|
+
checkBody();
|
|
196
|
+
}
|
|
197
|
+
checkHandlerBody(handler, event) {
|
|
198
|
+
const saved = { scope: this.scope, flow: this.flow, fn: this.fn };
|
|
199
|
+
this.scope = new Scope(this.scope);
|
|
200
|
+
this.flow = this.stableFlow();
|
|
201
|
+
this.fn = { results: [], returns: [], isConstructor: false };
|
|
202
|
+
try {
|
|
203
|
+
const name = {
|
|
204
|
+
kind: 'Identifier',
|
|
205
|
+
name: 'event',
|
|
206
|
+
start: handler.start,
|
|
207
|
+
end: handler.start,
|
|
208
|
+
};
|
|
209
|
+
if (event)
|
|
210
|
+
this.declareValue(name, 'param', event);
|
|
211
|
+
for (const statement of handler.body)
|
|
212
|
+
this.checkStatement(statement);
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
this.scope = saved.scope;
|
|
216
|
+
this.flow = saved.flow;
|
|
217
|
+
this.fn = saved.fn;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { DecoratorMemberChecker } from './decorator-members.ts';
|
|
3
|
+
import { type Member, type Type } from './types.ts';
|
|
4
|
+
export declare abstract class MemberChecker extends DecoratorMemberChecker {
|
|
5
|
+
/**
|
|
6
|
+
* Member access, indexing and calls. Returns the type and whether an optional link (`?.`)
|
|
7
|
+
* may short-circuit the chain: `a?.b.c()` is null as a whole when `a` is null.
|
|
8
|
+
*/
|
|
9
|
+
/** `expected` is for the result of a call: it can give type arguments, as in `let xs []number = empty()`. */
|
|
10
|
+
protected checkChain(node: ast.MemberExpression | ast.IndexExpression | ast.CallExpression, expected?: Type | null): [Type, boolean];
|
|
11
|
+
protected chainPart(node: ast.Expression): [Type, boolean];
|
|
12
|
+
/** The value of an object that must not be null, e.g. before a member assignment. */
|
|
13
|
+
protected nonNullValue(node: ast.Expression): Type;
|
|
14
|
+
/**
|
|
15
|
+
* `object[key]` with a key that is not a position: on an untyped value (`data[name]`), or on
|
|
16
|
+
* an object with `[key: string]: T` from a `.d.ts`. `null` for arrays and strings.
|
|
17
|
+
*/
|
|
18
|
+
protected keyedIndex(object: Type, index: ast.Expression): Type | null;
|
|
19
|
+
/**
|
|
20
|
+
* `styles["is-active"]`, or `styles[kind]` with `kind "primary" | "ghost"`: the fields of an
|
|
21
|
+
* object by keys whose values are known. `null` when they are not.
|
|
22
|
+
*/
|
|
23
|
+
private fieldsByKey;
|
|
24
|
+
protected expectIndex(node: ast.Expression): void;
|
|
25
|
+
/** The member `name` of a value of type `object`; `'any'` for untyped values. */
|
|
26
|
+
protected findMember(object: Type, name: string, node: ast.NodeBase): Member | 'any' | undefined;
|
|
27
|
+
/** The public member `name` of a type, without reporting anything; for members of unions. */
|
|
28
|
+
protected memberOf(type: Type, name: string): Member | 'any' | undefined;
|
|
29
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { bigintMember } from "./bigint.js";
|
|
2
|
+
import { boolMember, numberMember, stringMember } from "./builtins.js";
|
|
3
|
+
import { DecoratorMemberChecker } from "./decorator-members.js";
|
|
4
|
+
import { isUntyped, isNumeric } from "./helpers.js";
|
|
5
|
+
import { ANY, findClassMember, hasUntypedBase, isComparable, isNullable, isSubclass, literal, literalBase, memberNames, memberTypeOf, nonNull, NUMBER, property, STRING, typeToString, union, unionMembers, UNKNOWN, } from "./types.js";
|
|
6
|
+
// Member access and indexing, with `?.` chains.
|
|
7
|
+
export class MemberChecker extends DecoratorMemberChecker {
|
|
8
|
+
/**
|
|
9
|
+
* Member access, indexing and calls. Returns the type and whether an optional link (`?.`)
|
|
10
|
+
* may short-circuit the chain: `a?.b.c()` is null as a whole when `a` is null.
|
|
11
|
+
*/
|
|
12
|
+
/** `expected` is for the result of a call: it can give type arguments, as in `let xs []number = empty()`. */
|
|
13
|
+
checkChain(node, expected = null) {
|
|
14
|
+
if (node.kind === 'CallExpression') {
|
|
15
|
+
if (node.callee.kind === 'SuperExpression')
|
|
16
|
+
return [this.checkSuperCall(node), false];
|
|
17
|
+
let [callee, shortCircuits] = this.chainPart(node.callee);
|
|
18
|
+
this.checkDecoratorMutation(node);
|
|
19
|
+
if (isNullable(callee)) {
|
|
20
|
+
if (node.optional)
|
|
21
|
+
shortCircuits = true;
|
|
22
|
+
else
|
|
23
|
+
this.nullError(node.callee);
|
|
24
|
+
callee = nonNull(callee);
|
|
25
|
+
}
|
|
26
|
+
return [this.checkCall(node, callee, expected), shortCircuits];
|
|
27
|
+
}
|
|
28
|
+
if (node.kind === 'MemberExpression' && this.decoratorWithoutAt(node))
|
|
29
|
+
return [UNKNOWN, false];
|
|
30
|
+
let [object, shortCircuits] = node.object.kind === 'SuperExpression'
|
|
31
|
+
? [this.superType(node.object), false]
|
|
32
|
+
: this.chainPart(node.object);
|
|
33
|
+
if (isNullable(object)) {
|
|
34
|
+
if (node.optional)
|
|
35
|
+
shortCircuits = true;
|
|
36
|
+
else
|
|
37
|
+
this.nullError(node.object);
|
|
38
|
+
object = nonNull(object);
|
|
39
|
+
}
|
|
40
|
+
if (node.kind === 'MemberExpression') {
|
|
41
|
+
const member = this.findMember(object, node.property.name, node.property);
|
|
42
|
+
return [member === 'any' ? ANY : (member?.type ?? UNKNOWN), shortCircuits];
|
|
43
|
+
}
|
|
44
|
+
const keyed = this.keyedIndex(object, node.index);
|
|
45
|
+
if (keyed)
|
|
46
|
+
return [keyed, shortCircuits];
|
|
47
|
+
this.expectIndex(node.index);
|
|
48
|
+
if (object.kind === 'array')
|
|
49
|
+
return [object.element, shortCircuits];
|
|
50
|
+
if (object.kind === 'string')
|
|
51
|
+
return [STRING, shortCircuits];
|
|
52
|
+
if (object.kind === 'object' && object.numberIndex)
|
|
53
|
+
return [object.numberIndex, shortCircuits];
|
|
54
|
+
this.error(`cannot index ${typeToString(object)}`, node);
|
|
55
|
+
return [UNKNOWN, shortCircuits];
|
|
56
|
+
}
|
|
57
|
+
chainPart(node) {
|
|
58
|
+
if (node.kind === 'MemberExpression' ||
|
|
59
|
+
node.kind === 'IndexExpression' ||
|
|
60
|
+
node.kind === 'CallExpression') {
|
|
61
|
+
const [type, shortCircuits] = this.checkChain(node);
|
|
62
|
+
return [this.single(type, node), shortCircuits];
|
|
63
|
+
}
|
|
64
|
+
return [this.checkValue(node), false];
|
|
65
|
+
}
|
|
66
|
+
/** The value of an object that must not be null, e.g. before a member assignment. */
|
|
67
|
+
nonNullValue(node) {
|
|
68
|
+
const [type] = this.chainPart(node);
|
|
69
|
+
if (isNullable(type))
|
|
70
|
+
this.nullError(node);
|
|
71
|
+
return nonNull(type);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* `object[key]` with a key that is not a position: on an untyped value (`data[name]`), or on
|
|
75
|
+
* an object with `[key: string]: T` from a `.d.ts`. `null` for arrays and strings.
|
|
76
|
+
*/
|
|
77
|
+
keyedIndex(object, index) {
|
|
78
|
+
if (object.kind === 'object' && !object.index && !object.numberIndex) {
|
|
79
|
+
return this.fieldsByKey(object, index);
|
|
80
|
+
}
|
|
81
|
+
const type = isUntyped(object) ? object : object.kind === 'object' ? object.index : undefined;
|
|
82
|
+
if (!type)
|
|
83
|
+
return null;
|
|
84
|
+
const key = this.checkValue(index);
|
|
85
|
+
if (!isUntyped(key) && !isComparable(key, STRING) && !isNumeric(key)) {
|
|
86
|
+
this.error(`a key must be a string or a number, not ${typeToString(key)}`, index);
|
|
87
|
+
}
|
|
88
|
+
return type;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* `styles["is-active"]`, or `styles[kind]` with `kind "primary" | "ghost"`: the fields of an
|
|
92
|
+
* object by keys whose values are known. `null` when they are not.
|
|
93
|
+
*/
|
|
94
|
+
fieldsByKey(object, index) {
|
|
95
|
+
const names = [...memberNames(object.members)].filter((name) => object.members.get(name)?.method === false);
|
|
96
|
+
if (names.length === 0)
|
|
97
|
+
return null;
|
|
98
|
+
// A string literal gets a literal type where one of the names is expected.
|
|
99
|
+
const key = this.checkValue(index, union(names.map((name) => literal(name))));
|
|
100
|
+
const keys = unionMembers(key);
|
|
101
|
+
if (!keys.every((each) => each.kind === 'literal' && typeof each.value === 'string')) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return union(keys.map((each) => {
|
|
105
|
+
const member = this.findMember(object, String(each.value), index);
|
|
106
|
+
return member === 'any' ? ANY : (member?.type ?? UNKNOWN);
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
expectIndex(node) {
|
|
110
|
+
const type = this.checkValue(node, NUMBER);
|
|
111
|
+
if (!isNumeric(type))
|
|
112
|
+
this.error(`index must be a number, not ${typeToString(type)}`, node);
|
|
113
|
+
}
|
|
114
|
+
/** The member `name` of a value of type `object`; `'any'` for untyped values. */
|
|
115
|
+
findMember(object, name, node) {
|
|
116
|
+
let member;
|
|
117
|
+
switch (object.kind) {
|
|
118
|
+
case 'any':
|
|
119
|
+
case 'unknown':
|
|
120
|
+
case 'never':
|
|
121
|
+
return 'any';
|
|
122
|
+
case 'literal':
|
|
123
|
+
return this.findMember(literalBase(object), name, node);
|
|
124
|
+
case 'param':
|
|
125
|
+
// A value of a type parameter has the members of its constraint.
|
|
126
|
+
if (object.constraint)
|
|
127
|
+
return this.findMember(object.constraint, name, node);
|
|
128
|
+
this.error(`${object.name} has no member "${name}": give the type parameter a constraint, e.g. [${object.name} Shape]`, node);
|
|
129
|
+
return undefined;
|
|
130
|
+
case 'union': {
|
|
131
|
+
// A member of a union is one that every type in it has.
|
|
132
|
+
const found = object.types.map((type) => this.memberOf(type, name));
|
|
133
|
+
if (found.includes('any'))
|
|
134
|
+
return 'any';
|
|
135
|
+
const members = found.filter((each) => each !== undefined);
|
|
136
|
+
if (members.length < found.length) {
|
|
137
|
+
this.error(`${typeToString(object)} has no member "${name}" in every type: narrow it with typeof or instanceof`, node);
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
type: union(members.map((each) => each.type)),
|
|
142
|
+
method: members.every((each) => each.method),
|
|
143
|
+
visibility: 'public',
|
|
144
|
+
owner: null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
case 'number':
|
|
148
|
+
member = numberMember(name) ?? this.primitiveMember('number', name);
|
|
149
|
+
break;
|
|
150
|
+
case 'bigint':
|
|
151
|
+
member = bigintMember(name) ?? this.primitiveMember('bigint', name);
|
|
152
|
+
break;
|
|
153
|
+
case 'string':
|
|
154
|
+
member = stringMember(name) ?? this.primitiveMember('string', name);
|
|
155
|
+
break;
|
|
156
|
+
case 'bool':
|
|
157
|
+
member = boolMember(name) ?? this.primitiveMember('bool', name);
|
|
158
|
+
break;
|
|
159
|
+
case 'array':
|
|
160
|
+
member = this.arrayMemberOf(object.element, name);
|
|
161
|
+
break;
|
|
162
|
+
case 'object':
|
|
163
|
+
member = object.members.get(name) ?? (object.index && property(object.index));
|
|
164
|
+
break;
|
|
165
|
+
case 'class':
|
|
166
|
+
this.ensureClassResolved(object.info);
|
|
167
|
+
member = findClassMember(object.info, name, false);
|
|
168
|
+
if (!member && hasUntypedBase(object.info))
|
|
169
|
+
return 'any';
|
|
170
|
+
if (member && object.args)
|
|
171
|
+
member = { ...member, type: memberTypeOf(object, member) };
|
|
172
|
+
break;
|
|
173
|
+
case 'classValue':
|
|
174
|
+
this.ensureClassResolved(object.info);
|
|
175
|
+
member = findClassMember(object.info, name, true);
|
|
176
|
+
if (!member && hasUntypedBase(object.info))
|
|
177
|
+
return 'any';
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
if (!member) {
|
|
183
|
+
this.error(`${typeToString(object)} has no member "${name}"`, node);
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
const { owner, visibility } = member;
|
|
187
|
+
if (owner && visibility !== 'public') {
|
|
188
|
+
const current = this.cls?.info;
|
|
189
|
+
const allowed = visibility === 'private'
|
|
190
|
+
? current === owner
|
|
191
|
+
: current !== undefined && isSubclass(current, owner);
|
|
192
|
+
if (!allowed)
|
|
193
|
+
this.error(`"${name}" is ${visibility} in ${owner.name}`, node);
|
|
194
|
+
}
|
|
195
|
+
return member;
|
|
196
|
+
}
|
|
197
|
+
/** The public member `name` of a type, without reporting anything; for members of unions. */
|
|
198
|
+
memberOf(type, name) {
|
|
199
|
+
switch (type.kind) {
|
|
200
|
+
case 'any':
|
|
201
|
+
case 'unknown':
|
|
202
|
+
case 'never':
|
|
203
|
+
return 'any';
|
|
204
|
+
case 'literal':
|
|
205
|
+
return this.memberOf(literalBase(type), name);
|
|
206
|
+
case 'param':
|
|
207
|
+
return type.constraint ? this.memberOf(type.constraint, name) : undefined;
|
|
208
|
+
case 'number':
|
|
209
|
+
return numberMember(name) ?? this.primitiveMember('number', name);
|
|
210
|
+
case 'bigint':
|
|
211
|
+
return bigintMember(name) ?? this.primitiveMember('bigint', name);
|
|
212
|
+
case 'string':
|
|
213
|
+
return stringMember(name) ?? this.primitiveMember('string', name);
|
|
214
|
+
case 'bool':
|
|
215
|
+
return boolMember(name) ?? this.primitiveMember('bool', name);
|
|
216
|
+
case 'array':
|
|
217
|
+
return this.arrayMemberOf(type.element, name);
|
|
218
|
+
case 'object':
|
|
219
|
+
return type.members.get(name) ?? (type.index && property(type.index));
|
|
220
|
+
case 'class': {
|
|
221
|
+
this.ensureClassResolved(type.info);
|
|
222
|
+
const member = findClassMember(type.info, name, false);
|
|
223
|
+
if (!member && hasUntypedBase(type.info))
|
|
224
|
+
return 'any';
|
|
225
|
+
if (member?.visibility !== 'public')
|
|
226
|
+
return undefined;
|
|
227
|
+
return type.args ? { ...member, type: memberTypeOf(type, member) } : member;
|
|
228
|
+
}
|
|
229
|
+
default:
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { ExpressionChecker } from './expressions.ts';
|
|
3
|
+
import { type Type } from './types.ts';
|
|
4
|
+
export declare abstract class OperatorChecker extends ExpressionChecker {
|
|
5
|
+
/** In the operation of a `select` case, a channel may be null: that case never goes on. */
|
|
6
|
+
protected inSelectCase: boolean;
|
|
7
|
+
protected checkUnary(node: ast.UnaryExpression): Type;
|
|
8
|
+
protected expectBool(given: Type, node: ast.NodeBase, operator: string): void;
|
|
9
|
+
protected checkBinary(node: ast.BinaryExpression, expected: Type | null): Type;
|
|
10
|
+
/** The type of `left <operator> right`, also used for compound assignments like `+=`. */
|
|
11
|
+
protected binaryResult(operator: ast.BinaryOperator, givenLeft: Type, givenRight: Type, node: ast.NodeBase): Type;
|
|
12
|
+
protected checkConditional(node: ast.ConditionalExpression, expected: Type | null): Type;
|
|
13
|
+
}
|