@inlang/sdk 0.34.8 → 0.34.10

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 (55) hide show
  1. package/dist/adapter/solidAdapter.js +1 -1
  2. package/dist/adapter/solidAdapter.test.js +60 -23
  3. package/dist/api.d.ts +16 -8
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/createMessageLintReportsQuery.d.ts +5 -1
  6. package/dist/createMessageLintReportsQuery.d.ts.map +1 -1
  7. package/dist/createMessageLintReportsQuery.js +165 -62
  8. package/dist/createMessagesQuery.d.ts.map +1 -1
  9. package/dist/createMessagesQuery.js +30 -12
  10. package/dist/createNewProject.d.ts +0 -5
  11. package/dist/createNewProject.d.ts.map +1 -1
  12. package/dist/createNewProject.js +0 -5
  13. package/dist/lint/message/lintSingleMessage.d.ts.map +1 -1
  14. package/dist/lint/message/lintSingleMessage.js +3 -1
  15. package/dist/loadProject.d.ts.map +1 -1
  16. package/dist/loadProject.js +6 -2
  17. package/dist/loadProject.test.js +38 -25
  18. package/dist/persistence/filelock/acquireFileLock.d.ts.map +1 -1
  19. package/dist/persistence/filelock/acquireFileLock.js +2 -2
  20. package/dist/persistence/filelock/releaseLock.js +1 -1
  21. package/dist/reactivity/solid.d.ts +2 -1
  22. package/dist/reactivity/solid.d.ts.map +1 -1
  23. package/dist/reactivity/solid.js +3 -2
  24. package/dist/reactivity/solid.test.js +38 -1
  25. package/dist/v2/createMessageBundle.d.ts +25 -0
  26. package/dist/v2/createMessageBundle.d.ts.map +1 -0
  27. package/dist/v2/createMessageBundle.js +36 -0
  28. package/dist/v2/createMessageBundle.test.d.ts +2 -0
  29. package/dist/v2/createMessageBundle.test.d.ts.map +1 -0
  30. package/dist/v2/createMessageBundle.test.js +92 -0
  31. package/dist/v2/mocks/plural/bundle.d.ts +3 -0
  32. package/dist/v2/mocks/plural/bundle.d.ts.map +1 -0
  33. package/dist/v2/mocks/plural/bundle.js +140 -0
  34. package/dist/v2/mocks/plural/bundle.test.d.ts +2 -0
  35. package/dist/v2/mocks/plural/bundle.test.d.ts.map +1 -0
  36. package/dist/v2/mocks/plural/bundle.test.js +15 -0
  37. package/package.json +3 -3
  38. package/src/adapter/solidAdapter.test.ts +78 -33
  39. package/src/adapter/solidAdapter.ts +1 -1
  40. package/src/api.ts +15 -8
  41. package/src/createMessageLintReportsQuery.ts +190 -67
  42. package/src/createMessagesQuery.ts +33 -12
  43. package/src/createNewProject.ts +0 -5
  44. package/src/createNodeishFsWithWatcher.ts +1 -1
  45. package/src/lint/message/lintSingleMessage.ts +4 -1
  46. package/src/loadProject.test.ts +45 -24
  47. package/src/loadProject.ts +7 -2
  48. package/src/persistence/filelock/acquireFileLock.ts +4 -2
  49. package/src/persistence/filelock/releaseLock.ts +1 -1
  50. package/src/reactivity/solid.test.ts +54 -1
  51. package/src/reactivity/solid.ts +3 -0
  52. package/src/v2/createMessageBundle.test.ts +95 -0
  53. package/src/v2/createMessageBundle.ts +43 -0
  54. package/src/v2/mocks/plural/bundle.test.ts +18 -0
  55. package/src/v2/mocks/plural/bundle.ts +142 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
3
  "type": "module",
4
- "version": "0.34.8",
4
+ "version": "0.34.10",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -33,11 +33,11 @@
33
33
  "murmurhash3js": "^3.0.1",
34
34
  "solid-js": "1.6.12",
35
35
  "throttle-debounce": "^5.0.0",
36
+ "@inlang/message": "2.1.0",
36
37
  "@inlang/json-types": "1.1.0",
37
38
  "@inlang/language-tag": "1.5.1",
38
- "@inlang/message": "2.1.0",
39
- "@inlang/message-lint-rule": "1.4.7",
40
39
  "@inlang/module": "1.2.11",
40
+ "@inlang/message-lint-rule": "1.4.7",
41
41
  "@inlang/plugin": "2.4.11",
