functionalscript 0.46.0 → 0.47.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 (178) hide show
  1. package/fjs/basen/cbase32/module.f.mjs +18 -10
  2. package/fjs/basen/cbase32/proof.f.d.mts +3 -0
  3. package/fjs/basen/cbase32/proof.f.mjs +24 -2
  4. package/fjs/basen/proof.f.mjs +8 -16
  5. package/fjs/bnf/ll1/proof.f.d.mts +2 -0
  6. package/fjs/bnf/ll1/proof.f.mjs +33 -18
  7. package/fjs/bnf/module.f.d.mts +2 -2
  8. package/fjs/bnf/module.f.mjs +2 -2
  9. package/fjs/cas/proof.f.mjs +4 -26
  10. package/fjs/ci/common/module.f.mjs +6 -0
  11. package/fjs/ci/config/module.f.d.mts +7 -7
  12. package/fjs/ci/config/module.f.mjs +7 -7
  13. package/fjs/cli/module.f.mjs +14 -10
  14. package/fjs/crypto/vdf/module.f.d.mts +0 -1
  15. package/fjs/crypto/vdf/module.f.mjs +3 -14
  16. package/fjs/dev/module.f.mjs +10 -20
  17. package/fjs/djs/parser/module.f.d.mts +101 -53
  18. package/fjs/djs/parser/module.f.mjs +732 -525
  19. package/fjs/djs/parser/proof.f.d.mts +6 -0
  20. package/fjs/djs/parser/proof.f.mjs +315 -11
  21. package/fjs/djs/parser/types.d.ts +29 -2
  22. package/fjs/djs/tokenizer/module.f.mjs +122 -105
  23. package/fjs/djs/tokenizer/proof.f.mjs +17 -0
  24. package/fjs/djs/transpiler/module.f.mjs +4 -5
  25. package/fjs/djs/transpiler/proof.f.d.mts +2 -0
  26. package/fjs/djs/transpiler/proof.f.mjs +25 -0
  27. package/fjs/edag/amnesia/module.f.d.mts +20 -0
  28. package/fjs/edag/amnesia/module.f.mjs +303 -0
  29. package/fjs/edag/amnesia/proof.f.d.mts +79 -0
  30. package/fjs/edag/amnesia/proof.f.mjs +573 -0
  31. package/fjs/edag/amnesia/types.d.ts +15 -0
  32. package/fjs/edag/module.f.d.mts +410 -0
  33. package/fjs/edag/module.f.mjs +559 -0
  34. package/fjs/edag/proof.f.d.mts +116 -0
  35. package/fjs/edag/proof.f.mjs +686 -0
  36. package/fjs/edag/types.d.ts +55 -0
  37. package/fjs/effects/node/memory/module.d.mts +37 -3
  38. package/fjs/effects/node/memory/module.mjs +38 -4
  39. package/fjs/effects/node/memory/proof.d.mts +5 -2
  40. package/fjs/effects/node/memory/proof.mjs +36 -6
  41. package/fjs/effects/node/module.d.mts +9 -1
  42. package/fjs/effects/node/module.f.d.mts +39 -0
  43. package/fjs/effects/node/module.f.mjs +46 -0
  44. package/fjs/effects/node/module.mjs +234 -32
  45. package/fjs/effects/node/types.d.ts +28 -2
  46. package/fjs/effects/node/virtual/module.f.d.mts +19 -10
  47. package/fjs/effects/node/virtual/module.f.mjs +246 -32
  48. package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
  49. package/fjs/effects/node/virtual/proof.f.mjs +232 -8
  50. package/fjs/effects/node/virtual/types.d.ts +57 -1
  51. package/fjs/emergent_testing/browser/proof.d.mts +55 -0
  52. package/fjs/emergent_testing/browser/proof.mjs +352 -0
  53. package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
  54. package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
  55. package/fjs/emergent_testing/browser.d.mts +79 -0
  56. package/fjs/emergent_testing/browser.mjs +432 -0
  57. package/fjs/emergent_testing/proof.f.d.mts +1 -1
  58. package/fjs/emergent_testing/proof.f.mjs +2 -2
  59. package/fjs/js/string_escape/module.f.d.mts +47 -0
  60. package/fjs/js/string_escape/module.f.mjs +95 -0
  61. package/fjs/js/string_escape/proof.f.d.mts +8 -0
  62. package/fjs/js/string_escape/proof.f.mjs +68 -0
  63. package/fjs/js/tokenizer/module.f.mjs +14 -12
  64. package/fjs/mcp/cas/module.f.d.mts +1 -7
  65. package/fjs/mcp/cas/module.f.mjs +24 -15
  66. package/fjs/mcp/evo/module.f.mjs +9 -24
  67. package/fjs/mcp/module.f.d.mts +3 -1
  68. package/fjs/mcp/module.f.mjs +4 -2
  69. package/fjs/media/json/rtti/module.f.d.mts +2 -2
  70. package/fjs/media/json/rtti/module.f.mjs +1 -1
  71. package/fjs/media/json/schema/module.f.d.mts +15 -5
  72. package/fjs/media/json/schema/module.f.mjs +42 -15
  73. package/fjs/media/json/schema/proof.f.d.mts +10 -1
  74. package/fjs/media/json/schema/proof.f.mjs +88 -16
  75. package/fjs/media/json/serializer/module.f.mjs +14 -26
  76. package/fjs/media/json/types.d.ts +1 -1
  77. package/fjs/media/lock/module.f.d.mts +8 -3
  78. package/fjs/media/lock/module.f.mjs +9 -3
  79. package/fjs/media/lock/proof.f.d.mts +1 -1
  80. package/fjs/media/lock/proof.f.mjs +2 -2
  81. package/fjs/media/lock/types.d.ts +1 -3
  82. package/fjs/media/module.f.d.mts +20 -13
  83. package/fjs/media/module.f.mjs +29 -16
  84. package/fjs/media/note/module.f.d.mts +14 -7
  85. package/fjs/media/note/module.f.mjs +14 -7
  86. package/fjs/media/note/proof.f.mjs +1 -1
  87. package/fjs/media/proof.f.mjs +5 -5
  88. package/fjs/media/revision/module.f.d.mts +8 -2
  89. package/fjs/media/revision/module.f.mjs +9 -3
  90. package/fjs/media/revision/proof.f.mjs +1 -1
  91. package/fjs/media/revision/types.d.ts +1 -1
  92. package/fjs/media/type/module.f.d.mts +20 -0
  93. package/fjs/media/type/module.f.mjs +74 -0
  94. package/fjs/media/type/proof.f.d.mts +5 -0
  95. package/fjs/media/type/proof.f.mjs +24 -1
  96. package/fjs/module.f.mjs +6 -0
  97. package/fjs/path/module.f.d.mts +60 -3
  98. package/fjs/path/module.f.mjs +138 -14
  99. package/fjs/path/proof.f.d.mts +3 -0
  100. package/fjs/path/proof.f.mjs +298 -2
  101. package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
  102. package/fjs/protocol/json_rpc/module.f.mjs +49 -13
  103. package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
  104. package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
  105. package/fjs/protocol/json_rpc/types.d.ts +12 -2
  106. package/fjs/protocol/mcp/module.f.d.mts +89 -54
  107. package/fjs/protocol/mcp/module.f.mjs +110 -56
  108. package/fjs/protocol/mcp/proof.f.d.mts +13 -0
  109. package/fjs/protocol/mcp/proof.f.mjs +107 -7
  110. package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
  111. package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
  112. package/fjs/protocol/mcp/types.d.ts +14 -2
  113. package/fjs/text/utf8/module.f.mjs +27 -1
  114. package/fjs/text/utf8/proof.f.mjs +37 -0
  115. package/fjs/types/array/module.f.mjs +18 -11
  116. package/fjs/types/array/proof.f.mjs +27 -1
  117. package/fjs/types/bigfloat/module.f.d.mts +48 -9
  118. package/fjs/types/bigfloat/module.f.mjs +147 -59
  119. package/fjs/types/bigfloat/proof.f.d.mts +2 -0
  120. package/fjs/types/bigfloat/proof.f.mjs +136 -4
  121. package/fjs/types/bigfloat/types.d.ts +17 -0
  122. package/fjs/types/function/module.f.d.mts +11 -2
  123. package/fjs/types/function/module.f.mjs +20 -2
  124. package/fjs/types/function/proof.f.mjs +5 -1
  125. package/fjs/types/phantom/types.d.ts +21 -0
  126. package/fjs/types/result/module.f.d.mts +4 -4
  127. package/fjs/types/result/module.f.mjs +2 -2
  128. package/fjs/types/result/proof.f.d.mts +6 -2
  129. package/fjs/types/result/proof.f.mjs +17 -1
  130. package/fjs/types/rtti/common/module.f.d.mts +78 -5
  131. package/fjs/types/rtti/common/module.f.mjs +168 -3
  132. package/fjs/types/rtti/common/proof.f.d.mts +14 -0
  133. package/fjs/types/rtti/common/proof.f.mjs +61 -2
  134. package/fjs/types/rtti/common/types.d.ts +23 -1
  135. package/fjs/types/rtti/data/module.f.d.mts +57 -1
  136. package/fjs/types/rtti/data/module.f.mjs +241 -47
  137. package/fjs/types/rtti/data/proof.f.d.mts +10 -0
  138. package/fjs/types/rtti/data/proof.f.mjs +272 -39
  139. package/fjs/types/rtti/data/types.d.ts +14 -7
  140. package/fjs/types/rtti/host.proof.d.mts +33 -0
  141. package/fjs/types/rtti/host.proof.mjs +170 -0
  142. package/fjs/types/rtti/module.f.d.mts +51 -6
  143. package/fjs/types/rtti/module.f.mjs +50 -3
  144. package/fjs/types/rtti/parse/module.f.d.mts +31 -24
  145. package/fjs/types/rtti/parse/module.f.mjs +197 -59
  146. package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
  147. package/fjs/types/rtti/parse/proof.f.mjs +171 -47
  148. package/fjs/types/rtti/proof.f.d.mts +6 -0
  149. package/fjs/types/rtti/proof.f.mjs +42 -1
  150. package/fjs/types/rtti/ts/module.f.d.mts +9 -3
  151. package/fjs/types/rtti/ts/module.f.mjs +68 -15
  152. package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
  153. package/fjs/types/rtti/ts/proof.f.mjs +139 -6
  154. package/fjs/types/rtti/ts/types.d.ts +164 -16
  155. package/fjs/types/rtti/types.d.ts +31 -3
  156. package/fjs/types/rtti/validate/module.f.d.mts +120 -0
  157. package/fjs/types/rtti/validate/module.f.mjs +351 -0
  158. package/fjs/types/rtti/validate/proof.f.d.mts +154 -0
  159. package/fjs/types/rtti/validate/proof.f.mjs +781 -0
  160. package/fjs/types/ts/types.d.ts +4 -0
  161. package/fjs/web/module.f.d.mts +115 -0
  162. package/fjs/web/module.f.mjs +581 -0
  163. package/fjs/web/proof.f.d.mts +39 -0
  164. package/fjs/web/proof.f.mjs +413 -0
  165. package/fjs/web/types.d.ts +39 -0
  166. package/fjs/website/browser-prepare.d.mts +5 -0
  167. package/fjs/website/browser-prepare.mjs +115 -0
  168. package/fjs/website/browser-source.d.mts +37 -0
  169. package/fjs/website/browser-source.mjs +202 -0
  170. package/fjs/website/browser-source.proof.d.mts +31 -0
  171. package/fjs/website/browser-source.proof.mjs +162 -0
  172. package/fjs/website/browser.d.mts +5 -0
  173. package/fjs/website/browser.mjs +8 -0
  174. package/fjs/website/module.f.d.mts +3 -3
  175. package/fjs/website/module.f.mjs +62 -9
  176. package/fjs/website/proof.f.d.mts +4 -0
  177. package/fjs/website/proof.f.mjs +18 -3
  178. package/package.json +2 -3
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Static reading of authored source text: whether a module exports `proof`,
3
+ * and which modules a browser would have to link to load it.
4
+ *
5
+ * The manifest generator classifies modules without importing them — that is
6
+ * the point of reading them as text — and TypeScript 7 exposes no compiler API,
7
+ * so both questions are answered here by reading the source as tokens, with
8
+ * their own proofs, rather than by patterns over lines: whether a declaration
9
+ * fits on one line, and how it is spaced, are not part of the syntax.
10
+ */
11
+
12
+ /**
13
+ * The characters a name is spelled with. A name outside this set — a Unicode
14
+ * identifier, a quoted export name — reads as punctuation and so never matches
15
+ * `proof`, `from` or `import`, the only names looked for here.
16
+ *
17
+ * @type {(char: string) => boolean}
18
+ */
19
+ const nameChar = char =>
20
+ (char >= 'a' && char <= 'z')
21
+ || (char >= 'A' && char <= 'Z')
22
+ || (char >= '0' && char <= '9')
23
+ || char === '_' || char === '$'
24
+
25
+ /** @type {(char: string) => boolean} */
26
+ const space = char => char === ' ' || char === '\t' || char === '\n' || char === '\r'
27
+
28
+ /** @typedef {{ readonly kind: 'name' | 'string' | 'punctuation', readonly text: string }} _Token */
29
+
30
+ /**
31
+ * Separates tokens while they are collected. The scan is a single pass over a
32
+ * whole file, so tokens accumulate as text rather than into a growing array;
33
+ * authored source holds no U+0000 to be mistaken for the separator.
34
+ */
35
+ const separator = '\u0000'
36
+
37
+ /**
38
+ * The source as tokens: names, the text of string literals, and single
39
+ * characters of punctuation. A comment produces nothing, so what is written in
40
+ * one is never read as code. A template produces a backtick and nothing else —
41
+ * it can hold an entire embedded module, and it is never a module specifier.
42
+ *
43
+ * An escape inside a string becomes a space: escapes belong to prose, and a
44
+ * module specifier — the only string this module reads — has none.
45
+ *
46
+ * @type {(source: string) => readonly _Token[]}
47
+ */
48
+ const read = source => {
49
+ let out = ''
50
+ let index = 0
51
+ while (index < source.length) {
52
+ const char = source[index] ?? ''
53
+ const next = source[index + 1]
54
+ if (char === '/' && next === '/') {
55
+ while (index < source.length && source[index] !== '\n') { index += 1 }
56
+ continue
57
+ }
58
+ if (char === '/' && next === '*') {
59
+ index += 2
60
+ while (index < source.length
61
+ && !(source[index] === '*' && source[index + 1] === '/')) { index += 1 }
62
+ index += 2
63
+ continue
64
+ }
65
+ if (char === '\'' || char === '"' || char === '`') {
66
+ index += 1
67
+ let text = ''
68
+ while (index < source.length && source[index] !== char) {
69
+ if (source[index] === '\\') {
70
+ text += ' '
71
+ index += 2
72
+ continue
73
+ }
74
+ text += source[index]
75
+ index += 1
76
+ }
77
+ index += 1
78
+ out += char === '`' ? `${separator}p\`` : `${separator}s${text}`
79
+ continue
80
+ }
81
+ if (nameChar(char)) {
82
+ let text = ''
83
+ while (nameChar(source[index] ?? '')) {
84
+ text += source[index]
85
+ index += 1
86
+ }
87
+ out += `${separator}n${text}`
88
+ continue
89
+ }
90
+ if (!space(char)) { out += `${separator}p${char}` }
91
+ index += 1
92
+ }
93
+ return out.split(separator).slice(1).map(token => ({
94
+ kind: token[0] === 'n' ? 'name' : token[0] === 's' ? 'string' : 'punctuation',
95
+ text: token.slice(1),
96
+ }))
97
+ }
98
+
99
+ /**
100
+ * The tokens as bare words, every string literal standing in as a quote: a
101
+ * declaration is read by its names, and no string can pass for one.
102
+ *
103
+ * @type {(tokens: readonly _Token[]) => readonly string[]}
104
+ */
105
+ const words = tokens => tokens.map(token => token.kind === 'string' ? '\'' : token.text)
106
+
107
+ /** The keywords that can introduce an `export <keyword> proof` declaration. */
108
+ const declarations = ['const', 'let', 'var', 'function', 'class']
109
+
110
+ /**
111
+ * Where the declared name sits relative to its keyword. `function*` declares a
112
+ * generator, so the name follows the star.
113
+ *
114
+ * @type {(list: readonly string[], at: number) => number}
115
+ */
116
+ const declaredName = (list, at) =>
117
+ list[at] === 'function' && list[at + 1] === '*' ? at + 2 : at + 1
118
+
119
+ /**
120
+ * Whether a bracketed group starting at `at` binds `proof`. A named export list
121
+ * and a binding pattern name what they bind the same way — the last name of an
122
+ * entry — so `{ implementation as proof }` and `{ value: proof }` both bind
123
+ * `proof`, while `{ proof as implementation }` and `{ proof: alias }` bind the
124
+ * other name. An unclosed group is incomplete syntax and binds nothing.
125
+ *
126
+ * @type {(list: readonly string[], at: number, close: string) => boolean}
127
+ */
128
+ const groupBinds = (list, at, close) => {
129
+ const end = list.indexOf(close, at + 1)
130
+ if (end === -1) { return false }
131
+ return list.slice(at + 1, end)
132
+ .join(' ')
133
+ .split(',')
134
+ .some(item => {
135
+ const names = item.split(' ').filter(name => name !== '')
136
+ return names[names.length - 1] === 'proof'
137
+ })
138
+ }
139
+
140
+ /**
141
+ * Whether the words following an `export` bind the name `proof`: a declaration
142
+ * — `async`, `function*` and binding patterns included — a namespace re-export,
143
+ * or a named list.
144
+ *
145
+ * @type {(list: readonly string[], at: number) => boolean}
146
+ */
147
+ const bindsProof = (list, at) => {
148
+ const head = list[at]
149
+ if (head === undefined) { return false }
150
+ // `async` modifies the declaration that follows it and binds nothing itself.
151
+ if (head === 'async') { return bindsProof(list, at + 1) }
152
+ if (declarations.includes(head)) {
153
+ // A declaration binds one name, or a pattern of them: the repository
154
+ // exports through one already — `export const { merge, get } = map`.
155
+ const name = declaredName(list, at)
156
+ const bound = list[name]
157
+ if (bound === '{') { return groupBinds(list, name, '}') }
158
+ if (bound === '[') { return groupBinds(list, name, ']') }
159
+ return bound === 'proof'
160
+ }
161
+ if (head === '*') { return list[at + 1] === 'as' && list[at + 2] === 'proof' }
162
+ return head === '{' && groupBinds(list, at, '}')
163
+ }
164
+
165
+ /**
166
+ * Whether `source` exports a binding named `proof`. A mention inside a comment,
167
+ * a string, or a template is not one — the website generator embeds the page's
168
+ * entry module as source text — because none of them reaches the words below.
169
+ *
170
+ * @type {(source: string) => boolean}
171
+ */
172
+ export const exportsProof = source => {
173
+ const list = words(read(source))
174
+ return list.some((word, index) => word === 'export' && bindsProof(list, index + 1))
175
+ }
176
+
177
+ /**
178
+ * Every static module specifier in `source`: the string literal following the
179
+ * `from` of a declaration, or an `import` naming its module directly.
180
+ *
181
+ * A dynamic `import(...)` is left out, and left out structurally — its string
182
+ * follows a `(`, not the keyword. That is the reading the manifest wants: a
183
+ * dynamic import fails inside the test that reaches it rather than while the
184
+ * page links. A `from` written in prose or inside a string is left out for the
185
+ * same reason: neither is a sequence of tokens.
186
+ *
187
+ * @type {(source: string) => readonly string[]}
188
+ */
189
+ export const specifiers = source => {
190
+ const tokens = read(source)
191
+ return tokens.flatMap((token, index) => {
192
+ if (token.kind !== 'string') { return [] }
193
+ const previous = tokens[index - 1]
194
+ return previous?.kind === 'name'
195
+ && (previous.text === 'from' || previous.text === 'import')
196
+ ? [token.text]
197
+ : []
198
+ })
199
+ }
200
+
201
+ /** @type {(specifier: string) => boolean} */
202
+ export const local = specifier => specifier.startsWith('./') || specifier.startsWith('../')
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Proofs for the static source reading behind the browser proof manifest.
3
+ *
4
+ * A misread here is expensive and silent: a module wrongly selected fails the
5
+ * generated page while it links, before the runner can publish a report, and a
6
+ * module wrongly skipped simply never runs in the browser.
7
+ */
8
+ export declare const proof: {
9
+ exportsProof: {
10
+ declarations: () => void;
11
+ modifiers: () => void;
12
+ spacing: () => void;
13
+ patterns: () => void;
14
+ namespace: () => void;
15
+ namedList: () => void;
16
+ otherNames: () => void;
17
+ notCode: () => void;
18
+ unterminated: () => void;
19
+ };
20
+ specifiers: {
21
+ imports: () => void;
22
+ reExports: () => void;
23
+ compactStar: () => void;
24
+ notCode: () => void;
25
+ multiLine: () => void;
26
+ inText: () => void;
27
+ prose: () => void;
28
+ dynamic: () => void;
29
+ };
30
+ local: () => void;
31
+ };
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Proofs for the static source reading behind the browser proof manifest.
3
+ *
4
+ * A misread here is expensive and silent: a module wrongly selected fails the
5
+ * generated page while it links, before the runner can publish a report, and a
6
+ * module wrongly skipped simply never runs in the browser.
7
+ */
8
+
9
+ import { assert, assertStructurallySame } from '../asserts/module.f.mjs'
10
+ import { exportsProof, local, specifiers } from './browser-source.mjs'
11
+
12
+ /** @type {(source: string) => void} */
13
+ const exports = source => assert(exportsProof(source), source)
14
+
15
+ /** @type {(source: string) => void} */
16
+ const doesNot = source => assert(!exportsProof(source), source)
17
+
18
+ export const proof = {
19
+ exportsProof: {
20
+ declarations: () => {
21
+ exports('export const proof = {}')
22
+ exports('export let proof = {}')
23
+ exports('export var proof = {}')
24
+ exports('export function proof() {}')
25
+ exports('export class proof {}')
26
+ },
27
+ modifiers: () => {
28
+ // Both are zero-argument named exports the ordinary runner accepts,
29
+ // so the manifest has to see them too.
30
+ exports('export async function proof() {}')
31
+ exports('export function* proof() {}')
32
+ exports('export async function* proof() {}')
33
+ doesNot('export async function proofs() {}')
34
+ doesNot('export function* proofs() {}')
35
+ },
36
+ spacing: () => {
37
+ // Whitespace between the parts is not part of the syntax.
38
+ exports('export\n const\n proof = {}')
39
+ exports('export{ proof }')
40
+ exports('export {proof} from \'./x.mjs\'')
41
+ },
42
+ patterns: () => {
43
+ // A declaration can bind through a pattern, and the repository
44
+ // already exports through one: `export const { merge, get } = map`.
45
+ exports('export const { proof } = value')
46
+ exports('export const { value: proof } = source')
47
+ exports('export const { a, proof } = source')
48
+ exports('export const [proof] = source')
49
+ doesNot('export const { proof: alias } = source')
50
+ doesNot('export const { other } = source')
51
+ },
52
+ namespace: () => {
53
+ exports('export * as proof from \'./x.mjs\'')
54
+ exports('export *as proof from \'./x.mjs\'')
55
+ doesNot('export * from \'./x.mjs\'')
56
+ },
57
+ namedList: () => {
58
+ // A list entry exports the last name of its `as` chain.
59
+ exports('export { implementation as proof }')
60
+ exports('export { a, b as proof, c }')
61
+ doesNot('export { proof as implementation }')
62
+ doesNot('export { a, b }')
63
+ },
64
+ otherNames: () => {
65
+ // `proof` is a whole name, never a prefix of one.
66
+ doesNot('export const proofs = {}')
67
+ doesNot('export const proofOf = {}')
68
+ doesNot('export { proofs }')
69
+ doesNot('export default proof')
70
+ },
71
+ notCode: () => {
72
+ // The generator itself embeds a module as source text, so a mention
73
+ // inside a comment, a string, or a template is not an export.
74
+ doesNot('// export const proof = {}')
75
+ doesNot('/* export const proof = {} */')
76
+ doesNot('const source = \'export const proof = {}\'')
77
+ doesNot('const source = `export const proof = {}`')
78
+ doesNot('const source = \'export \\\' const proof = {}\'')
79
+ },
80
+ unterminated: () => {
81
+ // Truncated syntax answers `false` rather than running off the end.
82
+ doesNot('export')
83
+ doesNot('export const')
84
+ doesNot('export * as')
85
+ doesNot('export { a as proof')
86
+ },
87
+ },
88
+ specifiers: {
89
+ imports: () => {
90
+ assertStructurallySame(
91
+ [...specifiers('import { a } from \'./x.mjs\'\nimport \'node:fs\'\n')],
92
+ ['./x.mjs', 'node:fs'])
93
+ },
94
+ reExports: () => {
95
+ // Both spacings `exportsProof` accepts are walked for dependencies.
96
+ assertStructurallySame(
97
+ [...specifiers('export { proof } from \'./x.mjs\'\nexport{ proof } from \'./y.mjs\'\n')],
98
+ ['./x.mjs', './y.mjs'])
99
+ },
100
+ compactStar: () => {
101
+ // Every spacing the syntax allows, so a module whose dependencies
102
+ // are written compactly is still walked.
103
+ assertStructurallySame(
104
+ [...specifiers('export*as proof from \'node:fs\'\nimport*as dep from \'package\'\n')],
105
+ ['node:fs', 'package'])
106
+ },
107
+ notCode: () => {
108
+ // An embedded code sample is not a dependency: a proof read this
109
+ // way would be dropped from the suite as unlinkable.
110
+ assertStructurallySame(
111
+ [...specifiers('/*\nimport \'node:fs\'\n*/\n')],
112
+ [])
113
+ assertStructurallySame(
114
+ [...specifiers('const sample = `\nimport \'node:fs\'\n`\n')],
115
+ [])
116
+ },
117
+ multiLine: () => {
118
+ // Line boundaries are not part of the syntax, wherever they fall.
119
+ assertStructurallySame(
120
+ [...specifiers('import {\n a,\n} from \'./x.mjs\'\n')],
121
+ ['./x.mjs'])
122
+ assertStructurallySame(
123
+ [...specifiers('import\n\'node:fs\'\n')],
124
+ ['node:fs'])
125
+ assertStructurallySame(
126
+ [...specifiers('export { a }\nfrom\n\'package\'\n')],
127
+ ['package'])
128
+ },
129
+ inText: () => {
130
+ // A `from` inside prose or a string literal is not a declaration.
131
+ assertStructurallySame(
132
+ [...specifiers('const s = "tells \'empty\' from \'missing\'"\n')],
133
+ [])
134
+ assertStructurallySame(
135
+ [...specifiers('// import \'node:fs\'\n')],
136
+ [])
137
+ // Including when the prose trails a declaration that is real: a
138
+ // module blocked by its own comment would be dropped in silence.
139
+ assertStructurallySame(
140
+ [...specifiers('import \'./ok.mjs\' // import \'node:fs\'\n')],
141
+ ['./ok.mjs'])
142
+ },
143
+ prose: () => {
144
+ // A documentation line quoting names is not an import declaration.
145
+ assertStructurallySame(
146
+ [...specifiers(' * tells `\'empty\'` from `\'missing\'`\n')],
147
+ [])
148
+ },
149
+ dynamic: () => {
150
+ // Only what the browser links eagerly counts.
151
+ assertStructurallySame(
152
+ [...specifiers('const m = await import(\'./x.mjs\')\n')],
153
+ [])
154
+ },
155
+ },
156
+ local: () => {
157
+ assert(local('./x.mjs'))
158
+ assert(local('../x.mjs'))
159
+ assert(!local('node:fs'))
160
+ assert(!local('functionalscript/x.mjs'))
161
+ },
162
+ }
@@ -0,0 +1,5 @@
1
+ /** Browser-realm smoke proof for the generated website. */
2
+ export declare const proof: {
3
+ window: () => void;
4
+ document: () => void;
5
+ };
@@ -0,0 +1,8 @@
1
+ /** Browser-realm smoke proof for the generated website. */
2
+
3
+ import { assert } from '../asserts/module.f.mjs'
4
+
5
+ export const proof = {
6
+ window: () => assert(window.document === document),
7
+ document: () => assert(document.documentElement.localName === 'html'),
8
+ }
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * @module
5
5
  *
