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.
Files changed (314) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -2
  3. package/README.ru.md +178 -0
  4. package/dist/ast/declarations.d.ts +185 -0
  5. package/dist/ast/declarations.js +1 -0
  6. package/dist/ast/expressions.d.ts +201 -0
  7. package/dist/ast/expressions.js +1 -0
  8. package/dist/ast/markup.d.ts +141 -0
  9. package/dist/ast/markup.js +1 -0
  10. package/dist/ast/statements.d.ts +173 -0
  11. package/dist/ast/statements.js +1 -0
  12. package/dist/ast.d.ts +18 -0
  13. package/dist/ast.js +18 -0
  14. package/dist/attrs.d.ts +8 -0
  15. package/dist/attrs.js +23 -0
  16. package/dist/browser.d.ts +30 -0
  17. package/dist/browser.js +36 -0
  18. package/dist/build.d.ts +48 -0
  19. package/dist/build.js +112 -0
  20. package/dist/checker/assignments.d.ts +14 -0
  21. package/dist/checker/assignments.js +197 -0
  22. package/dist/checker/base.d.ts +124 -0
  23. package/dist/checker/base.js +180 -0
  24. package/dist/checker/bigint.d.ts +15 -0
  25. package/dist/checker/bigint.js +85 -0
  26. package/dist/checker/builtins.d.ts +9 -0
  27. package/dist/checker/builtins.js +307 -0
  28. package/dist/checker/bytes.d.ts +11 -0
  29. package/dist/checker/bytes.js +72 -0
  30. package/dist/checker/calls.d.ts +23 -0
  31. package/dist/checker/calls.js +210 -0
  32. package/dist/checker/channel.d.ts +14 -0
  33. package/dist/checker/channel.js +44 -0
  34. package/dist/checker/channels.d.ts +9 -0
  35. package/dist/checker/channels.js +95 -0
  36. package/dist/checker/checker.d.ts +4 -0
  37. package/dist/checker/checker.js +22 -0
  38. package/dist/checker/classes.d.ts +24 -0
  39. package/dist/checker/classes.js +223 -0
  40. package/dist/checker/completion-names.d.ts +11 -0
  41. package/dist/checker/completion-names.js +100 -0
  42. package/dist/checker/completion.d.ts +25 -0
  43. package/dist/checker/completion.js +222 -0
  44. package/dist/checker/component-values.d.ts +11 -0
  45. package/dist/checker/component-values.js +88 -0
  46. package/dist/checker/components.d.ts +37 -0
  47. package/dist/checker/components.js +287 -0
  48. package/dist/checker/context.d.ts +196 -0
  49. package/dist/checker/context.js +35 -0
  50. package/dist/checker/control-flow.d.ts +24 -0
  51. package/dist/checker/control-flow.js +237 -0
  52. package/dist/checker/declarations.d.ts +26 -0
  53. package/dist/checker/declarations.js +259 -0
  54. package/dist/checker/decorator-exports.d.ts +5 -0
  55. package/dist/checker/decorator-exports.js +40 -0
  56. package/dist/checker/decorator-members.d.ts +28 -0
  57. package/dist/checker/decorator-members.js +140 -0
  58. package/dist/checker/decorator-wrappers.d.ts +20 -0
  59. package/dist/checker/decorator-wrappers.js +132 -0
  60. package/dist/checker/decorators.d.ts +28 -0
  61. package/dist/checker/decorators.js +265 -0
  62. package/dist/checker/dom-svg.d.ts +7 -0
  63. package/dist/checker/dom-svg.js +19 -0
  64. package/dist/checker/dom.d.ts +22 -0
  65. package/dist/checker/dom.js +361 -0
  66. package/dist/checker/expressions.d.ts +32 -0
  67. package/dist/checker/expressions.js +244 -0
  68. package/dist/checker/flow.d.ts +18 -0
  69. package/dist/checker/flow.js +134 -0
  70. package/dist/checker/forwarding.d.ts +15 -0
  71. package/dist/checker/forwarding.js +78 -0
  72. package/dist/checker/functions.d.ts +24 -0
  73. package/dist/checker/functions.js +154 -0
  74. package/dist/checker/go-for.d.ts +12 -0
  75. package/dist/checker/go-for.js +93 -0
  76. package/dist/checker/goroutines.d.ts +19 -0
  77. package/dist/checker/goroutines.js +110 -0
  78. package/dist/checker/helpers.d.ts +35 -0
  79. package/dist/checker/helpers.js +162 -0
  80. package/dist/checker/imports.d.ts +7 -0
  81. package/dist/checker/imports.js +83 -0
  82. package/dist/checker/initializers.d.ts +22 -0
  83. package/dist/checker/initializers.js +104 -0
  84. package/dist/checker/iterables.d.ts +5 -0
  85. package/dist/checker/iterables.js +17 -0
  86. package/dist/checker/iterators.d.ts +13 -0
  87. package/dist/checker/iterators.js +95 -0
  88. package/dist/checker/loops.d.ts +10 -0
  89. package/dist/checker/loops.js +82 -0
  90. package/dist/checker/markup.d.ts +28 -0
  91. package/dist/checker/markup.js +220 -0
  92. package/dist/checker/members.d.ts +29 -0
  93. package/dist/checker/members.js +233 -0
  94. package/dist/checker/operators.d.ts +13 -0
  95. package/dist/checker/operators.js +177 -0
  96. package/dist/checker/promise.d.ts +17 -0
  97. package/dist/checker/promise.js +49 -0
  98. package/dist/checker/refs.d.ts +26 -0
  99. package/dist/checker/refs.js +136 -0
  100. package/dist/checker/resolution.d.ts +23 -0
  101. package/dist/checker/resolution.js +173 -0
  102. package/dist/checker/statements.d.ts +28 -0
  103. package/dist/checker/statements.js +212 -0
  104. package/dist/checker/styles.d.ts +14 -0
  105. package/dist/checker/styles.js +49 -0
  106. package/dist/checker/transfer.d.ts +5 -0
  107. package/dist/checker/transfer.js +83 -0
  108. package/dist/checker/types/bytes.d.ts +4 -0
  109. package/dist/checker/types/bytes.js +51 -0
  110. package/dist/checker/types/classes.d.ts +22 -0
  111. package/dist/checker/types/classes.js +93 -0
  112. package/dist/checker/types/constructors.d.ts +27 -0
  113. package/dist/checker/types/constructors.js +129 -0
  114. package/dist/checker/types/generics.d.ts +20 -0
  115. package/dist/checker/types/generics.js +75 -0
  116. package/dist/checker/types/lazy.d.ts +30 -0
  117. package/dist/checker/types/lazy.js +92 -0
  118. package/dist/checker/types/model.d.ts +149 -0
  119. package/dist/checker/types/model.js +15 -0
  120. package/dist/checker/types/params.d.ts +7 -0
  121. package/dist/checker/types/params.js +220 -0
  122. package/dist/checker/types/printing.d.ts +6 -0
  123. package/dist/checker/types/printing.js +109 -0
  124. package/dist/checker/types/relations.d.ts +16 -0
  125. package/dist/checker/types/relations.js +294 -0
  126. package/dist/checker/types.d.ts +9 -0
  127. package/dist/checker/types.js +10 -0
  128. package/dist/cli.d.ts +2 -0
  129. package/dist/cli.js +193 -0
  130. package/dist/codegen/analysis.d.ts +18 -0
  131. package/dist/codegen/analysis.js +144 -0
  132. package/dist/codegen/attributes.d.ts +40 -0
  133. package/dist/codegen/attributes.js +227 -0
  134. package/dist/codegen/calls.d.ts +22 -0
  135. package/dist/codegen/calls.js +106 -0
  136. package/dist/codegen/chan-helpers.d.ts +2 -0
  137. package/dist/codegen/chan-helpers.js +253 -0
  138. package/dist/codegen/component-helpers.d.ts +33 -0
  139. package/dist/codegen/component-helpers.js +18 -0
  140. package/dist/codegen/component-props.d.ts +42 -0
  141. package/dist/codegen/component-props.js +173 -0
  142. package/dist/codegen/component-values.d.ts +27 -0
  143. package/dist/codegen/component-values.js +146 -0
  144. package/dist/codegen/components.d.ts +53 -0
  145. package/dist/codegen/components.js +268 -0
  146. package/dist/codegen/control-flow.d.ts +25 -0
  147. package/dist/codegen/control-flow.js +152 -0
  148. package/dist/codegen/control-statements.d.ts +28 -0
  149. package/dist/codegen/control-statements.js +226 -0
  150. package/dist/codegen/creation.d.ts +31 -0
  151. package/dist/codegen/creation.js +113 -0
  152. package/dist/codegen/early-returns.d.ts +59 -0
  153. package/dist/codegen/early-returns.js +244 -0
  154. package/dist/codegen/element-helpers.d.ts +12 -0
  155. package/dist/codegen/element-helpers.js +54 -0
  156. package/dist/codegen/elements.d.ts +71 -0
  157. package/dist/codegen/elements.js +293 -0
  158. package/dist/codegen/emitter.d.ts +135 -0
  159. package/dist/codegen/emitter.js +296 -0
  160. package/dist/codegen/expressions.d.ts +49 -0
  161. package/dist/codegen/expressions.js +262 -0
  162. package/dist/codegen/functions.d.ts +28 -0
  163. package/dist/codegen/functions.js +170 -0
  164. package/dist/codegen/go-helpers.d.ts +9 -0
  165. package/dist/codegen/go-helpers.js +307 -0
  166. package/dist/codegen/helpers.d.ts +10 -0
  167. package/dist/codegen/helpers.js +356 -0
  168. package/dist/codegen/imported.d.ts +38 -0
  169. package/dist/codegen/imported.js +71 -0
  170. package/dist/codegen/js.d.ts +20 -0
  171. package/dist/codegen/js.js +113 -0
  172. package/dist/codegen/mutations.d.ts +10 -0
  173. package/dist/codegen/mutations.js +102 -0
  174. package/dist/codegen/parallel.d.ts +7 -0
  175. package/dist/codegen/parallel.js +63 -0
  176. package/dist/codegen/reactive.d.ts +95 -0
  177. package/dist/codegen/reactive.js +280 -0
  178. package/dist/codegen/refs.d.ts +62 -0
  179. package/dist/codegen/refs.js +160 -0
  180. package/dist/codegen/select-helpers.d.ts +2 -0
  181. package/dist/codegen/select-helpers.js +135 -0
  182. package/dist/codegen/simple-statements.d.ts +42 -0
  183. package/dist/codegen/simple-statements.js +253 -0
  184. package/dist/codegen/statements.d.ts +22 -0
  185. package/dist/codegen/statements.js +142 -0
  186. package/dist/codegen/styles.d.ts +54 -0
  187. package/dist/codegen/styles.js +157 -0
  188. package/dist/codegen/syntax.d.ts +18 -0
  189. package/dist/codegen/syntax.js +70 -0
  190. package/dist/codegen/thread-helpers.d.ts +2 -0
  191. package/dist/codegen/thread-helpers.js +198 -0
  192. package/dist/codegen/updates.d.ts +51 -0
  193. package/dist/codegen/updates.js +201 -0
  194. package/dist/codegen/web-components.d.ts +20 -0
  195. package/dist/codegen/web-components.js +149 -0
  196. package/dist/compile-file.d.ts +11 -0
  197. package/dist/compile-file.js +24 -0
  198. package/dist/compile.d.ts +53 -0
  199. package/dist/compile.js +187 -0
  200. package/dist/completion.d.ts +10 -0
  201. package/dist/completion.js +134 -0
  202. package/dist/config.d.ts +2 -0
  203. package/dist/config.js +44 -0
  204. package/dist/css/hash.d.ts +1 -0
  205. package/dist/css/hash.js +10 -0
  206. package/dist/css/parse.d.ts +42 -0
  207. package/dist/css/parse.js +138 -0
  208. package/dist/css/print.d.ts +9 -0
  209. package/dist/css/print.js +35 -0
  210. package/dist/css/selectors.d.ts +29 -0
  211. package/dist/css/selectors.js +168 -0
  212. package/dist/declarations.d.ts +3 -0
  213. package/dist/declarations.js +207 -0
  214. package/dist/decorator-copy.d.ts +18 -0
  215. package/dist/decorator-copy.js +158 -0
  216. package/dist/decorators.d.ts +61 -0
  217. package/dist/decorators.js +241 -0
  218. package/dist/diagnostics.d.ts +17 -0
  219. package/dist/diagnostics.js +22 -0
  220. package/dist/go-for.d.ts +26 -0
  221. package/dist/go-for.js +155 -0
  222. package/dist/goroutines.d.ts +37 -0
  223. package/dist/goroutines.js +253 -0
  224. package/dist/html-tag.d.ts +21 -0
  225. package/dist/html-tag.js +103 -0
  226. package/dist/index.d.ts +33 -0
  227. package/dist/index.js +55 -0
  228. package/dist/inline-packages.d.ts +32 -0
  229. package/dist/inline-packages.js +129 -0
  230. package/dist/iterators.d.ts +10 -0
  231. package/dist/iterators.js +33 -0
  232. package/dist/lexer/base.d.ts +24 -0
  233. package/dist/lexer/base.js +87 -0
  234. package/dist/lexer/lexer.d.ts +8 -0
  235. package/dist/lexer/lexer.js +134 -0
  236. package/dist/lexer/literals.d.ts +18 -0
  237. package/dist/lexer/literals.js +203 -0
  238. package/dist/lexer/markup.d.ts +10 -0
  239. package/dist/lexer/markup.js +93 -0
  240. package/dist/lexer/style.d.ts +17 -0
  241. package/dist/lexer/style.js +97 -0
  242. package/dist/lexer/tables.d.ts +53 -0
  243. package/dist/lexer/tables.js +101 -0
  244. package/dist/lexer/token.d.ts +42 -0
  245. package/dist/lexer/token.js +108 -0
  246. package/dist/loader.d.ts +1 -0
  247. package/dist/loader.js +50 -0
  248. package/dist/methods.d.ts +5 -0
  249. package/dist/methods.js +81 -0
  250. package/dist/modules.d.ts +3 -0
  251. package/dist/modules.js +49 -0
  252. package/dist/names.d.ts +23 -0
  253. package/dist/names.js +267 -0
  254. package/dist/parser/base.d.ts +96 -0
  255. package/dist/parser/base.js +202 -0
  256. package/dist/parser/classes.d.ts +8 -0
  257. package/dist/parser/classes.js +164 -0
  258. package/dist/parser/control-flow.d.ts +29 -0
  259. package/dist/parser/control-flow.js +232 -0
  260. package/dist/parser/declarations.d.ts +27 -0
  261. package/dist/parser/declarations.js +238 -0
  262. package/dist/parser/decorators.d.ts +30 -0
  263. package/dist/parser/decorators.js +176 -0
  264. package/dist/parser/expressions.d.ts +15 -0
  265. package/dist/parser/expressions.js +255 -0
  266. package/dist/parser/literals.d.ts +27 -0
  267. package/dist/parser/literals.js +206 -0
  268. package/dist/parser/markup.d.ts +22 -0
  269. package/dist/parser/markup.js +193 -0
  270. package/dist/parser/parser.d.ts +14 -0
  271. package/dist/parser/parser.js +22 -0
  272. package/dist/parser/simple-statements.d.ts +22 -0
  273. package/dist/parser/simple-statements.js +156 -0
  274. package/dist/parser/statements.d.ts +37 -0
  275. package/dist/parser/statements.js +267 -0
  276. package/dist/parser/syntax.d.ts +51 -0
  277. package/dist/parser/syntax.js +173 -0
  278. package/dist/parser/types.d.ts +23 -0
  279. package/dist/parser/types.js +218 -0
  280. package/dist/recursion.d.ts +25 -0
  281. package/dist/recursion.js +170 -0
  282. package/dist/refs.d.ts +49 -0
  283. package/dist/refs.js +210 -0
  284. package/dist/run.d.ts +1 -0
  285. package/dist/run.js +21 -0
  286. package/dist/source.d.ts +15 -0
  287. package/dist/source.js +34 -0
  288. package/dist/style-modules.d.ts +17 -0
  289. package/dist/style-modules.js +49 -0
  290. package/dist/styles.d.ts +36 -0
  291. package/dist/styles.js +160 -0
  292. package/dist/svg.d.ts +8 -0
  293. package/dist/svg.js +77 -0
  294. package/dist/ts-types.d.ts +11 -0
  295. package/dist/ts-types.js +133 -0
  296. package/dist/typed-arrays.d.ts +8 -0
  297. package/dist/typed-arrays.js +43 -0
  298. package/dist/typescript/convert-base.d.ts +42 -0
  299. package/dist/typescript/convert-base.js +135 -0
  300. package/dist/typescript/convert-objects.d.ts +28 -0
  301. package/dist/typescript/convert-objects.js +245 -0
  302. package/dist/typescript/convert.d.ts +14 -0
  303. package/dist/typescript/convert.js +67 -0
  304. package/dist/typescript/importer.d.ts +37 -0
  305. package/dist/typescript/importer.js +152 -0
  306. package/dist/typescript/library.d.ts +23 -0
  307. package/dist/typescript/library.js +81 -0
  308. package/dist/vite.d.ts +11 -0
  309. package/dist/vite.js +86 -0
  310. package/dist/walk-types.d.ts +4 -0
  311. package/dist/walk-types.js +56 -0
  312. package/dist/walk.d.ts +5 -0
  313. package/dist/walk.js +315 -0
  314. package/package.json +104 -4
