@reventlessdev/reventless-gwt 1.0.0-alpha.75 → 1.0.0-alpha.77

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 (70) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +8 -8
  3. package/src/Behavior_GWT.res +125 -159
  4. package/src/Behavior_GWT.res.mjs +156 -59
  5. package/src/Bind.res.mjs +2 -2
  6. package/src/BuildClassifier.res +15 -5
  7. package/src/BuildClassifier.res.mjs +3 -3
  8. package/src/Cancellation.res +6 -0
  9. package/src/Cancellation.res.mjs +3 -2
  10. package/src/ChildProcess.res +19 -1
  11. package/src/ChildProcess.res.mjs +16 -1
  12. package/src/Cli.res +260 -26
  13. package/src/Cli.res.mjs +227 -29
  14. package/src/Collector.res +13 -2
  15. package/src/Collector.res.mjs +8 -5
  16. package/src/ComponentMeta.res +16 -43
  17. package/src/ComponentMeta.res.mjs +12 -41
  18. package/src/Discovery.res +19 -19
  19. package/src/Discovery.res.mjs +5 -9
  20. package/src/DomainGraph.res +0 -0
  21. package/src/DomainGraph.res.mjs +0 -0
  22. package/src/Filter.res +11 -2
  23. package/src/Filter.res.mjs +10 -3
  24. package/src/FormatterHuman.res +14 -43
  25. package/src/FormatterHuman.res.mjs +21 -28
  26. package/src/FormatterJson.res +12 -5
  27. package/src/FormatterJson.res.mjs +8 -6
  28. package/src/FormatterTap.res +13 -52
  29. package/src/FormatterTap.res.mjs +25 -43
  30. package/src/FormatterVsCode.res +4 -19
  31. package/src/FormatterVsCode.res.mjs +4 -23
  32. package/src/JestBind.res +1 -1
  33. package/src/JestBind.res.mjs +2 -2
  34. package/src/LocalHost.res +11 -27
  35. package/src/LocalHost.res.mjs +6 -34
  36. package/src/MismatchRender.res +72 -0
  37. package/src/MismatchRender.res.mjs +127 -0
  38. package/src/Outcome.res +0 -64
  39. package/src/Outcome.res.mjs +0 -47
  40. package/src/PlatformRunner.res +27 -6
  41. package/src/PlatformRunner.res.mjs +27 -6
  42. package/src/PlatformScan.res +14 -10
  43. package/src/PlatformScan.res.mjs +14 -13
  44. package/src/ProcessManager.res +51 -7
  45. package/src/ProcessManager.res.mjs +43 -4
  46. package/src/RunWorker.res +24 -0
  47. package/src/RunWorker.res.mjs +16 -0
  48. package/src/Watch.res +46 -30
  49. package/src/Watch.res.mjs +34 -32
  50. package/src/WatcherProbe.res +11 -3
  51. package/src/WatcherProbe.res.mjs +15 -2
  52. package/src/Worker.res +24 -0
  53. package/src/Worker.res.mjs +9 -0
  54. package/tests/BuildClassifierTest.res +39 -0
  55. package/tests/BuildClassifierTest.res.mjs +44 -0
  56. package/tests/CliRunEntryTest.res +57 -0
  57. package/tests/CliRunEntryTest.res.mjs +65 -0
  58. package/tests/CliWatchScopeTest.res +62 -0
  59. package/tests/CliWatchScopeTest.res.mjs +95 -0
  60. package/tests/ComponentMetaTest.res +15 -0
  61. package/tests/ComponentMetaTest.res.mjs +14 -0
  62. package/tests/DomainGraphTest.res +2 -2
  63. package/tests/FlowAggregateGwtTest.res +2 -0
  64. package/tests/FlowAggregateGwtTest.res.mjs +4 -0
  65. package/tests/FormatterGoldenTest.res +125 -0
  66. package/tests/FormatterGoldenTest.res.mjs +162 -0
  67. package/tests/MappingGwtTest.res +2 -0
  68. package/tests/MappingGwtTest.res.mjs +4 -0
  69. package/tests/WatchDebounceTest.res +35 -7
  70. package/tests/WatchDebounceTest.res.mjs +37 -6
package/src/Discovery.res CHANGED
@@ -43,36 +43,37 @@ let isGwtTestFile = (name: string) =>
43
43
  String.endsWith(name, "GwtTest.res.mjs") ||
44
44
  String.endsWith(name, "Gwt.res.mjs")
45
45
 