6
- * @import { Write, WriteFile } from '../effects/node/types.ts'
6
+ * @import { All, Write, WriteFile } from '../effects/node/types.ts'
7
7
  * @import { Effect } from '../effects/types.ts'
8
8
  */
9
- import type { Write, WriteFile } from '../effects/node/types.ts';
9
+ import type { All, Write, WriteFile } from '../effects/node/types.ts';
10
10
  import type { Effect } from '../effects/types.ts';
11
- export declare const main: () => Effect<Write | WriteFile, 0, number>;
11
+ export declare const main: () => Effect<All | Write | WriteFile, 0, number>;
@@ -3,20 +3,73 @@
3
3
  *
4
4
  * @module
5
5
  *
6
- * @import { Write, WriteFile } from '../effects/node/types.ts'
6
+ * @import { All, Write, WriteFile } from '../effects/node/types.ts'
7
7
  * @import { Effect } from '../effects/types.ts'
8
8
  */
9
9
 
10
10
  import { htmlUtf8 } from '../media/html/module.f.mjs'
11
- import { exitStep, writeFile } from '../effects/node/module.f.mjs'
11
+ import { utf8 } from '../text/module.f.mjs'
12
+ import { allOk, exitStep, writeFile } from '../effects/node/module.f.mjs'
13
+ import { mapStep } from '../effects/module.f.mjs'
12
14
 