42
42
  "@inlang/project-settings": "2.4.2",
43
43
  "@inlang/result": "1.1.0",
@@ -1,13 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
2
  import { describe, it, expect } from "vitest"
3
3
  import type { ImportFunction } from "../resolve-modules/index.js"
4
- import {
5
- createResource,
6
- createSignal,
7
- createEffect,
8
- from,
9
- createRoot,
10
- } from "../reactivity/solid.js"
4
+ import { createEffect, from, createRoot } from "../reactivity/solid.js"
11
5
  import { solidAdapter } from "./solidAdapter.js"
12
6
  import { loadProject } from "../loadProject.js"
13
7
  import { mockRepo } from "@lix-js/client"
@@ -19,10 +13,6 @@ import type {
19
13
  Text,
20
14
  } from "../versionedInterfaces.js"
21
15
 
22
- function sleep(ms: number) {
23
- return new Promise((resolve) => setTimeout(resolve, ms))
24
- }
25
-
26
16
  // ------------------------------------------------------------------------------------------------
27
17
 
28
18
  const config: ProjectSettings = {
@@ -170,8 +160,8 @@ describe("installed", () => {
170
160
  // TODO: how can we await `setConfig` correctly
171
161
  await new Promise((resolve) => setTimeout(resolve, 0))
172
162
 
173
- expect(counterPlugins).toBe(2) // 2 times because effect creation + set
174
- expect(counterLint).toBe(2) // 2 times because effect creation + set
163
+ expect(counterPlugins).toBe(3) // 3 times because effect creation + setSettings, setResolvedModules
164
+ expect(counterLint).toBe(3) // 3 times because effect creation + setSettings, setResolvedModules
175
165
  })
176
166
  })
177
167
 
