functionalscript 0.17.0 → 0.19.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 (82) hide show
  1. package/fs/asn.1/module.f.js +7 -8
  2. package/fs/asn.1/test.f.js +11 -12
  3. package/fs/ci/common/module.f.d.ts +4 -5
  4. package/fs/ci/common/module.f.js +4 -4
  5. package/fs/ci/config/module.f.d.ts +5 -5
  6. package/fs/ci/config/module.f.js +5 -5
  7. package/fs/ci/test.f.js +2 -4
  8. package/fs/crypto/sign/module.f.js +3 -3
  9. package/fs/dev/module.f.d.ts +3 -3
  10. package/fs/dev/module.f.js +12 -9
  11. package/fs/dev/tf/module.d.ts +1 -1
  12. package/fs/dev/tf/module.f.d.ts +76 -7
  13. package/fs/dev/tf/module.f.js +166 -87
  14. package/fs/dev/tf/module.js +3 -53
  15. package/fs/dev/tf/scenarios/all.d.ts +1 -0
  16. package/fs/dev/tf/scenarios/all.js +3 -0
  17. package/fs/dev/tf/scenarios/fail.fail.f.d.ts +1 -0
  18. package/fs/dev/tf/scenarios/fail.fail.f.js +1 -0
  19. package/fs/dev/tf/scenarios/return-value.pass.f.d.ts +1 -0
  20. package/fs/dev/tf/scenarios/return-value.pass.f.js +2 -0
  21. package/fs/dev/tf/scenarios/throw.pass.f.d.ts +6 -0
  22. package/fs/dev/tf/scenarios/throw.pass.f.js +3 -0
  23. package/fs/dev/tf/test.f.d.ts +21 -20
  24. package/fs/dev/tf/test.f.js +254 -31
  25. package/fs/djs/module.f.d.ts +2 -2
  26. package/fs/djs/module.f.js +8 -5
  27. package/fs/djs/test.f.js +5 -6
  28. package/fs/djs/tokenizer-new/test.f.js +126 -78
  29. package/fs/djs/transpiler/module.f.js +2 -2
  30. package/fs/djs/transpiler/test.f.js +11 -12
  31. package/fs/fjs/module.f.d.ts +2 -7
  32. package/fs/fjs/module.f.js +16 -22
  33. package/fs/fjs/module.js +2 -2
  34. package/fs/io/module.d.ts +3 -3
  35. package/fs/io/module.f.d.ts +9 -2
  36. package/fs/io/module.f.js +13 -3
  37. package/fs/io/module.js +68 -17
  38. package/fs/path/module.f.d.ts +6 -0
  39. package/fs/path/module.f.js +6 -0
  40. package/fs/path/test.f.d.ts +3 -5
  41. package/fs/path/test.f.js +67 -49
  42. package/fs/sul/id/module.f.js +3 -4
  43. package/fs/sul/level/literal/module.f.js +3 -3
  44. package/fs/text/sgr/module.f.d.ts +9 -1
  45. package/fs/text/sgr/module.f.js +16 -5
  46. package/fs/text/sgr/test.f.js +16 -1
  47. package/fs/types/bit_vec/module.f.d.ts +9 -4
  48. package/fs/types/bit_vec/module.f.js +7 -9
  49. package/fs/types/btree/remove/module.f.d.ts +1 -1
  50. package/fs/types/btree/remove/module.f.js +7 -2
  51. package/fs/types/btree/set/module.f.d.ts +1 -1
  52. package/fs/types/btree/set/module.f.js +7 -2
  53. package/fs/types/btree/types/module.f.d.ts +1 -0
  54. package/fs/types/btree/types/module.f.js +1 -1
  55. package/fs/types/effects/node/module.f.d.ts +30 -17
  56. package/fs/types/effects/node/module.f.js +21 -2
  57. package/fs/types/effects/node/test.f.js +8 -5
  58. package/fs/types/effects/node/virtual/module.f.d.ts +12 -2
  59. package/fs/types/effects/node/virtual/module.f.js +31 -17
  60. package/fs/types/function/compare/module.f.d.ts +12 -0
  61. package/fs/types/function/compare/module.f.js +33 -0
  62. package/fs/types/function/operator/test.f.d.ts +10 -0
  63. package/fs/types/function/operator/test.f.js +81 -0
  64. package/fs/types/nullable/test.f.js +10 -1
  65. package/fs/types/range_map/module.f.js +3 -18
  66. package/fs/types/result/module.f.d.ts +4 -0
  67. package/fs/types/result/module.f.js +4 -0
  68. package/fs/types/result/test.f.d.ts +2 -4
  69. package/fs/types/result/test.f.js +24 -16
  70. package/fs/types/rtti/common/module.f.d.ts +10 -1
  71. package/fs/types/rtti/common/module.f.js +7 -2
  72. package/fs/types/rtti/parse/module.f.js +35 -46
  73. package/fs/types/rtti/validate/module.f.js +9 -12
  74. package/fs/types/sorted_list/module.f.d.ts +1 -2
  75. package/fs/types/sorted_list/module.f.js +8 -21
  76. package/fs/types/sorted_set/module.f.d.ts +1 -3
  77. package/fs/types/ts/test.f.d.ts +18 -0
  78. package/fs/types/ts/test.f.js +111 -0
  79. package/fs/types/uint8array/module.f.js +7 -1
  80. package/fs/types/uint8array/test.f.d.ts +1 -0
  81. package/fs/types/uint8array/test.f.js +5 -1
  82. package/package.json +4 -4