13
- const html = htmlUtf8()(
14
- ['a',
15
- { href: 'https://github.com/functionalscript/functionalscript' },
16
- 'GitHub Repository'
17
- ])
15
+ const html = htmlUtf8(
16
+ ['title', 'Emergent Testing in the Browser'],
17
+ ['style', `
18
+ :root { color-scheme: light dark; --bg: white; --text: black; --pass: #137333; --fail: #b3261e }
19
+ @media (prefers-color-scheme: dark) {
20
+ :root { --bg: #121212; --text: #f1f1f1; --pass: #81c995; --fail: #f28b82 }
21
+ }
22
+ body { background-color: var(--bg); color: var(--text); font: 16px system-ui; margin: 3rem auto; max-width: 48rem; padding: 0 1rem }
23
+ [data-state="passed"] [data-test-summary] { color: var(--pass) }
24
+ [data-state="failed"] [data-test-summary], [data-state="infrastructure-error"] [data-test-summary] { color: var(--fail) }
25
+ [data-test-results] { color: var(--text) }
26
+ [data-status="passed"]::marker { color: var(--pass) }
27
+ [data-status="failed"] { color: var(--fail) }
28
+ pre { white-space: pre-wrap }
29
+ `]
30
+ )(
31
+ ['main', { 'data-browser-tests': '', 'data-state': 'loading' },
32
+ ['p', ['a',
33
+ { href: 'https://github.com/functionalscript/functionalscript' },
34
+ 'GitHub Repository'
35
+ ]],
36
+ ['h1', 'Emergent Testing in the Browser'],
37
+ ['p',
38
+ 'FunctionalScript derives this browser-native unit-test suite from exported proofs. ',
39
+ ['a',
40
+ { href: 'https://medium.com/javascript-in-plain-english/emergent-testing-in-javascript-e44760d71688' },
41
+ 'Read “Emergent Testing in JavaScript”'
42
+ ],
43
+ '.'
44
+ ],
45
+ ['p', { 'data-test-summary': '' }, 'Loading…'],
46
+ ['button', { type: 'button', 'data-test-run': '' }, 'Run again'],
47
+ ['pre', ['ol', { 'data-test-results': '' }]]
48
+ ],
49
+ ['script', { type: 'module', src: './_browser-test-entry.mjs' }]
50
+ )
18
51
 
