rooklang 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +195 -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 +105 -4
@@ -0,0 +1,237 @@
1
+ import { assignedNames } from "../names.js";
2
+ import { NARROWABLE } from "./context.js";
3
+ import { narrowByTypeof, literalValue, withoutLiteral, withNarrowing, mergeFlows, leaves, } from "./flow.js";
4
+ import { instanceTypeOf } from "./helpers.js";
5
+ import { StatementChecker } from "./statements.js";
6
+ import { ANY, BOOL, isAssignable, isComparable, isNullable, isSubclass, literal, nonNull, NULL, typeToString, union, unionMembers, widenLiterals, } from "./types.js";
7
+ // if, for, switch and try, and how conditions narrow the types of variables in the branches.
8
+ export class ControlFlowChecker extends StatementChecker {
9
+ checkIf(node) {
10
+ this.checkCondition(node.condition);
11
+ const before = this.flow;
12
+ // Both are found from the flow before the `if`, not from the end of a branch.
13
+ const whenTrue = this.narrow(node.condition, true);
14
+ const whenFalse = this.narrow(node.condition, false);
15
+ this.flow = withNarrowing(before, whenTrue);
16
+ this.checkBlock(node.consequent.body);
17
+ const afterThen = this.flow;
18
+ this.flow = withNarrowing(before, whenFalse);
19
+ const { alternate } = node;
20
+ if (alternate?.kind === 'IfStatement')
21
+ this.checkIf(alternate);
22
+ else if (alternate)
23
+ this.checkBlock(alternate.body);
24
+ const afterElse = this.flow;
25
+ // A branch that always leaves (return, throw, break, continue) does not reach the code after
26
+ // the `if`, so `if x == null { return }` narrows `x` for the rest of the block.
27
+ const thenLeaves = leaves(node.consequent);
28
+ const elseLeaves = alternate !== null && leaves(alternate);
29
+ if (thenLeaves && !elseLeaves)
30
+ this.flow = afterElse;
31
+ else if (elseLeaves && !thenLeaves)
32
+ this.flow = afterThen;
33
+ else
34
+ this.flow = mergeFlows(afterThen, afterElse);
35
+ }
36
+ checkFor(node) {
37
+ this.withScope(() => {
38
+ if (node.init)
39
+ this.checkSimpleStatement(node.init);
40
+ // Variables assigned in the loop may change between iterations.
41
+ this.dropNarrowing(assignedNames(node));
42
+ const entry = this.flow;
43
+ if (node.condition)
44
+ this.checkCondition(node.condition);
45
+ this.flow = withNarrowing(entry, node.condition ? this.narrow(node.condition, true) : []);
46
+ this.checkBlock(node.body.body);
47
+ if (node.update)
48
+ this.checkSimpleStatement(node.update);
49
+ this.flow = entry;
50
+ });
51
+ }
52
+ checkSimpleStatement(node) {
53
+ this.checkStatement(node);
54
+ }
55
+ checkSwitch(node) {
56
+ const discriminant = node.discriminant ? this.checkValue(node.discriminant) : null;
57
+ this.dropNarrowing(assignedNames(node));
58
+ const entry = this.flow;
59
+ for (const switchCase of node.cases) {
60
+ this.flow = entry;
61
+ for (const test of switchCase.tests) {
62
+ if (discriminant === null) {
63
+ this.checkCondition(test);
64
+ continue;
65
+ }
66
+ const type = this.checkValue(test, discriminant);
67
+ if (!isComparable(type, discriminant)) {
68
+ this.error(`cannot compare ${typeToString(discriminant)} with ${typeToString(type)}`, test);
69
+ }
70
+ }
71
+ const narrowing = discriminant === null
72
+ ? switchCase.tests.length === 1
73
+ ? this.narrow(switchCase.tests[0], true)
74
+ : []
75
+ : this.narrowCase(node, switchCase, entry);
76
+ this.flow = withNarrowing(entry, narrowing);
77
+ this.checkBlock(switchCase.body);
78
+ }
79
+ this.flow = entry;
80
+ }
81
+ /**
82
+ * `switch filter { case "all": ... default: ... }`: in a case the discriminant has the literals
83
+ * of its tests, in `default` the literals of no case.
84
+ */
85
+ narrowCase(node, switchCase, entry) {
86
+ const binding = node.discriminant ? this.narrowableBinding(node.discriminant) : undefined;
87
+ if (!binding?.type)
88
+ return [];
89
+ const current = entry.get(binding) ?? binding.type;
90
+ if (!unionMembers(nonNull(current)).some((member) => member.kind === 'literal'))
91
+ return [];
92
+ if (switchCase.tests.length > 0) {
93
+ const values = switchCase.tests.map(literalValue);
94
+ if (values.some((value) => value === undefined))
95
+ return [];
96
+ return [[binding, union(values.map((value) => literal(value)))]];
97
+ }
98
+ let rest = current;
99
+ for (const other of node.cases) {
100
+ for (const test of other.tests) {
101
+ const value = literalValue(test);
102
+ if (value !== undefined)
103
+ rest = withoutLiteral(rest, value);
104
+ }
105
+ }
106
+ return [[binding, rest]];
107
+ }
108
+ checkTry(node) {
109
+ this.dropNarrowing(assignedNames(node));
110
+ const entry = this.flow;
111
+ this.flow = new Map(entry);
112
+ this.checkBlock(node.block.body);
113
+ const { handler, finalizer } = node;
114
+ if (handler) {
115
+ this.flow = new Map(entry);
116
+ this.withScope(() => {
117
+ if (handler.param)
118
+ this.declareValue(handler.param, 'catch', ANY);
119
+ this.checkStatementList(handler.body.body, false);
120
+ });
121
+ }
122
+ if (finalizer) {
123
+ this.flow = new Map(entry);
124
+ this.checkBlock(finalizer.body);
125
+ }
126
+ this.flow = entry;
127
+ }
128
+ checkCondition(node) {
129
+ const type = widenLiterals(this.checkValue(node, BOOL));
130
+ if (type.kind === 'bool' || type.kind === 'any' || type.kind === 'unknown')
131
+ return;
132
+ const hint = isNullable(type)
133
+ ? ': compare it with null, e.g. "x != null"'
134
+ : type.kind === 'number'
135
+ ? ': compare it, e.g. "n != 0"'
136
+ : type.kind === 'bigint'
137
+ ? ': compare it, e.g. "n != 0n"'
138
+ : type.kind === 'string'
139
+ ? ': compare it, e.g. s != ""'
140
+ : '';
141
+ this.error(`condition must be bool, not ${typeToString(type)}${hint}`, node);
142
+ }
143
+ /** Variables whose type is narrowed when `node` evaluates to `assumeTrue`. */
144
+ narrow(node, assumeTrue) {
145
+ if (node.kind === 'UnaryExpression' && node.operator === '!') {
146
+ return this.narrow(node.argument, !assumeTrue);
147
+ }
148
+ if (node.kind !== 'BinaryExpression')
149
+ return [];
150
+ const { operator, left, right } = node;
151
+ // `a && b` is true, or `a || b` is false: both sides hold, and the right one is narrowed in
152
+ // the flow that the left one gives.
153
+ if ((operator === '&&' && assumeTrue) || (operator === '||' && !assumeTrue)) {
154
+ const first = this.narrow(left, assumeTrue);
155
+ const saved = this.flow;
156
+ this.flow = withNarrowing(saved, first);
157
+ try {
158
+ return [...first, ...this.narrow(right, assumeTrue)];
159
+ }
160
+ finally {
161
+ this.flow = saved;
162
+ }
163
+ }
164
+ if (operator === '&&' || operator === '||')
165
+ return [];
166
+ // `x != null` when true, `x == null` when false: `x` is not null.
167
+ if ((operator === '!=' && assumeTrue) || (operator === '==' && !assumeTrue)) {
168
+ const target = right.kind === 'NullLiteral' ? left : left.kind === 'NullLiteral' ? right : null;
169
+ const binding = target ? this.narrowableBinding(target) : undefined;
170
+ if (binding?.type)
171
+ return [[binding, nonNull(this.flow.get(binding) ?? binding.type)]];
172
+ }
173
+ if (operator === 'instanceof')
174
+ return this.narrowInstanceof(left, right, assumeTrue);
175
+ if (operator !== '==' && operator !== '!=')
176
+ return [];
177
+ // Whether the comparison is found to give "equal".
178
+ const equal = (operator === '==') === assumeTrue;
179
+ // `typeof x == "string"`
180
+ const [check, tag] = left.kind === 'UnaryExpression' && left.operator === 'typeof'
181
+ ? [left, right]
182
+ : right.kind === 'UnaryExpression' && right.operator === 'typeof'
183
+ ? [right, left]
184
+ : [null, null];
185
+ if (check && tag?.kind === 'StringLiteral') {
186
+ const binding = this.narrowableBinding(check.argument);
187
+ if (!binding?.type)
188
+ return [];
189
+ const type = narrowByTypeof(this.flow.get(binding) ?? binding.type, tag.value, equal);
190
+ return type ? [[binding, type]] : [];
191
+ }
192
+ // `filter == "all"`: narrows a type with literals in it.
193
+ const value = literalValue(right) ?? literalValue(left);
194
+ const target = literalValue(right) !== undefined ? left : right;
195
+ const binding = value === undefined ? undefined : this.narrowableBinding(target);
196
+ if (value === undefined || !binding?.type)
197
+ return [];
198
+ const current = this.flow.get(binding) ?? binding.type;
199
+ if (!unionMembers(nonNull(current)).some((member) => member.kind === 'literal'))
200
+ return [];
201
+ return [[binding, equal ? literal(value) : withoutLiteral(current, value)]];
202
+ }
203
+ /** `x instanceof Date`: an instance of the class when true, the rest of a union when false. */
204
+ narrowInstanceof(left, right, assumeTrue) {
205
+ const binding = this.narrowableBinding(left);
206
+ const classType = this.typeOfChecked(right);
207
+ const instance = instanceTypeOf(classType);
208
+ if (!binding?.type || !instance)
209
+ return [];
210
+ const current = this.flow.get(binding) ?? binding.type;
211
+ const members = unionMembers(nonNull(current));
212
+ if (assumeTrue) {
213
+ const kept = members.filter((member) => isAssignable(member, instance));
214
+ return [[binding, kept.length > 0 ? union(kept) : instance]];
215
+ }
216
+ // A class keeps what is not its subclass; a DOM interface what is not assignable to it.
217
+ const kept = members.filter((member) => classType.kind === 'classValue'
218
+ ? !(member.kind === 'class' && isSubclass(member.info, classType.info))
219
+ : !isAssignable(member, instance));
220
+ return [[binding, union(isNullable(current) ? [...kept, NULL] : kept)]];
221
+ }
222
+ narrowableBinding(node) {
223
+ if (node.kind !== 'Identifier')
224
+ return undefined;
225
+ const binding = this.lookupValue(node.name);
226
+ return binding && NARROWABLE.has(binding.kind) ? binding : undefined;
227
+ }
228
+ /** An assignment to a variable ends its narrowing. */
229
+ forget(target) {
230
+ const binding = this.narrowableBinding(target);
231
+ if (binding)
232
+ this.flow.delete(binding);
233
+ }
234
+ dropNarrowing(names) {
235
+ this.flow = new Map([...this.flow].filter(([binding]) => !names.has(binding.name)));
236
+ }
237
+ }
@@ -0,0 +1,26 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { ImportChecker } from './imports.ts';
3
+ import { type FunctionType, type ObjectType, type Type, type TypeParam } from './types.ts';
4
+ export declare abstract class DeclarationChecker extends ImportChecker {
5
+ /**
6
+ * Declares the names of a statement list first (so functions, classes and types can be used
7
+ * before their declaration), then checks the statements in order. Function and method bodies are
8
+ * checked last, when every name of the list is known.
9
+ */
10
+ protected checkStatementList(statements: readonly ast.Statement[], topLevel: boolean): void;
11
+ protected declareStatements(statements: readonly ast.Statement[], topLevel: boolean): (() => void)[];
12
+ /** Members of an interface or object type: `name string; area() number`. */
13
+ protected fillMembers(object: ObjectType, members: readonly ast.TypeMember[]): void;
14
+ /** `[T any, U Shape]`: the parameters, with constraints resolved later by withTypeParams. */
15
+ protected createTypeParams(nodes: readonly ast.TypeParameter[]): TypeParam[];
16
+ /**
17
+ * Runs `check` in a scope where type parameters are types. With their declarations, it first
18
+ * resolves the constraints (`any` means none).
19
+ */
20
+ protected withTypeParams<T>(params: readonly TypeParam[], nodes: readonly ast.TypeParameter[] | null, check: () => T): T;
21
+ /** The type of a function; an async one returns a Promise of its results, and one whose
22
+ * parameter is `yield` is an iterator (iterators.ts). */
23
+ protected signature(params: readonly ast.Parameter[], results: readonly ast.TypeNode[], async?: boolean): FunctionType;
24
+ /** `Promise<...>` of the results of an async function, with the global `Promise` type. */
25
+ protected promiseOf(results: readonly Type[]): Type;
26
+ }
@@ -0,0 +1,259 @@
1
+ import { decorate, decoratorNames } from "../decorators.js";
2
+ import { isIterator } from "../iterators.js";
3
+ import { collectValueNames } from "../names.js";
4
+ import { recursiveComponents } from "../recursion.js";
5
+ import { Scope } from "./context.js";
6
+ import { ImportChecker } from "./imports.js";
7
+ import { awaited, isPromise, promiseOf } from "./promise.js";
8
+ import { createClass, func, typeToString, UNKNOWN, } from "./types.js";
9
+ // Declarations of a list of statements: imports, types, functions, components, decorators and
10
+ // classes, declared before the statements are checked, so that they can refer to each other.
11
+ export class DeclarationChecker extends ImportChecker {
12
+ /**
13
+ * Declares the names of a statement list first (so functions, classes and types can be used
14
+ * before their declaration), then checks the statements in order. Function and method bodies are
15
+ * checked last, when every name of the list is known.
16
+ */
17
+ checkStatementList(statements, topLevel) {
18
+ const bodies = this.declareStatements(statements, topLevel);
19
+ for (const statement of statements)
20
+ this.checkStatement(statement);
21
+ for (const checkBody of bodies)
22
+ checkBody();
23
+ }
24
+ declareStatements(statements, topLevel) {
25
+ const classes = [];
26
+ const interfaces = [];
27
+ const functions = [];
28
+ const components = [];
29
+ const decorators = [];
30
+ for (const statement of statements) {
31
+ switch (statement.kind) {
32
+ case 'ImportDeclaration':
33
+ this.declareImport(statement);
34
+ break;
35
+ case 'ClassDeclaration': {
36
+ const info = createClass(statement.name.name);
37
+ info.typeParams = this.createTypeParams(statement.typeParams);
38
+ this.declareType(statement.name, info.instance);
39
+ this.declareValue(statement.name, 'class', info.value);
40
+ classes.push([statement, info]);
41
+ break;
42
+ }
43
+ case 'InterfaceDeclaration': {
44
+ const object = {
45
+ kind: 'object',
46
+ name: statement.name.name,
47
+ members: new Map(),
48
+ call: null,
49
+ };
50
+ if (statement.typeParams.length > 0) {
51
+ object.typeParams = this.createTypeParams(statement.typeParams);
52
+ }
53
+ this.declareType(statement.name, object);
54
+ interfaces.push([statement, object]);
55
+ break;
56
+ }
57
+ case 'TypeAliasDeclaration':
58
+ this.declareType(statement.name, {
59
+ kind: 'alias',
60
+ node: statement,
61
+ scope: this.scope,
62
+ params: this.createTypeParams(statement.typeParams),
63
+ resolved: null,
64
+ resolving: false,
65
+ });
66
+ break;
67
+ case 'FuncDeclaration':
68
+ functions.push([statement, this.declareValue(statement.name, 'function', null)]);
69
+ break;
70
+ case 'ComponentDeclaration': {
71
+ const info = {
72
+ node: statement,
73
+ props: new Map(),
74
+ freeNames: null,
75
+ isFunction: false,
76
+ decorators: [],
77
+ expanded: statement,
78
+ result: null,
79
+ root: null,
80
+ inline: null,
81
+ expressionTypes: null,
82
+ };
83
+ this.declareValue(statement.name, 'component', UNKNOWN).component = info;
84
+ components.push(info);
85
+ break;
86
+ }
87
+ case 'DecoratorDeclaration': {
88
+ const name = statement.name.name;
89
+ const info = {
90
+ node: statement,
91
+ params: [],
92
+ members: new Map(),
93
+ names: decoratorNames(statement),
94
+ freeNames: null,
95
+ wrapper: null,
96
+ needed: [],
97
+ moduleNames: [],
98
+ expressionTypes: null,
99
+ inline: null,
100
+ };
101
+ if (this.decorators.has(name)) {
102
+ this.error(`the decorator @${name} is already declared`, statement.name);
103
+ }
104
+ else {
105
+ this.decorators.set(name, info);
106
+ }
107
+ decorators.push(info);
108
+ break;
109
+ }
110
+ case 'VariableDeclaration':
111
+ // Top-level variables can be used in functions declared before them.
112
+ if (topLevel) {
113
+ for (const name of statement.names)
114
+ this.declareValue(name, statement.keyword, null);
115
+ }
116
+ break;
117
+ default:
118
+ break;
119
+ }
120
+ }
121
+ for (const [node, object] of interfaces) {
122
+ this.withTypeParams(object.typeParams ?? [], node.typeParams, () => this.fillMembers(object, node.members));
123
+ }
124
+ for (const statement of statements) {
125
+ if (statement.kind !== 'TypeAliasDeclaration')
126
+ continue;
127
+ const entry = this.scope.types.get(statement.name.name);
128
+ if (entry?.kind === 'alias')
129
+ this.resolveAlias(entry);
130
+ }
131
+ for (const [node, binding] of functions) {
132
+ const typeParams = this.createTypeParams(node.typeParams);
133
+ binding.type = this.withTypeParams(typeParams, node.typeParams, () => ({
134
+ ...this.signature(node.params, node.results, node.async),
135
+ typeParams,
136
+ }));
137
+ }
138
+ for (const info of components)
139
+ this.resolveProps(info);
140
+ for (const info of decorators)
141
+ this.resolveDecoratorParams(info);
142
+ // Decorators of other modules use the names of those through hidden exports.
143
+ const sources = new Map([...this.decorators].map(([name, info]) => {
144
+ const moduleNames = this.importedDecorators.has(info) ? info.moduleNames : [];
145
+ return [name, { node: info.node, moduleNames }];
146
+ }));
147
+ for (const info of components) {
148
+ info.decorators = info.node.decorators.flatMap((use) => this.decorators.get(use.name.name) ?? []);
149
+ info.expanded = decorate(info.node, sources);
150
+ }
151
+ // The code of the decorators is part of the component: it may use components too.
152
+ const recursive = recursiveComponents(new Map(components.map((info) => [info.node.name.name, info.expanded])));
153
+ // A component used as a value is called by tags that take it from there.
154
+ const names = new Set(components.map((info) => info.node.name.name));
155
+ const values = new Set();
156
+ collectValueNames(this.program, values, (name) => names.has(name));
157
+ for (const info of components) {
158
+ // Other modules call an exported component, so it is a function in its own module too.
159
+ info.isFunction =
160
+ recursive.has(info.expanded) ||
161
+ info.node.htmlTag !== null ||
162
+ info.node.exported ||
163
+ values.has(info.node.name.name);
164
+ }
165
+ for (const [node, info] of classes) {
166
+ this.unresolvedClasses.set(info, () => this.resolveClass(node, info));
167
+ }
168
+ for (const [, info] of classes)
169
+ this.ensureClassResolved(info);
170
+ const bodies = [];
171
+ for (const [node, binding] of functions) {
172
+ const type = binding.type;
173
+ // A nested `func` declaration is a JS `function`: it has no `this` of its own class.
174
+ bodies.push(() => this.withClass(null, () => this.checkFunction(node.params, type, type.results, node.body, { async: node.async })));
175
+ }
176
+ for (const [node, info] of classes)
177
+ bodies.push(() => this.checkClassBodies(node, info));
178
+ // Components use the types of the public members of decorators, and so do decorators that
179
+ // need them.
180
+ for (const info of this.orderByNeeds(decorators)) {
181
+ bodies.push(() => this.checkDecoratorBody(info));
182
+ }
183
+ for (const info of components)
184
+ bodies.push(() => this.checkComponentBody(info));
185
+ // The values of `style` are read once the markup exists: they see the whole body.
186
+ for (const style of statements.filter((node) => node.kind === 'StyleStatement')) {
187
+ bodies.push(() => this.checkStyle(style));
188
+ }
189
+ return bodies;
190
+ }
191
+ /** Members of an interface or object type: `name string; area() number`. */
192
+ fillMembers(object, members) {
193
+ for (const member of members) {
194
+ // `[string]: T`: the parser allows only string and number keys, which JS objects have.
195
+ if (member.kind === 'IndexSignature') {
196
+ object.index = this.resolveType(member.type);
197
+ continue;
198
+ }
199
+ if (object.members.has(member.name.name)) {
200
+ this.error(`duplicate member "${member.name.name}"`, member.name);
201
+ }
202
+ object.members.set(member.name.name, {
203
+ type: member.kind === 'PropertySignature'
204
+ ? this.resolveType(member.type)
205
+ : this.signature(member.params, member.results),
206
+ method: member.kind === 'MethodSignature',
207
+ visibility: 'public',
208
+ owner: null,
209
+ });
210
+ }
211
+ }
212
+ /** `[T any, U Shape]`: the parameters, with constraints resolved later by withTypeParams. */
213
+ createTypeParams(nodes) {
214
+ return nodes.map((node) => ({ kind: 'param', name: node.name.name, constraint: null }));
215
+ }
216
+ /**
217
+ * Runs `check` in a scope where type parameters are types. With their declarations, it first
218
+ * resolves the constraints (`any` means none).
219
+ */
220
+ withTypeParams(params, nodes, check) {
221
+ if (params.length === 0)
222
+ return check();
223
+ const saved = this.scope;
224
+ this.scope = new Scope(saved);
225
+ try {
226
+ for (const param of params)
227
+ this.scope.types.set(param.name, param);
228
+ nodes?.forEach((node, i) => {
229
+ if (!node.constraint)
230
+ return;
231
+ const constraint = this.resolveType(node.constraint);
232
+ params[i].constraint = constraint.kind === 'any' ? null : constraint;
233
+ });
234
+ return check();
235
+ }
236
+ finally {
237
+ this.scope = saved;
238
+ }
239
+ }
240
+ /** The type of a function; an async one returns a Promise of its results, and one whose
241
+ * parameter is `yield` is an iterator (iterators.ts). */
242
+ signature(params, results, async = false) {
243
+ const types = results.map((result) => this.resolveType(result));
244
+ if (async) {
245
+ types.forEach((type, i) => {
246
+ if (!isPromise(type))
247
+ return;
248
+ this.error(`an async function declares what its Promise gives: write "${typeToString(awaited(type))}", not "${typeToString(type)}"`, results[i]);
249
+ types[i] = awaited(type);
250
+ });
251
+ }
252
+ return func(params.map((param) => (param.type ? this.resolveType(param.type) : UNKNOWN)), async ? [this.promiseOf(types)] : types, { iterator: isIterator(params) });
253
+ }
254
+ /** `Promise<...>` of the results of an async function, with the global `Promise` type. */
255
+ promiseOf(results) {
256
+ const entry = this.globals.types.get('Promise') ?? this.options.library?.type('Promise');
257
+ return promiseOf(entry?.kind === 'alias' ? undefined : entry, results);
258
+ }
259
+ }
@@ -0,0 +1,5 @@
1
+ import type { DecoratorInfo } from './context.ts';
2
+ import { DecoratorWrapperChecker } from './decorator-wrappers.ts';
3
+ export declare abstract class DecoratorExportChecker extends DecoratorWrapperChecker {
4
+ protected checkExportedDecorator(info: DecoratorInfo): void;
5
+ }
@@ -0,0 +1,40 @@
1
+ import { moduleNamesOf } from "../decorators.js";
2
+ import { assignedNames, componentValues } from "../names.js";
3
+ import { DecoratorWrapperChecker } from "./decorator-wrappers.js";
4
+ // Exported decorators, `export dec visible() { ... }`. Other modules inline their code, which uses
5
+ // the names of this module through hidden exports, `export { log as $$visible$log }`; so the code
6
+ // must work from there.
7
+ export class DecoratorExportChecker extends DecoratorWrapperChecker {
8
+ checkExportedDecorator(info) {
9
+ const { node } = info;
10
+ const name = node.name.name;
11
+ info.moduleNames = moduleNamesOf(node, this.program);
12
+ info.expressionTypes = this.checkedTypes;
13
+ // A module that applies it applies those it needs too, so it must be able to import them.
14
+ for (const needed of info.needed) {
15
+ const other = needed.node.name.name;
16
+ if (needed.node.exported)
17
+ continue;
18
+ this.error(`@${name} is exported, so @${other} that it needs must be exported too: export dec ${other}(...)`, node.needs.find((need) => need.name === other) ?? node.name);
19
+ }
20
+ // Other modules can read the variables of this one, but not assign them.
21
+ const assigned = assignedNames(node.body);
22
+ for (const used of info.moduleNames) {
23
+ if (!assigned.has(used))
24
+ continue;
25
+ this.error(`@${name} is exported, so it cannot change "${used}" of its module: other modules can only read it; change it in a function of the module`, node.name);
26
+ }
27
+ // Its code goes into components elsewhere with the components of this module that it uses.
28
+ info.inline = this.modulePackages().decorators.get(name) ?? null;
29
+ const [value] = componentValues(node, (other) => this.lookupValue(other)?.component !== undefined);
30
+ if (value !== undefined) {
31
+ this.error(`@${name} is exported, so its code cannot use the component ${value} of its module as a value yet: use it as a tag, <${value} />`, node.name);
32
+ }
33
+ else if (info.inline === null) {
34
+ this.error(`@${name} is exported, so its code goes into other modules with the components it uses: they cannot use components or decorators that this module imports yet, nor assign variables of this module`, node.name);
35
+ }
36
+ // The type of what its wrapper gives: the tags of its markup are names of this module.
37
+ if (info.wrapper)
38
+ info.wrapper.result = this.wrapperResult(info.wrapper, new Set());
39
+ }
40
+ }
@@ -0,0 +1,28 @@
1
+ import type * as ast from '../ast.ts';
2
+ import type { DecoratorInfo, PublicMember } from './context.ts';
3
+ import { DecoratorChecker } from './decorators.ts';
4
+ import { type Type } from './types.ts';
5
+ export declare abstract class DecoratorMemberChecker extends DecoratorChecker {
6
+ /** `@visible.show`: a public function; state and constants are read with `get`. */
7
+ protected checkDecoratorMember(node: ast.DecoratorMember): Type;
8
+ /** `get(@visible.shown)`: public state or a constant. */
9
+ protected checkDecoratorGet(node: ast.DecoratorGet): Type;
10
+ /**
11
+ * A public member of a decorator applied to the component being checked, or of one that the
12
+ * decorator being checked needs.
13
+ */
14
+ protected findDecoratorMember(node: ast.DecoratorMember): PublicMember | null;
15
+ /** `get(@cart.count)` in `dec cartBadge() needs cart`. */
16
+ protected neededMember(node: ast.DecoratorMember, decorator: ast.DecoratorDeclaration): PublicMember | null;
17
+ protected publicMember(node: ast.DecoratorMember, info: DecoratorInfo): PublicMember | null;
18
+ /**
19
+ * Members of a decorator are read-only outside it: `get(@cart.count) = 0`, `get(@cart.items)[0]
20
+ * = x`. Returns whether `target` is one of them.
21
+ */
22
+ protected checkDecoratorWrite(target: ast.Expression): boolean;
23
+ /** `get(@cart.items).push(x)`: a method that changes the array. */
24
+ protected checkDecoratorMutation(node: ast.CallExpression): void;
25
+ protected readOnlyError(member: ast.DecoratorMember, node: ast.NodeBase): void;
26
+ /** `card.count` without `@`: `card` is not a value, but a decorator may have that name. */
27
+ protected decoratorWithoutAt(node: ast.MemberExpression): boolean;
28
+ }