@@ -219,7 +209,7 @@ describe("messages", () => {
219
209
  { from }
220
210
  )
221
211
 
222
- let effectOnMessagesCounter = 0
212
+ let effectOnMessagesCounter = -1
223
213
  createEffect(() => {
224
214
  project.query.messages.getAll()
225
215
  effectOnMessagesCounter += 1
@@ -232,7 +222,7 @@ describe("messages", () => {
232
222
  // TODO: how can we await `setConfig` correctly
233
223
  await new Promise((resolve) => setTimeout(resolve, 510))
234
224
 
235
- expect(effectOnMessagesCounter).toBe(7) // 7 = initial effect, setSetting, loadMessage (2x - one per message), setResolvedPlugins, loadMessages (2x - one per message)
225
+ expect(effectOnMessagesCounter).toBe(3) // 3 = setSetting, loadMessage (2x - one per message)
236
226
  expect(Object.values(project.query.messages.getAll()).length).toBe(2)
237
227
  })
238
228
 
@@ -363,6 +353,65 @@ describe("messages", () => {
363
353
  })
364
354
 
365
355
  describe("lint", () => {
356
+ it("should react to changes in config", async () => {
357
+ await createRoot(async () => {
358
+ const repo = await mockRepo()
359
+ const fs = repo.nodeishFs
360
+ await fs.mkdir("/user/project.inlang", { recursive: true })
361
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(config))
362
+ const project = solidAdapter(
363
+ await loadProject({
364
+ projectPath: "/user/project.inlang",
365
+ repo,
366
+ _import: $import,
367
+ }),
368
+ { from }
369
+ )
370
+
371
+ // set counter to -1 since creating effect will execute once
372
+ let counter = -1
373
+ createEffect(() => {
374
+ project.query.messageLintReports.getAll()
375
+ counter += 1
376
+ })
377
+
378
+ expect(counter).toBe(0)
379
+
380
+ // wait for all int reports beeing executed
381
+ await new Promise((resolve) => setTimeout(resolve, 510))
382
+
383
+ // 1 because we batch the two lint rules
384
+ expect(counter).toBe(1)
385
+
386
+ const currentSettings = project.settings()!
387
+ const newConfig = { ...currentSettings, languageTags: ["en", "de"] }
388
+ project.setSettings(newConfig)
389
+
390
+ // set settings trigges synchronous -> +1
391
+ expect(counter).toBe(2)
392
+
393
+ await new Promise((resolve) => setTimeout(resolve, 510))
394
+
395
+ // 4 = 2 + batched(1 (effect->settings) and 1 (effect->Resolved Plugin))
396
+ expect(counter).toBe(3)
397
+ expect(project.query.messageLintReports.getAll()).toEqual([])
398
+
399
+ await new Promise((resolve) => setTimeout(resolve, 510))
400
+
401
+ const newConfig2 = { ...project.settings()!, languageTags: ["en", "de", "fr"] }
402
+ project.setSettings(newConfig2)
403
+
404
+ // set settings trigges synchronous -> +1
405
+ expect(counter).toBe(4)
406
+
407
+ await new Promise((resolve) => setTimeout(resolve, 510))
408
+
409
+ // 5 -> 4 + 1 new lint report batch
410
+ expect(counter).toBe(5)
411
+ expect(project.query.messageLintReports.getAll()).toEqual([])
412
+ })
413
+ })
414
+
366
415
  it.todo("should react to changes to packages")
367
416
  it.todo("should react to changes to modules")
368
417
 
@@ -381,23 +430,16 @@ describe("lint", () => {
381
430
  { from }
382
431
  )
383
432
 
384
- const [messages, setMessages] = createSignal<readonly Message[]>()
385
-
386
- let counter = 0
387
-
433
+ let counter = -1 // -1 to start counting after the initial effect
388
434
  createEffect(() => {
389
- setMessages(project.query.messages.getAll())
390
- })
391
-
392
- const [lintReports] = createResource(messages, async () => {
393
- const reports = await project.query.messageLintReports.getAll()
435
+ project.query.messageLintReports.getAll()
394
436
  counter += 1
395
- return reports
396
437
  })
438
+ expect(counter).toBe(0)
397
439
 
398
- await sleep(10)
440
+ await new Promise((resolve) => setTimeout(resolve, 510))
441
+ // 1 -> lint rules are batched - we expect one signal on getAll
399
442
  expect(counter).toBe(1)
400
- expect(lintReports()).toStrictEqual([])
401
443
 
402
444
  project.query.messages.update({
403
445
  where: { id: "a" },
@@ -406,10 +448,11 @@ describe("lint", () => {
406
448
  variants: [{ languageTag: "en", match: [], pattern: [{ type: "Text", value: "new" }] }],
407
449
  },
408
450
  })
451
+ await new Promise((resolve) => setTimeout(resolve, 510))
409
452
 
410
- await sleep(10)
411
- expect(counter).toBe(2)
412
- expect(lintReports()).toStrictEqual([])
453
+ // 1 -> previous two messages + 0 - report results are the same - no effect
454
+ expect(counter).toBe(1)
455
+ expect(project.query.messageLintReports.getAll()).toEqual([])
413
456
 
414
457
  project.query.messages.update({
415
458
  where: { id: "a" },
@@ -419,9 +462,11 @@ describe("lint", () => {
419
462
  },
420
463
  })
421
464
 
422
- await sleep(10)
423
- expect(counter).toBe(3)
424
- expect(lintReports()).toStrictEqual([])
465
+ await new Promise((resolve) => setTimeout(resolve, 510))
466
+
467
+ // 2 -> the updated message does not update the lint rules - result is the same
468
+ expect(counter).toBe(1)
469
+ expect(project.query.messageLintReports.getAll()).toEqual([])
425
470
  })
426
471
  })
427
472
  })
@@ -39,7 +39,7 @@ export const solidAdapter = (
39
39
  },
40
40
  messageLintReports: {
41
41
  get: project.query.messageLintReports.get,
42
- getAll: project.query.messageLintReports.getAll,
42
+ getAll: convert(project.query.messageLintReports.getAll),
43
43
  },
44
44
  },
45
45
  } satisfies InlangProjectWithSolidAdapter