19
- /** @type {Effect<WriteFile | Write, 0, number>} */
20
- const program = exitStep(writeFile('index.html', html))
52
+ const entry = utf8(`import { startBrowserTestSources } from './fjs/emergent_testing/browser.mjs'
53
+ import { browserProofSources } from './fjs/emergent_testing/_browser-suite.mjs'
54
+
55
+ const root = /** @type {Element} */ (document.querySelector('[data-browser-tests]'))
56
+ const sources = [...browserProofSources, './fjs/website/browser.mjs']
57
+ const runButton = /** @type {Element} */ (document.querySelector('[data-test-run]'))
58
+ const start = () => {
59
+ runButton.setAttribute('disabled', '')
60
+ return startBrowserTestSources(root, sources, source => import(source)).then(report => {
61
+ runButton.removeAttribute('disabled')
62
+ return report
63
+ })
64
+ }
65
+ runButton.addEventListener('click', start)
66
+ if (new URL(location.href).searchParams.get('run') !== 'false') { start() }
67
+ `)
68
+
69
+ /** @type {Effect<WriteFile | Write | All, 0, number>} */
70
+ const program = exitStep(mapStep(allOk(
71
+ writeFile('index.html', html),
72
+ writeFile('_browser-test-entry.mjs', entry)
73
+ ), () => undefined))
21
74
 