@@ -1,36 +1,259 @@
1
- // A helper whose own `name` is not `'throw'`. The test framework should still
2
- // treat it as a pass-on-throw test when the enclosing key is `throw`.
3
- const helperThrows = () => { throw 'helper failure'; };
4
- export default {
5
- throw: () => {
6
- throw [() => { }, () => { }];
1
+ import { pure } from "../../types/effects/module.f.js";
2
+ import { emptyState } from "../../types/effects/node/virtual/module.f.js";
3
+ import { virtual } from "../../types/effects/node/virtual/module.f.js";
4
+ import { assert, assertEq, todo } from "../module.f.js";
5
+ import { testAll, defaultReporter, fmtPath, fmtTerm, fmtImport, ghEscape, isInteger, isIdentifier, defaultTest, } from "./module.f.js";
6
+ const makeReporter = () => {
7
+ const events = [];
8
+ const reporter = {
9
+ result: (file, path, r) => { events.push(['result', file, [...path], r]); return pure(undefined); },
10
+ summary: (pass, fail, time) => { events.push(['summary', pass, fail, time]); return pure(undefined); },
11
+ test: defaultTest,
12
+ };
13
+ return [reporter, () => events];
14
+ };
15
+ const noopTestContext = { test: todo };
16
+ const options = (initCwd, github = false) => ({
17
+ args: [],
18
+ env: { INIT_CWD: initCwd, ...(github ? { GITHUB_ACTION: 'true' } : {}) },
19
+ std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
20
+ testContext: noopTestContext,
21
+ bunTestContext: noopTestContext,
22
+ playwrightTestContext: noopTestContext,
23
+ engine: 'node',
24
+ });
25
+ const ok0 = () => ({ result: ['ok', undefined], duration: 0 });
26
+ const fail0 = () => ({ result: ['error', 'oops'], duration: 0 });
27
+ const ok1 = () => ({ result: ['ok', undefined], duration: 1 });
28
+ const run = (dir, initCwd = '.') => {
29
+ const [reporter, getEvents] = makeReporter();
30
+ const state = { ...emptyState, root: dir };
31
+ const [, exitCode] = virtual(state)(testAll(reporter)(options(initCwd)));
32
+ return [getEvents(), exitCode];
33
+ };
34
+ // Runs the real `defaultReporter` and returns its captured stdout/stderr so the
35
+ // terminal and GitHub output formats can be asserted directly.
36
+ const runMain = (dir, github = false) => {
37
+ const state = { ...emptyState, root: dir };
38
+ const opts = options('.', github);
39
+ const [finalState, exitCode] = virtual(state)(testAll(defaultReporter(opts))(opts));
40
+ return [finalState.stdout, finalState.stderr, exitCode];
41
+ };
42
+ // flat object: two passing tests
43
+ export const flat = () => {
44
+ const [events, exit] = run({
45
+ 'a.test.f.ts': () => ({ a: ok0, b: ok1 }),
46
+ });
47
+ assertEq(exit, 0);
48
+ const [e0, e1, e2] = events;
49
+ assert(e0[0] === 'result' && e0[2][0] === 'a');
50
+ assert(e1[0] === 'result' && e1[2][0] === 'b');
51
+ assertEq(e2[0], 'summary');
52
+ const [, pass, fail] = e2;
53
+ assertEq(pass, 2);
54
+ assertEq(fail, 0);
55
+ };
56
+ // nested object: leaf tests carry the full path including the sub-tree key
57
+ export const nested = () => {
58
+ const [events, exit] = run({
59
+ 'n.test.f.ts': () => ({ math: { add: ok0, sub: ok0 } }),
60
+ });
61
+ assertEq(exit, 0);
62
+ const [e0, e1, e2] = events;
63
+ assert(e0[0] === 'result' && e0[2][1] === 'add');
64
+ assert(e1[0] === 'result' && e1[2][1] === 'sub');
65
+ assertEq(e2[0], 'summary');
66
+ const [, pass, fail] = e2;
67
+ assertEq(pass, 2);
68
+ assertEq(fail, 0);
69
+ };
70
+ // throw key: tests inside 'throw' pass on error result
71
+ export const throwKey = () => {
72
+ const [events, exit] = run({
73
+ 't.test.f.ts': () => ({ throw: { a: fail0 } }),
74
+ });
75
+ assertEq(exit, 0);
76
+ const [e0, e1] = events;
77
+ assert(e0[0] === 'result' && e0[2][0] === 'throw' && e0[2][1] === 'a');
78
+ assertEq(e1[0], 'summary');
79
+ const [, pass, fail] = e1;
80
+ assertEq(pass, 1);
81
+ assertEq(fail, 0);
82
+ };
83
+ // throw key fails when test does not throw (returns ok in throw context)
84
+ export const throwKeyFail = () => {
85
+ const [events, exit] = run({
86
+ 't.test.f.ts': () => ({ throw: { a: ok0 } }),
87
+ });
88
+ assertEq(exit, 1);
89
+ const [e0, e1] = events;
90
+ assertEq(e0[0], 'result');
91
+ const [, pass, fail] = e1;
92
+ assertEq(pass, 0);
93
+ assertEq(fail, 1);
94
+ };
95
+ // mixed pass/fail updates summary counts
96
+ export const mixedPassFail = () => {
97
+ const [events, exit] = run({
98
+ 'm.test.f.ts': () => ({ good: ok0, bad: fail0 }),
99
+ });
100
+ assertEq(exit, 1);
101
+ const summary = events[events.length - 1];
102
+ assertEq(summary[0], 'summary');
103
+ const [, pass, fail] = summary;
104
+ assertEq(pass, 1);
105
+ assertEq(fail, 1);
106
+ };
107
+ // return-value sub-tree: passing test's return value is walked
108
+ export const returnValueSubTree = () => {
109
+ const inner = () => ({ result: ['ok', undefined], duration: 0 });
110
+ const [events, exit] = run({
111
+ 'r.test.f.ts': () => ({
112
+ outer: () => ({
113
+ result: ['ok', { inner }],
114
+ duration: 0,
115
+ }),
116
+ }),
117
+ });
118
+ // outer passes, then inner (from return value) also passes
119
+ assertEq(exit, 0);
120
+ const passEvents = events.filter(e => e[0] === 'result');
121
+ assertEq(passEvents.length, 2);
122
+ const [p0, p1] = passEvents;
123
+ assertEq(p0[2][0], 'outer');
124
+ assertEq(p1[2][2], 'inner');
125
+ };
126
+ // integer-indexed array keys appear as numeric path segments
127
+ export const arrayKeys = () => {
128
+ const [events, exit] = run({
129
+ 'a.test.f.ts': () => ({ arr: [ok0, ok0] }),
130
+ });
131
+ assertEq(exit, 0);
132
+ const passEvents = events.filter(e => e[0] === 'result');
133
+ assertEq(passEvents.length, 2);
134
+ assertEq(passEvents[0][2][1], '0');
135
+ assertEq(passEvents[1][2][1], '1');
136
+ };
137
+ // non-test files are skipped (only files ending in test.f.ts/js are loaded)
138
+ export const nonTestFilesSkipped = () => {
139
+ const [events, exit] = run({
140
+ 'helper.ts': () => ({ a: ok0 }),
141
+ 'b.test.f.ts': () => ({ x: ok0 }),
142
+ });
143
+ assertEq(exit, 0);
144
+ const results = events.filter(e => e[0] === 'result');
145
+ assertEq(results.length, 1);
146
+ assertEq(results[0][1], './b.test.f.ts');
147
+ };
148
+ // multiple test files each produce result events
149
+ export const multipleFiles = () => {
150
+ const [events, exit] = run({
151
+ 'a.test.f.ts': () => ({ x: ok0 }),
152
+ 'b.test.f.ts': () => ({ y: ok0 }),
153
+ });
154
+ assertEq(exit, 0);
155
+ const results = events.filter(e => e[0] === 'result');
156
+ assertEq(results.length, 2);
157
+ const [, pass, fail] = events[events.length - 1];
158
+ assertEq(pass, 2);
159
+ assertEq(fail, 0);
160
+ };
161
+ // a function literally named `throw` is a throwing test even when its key is not `throw`
162
+ export const throwByFunctionName = () => {
163
+ // // `bun` calls the function `named` instead of `throw`
164
+ // const named = ({ throw: () => fail0() }).throw
165
+ const x = { throw: () => fail0() };
166
+ const [events, exit] = run({
167
+ 't.test.f.ts': () => ({ here: x.throw }),
168
+ });
169
+ assertEq(exit, 0);
170
+ const passEvents = events.filter(e => e[0] === 'result');
171
+ assertEq(passEvents.length, 1);
172
+ assertEq(passEvents[0][2][0], 'here');
173
+ };
174
+ // every module export — `default` and named — becomes a top-level path segment
175
+ export const namedExports = () => {
176
+ const [events, exit] = run({
177
+ 'e.test.f.ts': () => ({ default: ok0, helper: ok0 }),
178
+ });
179
+ assertEq(exit, 0);
180
+ const passEvents = events.filter(e => e[0] === 'result');
181
+ assertEq(passEvents.length, 2);
182
+ assertEq(passEvents[0][2][0], 'default');
183
+ assertEq(passEvents[1][2][0], 'helper');
184
+ };
185
+ // the default (non-GitHub) reporter formats module/pass/summary lines on stdout
186
+ export const defaultReporterOutput = () => {
187
+ const [stdout, stderr, exit] = runMain({
188
+ 'a.test.f.ts': () => ({ x: ok0 }),
189
+ });
190
+ assertEq(exit, 0);
191
+ assertEq(stderr, '');
192
+ assertEq(stdout, 'import("./a.test.f.ts").x(): ok, 0.0000 ms\n'
193
+ + 'Number of tests: pass: 1, fail: 0, total: 1\n'
194
+ + 'Time: 0.0000 ms\n');
195
+ };
196
+ // a failure on the non-GitHub reporter writes the error to stderr, not stdout
197
+ export const defaultReporterFailOutput = () => {
198
+ const [, stderr, exit] = runMain({
199
+ 'a.test.f.ts': () => ({ bad: fail0 }),
200
+ });
201
+ assertEq(exit, 1);
202
+ assertEq(stderr, 'import("./a.test.f.ts").bad(): error, 0.0000 ms\noops\n');
203
+ };
204
+ // the GitHub reporter emits an `::error` annotation with a percent-encoded
205
+ // title (the JSON path) and message
206
+ export const githubReporterOutput = () => {
207
+ const [, stderr, exit] = runMain({
208
+ 's.test.f.ts': () => ({ 'a:b,c%d': fail0 }),
209
+ }, true);
210
+ assertEq(exit, 1);
211
+ assertEq(stderr, '::error file=./s.test.f.ts,line=1,title=import("./s.test.f.ts")["a%3Ab%2Cc%25d"]()::oops\n');
212
+ };
213
+ // direct unit tests for the pure path-format helpers
214
+ export const helpers = {
215
+ isInteger: () => {
216
+ assert(isInteger('0'));
217
+ assert(isInteger('123'));
218
+ assert(!isInteger(''));
219
+ assert(!isInteger('01'));
220
+ assert(!isInteger('1a'));
221
+ assert(!isInteger('-1'));
7
222
  },
8
- // `throw` key with a function reference (function name is `helperThrows`).
9
- throwReference: {
10
- throw: helperThrows,
223
+ isIdentifier: () => {
224
+ assert(isIdentifier('abc'));
225
+ assert(isIdentifier('_x'));
226
+ assert(isIdentifier('$y'));
227
+ assert(isIdentifier('a1'));
228
+ assert(!isIdentifier(''));
229
+ assert(!isIdentifier('1a'));
230
+ assert(!isIdentifier('a-b'));
11
231
  },
12
- // `throw` key with a group: every leaf function under it must throw to pass.
13
- throwGroup: {
14
- throw: {
15
- inline: () => { throw 'inline failure'; },
16
- reference: helperThrows,
17
- nested: {
18
- deep: () => { throw 'deep failure'; },
19
- },
20
- },
232
+ fmtImport: () => {
233
+ assertEq(fmtImport('./a.test.f.ts', []), 'import("./a.test.f.ts")()');
234
+ assertEq(fmtImport('./a.test.f.ts', ['math', 'add']), 'import("./a.test.f.ts").math.add()');
235
+ assertEq(fmtImport('./a.test.f.ts', ['users', '3']), 'import("./a.test.f.ts").users[3]()');
236
+ assertEq(fmtImport('./a.test.f.ts', ['x', 'hello world']), 'import("./a.test.f.ts").x["hello world"]()');
237
+ assertEq(fmtImport('./a.test.f.ts', ['outer', null, 'inner']), 'import("./a.test.f.ts").outer().inner()');
21
238
  },
22
- };
23
- // Non-default exports are walked as a sibling test group (see issue 27).
24
- export const namedExportTest = () => {
25
- if (2 + 2 !== 4) {
26
- throw 'arithmetic broken';
27
- }
28
- };
29
- export const namedExportGroup = {
30
- nested: () => {
31
- if ('a' + 'b' !== 'ab') {
32
- throw 'string concat broken';
33
- }
239
+ fmtPath: () => {
240
+ assertEq(fmtPath([]), '');
241
+ assertEq(fmtPath(['math', 'add']), '.math.add');
242
+ assertEq(fmtPath(['users', '3', 'name']), '.users[3].name');
243
+ assertEq(fmtPath(['x', 'hello world']), '.x["hello world"]');
244
+ assertEq(fmtPath(['outer', null, 'inner']), '.outer().inner');
245
+ },
246
+ fmtTerm: () => {
247
+ assertEq(fmtTerm([]), '()');
248
+ assertEq(fmtTerm(['math', 'add']), '| | add');
249
+ assertEq(fmtTerm(['a', '0']), '| | 0');
250
+ assertEq(fmtTerm(['x', 'hello world']), '| | "hello world"');
251
+ },
252
+ ghEscape: () => {
253
+ assertEq(ghEscape('a%b'), 'a%25b');
254
+ assertEq(ghEscape('a:b'), 'a%3Ab');
255
+ assertEq(ghEscape('a,b'), 'a%2Cb');
256
+ assertEq(ghEscape('a\r\nb'), 'a%0D%0Ab');
257
+ assertEq(ghEscape('a%b:c,d'), 'a%25b%3Ac%2Cd');
34
258
  },
35
- throw: () => { throw 'expected to throw'; },
36
259
  };
@@ -5,13 +5,13 @@
5
5
  */
6
6
  import type { Primitive as JsonPrimitive } from '../json/module.f.ts';
7
7
  import { type Effect } from '../types/effects/module.f.ts';
8
- import { type Error, type WriteFile, type ReadFile } from '../types/effects/node/module.f.ts';
8
+ import { type WriteFile, type ReadFile, type Write } from '../types/effects/node/module.f.ts';
9
9
  export type Object = {
10
10
  readonly [k in string]: Unknown;
11
11
  };
12
12
  export type Array = readonly Unknown[];
13
13
  export type Primitive = JsonPrimitive | bigint | undefined;
14
14
  export type Unknown = Primitive | Object | Array;
15
- type CompileOp = ReadFile | WriteFile | Error;
15
+ type CompileOp = ReadFile | WriteFile | Write;
16
16
  export declare const compile: (args: readonly string[]) => Effect<CompileOp, number>;
17
17
  export {};
@@ -1,23 +1,26 @@
1
1
  import { transpile } from "./transpiler/module.f.js";
2
2
  import { stringify, stringifyAsTree } from "./serializer/module.f.js";
3
3
  import { sort } from "../types/object/module.f.js";
4
- import { encodeUtf8, toVec } from "../types/uint8array/module.f.js";
5
4
  import { pure } from "../types/effects/module.f.js";
6
- import { error, writeFile, } from "../types/effects/node/module.f.js";
5
+ import { writeFile, error, } from "../types/effects/node/module.f.js";
6
+ import { utf8 } from "../text/module.f.js";
7
7
  export const compile = args => {
8
8
  if (args.length < 2) {
9
- return error('Error: Requires 2 or more arguments').step(() => pure(1));
9
+ return error('Error: Requires 2 or more arguments')
10
+ .step(() => pure(1));
10
11
  }
11
12
  const inputFileName = args[0];
12
13
  const outputFileName = args[1];
13
14
  return transpile(inputFileName).step((result) => {
14
15
  if (result[0] === 'error') {
15
16
  const metadata = result[1].metadata;
16
- return error(`${metadata?.path}:${metadata?.line}:${metadata?.column} - error: ${result[1].message}`).step(() => pure(0));
17
+ return error(`${metadata?.path}:${metadata?.line}:${metadata?.column} - error: ${result[1].message}`)
18
+ .step(() => pure(0));
17
19
  }
18
20
  const content = outputFileName.endsWith('.json')
19
21
  ? stringifyAsTree(sort)(result[1])
20
22
  : stringify(sort)(result[1]);
21
- return writeFile(outputFileName, toVec(encodeUtf8(content))).step(() => pure(0));
23
+ return writeFile(outputFileName, utf8(content))
24
+ .step(() => pure(0));
22
25
  });
23
26
  };
package/fs/djs/test.f.js CHANGED
@@ -1,14 +1,13 @@
1
1
  import { compile } from "./module.f.js";
2
2
  import { virtual, emptyState } from "../types/effects/node/virtual/module.f.js";
3
- import { encodeUtf8, toVec, fromVec, decodeUtf8 } from "../types/uint8array/module.f.js";
4
3
  import { isVec } from "../types/bit_vec/module.f.js";
5
- const fileVec = (s) => toVec(encodeUtf8(s));
4
+ import { utf8, utf8ToString } from "../text/module.f.js";
6
5
  const readOutput = (root, path) => {
7
6
  const file = root[path];
8
7
  if (!isVec(file)) {
9
8
  throw `${path} is not a file`;
10
9
  }
11
- return decodeUtf8(fromVec(file));
10
+ return utf8ToString(file);
12
11
  };
13
12
  export default {
14
13
  tooFewArgs: {
@@ -32,7 +31,7 @@ export default {
32
31
  },
33
32
  },
34
33
  success: () => {
35
- const root = { 'input.f.js': fileVec('export default 42') };
34
+ const root = { 'input.f.js': utf8('export default 42') };
36
35
  const [state, code] = virtual({ ...emptyState, root })(compile(['input.f.js', 'output.f.js']));
37
36
  if (code !== 0) {
38
37
  throw code;
@@ -43,7 +42,7 @@ export default {
43
42
  }
44
43
  },
45
44
  jsonOutput: () => {
46
- const root = { 'input.f.js': fileVec('export default 42') };
45
+ const root = { 'input.f.js': utf8('export default 42') };
47
46
  const [state, code] = virtual({ ...emptyState, root })(compile(['input.f.js', 'output.json']));
48
47
  if (code !== 0) {
49
48
  throw code;
@@ -63,7 +62,7 @@ export default {
63
62
  }
64
63
  },
65
64
  parseError: () => {
66
- const root = { 'bad.f.js': fileVec('export default @') };
65
+ const root = { 'bad.f.js': utf8('export default @') };
67
66
  const [state, code] = virtual({ ...emptyState, root })(compile(['bad.f.js', 'output.f.js']));
68
67
  if (code !== 0) {
69
68
  throw code;