@@ -0,0 +1,134 @@
1
+ import { containsBreak } from "../walk.js";
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 statements
4
+ // execution never continues after.
5
+ /** What a value of unknown type is after `typeof x == tag`. */
6
+ const PRIMITIVE_TAGS = new Map([
7
+ ['string', STRING],
8
+ ['number', NUMBER],
9
+ ['bigint', BIGINT],
10
+ ['boolean', BOOL],
11
+ ]);
12
+ /** What `typeof` gives for every value of a type, or `null` when it may give several things. */
13
+ export function typeofTag(type) {
14
+ switch (type.kind) {
15
+ case 'string':
16
+ case 'number':
17
+ case 'bigint':
18
+ return type.kind;
19
+ case 'bool':
20
+ return 'boolean';
21
+ case 'literal':
22
+ return typeof type.value;
23
+ case 'function':
24
+ case 'component':
25
+ case 'classValue':
26
+ return 'function';
27
+ case 'object':
28
+ return type.call ? 'function' : 'object';
29
+ case 'class':
30
+ case 'array':
31
+ case 'tuple':
32
+ case 'null':
33
+ return 'object';
34
+ default:
35
+ return null;
36
+ }
37
+ }
38
+ /** The type after `typeof x == tag` is found true (`matches`) or false. */
39
+ export function narrowByTypeof(type, tag, matches) {
40
+ const base = nonNull(type);
41
+ if (base.kind === 'any' || base.kind === 'unknown') {
42
+ if (!matches)
43
+ return null;
44
+ return PRIMITIVE_TAGS.get(tag) ?? null;
45
+ }
46
+ const kept = unionMembers(base).filter((member) => {
47
+ const memberTag = typeofTag(member);
48
+ return memberTag === null || (memberTag === tag) === matches;
49
+ });
50
+ // typeof null is "object".
51
+ const keepsNull = isNullable(type) && (tag === 'object') === matches;
52
+ return union(keepsNull ? [...kept, NULL] : kept);
53
+ }
54
+ /** `"all"`, `42` or `true` written in the code: its value, or `undefined` for other expressions. */
55
+ export function literalValue(node) {
56
+ switch (node.kind) {
57
+ case 'StringLiteral':
58
+ case 'NumberLiteral':
59
+ case 'BooleanLiteral':
60
+ return node.value;
61
+ default:
62
+ return undefined;
63
+ }
64
+ }
65
+ /** A type without one literal value: what is left after `x != "all"`. */
66
+ export function withoutLiteral(type, value) {
67
+ const kept = unionMembers(nonNull(type)).filter((member) => !(member.kind === 'literal' && member.value === value));
68
+ return union(isNullable(type) ? [...kept, NULL] : kept);
69
+ }
70
+ export function withNarrowing(flow, narrowing) {
71
+ const result = new Map(flow);
72
+ for (const [binding, type] of narrowing)
73
+ result.set(binding, type);
74
+ return result;
75
+ }
76
+ /** Narrowing that holds after both branches: the variables narrowed the same way in both. */
77
+ export function mergeFlows(a, b) {
78
+ const result = new Map();
79
+ for (const [binding, type] of a) {
80
+ const other = b.get(binding);
81
+ if (other && typesEqual(type, other))
82
+ result.set(binding, type);
83
+ }
84
+ return result;
85
+ }
86
+ /** Go's terminating statements: execution never continues after them. */
87
+ export function isTerminating(node) {
88
+ switch (node.kind) {
89
+ case 'ReturnStatement':
90
+ case 'ThrowStatement':
91
+ return true;
92
+ case 'BlockStatement': {
93
+ const last = node.body.at(-1);
94
+ return last !== undefined && isTerminating(last);
95
+ }
96
+ case 'IfStatement':
97
+ return (node.alternate !== null && isTerminating(node.consequent) && isTerminating(node.alternate));
98
+ case 'ForStatement':
99
+ return node.condition === null && !containsBreak(node.body.body);
100
+ case 'SwitchStatement':
101
+ return (node.cases.some((switchCase) => switchCase.tests.length === 0) &&
102
+ node.cases.every((switchCase) => {
103
+ const last = switchCase.body.at(-1);
104
+ return last !== undefined && isTerminating(last) && !containsBreak(switchCase.body);
105
+ }));
106
+ // A select runs one of its cases, waiting for one if there is no default.
107
+ case 'SelectStatement':
108
+ return node.cases.every((selectCase) => {
109
+ const last = selectCase.body.at(-1);
110
+ return last !== undefined && isTerminating(last) && !containsBreak(selectCase.body);
111
+ });
112
+ case 'TryStatement':
113
+ return ((isTerminating(node.block) && (!node.handler || isTerminating(node.handler.body))) ||
114
+ (node.finalizer !== null && isTerminating(node.finalizer)));
115
+ default:
116
+ return false;
117
+ }
118
+ }
119
+ /** Whether execution never continues after the statement, counting `break` and `continue`. */
120
+ export function leaves(node) {
121
+ switch (node.kind) {
122
+ case 'BreakStatement':
123
+ case 'ContinueStatement':
124
+ return true;
125
+ case 'BlockStatement': {
126
+ const last = node.body.at(-1);
127
+ return last !== undefined && leaves(last);
128
+ }
129
+ case 'IfStatement':
130
+ return node.alternate !== null && leaves(node.consequent) && leaves(node.alternate);
131
+ default:
132
+ return isTerminating(node);
133
+ }
134
+ }
@@ -0,0 +1,15 @@
1
+ import type * as ast from '../ast.ts';
2
+ import type { ComponentInfo } from './context.ts';
3
+ import { MarkupChecker } from './markup.ts';
4
+ import type { ObjectType } from './types.ts';
5
+ /** The type of `attrs` in the body: it can only be spread. */
6
+ export declare const ATTRIBUTES: ObjectType;
7
+ export declare abstract class ForwardingChecker extends MarkupChecker {
8
+ /**
9
+ * An attribute of a use that is not a property: checked as an attribute of the element that
10
+ * `{...attrs}` is spread on. False when the component takes no attributes.
11
+ */
12
+ protected checkForwarded(attribute: ast.JsxAttribute, info: ComponentInfo, tag: string): boolean;
13
+ /** `...attrs` is spread on one element of markup the component creates once. */
14
+ protected checkAttrsSpreads(info: ComponentInfo): void;
15
+ }
@@ -0,0 +1,78 @@
1
+ import { attrsParam, attrsSpreads, attrsTarget } from "../attrs.js";
2
+ import { isComponentUse } from "../refs.js";
3
+ import { isSvgElement } from "../svg.js";
4
+ import { MarkupChecker } from "./markup.js";
5
+ // Attributes passed on: `...attrs`, the last property of a component, takes the attributes of a
6
+ // use that are not properties, and `{...attrs}` puts them on one element of the component. A use
7
+ // is checked as if they were written on that element.
8
+ /** The type of `attrs` in the body: it can only be spread. */
9
+ export const ATTRIBUTES = {
10
+ kind: 'object',
11
+ name: 'Attributes',
12
+ members: new Map(),
13
+ call: null,
14
+ };
15
+ const WHERE = {
16
+ for: '{for}',
17
+ if: '{if}',
18
+ switch: '{switch}',
19
+ expression: 'an expression',
20
+ function: 'a function',
21
+ block: 'a block of the body',
22
+ };
23
+ export class ForwardingChecker extends MarkupChecker {
24
+ /**
25
+ * An attribute of a use that is not a property: checked as an attribute of the element that
26
+ * `{...attrs}` is spread on. False when the component takes no attributes.
27
+ */
28
+ checkForwarded(attribute, info, tag) {
29
+ if (!attrsParam(info.node))
30
+ return false;
31
+ const target = attrsTarget(info.node);
32
+ const name = attribute.name.name;
33
+ const { value } = attribute;
34
+ // The component itself reports a missing spread.
35
+ if (!target?.tag)
36
+ return true;
37
+ if (name.startsWith('bind:')) {
38
+ this.error(`${name} does not go through the attributes of <${tag}>`, attribute.name);
39
+ }
40
+ else if ((name === 'class' || name === 'style') && value !== null) {
41
+ // They are added to those of the element, so they cannot change.
42
+ if (value.kind !== 'StringLiteral') {
43
+ this.error(`${name} passed to <${tag}> is added to that of its element, so it is a string: ${name}="..."`, value);
44
+ }
45
+ }
46
+ else {
47
+ const element = target.tag.name;
48
+ const svg = isSvgElement(element, false);
49
+ this.checkAttribute(attribute, element, this.elementOf(element, svg), svg);
50
+ }
51
+ return true;
52
+ }
53
+ /** `...attrs` is spread on one element of markup the component creates once. */
54
+ checkAttrsSpreads(info) {
55
+ const param = attrsParam(info.node);
56
+ if (!param)
57
+ return;
58
+ const name = param.name.name;
59
+ const spreads = attrsSpreads(info.node);
60
+ if (info.node.htmlTag) {
61
+ this.error(`a web component cannot take ...${name}: its attributes are its own`, param);
62
+ }
63
+ else if (spreads.length === 0) {
64
+ this.error(`...${name} is not spread on an element; put it among the attributes of one, e.g. <button {...${name}}>`, param);
65
+ }
66
+ spreads.forEach(({ attribute, element, repeated }, i) => {
67
+ if (isComponentUse(element)) {
68
+ this.error(`{...${name}} goes on an element, not on <${element.tag.name}>: the attributes are those of an element`, attribute);
69
+ }
70
+ else if (repeated !== null) {
71
+ this.error(`{...${name}} is inside ${WHERE[repeated]}, which may create the element more than once; spread it on an element the component creates once`, attribute);
72
+ }
73
+ else if (i > 0) {
74
+ this.error(`...${name} is spread on another element already: the attributes go to one element`, attribute);
75
+ }
76
+ });
77
+ }
78
+ }
@@ -0,0 +1,24 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { type BindingKind, Scope, type Flow } from './context.ts';
3
+ import { ControlFlowChecker } from './control-flow.ts';
4
+ import { type FunctionType, type Type } from './types.ts';
5
+ export declare abstract class FunctionChecker extends ControlFlowChecker {
6
+ /**
7
+ * Checks a function body and returns its result types: the declared ones, or those inferred from
8
+ * the `return` statements when `results` is `null`.
9
+ */
10
+ protected checkFunction(params: readonly ast.Parameter[], type: FunctionType, results: Type[] | null, body: ast.BlockStatement | ast.Expression, options?: {
11
+ isConstructor?: boolean;
12
+ isComponent?: boolean;
13
+ closure?: boolean;
14
+ paramKind?: BindingKind;
15
+ async?: boolean;
16
+ inspect?: (scope: Scope) => void;
17
+ }): Type[];
18
+ /** The body of `x => x * 2`. */
19
+ protected checkExpressionBody(body: ast.Expression, results: Type[] | null): Type[];
20
+ protected inferResults(returns: Type[][], body: ast.BlockStatement): Type[];
21
+ protected stableFlow(): Flow;
22
+ protected checkArrowFunction(node: ast.ArrowFunction, expected: Type | null): Type;
23
+ protected checkFuncExpression(node: ast.FuncExpression): Type;
24
+ }
@@ -0,0 +1,154 @@
1
+ import { Scope } from "./context.js";
2
+ import { ControlFlowChecker } from "./control-flow.js";
3
+ import { isTerminating } from "./flow.js";
4
+ import { isUntyped, callSignature } from "./helpers.js";
5
+ import { awaitedResults, isPromise } from "./promise.js";
6
+ import { commonType, containsTypeParam, func, nonNull, UNKNOWN, } from "./types.js";
7
+ // Bodies of functions, arrow functions and func literals, and the results they return.
8
+ export class FunctionChecker extends ControlFlowChecker {
9
+ /**
10
+ * Checks a function body and returns its result types: the declared ones, or those inferred from
11
+ * the `return` statements when `results` is `null`.
12
+ */
13
+ checkFunction(params, type, results, body, options = {}) {
14
+ const saved = { scope: this.scope, flow: this.flow, fn: this.fn };
15
+ const { async = false } = options;
16
+ // An async function returns a Promise: its body returns what the Promise gives.
17
+ const declared = async && results ? awaitedResults(results) : results;
18
+ const wrap = (types) => (async && !results ? [this.promiseOf(types)] : types);
19
+ this.scope = new Scope(this.scope);
20
+ // A closure keeps the narrowing of variables that cannot change before it runs.
21
+ this.flow = options.closure ? this.stableFlow() : new Map();
22
+ this.fn = {
23
+ results: declared,
24
+ returns: [],
25
+ isConstructor: options.isConstructor ?? false,
26
+ isComponent: options.isComponent ?? false,
27
+ isAsync: async,
28
+ };
29
+ try {
30
+ // A generic function: its type parameters are types in its body.
31
+ for (const typeParam of type.typeParams)
32
+ this.scope.types.set(typeParam.name, typeParam);
33
+ params.forEach((param, i) => this.declareValue(param.name, options.paramKind ?? 'param', type.params[i] ?? UNKNOWN));
34
+ if (body.kind === 'BlockStatement') {
35
+ this.checkStatementList(body.body, false);
36
+ options.inspect?.(this.scope);
37
+ if (declared && declared.length > 0 && !isTerminating(body)) {
38
+ this.error('missing return at the end of the function', {
39
+ start: body.end - 1,
40
+ end: body.end,
41
+ });
42
+ }
43
+ return results ?? wrap(this.inferResults(this.fn.returns, body));
44
+ }
45
+ const types = this.checkExpressionBody(body, declared);
46
+ return results ?? wrap(types);
47
+ }
48
+ finally {
49
+ this.scope = saved.scope;
50
+ this.flow = saved.flow;
51
+ this.fn = saved.fn;
52
+ }
53
+ }
54
+ /** The body of `x => x * 2`. */
55
+ checkExpressionBody(body, results) {
56
+ if (results === null || results.length === 0) {
57
+ const type = this.checkExpression(body, null);
58
+ if (results !== null)
59
+ return [];
60
+ return type.kind === 'tuple' ? type.types : type.kind === 'void' ? [] : [type];
61
+ }
62
+ if (results.length === 1) {
63
+ const type = this.checkValue(body, results[0] ?? null);
64
+ this.expectAssignable(type, results[0], body, ' in return');
65
+ return results;
66
+ }
67
+ const types = this.unpack(body, results.length, null, 'return');
68
+ types.forEach((type, i) => this.expectAssignable(type, results[i], body, ' in return'));
69
+ return results;
70
+ }
71
+ inferResults(returns, body) {
72
+ const first = returns[0];
73
+ if (first === undefined)
74
+ return [];
75
+ const results = [...first];
76
+ for (const types of returns.slice(1)) {
77
+ if (types.length !== results.length) {
78
+ this.error('return statements return different numbers of values', body);
79
+ return results;
80
+ }
81
+ types.forEach((type, i) => {
82
+ // Different types of returns make a union, as in TypeScript.
83
+ results[i] = commonType(results[i], type);
84
+ });
85
+ }
86
+ return results;
87
+ }
88
+ stableFlow() {
89
+ const flow = new Map();
90
+ for (const [binding, type] of this.flow) {
91
+ if (binding.kind === 'const' || !this.assigned.has(binding.name))
92
+ flow.set(binding, type);
93
+ }
94
+ return flow;
95
+ }
96
+ checkArrowFunction(node, expected) {
97
+ const context = expected ? callSignature(nonNull(expected)) : null;
98
+ // Callbacks passed to untyped JS functions get untyped parameters.
99
+ const untypedContext = expected !== null && isUntyped(nonNull(expected));
100
+ const params = node.params.map((param, i) => {
101
+ if (param.type)
102
+ return this.resolveType(param.type);
103
+ const fromContext = context ? (context.params[i] ?? context.rest) : null;
104
+ if (fromContext && !containsTypeParam(fromContext, this.inferring))
105
+ return fromContext;
106
+ if (untypedContext)
107
+ return nonNull(expected);
108
+ this.error(`cannot infer the type of parameter "${param.name.name}": add a type`, param);
109
+ return UNKNOWN;
110
+ });
111
+ // Known result types from the context are checked; otherwise they are inferred. Those of an
112
+ // async function must be a Promise.
113
+ const contextResults = context &&
114
+ context.results.length > 0 &&
115
+ !context.results.some((type) => containsTypeParam(type, this.inferring)) &&
116
+ (!node.async || (context.results.length === 1 && isPromise(context.results[0])))
117
+ ? context.results
118
+ : null;
119
+ const type = func(params, []);
120
+ const checkBody = () => {
121
+ const inferring = this.inferring;
122
+ this.inferring = new Set();
123
+ try {
124
+ return this.checkFunction(node.params, type, contextResults, node.body, {
125
+ closure: true,
126
+ async: node.async,
127
+ });
128
+ }
129
+ finally {
130
+ this.inferring = inferring;
131
+ }
132
+ };
133
+ // In the initializer of a variable that the body uses, the body can wait for the variable's
134
+ // type if the function's type is known without it: from the context, or a callback whose
135
+ // result is not used.
136
+ const knownResults = contextResults ?? (untypedContext || context?.results.length === 0 ? [] : null);
137
+ if (knownResults && !params.includes(UNKNOWN) && this.deferBody(node.body, checkBody)) {
138
+ type.results = node.async && !contextResults ? [this.promiseOf([])] : knownResults;
139
+ return type;
140
+ }
141
+ type.results = checkBody();
142
+ return type;
143
+ }
144
+ checkFuncExpression(node) {
145
+ const type = this.signature(node.params, node.results, node.async);
146
+ const checkBody = () => this.checkFunction(node.params, type, type.results, node.body, {
147
+ closure: true,
148
+ async: node.async,
149
+ });
150
+ if (!this.deferBody(node.body, checkBody))
151
+ checkBody();
152
+ return type;
153
+ }
154
+ }
@@ -0,0 +1,12 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { LoopChecker } from './loops.ts';
3
+ export declare abstract class GoForChecker extends LoopChecker {
4
+ protected checkForIn(node: ast.ForInStatement): void;
5
+ private checkGoFor;
6
+ /**
7
+ * The names that the body uses from around it: those of the module go to the worker module as
8
+ * with a function there; local ones are copied to each worker, so they must be data. Either way
9
+ * a worker has its own, so changes to them there would not come back.
10
+ */
11
+ private checkCaptures;
12
+ }
@@ -0,0 +1,93 @@
1
+ import { arrayName, arrayUses, loopParams } from "../go-for.js";
2
+ import { recordCaptures } from "../goroutines.js";
3
+ import { assignedNames, freeNames } from "../names.js";
4
+ import { forEachChild } from "../walk.js";
5
+ import { firstUse } from "./goroutines.js";
6
+ import { LoopChecker } from "./loops.js";
7
+ import { notCopied } from "./transfer.js";
8
+ import { typedArrayOf, typeToString } from "./types.js";
9
+ // `go for i, v in items { ... }`: the parts of an array or a typed array run as goroutines. Each
10
+ // worker gets its part and copies of the local names that the body uses, so the body reaches the
11
+ // array only through its own element, `items[i]`, and changes nothing else that would stay here.
12
+ export class GoForChecker extends LoopChecker {
13
+ checkForIn(node) {
14
+ super.checkForIn(node);
15
+ if (node.go)
16
+ this.checkGoFor(node);
17
+ }
18
+ checkGoFor(node) {
19
+ const iterable = this.checkedTypes.get(node.iterable);
20
+ if (iterable && !isSplit(iterable)) {
21
+ if (iterable.kind !== 'unknown' && iterable.kind !== 'any') {
22
+ this.error(`"go for" splits an array among workers, but ${typeToString(iterable)} is not an array; loop over a []T or a typed array, e.g. "go for i, v in pixels { ... }"`, node.iterable);
23
+ }
24
+ }
25
+ else if (iterable) {
26
+ const reason = notCopied(iterable);
27
+ if (reason) {
28
+ this.error(`"go for" copies the parts of the array to workers, but ${reason}`, node.iterable);
29
+ }
30
+ }
31
+ const array = arrayName(node);
32
+ for (const use of arrayUses(node).others) {
33
+ this.error(node.key && node.key.name !== '_'
34
+ ? `"go for" gives each worker only its part of "${array}", so the body uses it only as ${array}[${node.key.name}]; read other elements from another array, e.g. "go for i, _ in out { out[i] = src[i] + src[i + 1] }"`
35
+ : `"go for" gives each worker only its part of "${array}", so the body uses it only as its own element; name the index to write it, e.g. "go for i, v in ${array} { ${array}[i] = v * 2 }"`, use);
36
+ }
37
+ this.checkCaptures(node, array);
38
+ }
39
+ /**
40
+ * The names that the body uses from around it: those of the module go to the worker module as
41
+ * with a function there; local ones are copied to each worker, so they must be data. Either way
42
+ * a worker has its own, so changes to them there would not come back.
43
+ */
44
+ checkCaptures(node, array) {
45
+ const captured = [];
46
+ const assigned = assignedNames(node.body);
47
+ const written = writtenNames(node.body);
48
+ for (const name of freeNames({ params: loopParams(node), body: node.body })) {
49
+ const binding = this.lookupValue(name);
50
+ if (name === array || !binding || binding === this.globals.values.get(name))
51
+ continue;
52
+ const at = firstUse(node.body, name) ?? node;
53
+ const module = binding === this.moduleScope.values.get(name);
54
+ const reason = module ? null : binding.type && notCopied(binding.type);
55
+ if (module)
56
+ this.checkReach('the body of "go for"', [name], at);
57
+ else
58
+ captured.push(name);
59
+ if (reason) {
60
+ this.error(`the body of "go for" runs in workers, which get a copy of "${name}", but ${reason}`, at);
61
+ }
62
+ else if (assigned.has(name)) {
63
+ this.error(`the body of "go for" runs in workers, which get a copy of "${name}", so changing it there does not change it here; write the results into the array of the loop, or send them through a channel`, at);
64
+ }
65
+ else if (written.has(name)) {
66
+ this.error(`the body of "go for" runs in workers, which get a copy of "${name}", so changing it there does not change it here; loop over the array you write, e.g. "go for i, _ in ${name} { ${name}[i] = ... }"`, at);
67
+ }
68
+ }
69
+ recordCaptures(node, captured);
70
+ }
71
+ }
72
+ /** An array or a typed array, which `go for` splits into parts. */
73
+ function isSplit(type) {
74
+ return type.kind === 'array' || typedArrayOf(type) !== null;
75
+ }
76
+ /** The names whose elements or properties code writes: `out[i] = v`, `point.x++`. */
77
+ function writtenNames(node, names = new Set()) {
78
+ const add = (target) => {
79
+ if (target.kind !== 'IndexExpression' && target.kind !== 'MemberExpression')
80
+ return;
81
+ let root = target;
82
+ while (root.kind === 'IndexExpression' || root.kind === 'MemberExpression')
83
+ root = root.object;
84
+ if (root.kind === 'Identifier')
85
+ names.add(root.name);
86
+ };
87
+ if (node.kind === 'AssignmentStatement')
88
+ node.targets.forEach(add);
89
+ else if (node.kind === 'IncDecStatement')
90
+ add(node.target);
91
+ forEachChild(node, (child) => writtenNames(child, names));
92
+ return names;
93
+ }
@@ -0,0 +1,19 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { CallChecker } from './calls.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class GoroutineChecker extends CallChecker {
5
+ /** The functions already checked for what their code reaches, so each is reported once. */
6
+ private readonly reached;
7
+ protected checkGo(node: ast.GoExpression): Type;
8
+ /** A function declared at the top level of the module, or an imported name. */
9
+ private isModuleFunction;
10
+ /**
11
+ * The names that a literal uses from around it: those of the module go to the worker module as
12
+ * with a function there; local ones are copied to the worker, so they must be data, and changing
13
+ * a copy would not change them.
14
+ */
15
+ private checkLiteral;
16
+ protected checkReach(name: string, names: string[], node: ast.NodeBase): void;
17
+ }
18
+ /** The first place where code uses a name, for a message about it. */
19
+ export declare function firstUse(node: ast.Node, name: string): ast.Identifier | null;
@@ -0,0 +1,110 @@
1
+ import { reach, recordCaptures } from "../goroutines.js";
2
+ import { assignedNames, freeNames } from "../names.js";
3
+ import { forEachChild } from "../walk.js";
4
+ import { CallChecker } from "./calls.js";
5
+ import { awaited } from "./promise.js";
6
+ import { notCopied } from "./transfer.js";
7
+ import { UNKNOWN } from "./types.js";
8
+ // `go work(x)`: the call of a function of the module in a worker; `go func(x) { ... }(y)`: of a
9
+ // function literal, which gets copies of the local names it uses. The worker gets copies of the
10
+ // arguments and gives back a copy of the result, so both must be data that can be copied; and the
11
+ // code of the function must not need what a worker does not have: the variables of the module and
12
+ // a document.
13
+ export class GoroutineChecker extends CallChecker {
14
+ /** The functions already checked for what their code reaches, so each is reported once. */
15
+ reached = new Set();
16
+ checkGo(node) {
17
+ const { call } = node;
18
+ const result = this.checkExpression(call, null);
19
+ const { callee } = call;
20
+ if (callee.kind !== 'FuncExpression' &&
21
+ (callee.kind !== 'Identifier' || !this.isModuleFunction(callee.name))) {
22
+ this.error('"go" calls a function declared at the top level of a module or imported, or a function literal: a worker has only those, e.g. "go work(x)" or "go func() { ... }()"', callee);
23
+ return UNKNOWN;
24
+ }
25
+ const name = callee.kind === 'Identifier' ? `"${callee.name}"` : 'the goroutine';
26
+ call.arguments.forEach((arg, i) => {
27
+ const value = arg.kind === 'SpreadElement' ? arg.argument : arg;
28
+ const type = this.checkedTypes.get(value);
29
+ const element = arg.kind === 'SpreadElement' && type?.kind === 'array' ? type.element : type;
30
+ const reason = element && notCopied(element);
31
+ if (reason) {
32
+ this.error(`${name} runs in a worker, which gets a copy of argument ${i + 1}, but ${reason}`, value);
33
+ }
34
+ });
35
+ // An async function gives what its Promise gives, once the worker has waited for it.
36
+ const value = awaited(result);
37
+ const results = value.kind === 'tuple' ? value.types : value.kind === 'void' ? [] : [value];
38
+ const reason = results.map((type) => notCopied(type)).find((found) => found !== null);
39
+ if (reason) {
40
+ this.error(`${name} runs in a worker, which sends back a copy of its result, but ${reason}`, node);
41
+ }
42
+ if (callee.kind === 'Identifier') {
43
+ if (!this.reached.has(callee.name)) {
44
+ this.reached.add(callee.name);
45
+ this.checkReach(name, [callee.name], node);
46
+ }
47
+ }
48
+ else {
49
+ this.checkLiteral(node, callee);
50
+ }
51
+ return this.promiseOf(results);
52
+ }
53
+ /** A function declared at the top level of the module, or an imported name. */
54
+ isModuleFunction(name) {
55
+ const binding = this.lookupValue(name);
56
+ return (binding !== undefined &&
57
+ binding === this.moduleScope.values.get(name) &&
58
+ (binding.kind === 'function' || binding.kind === 'import'));
59
+ }
60
+ /**
61
+ * The names that a literal uses from around it: those of the module go to the worker module as
62
+ * with a function there; local ones are copied to the worker, so they must be data, and changing
63
+ * a copy would not change them.
64
+ */
65
+ checkLiteral(node, literal) {
66
+ const captured = [];
67
+ const module = [];
68
+ const assigned = assignedNames(literal.body);
69
+ for (const name of freeNames(literal)) {
70
+ const binding = this.lookupValue(name);
71
+ if (!binding || binding === this.globals.values.get(name))
72
+ continue;
73
+ if (binding === this.moduleScope.values.get(name)) {
74
+ module.push(name);
75
+ continue;
76
+ }
77
+ captured.push(name);
78
+ const reason = binding.type && notCopied(binding.type);
79
+ const at = firstUse(literal.body, name) ?? node;
80
+ if (reason) {
81
+ this.error(`the goroutine runs in a worker, which gets a copy of "${name}", but ${reason}`, at);
82
+ }
83
+ else if (assigned.has(name)) {
84
+ this.error(`the goroutine runs in a worker, which gets a copy of "${name}", so changing it there does not change it here; send the new value through a channel or return it`, at);
85
+ }
86
+ }
87
+ recordCaptures(node, captured);
88
+ for (const name of module)
89
+ this.checkReach('the goroutine', [name], firstUse(literal.body, name) ?? node);
90
+ }
91
+ checkReach(name, names, node) {
92
+ const { lets, markup } = reach(this.program, names);
93
+ for (const variable of lets) {
94
+ this.error(`${name} runs in a worker, which does not share the variables of the module, but its code uses "${variable}"; pass the value as an argument, or make it a const`, node);
95
+ }
96
+ if (markup !== null) {
97
+ this.error(`${name} runs in a worker, which has no document, but its code uses ${markup}`, node);
98
+ }
99
+ }
100
+ }
101
+ /** The first place where code uses a name, for a message about it. */
102
+ export function firstUse(node, name) {
103
+ if (node.kind === 'Identifier')
104
+ return node.name === name ? node : null;
105
+ let found = null;
106
+ forEachChild(node, (child) => {
107
+ found ??= firstUse(child, name);
108
+ });
109
+ return found;
110
+ }