22
75
  export const main = () => program
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @import { Dir } from '../effects/node/virtual/types.ts'
3
+ * @import { Vec } from '../types/bit_vec/types.ts'
4
+ */
1
5
  export declare const proof: {
2
6
  main: () => void;
3
7
  run: () => void;
@@ -1,15 +1,30 @@
1
+ /**
2
+ * @import { Dir } from '../effects/node/virtual/types.ts'
3
+ * @import { Vec } from '../types/bit_vec/types.ts'
4
+ */
5
+
1
6
  import { exitCode } from '../effects/node/module.f.mjs'
2
7
  import { main } from './module.f.mjs'
3
8
  import { emptyState, virtual } from '../effects/node/virtual/module.f.mjs'
4
- import { assertEq, assertNotNullish } from '../asserts/module.f.mjs'
9
+ import { assert, assertEq, assertNotNullish } from '../asserts/module.f.mjs'
10
+ import { utf8ToString } from '../text/module.f.mjs'
5
11
 
6
12
  export const proof = {
7
13
  main: () => {
8
14
  assertNotNullish(main(), 'expected a program effect')
9
15
  },
10
16
  run: () => {
11
- const state = { ...emptyState, root: { '.github': { workflows: {} } } }
12
- const [_, result] = virtual(state)(main())
17
+ /** @type {Dir} */
18
+ const root = { '.github': { workflows: {} } }
19
+ const state = { ...emptyState, root }
20
+ const [generated, result] = virtual(state)(main())
13
21
  assertEq(exitCode(result), 0)
22
+ const page = assertNotNullish(generated.root['index.html'], 'expected generated HTML')
23
+ assertNotNullish(generated.root['_browser-test-entry.mjs'], 'expected generated entry module')
24
+ assert(Array.isArray(page), 'expected the generated HTML to be a file')
25
+ const source = page.map(value => utf8ToString(/** @type {Vec} */ (value))).join('')
26
+ assert(source.includes('<h1>Emergent Testing in the Browser</h1>'))
27
+ assert(source.includes('emergent-testing-in-javascript-e44760d71688'))
28
+ assert(!source.includes('?sk='))
14
29
  },
15
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -17,8 +17,7 @@
17
17
  "ci-update": "node ./fjs/module.mjs ci && node ./fjs/module.mjs r ./fjs/nanvm/update/module.f.mjs",
18
18
  "dev-update": "node ./fjs/module.mjs r ./fjs/dev/update/module.f.mjs",
19
19
  "update": "npm run ci-update && npm install && deno install && bun install",
20
- "index-html": "node ./fjs/module.mjs r ./fjs/website/module.f.mjs",
21
- "website": "npm run prepack && npm run index-html"
20
+ "website": "node ./fjs/website/browser-prepare.mjs"
22
21
  },
23
22
  "engines": {
24
23
  "node": ">=22"