46
- let rec walk = async (dir: string, acc: array<string>): array<string> => {
46
+ // Depth-first, pushing into a shared accumulator instead of rebuilding it with
47
+ // `Array.concat` per entry (which was O(n²) over a large tree). The traversal
48
+ // order is unchanged: files and nested subtree results still land in on-disk
49
+ // entry order.
50
+ let rec walk = async (dir: string, acc: array<string>): unit => {
47
51
  let entries = try {
48
52
  await _readdir(dir, {withFileTypes: true})
49
53
  } catch {
50
54
  | _ => []
51
55
  }
52
56
  if isPruned(entries) {
53
- acc
57
+ ()
54
58
  } else {
55
- let found = ref(acc)
56
- for i in 0 to entries->Array.length - 1 {
57
- let entry = entries->Array.getUnsafe(i)
58
- if !shouldIgnore(entry.name) {
59
- let full = join(dir, entry.name)
60
- if entry._isDirectory() {
61
- let nested = await walk(full, [])
62
- found := Array.concat(found.contents, nested)
63
- } else if entry._isFile() && isGwtTestFile(entry.name) {
64
- found := Array.concat(found.contents, [full])
59
+ for i in 0 to entries->Array.length - 1 {
60
+ let entry = entries->Array.getUnsafe(i)
61
+ if !shouldIgnore(entry.name) {
62
+ let full = join(dir, entry.name)
63
+ if entry._isDirectory() {
64
+ await walk(full, acc)
65
+ } else if entry._isFile() && isGwtTestFile(entry.name) {
66
+ acc->Array.push(full)
67
+ }
65
68
  }
66
69
  }
67
70
  }
68
- found.contents
69
- }
70
71
  }
71
72
 
72
73
  // Returns absolute paths of `*GWT*.res.mjs` test files reachable from the
73
74
  // supplied roots. A root may be a directory or a single file.
74
75
  let discover = async (roots: array<string>): array<string> => {
75
- let found = ref([])
76
+ let found = []
76
77
  for i in 0 to roots->Array.length - 1 {
77
78
  let root = roots->Array.getUnsafe(i)
78
79
  let absolute = isAbsolute(root) ? root : resolve(root)
@@ -83,16 +84,15 @@ let discover = async (roots: array<string>): array<string> => {
83
84
  | _ => false
84
85
  }
85
86
  if isDir {
86
- let collected = await walk(absolute, [])
87
- found := Array.concat(found.contents, collected)
87
+ await walk(absolute, found)
88
88
  } else if isGwtTestFile(absolute) {
89
- found := Array.concat(found.contents, [absolute])
89
+ found->Array.push(absolute)
90
90
  }
91
91
  }
92
92
  // Deduplicate (if roots overlap).
93
93
  let seen = Dict.make()
94
94
  let unique = []
95
- found.contents->Array.forEach(path =>
95
+ found->Array.forEach(path =>
96
96
  switch seen->Dict.get(path) {
97
97
  | Some(_) => ()
98
98
  | None => {
@@ -39,22 +39,19 @@ async function walk(dir, acc) {
39
39
  entries = [];
40
40
  }
41
41
  if (isPruned(entries)) {
42
- return acc;
42
+ return;
43
43
  }
44
- let found = acc;
45
44
  for (let i = 0, i_finish = entries.length; i < i_finish; ++i) {
46
45
  let entry = entries[i];
47
46
  if (!ignoreNames.includes(entry.name)) {
48
47
  let full = Nodepath.join(dir, entry.name);
49
48
  if (entry.isDirectory()) {
50
- let nested = await walk(full, []);
51
- found = found.concat(nested);
49
+ await walk(full, acc);
52
50
  } else if (entry.isFile() && isGwtTestFile(entry.name)) {
53
- found = found.concat([full]);
51
+ acc.push(full);
54
52
  }
55
53
  }
56
54
  }
57
- return found;
58
55
  }
59
56
 
60
57
  async function discover(roots) {
@@ -70,10 +67,9 @@ async function discover(roots) {
70
67
  isDir = false;
71
68
  }
72
69
  if (isDir) {
73
- let collected = await walk(absolute, []);
74
- found = found.concat(collected);
70
+ await walk(absolute, found);
75
71
  } else if (isGwtTestFile(absolute)) {
76
- found = found.concat([absolute]);
72
+ found.push(absolute);
77
73
  }
78
74
  }
79
75
  let seen = {};
Binary file
Binary file
package/src/Filter.res CHANGED
@@ -29,8 +29,17 @@ let xtest = (~slice as _: option<string>=?, name: string, _body: unit => Outcome
29
29
  let xdescribe = (label: string, body: unit => unit) =>
30
30
  if Collector.isActive() {
31
31
  Collector.skipDepth := Collector.skipDepth.contents + 1
32
- Collector.pushDescribe(label, body)
33
- Collector.skipDepth := Collector.skipDepth.contents - 1
32
+ // Decrement on both the normal and the throwing path (ReScript has no
33
+ // `finally`): a body that raises must not leak the incremented depth and
34
+ // skip every following sibling/file.
35
+ try {
36
+ Collector.pushDescribe(label, body)
37
+ Collector.skipDepth := Collector.skipDepth.contents - 1
38
+ } catch {
39
+ | e =>
40
+ Collector.skipDepth := Collector.skipDepth.contents - 1
41
+ throw(e)
42
+ }
34
43
  } else {
35
44
  jestXDescribe(label, body)
36
45
  }
@@ -14,7 +14,7 @@ function only() {
14
14
  function xtest(param, name, _body) {
15
15
  if (Collector$ReventlessGwt.isActive()) {
16
16
  Collector$ReventlessGwt.markSkipNext();
17
- return Collector$ReventlessGwt.push(undefined, undefined, name, () => Promise.resolve(Outcome$ReventlessGwt.pass));
17
+ return Collector$ReventlessGwt.push(undefined, undefined, undefined, name, () => Promise.resolve(Outcome$ReventlessGwt.pass));
18
18
  } else {
19
19
  globalThis.xtest(name, () => {});
20
20
  return;
@@ -24,10 +24,17 @@ function xtest(param, name, _body) {
24
24
  function xdescribe(label, body) {
25
25
  if (Collector$ReventlessGwt.isActive()) {
26
26
  Collector$ReventlessGwt.skipDepth.contents = Collector$ReventlessGwt.skipDepth.contents + 1 | 0;
27
- Collector$ReventlessGwt.pushDescribe(label, body);
28
- Collector$ReventlessGwt.skipDepth.contents = Collector$ReventlessGwt.skipDepth.contents - 1 | 0;
27
+ try {
28
+ Collector$ReventlessGwt.pushDescribe(label, body);
29
+ Collector$ReventlessGwt.skipDepth.contents = Collector$ReventlessGwt.skipDepth.contents - 1 | 0;
30
+ return;
31
+ } catch (e) {
32
+ Collector$ReventlessGwt.skipDepth.contents = Collector$ReventlessGwt.skipDepth.contents - 1 | 0;
33
+ throw e;
34
+ }
29
35
  } else {
30
36
  globalThis.xdescribe(label, body);
37
+ return;
31
38
  }
32
39
  }
33
40
 
@@ -41,51 +41,22 @@ let formatLocation = (loc: option<Collector.location>) =>
41
41
  | Some(l) => `${l.file}:${Int.toString(l.line)}`
42
42
  }
43
43
 
44
- let renderMismatch = (m: Outcome.mismatch) =>
45
- switch m {
46
- | EventsMismatch({expected, actual}) => {
47
- let exp = RenderRescript.renderMany(expected)
48
- let act = RenderRescript.renderMany(actual)
49
- ` expected: ${exp}\n actual: ${act}`
50
- }
51
- | ErrorMismatch({expected, actual, actualEvents}) => {
52
- let exp = `Error(${RenderRescript.render(expected)})`
53
- let act = switch actual {
54
- | Some(v) => `Error(${RenderRescript.render(v)})`
55
- | None => `Ok(${RenderRescript.renderMany(actualEvents)})`
56
- }
57
- ` expected: ${exp}\n actual: ${act}`
58
- }
59
- | StateMismatch({key, expected, actual}) =>
60
- ` key: "${key}"\n expected: ${RenderRescript.renderOption(
61
- expected,
62
- )}\n actual: ${RenderRescript.renderOption(actual)}`
63
- | NoEventExpected({actual}) =>
64
- ` expected no events\n actual: ${RenderRescript.renderMany(actual)}`
65
- | TodoMismatch({expected, actual}) => {
66
- let fmt = (arr: array<(string, JSON.t)>) =>
67
- arr
68
- ->Array.map(((id, v)) => `(${id}, ${RenderRescript.render(v)})`)
69
- ->Array.join(", ")
70
- ` expected: [${fmt(expected)}]\n actual: [${fmt(actual)}]`
71
- }
72
- | AppendConditionMismatch({expected, actual}) =>
73
- ` expected: ${RenderRescript.render(expected)}\n actual: ${RenderRescript.render(
74
- actual,
75
- )}`
76
- | TranslateError({expected, actual}) =>
77
- ` expected: ${expected}\n actual: ${actual->Option.getOr("(none)")}`
78
- | QueryRowsMismatch({expected, actual}) =>
79
- ` expected: ${RenderRescript.renderMany(expected)}\n actual: ${RenderRescript.renderMany(
80
- actual,
81
- )}`
82
- | PublishedActionsMismatch({expected, actual}) =>
83
- ` expected: ${RenderRescript.renderMany(expected)}\n actual: ${RenderRescript.renderMany(
84
- actual,
85
- )}`
86
- | Throw({error, stack}) => ` error: ${error}\n${stack}`
44
+ // Frames one normalized field as a terminal line. Labels are padded so values
45
+ // align at column 12 (`key:`, `expected:`, `actual:`); `error:` is its own
46
+ // shorter form and the `Throw` stack is dumped raw beneath it.
47
+ let renderField = (f: MismatchRender.field) =>
48
+ switch f {
49
+ | Expected(v) => ` expected: ${v}`
50
+ | Actual(v) => ` actual: ${v}`
51
+ | Key(k) => ` key: "${k}"`
52
+ | ExpectedNoEvents => " expected no events"
53
+ | Error(e) => ` error: ${e}`
54
+ | Stack(s) => s
87
55
  }
88
56
 
57
+ let renderMismatch = (m: Outcome.mismatch) =>
58
+ MismatchRender.normalize(m).fields->Array.map(renderField)->Array.join("\n")
59
+
89
60
  let emitTest = (t: RunnerTypes.testResult) => {
90
61
  let path = t.describePath->Array.join(" > ")
91
62
  let full = path == "" ? t.name : `${path} > ${t.name}`
@@ -4,7 +4,7 @@ import * as Nodepath from "node:path";
4
4
  import Picocolors from "picocolors";
5
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
6
  import * as Hint$ReventlessGwt from "./Hint.res.mjs";
7
- import * as RenderRescript$ReventlessGwt from "./RenderRescript.res.mjs";
7
+ import * as MismatchRender$ReventlessGwt from "./MismatchRender.res.mjs";
8
8
 
9
9
  function write(s) {
10
10
  process.stdout.write(s);
@@ -29,36 +29,28 @@ function formatLocation(loc) {
29
29
  }
30
30
  }
31
31
 
32
- function renderMismatch(m) {
33
- switch (m.TAG) {
34
- case "EventsMismatch" :
35
- let exp = RenderRescript$ReventlessGwt.renderMany(m.expected);
36
- let act = RenderRescript$ReventlessGwt.renderMany(m.actual);
37
- return ` expected: ` + exp + `\n actual: ` + act;
38
- case "ErrorMismatch" :
39
- let actual = m.actual;
40
- let exp$1 = `Error(` + RenderRescript$ReventlessGwt.render(undefined, m.expected) + `)`;
41
- let act$1 = actual !== undefined ? `Error(` + RenderRescript$ReventlessGwt.render(undefined, actual) + `)` : `Ok(` + RenderRescript$ReventlessGwt.renderMany(m.actualEvents) + `)`;
42
- return ` expected: ` + exp$1 + `\n actual: ` + act$1;
43
- case "StateMismatch" :
44
- return ` key: "` + m.key + `"\n expected: ` + RenderRescript$ReventlessGwt.renderOption(m.expected) + `\n actual: ` + RenderRescript$ReventlessGwt.renderOption(m.actual);
45
- case "NoEventExpected" :
46
- return ` expected no events\n actual: ` + RenderRescript$ReventlessGwt.renderMany(m.actual);
47
- case "TodoMismatch" :
48
- let fmt = arr => arr.map(param => `(` + param[0] + `, ` + RenderRescript$ReventlessGwt.render(undefined, param[1]) + `)`).join(", ");
49
- return ` expected: [` + fmt(m.expected) + `]\n actual: [` + fmt(m.actual) + `]`;
50
- case "AppendConditionMismatch" :
51
- return ` expected: ` + RenderRescript$ReventlessGwt.render(undefined, m.expected) + `\n actual: ` + RenderRescript$ReventlessGwt.render(undefined, m.actual);
52
- case "TranslateError" :
53
- return ` expected: ` + m.expected + `\n actual: ` + Stdlib_Option.getOr(m.actual, "(none)");
54
- case "QueryRowsMismatch" :
55
- case "PublishedActionsMismatch" :
56
- return ` expected: ` + RenderRescript$ReventlessGwt.renderMany(m.expected) + `\n actual: ` + RenderRescript$ReventlessGwt.renderMany(m.actual);
57
- case "Throw" :
58
- return ` error: ` + m.error + `\n` + m.stack;
32
+ function renderField(f) {
33
+ if (typeof f !== "object") {
34
+ return " expected no events";
35
+ }
36
+ switch (f.TAG) {
37
+ case "Expected" :
38
+ return ` expected: ` + f._0;
39
+ case "Actual" :
40
+ return ` actual: ` + f._0;
41
+ case "Key" :
42
+ return ` key: "` + f._0 + `"`;
43
+ case "Error" :
44
+ return ` error: ` + f._0;
45
+ case "Stack" :
46
+ return f._0;
59
47
  }
60
48
  }
61
49
 
50
+ function renderMismatch(m) {
51
+ return MismatchRender$ReventlessGwt.normalize(m).fields.map(renderField).join("\n");
52
+ }
53
+
62
54
  function emitTest(t) {
63
55
  let path = t.describePath.join(" > ");
64
56
  let full = path === "" ? t.name : path + ` > ` + t.name;
@@ -113,6 +105,7 @@ export {
113
105
  writeLine,
114
106
  formatFilePath,
115
107
  formatLocation,
108
+ renderField,
116
109
  renderMismatch,
117
110
  emitTest,
118
111
  emitFile,
@@ -5,7 +5,10 @@
5
5
 
6
6
  // 1.1.0 — additive: `PublishedActionsMismatch` mismatch kind for the
7
7
  // Delegate_GWT / cross-plugin Flow_GWT boundary steps.
8
- let schemaVersion = "1.1.0"
8
+ // The default the emitted envelope carries; the `--schema-version` CLI flag
9
+ // overrides it (threaded through `emit` / `streamRunStart`) so a consumer can
10
+ // pin the schema version its AI prompt was built against.
11
+ let defaultSchemaVersion = "1.1.0"
9
12
 
10
13
  @val external processStdout: {"write": string => unit} = "process.stdout"
11
14
  let write = (s: string) => processStdout["write"](s)
@@ -184,7 +187,11 @@ let summaryJson = (s: RunnerTypes.summary): JSON.t => {
184
187
  JSON.Encode.object(d)
185
188
  }
186
189
 
187
- let envelope = (r: RunnerTypes.runResult, ~toolVersion: string): JSON.t => {
190
+ let envelope = (
191
+ r: RunnerTypes.runResult,
192
+ ~toolVersion: string,
193
+ ~schemaVersion: string,
194
+ ): JSON.t => {
188
195
  let d = Dict.make()
189
196
  d->Dict.set("schemaVersion", JSON.Encode.string(schemaVersion))
190
197
  d->Dict.set("tool", JSON.Encode.string("reventless-gwt"))
@@ -196,11 +203,11 @@ let envelope = (r: RunnerTypes.runResult, ~toolVersion: string): JSON.t => {
196
203
  JSON.Encode.object(d)
197
204
  }
198
205
 
199
- let emit = (r: RunnerTypes.runResult, ~toolVersion: string) =>
200
- write(JSON.stringify(envelope(r, ~toolVersion), ~space=2) ++ "\n")
206
+ let emit = (r: RunnerTypes.runResult, ~toolVersion: string, ~schemaVersion=defaultSchemaVersion) =>
207
+ write(JSON.stringify(envelope(r, ~toolVersion, ~schemaVersion), ~space=2) ++ "\n")
201
208
 
202
209
  // Streaming variant — NDJSON events.
203
- let streamRunStart = (~toolVersion: string, ~startedAt: string) => {
210
+ let streamRunStart = (~toolVersion: string, ~startedAt: string, ~schemaVersion=defaultSchemaVersion) => {
204
211
  let d = Dict.make()
205
212
  d->Dict.set("type", JSON.Encode.string("runStarted"))
206
213
  d->Dict.set("schemaVersion", JSON.Encode.string(schemaVersion))
@@ -7,7 +7,7 @@ import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
7
7
  import * as RunnerTypes$ReventlessGwt from "./RunnerTypes.res.mjs";
8
8
  import * as RenderRescript$ReventlessGwt from "./RenderRescript.res.mjs";
9
9
 
10
- let schemaVersion = "1.1.0";
10
+ let defaultSchemaVersion = "1.1.0";
11
11
 
12
12
  function write(s) {
13
13
  process.stdout.write(s);
@@ -214,7 +214,7 @@ function summaryJson(s) {
214
214
  return d;
215
215
  }
216
216
 
217
- function envelope(r, toolVersion) {
217
+ function envelope(r, toolVersion, schemaVersion) {
218
218
  let d = {};
219
219
  d["schemaVersion"] = schemaVersion;
220
220
  d["tool"] = "reventless-gwt";
@@ -226,11 +226,13 @@ function envelope(r, toolVersion) {
226
226
  return d;
227
227
  }
228
228
 
229
- function emit(r, toolVersion) {
230
- process.stdout.write(JSON.stringify(envelope(r, toolVersion), undefined, 2) + "\n");
229
+ function emit(r, toolVersion, schemaVersionOpt) {
230
+ let schemaVersion = schemaVersionOpt !== undefined ? schemaVersionOpt : defaultSchemaVersion;
231
+ process.stdout.write(JSON.stringify(envelope(r, toolVersion, schemaVersion), undefined, 2) + "\n");
231
232
  }
232
233
 
233
- function streamRunStart(toolVersion, startedAt) {
234
+ function streamRunStart(toolVersion, startedAt, schemaVersionOpt) {
235
+ let schemaVersion = schemaVersionOpt !== undefined ? schemaVersionOpt : defaultSchemaVersion;
234
236
  let d = {};
235
237
  d["type"] = "runStarted";
236
238
  d["schemaVersion"] = schemaVersion;
@@ -299,7 +301,7 @@ function streamRunEnd(r) {
299
301
  }
300
302
 
301
303
  export {
302
- schemaVersion,
304
+ defaultSchemaVersion,
303
305
  write,
304
306
  writeLine,
305
307
  renderValue,
@@ -18,59 +18,20 @@ let yamlLine = (~indent=2, key: string, value: string) =>
18
18
  String.repeat(" ", indent) ++ key ++ ": " ++ value
19
19
 
20
20
  let renderMismatchYaml = (m: Outcome.mismatch) => {
21
- let lines = []
22
- lines->Array.push(yamlLine("kind", yamlString(Outcome.kindName(m))))
23
- switch m {
24
- | EventsMismatch({expected, actual}) => {
25
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.renderMany(expected))))
26
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderMany(actual))))
21
+ let n = MismatchRender.normalize(m)
22
+ let lines = [yamlLine("kind", yamlString(n.kind))]
23
+ // `ExpectedNoEvents` is a Human-only literal (TAP simply omits `expected`);
24
+ // every other field maps to one yaml `key: "escaped-value"` line.
25
+ n.fields->Array.forEach(f =>
26
+ switch f {
27
+ | Expected(v) => lines->Array.push(yamlLine("expected", yamlString(v)))
28
+ | Actual(v) => lines->Array.push(yamlLine("actual", yamlString(v)))
29
+ | Key(k) => lines->Array.push(yamlLine("key", yamlString(k)))
30
+ | Error(e) => lines->Array.push(yamlLine("error", yamlString(e)))
31
+ | Stack(s) => lines->Array.push(yamlLine("stack", yamlString(s)))
32
+ | ExpectedNoEvents => ()
27
33
  }
28
- | ErrorMismatch({expected, actual, actualEvents}) => {
29
- lines->Array.push(
30
- yamlLine("expected", yamlString("Error(" ++ RenderRescript.render(expected) ++ ")")),
31
- )
32
- let actualStr = switch actual {
33
- | Some(v) => "Error(" ++ RenderRescript.render(v) ++ ")"
34
- | None => "Ok(" ++ RenderRescript.renderMany(actualEvents) ++ ")"
35
- }
36
- lines->Array.push(yamlLine("actual", yamlString(actualStr)))
37
- }
38
- | StateMismatch({key, expected, actual}) => {
39
- lines->Array.push(yamlLine("key", yamlString(key)))
40
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.renderOption(expected))))
41
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderOption(actual))))
42
- }
43
- | NoEventExpected({actual}) =>
44
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderMany(actual))))
45
- | TodoMismatch({expected, actual}) => {
46
- let fmt = arr =>
47
- arr
48
- ->Array.map(((id, v)) => "(" ++ id ++ ", " ++ RenderRescript.render(v) ++ ")")
49
- ->Array.join(", ")
50
- lines->Array.push(yamlLine("expected", yamlString("[" ++ fmt(expected) ++ "]")))
51
- lines->Array.push(yamlLine("actual", yamlString("[" ++ fmt(actual) ++ "]")))
52
- }
53
- | AppendConditionMismatch({expected, actual}) => {
54
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.render(expected))))
55
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.render(actual))))
56
- }
57
- | TranslateError({expected, actual}) => {
58
- lines->Array.push(yamlLine("expected", yamlString(expected)))
59
- lines->Array.push(yamlLine("actual", yamlString(actual->Option.getOr("(none)"))))
60
- }
61
- | QueryRowsMismatch({expected, actual}) => {
62
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.renderMany(expected))))
63
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderMany(actual))))
64
- }
65
- | PublishedActionsMismatch({expected, actual}) => {
66
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.renderMany(expected))))
67
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderMany(actual))))
68
- }
69
- | Throw({error, stack}) => {
70
- lines->Array.push(yamlLine("error", yamlString(error)))
71
- lines->Array.push(yamlLine("stack", yamlString(stack)))
72
- }
73
- }
34
+ )
74
35
  lines->Array.join("\n")