package/src/api.ts CHANGED
@@ -65,7 +65,7 @@ export type InlangProject = {
65
65
 
66
66
  /**
67
67
  * WIP template for async V2 crud interfaces
68
- * E.g. `project.messageBundles.get({ id: "..." })`
68
+ * E.g. `await project.messageBundles.get({ id: "..." })`
69
69
  **/
70
70
  interface Query<T> {
71
71
  get: (args: unknown) => Promise<T>
@@ -82,9 +82,9 @@ export type Subscribable<Value> = {
82
82
  }
83
83
 
84
84
  export type MessageQueryDelegate = {
85
- onMessageCreate: (messageId: string, message: Message) => void
86
- onMessageUpdate: (messageId: string, message: Message) => void
87
- onMessageDelete: (messageId: string) => void
85
+ onMessageCreate: (messageId: string, message: Message, messages: Message[]) => void
86
+ onMessageUpdate: (messageId: string, message: Message, messages: Message[]) => void
87
+ onMessageDelete: (messageId: string, messages: Message[]) => void
88
88
  onLoaded: (messages: Message[]) => void
89
89
  onCleanup: () => void
90
90
  }
@@ -109,12 +109,19 @@ export type MessageQueryApi = {
109
109
  update: (args: { where: { id: Message["id"] }; data: Partial<Message> }) => boolean
110
110
  upsert: (args: { where: { id: Message["id"] }; data: Message }) => void
111
111
  delete: (args: { where: { id: Message["id"] } }) => boolean
112
- setDelegate: (delegate: MessageQueryDelegate) => void
112
+ setDelegate: (delegate: MessageQueryDelegate | undefined, callOnLoad: boolean) => void
113
113
  }
114
114
 
115
115
  export type MessageLintReportsQueryApi = {
116
- getAll: () => Promise<MessageLintReport[]>
117
- get: (args: {
116
+ getAll: Subscribable<MessageLintReport[]> & {
117
+ settled: () => Promise<MessageLintReport[]>
118
+ }
119
+ get: ((args: {
118
120
  where: { messageId: MessageLintReport["messageId"] }
119
- }) => Promise<Readonly<MessageLintReport[]>>
121
+ }) => Readonly<MessageLintReport[]>) & {
122
+ subscribe: (
123
+ args: { where: { messageId: MessageLintReport["messageId"] } },
124
+ callback: (MessageLintRules: Readonly<MessageLintReport[]>) => void
125
+ ) => void
126
+ }
120
127
  }
@@ -10,12 +10,19 @@ import type { resolveModules } from "./resolve-modules/index.js"
10
10
  import type { MessageLintReport, Message } from "./versionedInterfaces.js"
11
11
  import { lintSingleMessage } from "./lint/index.js"
12
12
 
13
- function sleep(ms: number) {
14
- return new Promise((resolve) => setTimeout(resolve, ms))
15
- }
13
+ import { ReactiveMap } from "./reactivity/map.js"
14
+ import { createMemo, onCleanup, untrack, batch } from "./reactivity/solid.js"
15
+ import { createSubscribable } from "./loadProject.js"
16
+
17
+ import _debug from "debug"
18
+ const debug = _debug("sdk:lintReports")
16
19
 
17
20
  /**
18
- * Creates a ~~reactive~~ query API for lint reports.
21
+ * Creates a non-reactive async query API for lint reports.
22
+ * e.g. used in editor/.../Listheader.tsx
23
+ *
24
+ * TODO MESDK-50: reactivity should be restored in future
25
+ * See https://github.com/opral/monorepo/pull/2378 for why this design.
19
26
  */
20
27
  export function createMessageLintReportsQuery(
21
28
  messagesQuery: MessageQueryApi,
@@ -23,80 +30,196 @@ export function createMessageLintReportsQuery(
23
30
  installedMessageLintRules: () => Array<InstalledMessageLintRule>,
24
31
  resolvedModules: () => Awaited<ReturnType<typeof resolveModules>> | undefined
25
32
  ): InlangProject["query"]["messageLintReports"] {
26
- const index = new Map<MessageLintReport["messageId"], MessageLintReport[]>()
27
-
28
- const modules = resolvedModules()
29
-
30
- const rulesArray = modules?.messageLintRules
31
- const messageLintRuleLevels = Object.fromEntries(
32
- installedMessageLintRules().map((rule) => [rule.id, rule.level])
33
- )
34
- const settingsObject = () => {
35
- return {
36
- ...settings(),
37
- messageLintRuleLevels,
38
- }
39
- }
33
+ // @ts-expect-error Reactive map seems to have a problem with Type aliases here
34
+ const index = new ReactiveMap<MessageLintReport["messageId"], MessageLintReport[]>()
35
+
36
+ debug("resetting settledReports")
37
+ let settledReports = Promise.resolve()
38
+
39
+ let currentBatchEnd: undefined | (() => Promise<void>) = undefined
40
+
41
+ const updatedReports: { [messageId: string]: MessageLintReport[] } = {}
42
+
43
+ // triggered whenever settings or resolved modules changes
44
+ createMemo(() => {
45
+ // we clear the index independent from the change for
46
+ index.clear()
47
+
48
+ onCleanup(() => {
49
+ messagesQuery.setDelegate(undefined, false)
50
+ })
51
+
52
+ // settings is a signal - effect will be called whenever settings changes
53
+ const _settings = settings()
54
+ if (!_settings) return
40
55
 
41
- const lintMessage = (message: Message, messages: Message[]) => {
42
- if (!rulesArray) {
43
- return
56
+ const _resolvedModules = resolvedModules()
57
+ if (!_resolvedModules) return
58
+
59
+ // resolvedModules is a signal - effect will be called whenever resolvedModules changes
60
+ const rulesArray = _resolvedModules.messageLintRules
61
+
62
+ const messageLintRuleLevels = Object.fromEntries(
63
+ installedMessageLintRules().map((rule) => [rule.id, rule.level])
64
+ )
65
+ const settingsObject = () => {
66
+ return {
67
+ ...settings(),
68
+ messageLintRuleLevels,
69
+ }
44
70
  }
45
71
 
46
- // TODO unhandled promise rejection (as before the refactor) but won't tackle this in this pr
47
- lintSingleMessage({
48
- rules: rulesArray,
49
- settings: settingsObject(),
50
- messages: messages,
51
- message: message,
52
- }).then((report) => {
53
- if (report.errors.length === 0 && index.get(message.id) !== report.data) {
54
- // console.log("lintSingleMessage", messageId, report.data.length)
55
- index.set(message.id, report.data)
72
+ const sheduleLintMessage = (message: Message, messages: Message[]) => {
73
+ debug("shedule Lint for message:", message.id)
74
+
75
+ const updateOutstandingReportsOnLast = async () => {
76
+ if (currentBatchEnd !== updateOutstandingReportsOnLast) {
77
+ debug("skip triggering reactivy", message.id)
78
+ return
79
+ }
80
+ debug("finished queue - trigger reactivity", message.id)
81
+
82
+ batch(() => {
83
+ for (const [id, reports] of Object.entries(updatedReports)) {
84
+ const currentReports = index.get(id)
85
+ // we only update the report if it differs from the known one - to not trigger reactivity
86
+ if (!reportsEqual(currentReports, reports)) {
87
+ debug("lint reports for message: ", id, " now n:", reports.length)
88
+ index.set(id, reports)
89
+ }
90
+ }
91
+ })
56
92
  }
93
+
94
+ currentBatchEnd = updateOutstandingReportsOnLast
95
+
96
+ const scheduledLint = lintSingleMessage({
97
+ rules: rulesArray,
98
+ settings: settingsObject(),
99
+ messages: messages,
100
+ message: message,
101
+ }).then((reportsResult) => {
102
+ if (reportsResult.errors.length === 0) {
103
+ debug("lint reports for message: ", message.id, "n:", reportsResult.data.length)
104
+ updatedReports[message.id] = reportsResult.data
105
+ }
106
+
107
+ return updateOutstandingReportsOnLast()
108
+ })
109
+ settledReports = settledReports.then(() => scheduledLint)
110
+ }
111
+
112
+ // setup delegate of message query
113
+ const messageQueryChangeDelegate: MessageQueryDelegate = {
114
+ onCleanup: () => {
115
+ // NOTE: we could cancel all running lint rules - but results get overritten anyway
116
+ index.clear()
117
+ },
118
+ onLoaded: (messages: Message[]) => {
119
+ // for (const message of messages) {
120
+ // lintMessage(message, messages)
121
+ // }
122
+ debug("sheduluing Lint for all messages - on load")
123
+ batch(() => {
124
+ debug("sheduluing Lint for all messages - subsquencial call?")
125
+ for (const message of messages) {
126
+ // NOTE: this potentually creates thousands of promisses we could create a promise that batches linting
127
+ // NOTE: this produces a lot of signals - we could batch the
128
+ sheduleLintMessage(message, messages)
129
+ }
130
+ })
131
+ },
132
+ onMessageCreate: (messageId: string, message: Message, messages: Message[]) => {
133
+ // NOTE: unhandled promise rejection (as before the refactor) but won't tackle this in this pr
134
+ // TODO MESDK-105 reevaluate all lint's instead of those for the messsage that where created
135
+ debug("shedule Lint for message - onMessageCreate", message.id)
136
+ sheduleLintMessage(message, messages)
137
+ },
138
+ onMessageUpdate: (messageId: string, message: Message, messages: Message[]) => {
139
+ // NOTE: unhandled promise rejection (as before the refactor) but won't tackle this in this pr
140
+ // TODO MESDK-105 reevaluate all lint's instead of those for the messsage that changed
141
+ debug("shedule Lint for message - onMessageUpdate", message.id)
142
+ sheduleLintMessage(message, messages)
143
+ },
144
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO MESDK-105 we gonna need the mesage Property for evaluation
145
+ onMessageDelete: (messageId: string, _messages: Message[]) => {
146
+ index.delete(messageId)
147
+ },
148
+ }
149
+
150
+ untrack(() => {
151
+ messagesQuery.setDelegate(messageQueryChangeDelegate, true)
57
152
  })
153
+ })
154
+
155
+ const get = (args: Parameters<MessageLintReportsQueryApi["get"]>[0]) => {
156
+ debug("get", args.where.messageId)
157
+ return structuredClone(index.get(args.where.messageId))
58
158
  }
59
159
 
60
- const messages = messagesQuery.getAll() as Message[]
61
- // load report for all messages once
62
- for (const message of messages) {
63
- // NOTE: this potentually creates thousands of promisses we could create a promise that batches linting
64
- lintMessage(message, messages)
160
+ const getAll = () => {
161
+ const flatValues = [...index.values()].flat()
162
+ debug("getAll", flatValues.length)
163
+ return structuredClone(flatValues.length === 0 ? [] : flatValues) as MessageLintReport[]
65
164
  }
66
165
 
67
- const messageQueryChangeDelegate: MessageQueryDelegate = {
68
- onCleanup: () => {
69
- // NOTE: we could cancel all running lint rules - but results get overritten anyway
70
- index.clear()
71
- },
72
- onLoaded: (messages: Message[]) => {
73
- for (const message of messages) {
74
- lintMessage(message, messages)
75
- }
76
- },
77
- onMessageCreate: (messageId: string, message: Message) => {
78
- lintMessage(message, messages)
79
- },
80
- onMessageUpdate: (messageId: string, message: Message) => {
81
- lintMessage(message, messages)
82
- },
83
- onMessageDelete: (messageId: string) => {
84
- index.delete(messageId)
85
- },
166
+ return {
167
+ getAll: Object.assign(createSubscribable(getAll), {
168
+ settled: async () => {
169
+ await settledReports
170
+ return getAll()
171
+ },
172
+ }),
173
+ get: Object.assign(get, {
174
+ subscribe: (
175
+ args: Parameters<MessageLintReportsQueryApi["get"]["subscribe"]>[0],
176
+ callback: Parameters<MessageLintReportsQueryApi["get"]["subscribe"]>[1]
177
+ ) => createSubscribable(() => get(args)).subscribe(callback),
178
+ }) as any,
86
179
  }
180
+ }
87
181
 
88
- messagesQuery.setDelegate(messageQueryChangeDelegate)
182
+ function reportsEqual(
183
+ reportsA: MessageLintReport[] | undefined,
184
+ reportsB: MessageLintReport[] | undefined
185
+ ) {
186
+ if (reportsA === undefined && reportsB === undefined) {
187
+ return true
188
+ } else if (reportsA === undefined || reportsB === undefined) {
189
+ return false
190
+ }
89
191
 
90
- return {
91
- getAll: async () => {
92
- await sleep(0) // evaluate on next tick to allow for out-of-order effects
93
- return structuredClone(
94
- [...index.values()].flat().length === 0 ? [] : [...index.values()].flat()
95
- )
96
- },
97
- get: async (args: Parameters<MessageLintReportsQueryApi["get"]>[0]) => {
98
- await sleep(0) // evaluate on next tick to allow for out-of-order effects
99
- return structuredClone(index.get(args.where.messageId) ?? [])
100
- },
192
+ if (reportsA.length !== reportsB.length) {
193
+ return false
194
+ }
195
+
196
+ for (const [i, element] of reportsA.entries()) {
197
+ if (element?.languageTag !== reportsB[i]?.languageTag) {
198
+ return false
199
+ }
200
+
201
+ if (element?.level !== reportsB[i]?.level) {
202
+ return false
203
+ }
204
+
205
+ if (element?.ruleId !== reportsB[i]?.ruleId) {
206
+ return false
207
+ }
208
+
209
+ if (typeof element?.body !== typeof reportsB[i]?.body) {
210
+ return false
211
+ }
212
+
213
+ if (typeof element?.body === "string") {
214
+ if (reportsB[i]?.body !== reportsB[i]?.body) {
215
+ return false
216
+ }
217
+ } else {
218
+ // NOTE: this was the fastest way to check if both bodies are equal - we can optimize that later if needed
219
+ if (JSON.stringify(element?.body) !== JSON.stringify(element?.body)) {
220
+ return false
221
+ }
222
+ }
101
223
  }
224
+ return true
102
225
  }