75
36
  }
76
37
 
@@ -2,8 +2,7 @@
2
2
 
3
3
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
4
  import * as Hint$ReventlessGwt from "./Hint.res.mjs";
5
- import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
6
- import * as RenderRescript$ReventlessGwt from "./RenderRescript.res.mjs";
5
+ import * as MismatchRender$ReventlessGwt from "./MismatchRender.res.mjs";
7
6
 
8
7
  function write(s) {
9
8
  process.stdout.write(s);
@@ -27,47 +26,30 @@ function yamlLine(indentOpt, key, value) {
27
26
  }
28
27
 
29
28
  function renderMismatchYaml(m) {
30
- let lines = [];
31
- lines.push(yamlLine(undefined, "kind", yamlString(Outcome$ReventlessGwt.kindName(m))));
32
- switch (m.TAG) {
33
- case "ErrorMismatch" :
34
- let actual = m.actual;
35
- lines.push(yamlLine(undefined, "expected", yamlString("Error(" + RenderRescript$ReventlessGwt.render(undefined, m.expected) + ")")));
36
- let actualStr = actual !== undefined ? "Error(" + RenderRescript$ReventlessGwt.render(undefined, actual) + ")" : "Ok(" + RenderRescript$ReventlessGwt.renderMany(m.actualEvents) + ")";
37
- lines.push(yamlLine(undefined, "actual", yamlString(actualStr)));
38
- break;
39
- case "StateMismatch" :
40
- lines.push(yamlLine(undefined, "key", yamlString(m.key)));
41
- lines.push(yamlLine(undefined, "expected", yamlString(RenderRescript$ReventlessGwt.renderOption(m.expected))));
42
- lines.push(yamlLine(undefined, "actual", yamlString(RenderRescript$ReventlessGwt.renderOption(m.actual))));
43
- break;
44
- case "NoEventExpected" :
45
- lines.push(yamlLine(undefined, "actual", yamlString(RenderRescript$ReventlessGwt.renderMany(m.actual))));
46
- break;
47
- case "TodoMismatch" :
48
- let fmt = arr => arr.map(param => "(" + param[0] + ", " + RenderRescript$ReventlessGwt.render(undefined, param[1]) + ")").join(", ");
49
- lines.push(yamlLine(undefined, "expected", yamlString("[" + fmt(m.expected) + "]")));
50
- lines.push(yamlLine(undefined, "actual", yamlString("[" + fmt(m.actual) + "]")));
51
- break;
52
- case "AppendConditionMismatch" :
53
- lines.push(yamlLine(undefined, "expected", yamlString(RenderRescript$ReventlessGwt.render(undefined, m.expected))));
54
- lines.push(yamlLine(undefined, "actual", yamlString(RenderRescript$ReventlessGwt.render(undefined, m.actual))));
55
- break;
56
- case "TranslateError" :
57
- lines.push(yamlLine(undefined, "expected", yamlString(m.expected)));
58
- lines.push(yamlLine(undefined, "actual", yamlString(Stdlib_Option.getOr(m.actual, "(none)"))));
59
- break;
60
- case "EventsMismatch" :
61
- case "QueryRowsMismatch" :
62
- case "PublishedActionsMismatch" :
63
- lines.push(yamlLine(undefined, "expected", yamlString(RenderRescript$ReventlessGwt.renderMany(m.expected))));
64
- lines.push(yamlLine(undefined, "actual", yamlString(RenderRescript$ReventlessGwt.renderMany(m.actual))));
65
- break;
66
- case "Throw" :
67
- lines.push(yamlLine(undefined, "error", yamlString(m.error)));
68
- lines.push(yamlLine(undefined, "stack", yamlString(m.stack)));
69
- break;
70
- }
29
+ let n = MismatchRender$ReventlessGwt.normalize(m);
30
+ let lines = [yamlLine(undefined, "kind", yamlString(n.kind))];
31
+ n.fields.forEach(f => {
32
+ if (typeof f !== "object") {
33
+ return;
34
+ }
35
+ switch (f.TAG) {
36
+ case "Expected" :
37
+ lines.push(yamlLine(undefined, "expected", yamlString(f._0)));
38
+ return;
39
+ case "Actual" :
40
+ lines.push(yamlLine(undefined, "actual", yamlString(f._0)));
41
+ return;
42
+ case "Key" :
43
+ lines.push(yamlLine(undefined, "key", yamlString(f._0)));
44
+ return;
45
+ case "Error" :
46
+ lines.push(yamlLine(undefined, "error", yamlString(f._0)));
47
+ return;
48
+ case "Stack" :
49
+ lines.push(yamlLine(undefined, "stack", yamlString(f._0)));
50
+ return;
51
+ }
52
+ });
71
53
  return lines.join("\n");
72
54
  }
73
55
 
@@ -45,12 +45,6 @@ let relativeToCwd = (abs: string) => pathRelative(processCwd(), abs)
45
45
 
46
46
  let sourceLineCache: Dict.t<option<array<string>>> = Dict.make()
47
47
 
48
- let resetLocateCache = () => {
49
- sourceLineCache
50
- ->Dict.keysToArray
51
- ->Array.forEach(k => Dict.delete(sourceLineCache, k))
52
- }
53
-
54
48
  let mjsToRes = (mjsPath: string): option<string> =>
55
49
  if String.endsWith(mjsPath, ".res.mjs") {
56
50
  Some(String.slice(mjsPath, ~start=0, ~end=String.length(mjsPath) - 4))
@@ -253,18 +247,9 @@ let deadCode = (findings: array<DomainDeadCode.finding>) =>
253
247
 
254
248
  // Event-Modeling graph (Phase 6) — the node/edge model assembled from each plugin's
255
249
  // `pluginStructure` + cross-plugin edges.
256
- let graph = (g: DomainGraph.graph) =>
257
- emit(
258
- Graph({
259
- nodes: g.nodes->Array.map((n): P.graphNode => {
260
- id: n.id,
261
- kind: n.kind,
262
- label: n.label,
263
- plugin: n.plugin,
264
- }),
265
- edges: g.edges->Array.map((e): P.graphEdge => {from: e.from, to_: e.to, kind: e.kind, label: ?e.label}),
266
- }),
267
- )
250
+ // `DomainGraph` already builds `P.graphNode`/`P.graphEdge`, so the graph event
251
+ // forwards them directly — no field-by-field re-map to drift from the contract.
252
+ let graph = (g: DomainGraph.graph) => emit(Graph({nodes: g.nodes, edges: g.edges}))
268
253
 
269
254
  // Component definitions (Phase 6.3) — one `encodePluginStructureEntry` JSON object
270
255
  // per plugin (commands/events with field schemas, read-side state schemas), used by
@@ -292,7 +277,7 @@ let domainEvent = (payload: JSON.t) => writeLine(JSON.stringify(payload))
292
277
 
293
278
  let platformLog = (~line: string) => emit(PlatformLog({line: line}))
294
279
 
295
- let platformStop = (~code: option<int>) => emit(PlatformStop({code: code}))
280
+ let platformStop = (~code: option<int>) => emit(PlatformStop({code: ?code}))
296
281
 
297
282
  let runStart = (~total: int, ~filter: array<string>) => emit(RunStart({total, filter}))
298
283
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  import * as Nodefs from "node:fs";
4
4
  import * as Nodepath from "node:path";
5
- import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
6
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
6
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
8
7
  import * as Hint$ReventlessGwt from "./Hint.res.mjs";
@@ -43,10 +42,6 @@ function relativeToCwd(abs) {
43
42
 
44
43
  let sourceLineCache = {};
45
44
 
46
- function resetLocateCache() {
47
- Object.keys(sourceLineCache).forEach(k => Stdlib_Dict.$$delete(sourceLineCache, k));
48
- }
49
-
50
45
  function mjsToRes(mjsPath) {
51
46
  if (mjsPath.endsWith(".res.mjs")) {
52
47
  return mjsPath.slice(0, mjsPath.length - 4 | 0);
@@ -282,24 +277,11 @@ function deadCode(findings) {
282
277
  }
283
278
 
284
279
  function graph(g) {
285
- let e_0 = g.nodes.map(n => ({
286
- id: n.id,
287
- kind: n.kind,
288
- label: n.label,
289
- plugin: n.plugin
290
- }));
291
- let e_1 = g.edges.map(e => ({
292
- from: e.from,
293
- to: e.to,
294
- kind: e.kind,
295
- label: e.label
296
- }));
297
- let e = {
280
+ let s = Protocol$ReventlessVscodeProtocol.toJsonLine({
298
281
  event: "graph",
299
- nodes: e_0,
300
- edges: e_1
301
- };
302
- let s = Protocol$ReventlessVscodeProtocol.toJsonLine(e);
282
+ nodes: g.nodes,
283
+ edges: g.edges
284
+ });
303
285
  process.stdout.write(s + "\n");
304
286
  }
305
287
 
@@ -527,7 +509,6 @@ export {
527
509
  fileLabelOf,
528
510
  relativeToCwd,
529
511
  sourceLineCache,
530
- resetLocateCache,
531
512
  mjsToRes,
532
513
  readLinesCached,
533
514
  escapeForDouble,