@blumintinc/eslint-plugin-blumint 1.20.17 → 1.20.18

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.
package/lib/index.js CHANGED
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.17',
226
+ version: '1.20.18',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -191,6 +191,40 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
191
191
  * name (getRefreshToken must NOT match).
192
192
  */
193
193
  const REFETCH_PATTERN = /^(refresh|reload|refetch|revalidate|resync|sync)/i;
194
+ /**
195
+ * Matches navigation callees by their leading verb. A route transition is an
196
+ * ordering barrier rather than a data dependency: the awaits around it are
197
+ * sequenced so their side effects land on the intended page. `await
198
+ * push(url)` followed by `await acceptInvite(...)` is written that way so the
199
+ * accept flow's dialogs mount on the destination page; Promise.all starts the
200
+ * accept flow concurrently with the route transition, so its dialogs open on
201
+ * the source page and are unmounted mid-navigation. The reverse order is
202
+ * equally load-bearing -- parallelizing `await save()` with a following
203
+ * `await push(url)` can navigate away before the save settles -- so a
204
+ * navigation anywhere in the run blocks the whole run. Anchored at the start
205
+ * so it fires on the callee's own verb (pushRoute, navigateTo,
206
+ * redirectToLogin) rather than on an arbitrary substring elsewhere in the
207
+ * name.
208
+ */
209
+ const NAVIGATION_PATTERN = /^(push|replace|navigate|redirect|reroute|goto)/i;
210
+ /**
211
+ * Matches router-like receivers so that every method invoked on one counts
212
+ * as navigation (`router.back()`, `history.go(-1)`, `navigation.reset()`).
213
+ * Keyed on the receiver rather than the method because the remaining history
214
+ * verbs (back, forward, go) are far too generic to match on their own.
215
+ */
216
+ const NAVIGATION_RECEIVER_PATTERN = /^(router|history|navigation|nav)$/i;
217
+ /**
218
+ * Checks whether an awaited call performs a route transition.
219
+ */
220
+ function isNavigationCall(awaitExpr) {
221
+ const receiverName = getCalleeReceiverName(awaitExpr);
222
+ if (receiverName && NAVIGATION_RECEIVER_PATTERN.test(receiverName)) {
223
+ return true;
224
+ }
225
+ const methodName = getCalleeMethodName(awaitExpr);
226
+ return !!methodName && NAVIGATION_PATTERN.test(methodName);
227
+ }
194
228
  /**
195
229
  * Extracts the callee's method name (the identifier bearing the leading
196
230
  * verb) from an await expression argument. Handles both direct
@@ -364,6 +398,22 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
364
398
  return true;
365
399
  }
366
400
  }
401
+ // 6. Navigation ordering barrier. An awaited route transition sequences
402
+ // the awaits around it by UI lifetime rather than by data: the operations
403
+ // before it must settle on the source page, and the operations after it
404
+ // must mount on the destination page. Promise.all runs every operand
405
+ // concurrently, which races both of those against the route change --
406
+ // dialogs opened by a following await appear on the source page and are
407
+ // destroyed when the transition lands. Unlike the guard and refetch
408
+ // barriers, position does not matter: a navigation is a barrier whether it
409
+ // leads or trails the run. Captured results qualify too, since the hazard
410
+ // is the transition itself, not the value it returns.
411
+ for (const node of awaitNodes) {
412
+ const awaitExpr = getAwaitExpression(node);
413
+ if (awaitExpr && isNavigationCall(awaitExpr)) {
414
+ return true;
415
+ }
416
+ }
367
417
  // If any node is a variable declaration with destructuring, consider it as having dependencies
368
418
  for (const node of awaitNodes) {
369
419
  if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
@@ -374,7 +424,7 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
374
424
  }
375
425
  }
376
426
  }
377
- // 6. Shared-receiver ordering barrier. Two awaited calls whose callees are
427
+ // 7. Shared-receiver ordering barrier. Two awaited calls whose callees are
378
428
  // member expressions on the SAME receiver identifier (e.g. `ref.set(x)`
379
429
  // then `ref.get()`) can carry a read-after-write / write-after-write
380
430
  // dependency: the later call may observe or overwrite state the earlier
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.17",
3
+ "version": "1.20.18",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "version": "1.20.18",
4
+ "date": "2026-07-29T17:40:51.418Z",
5
+ "rules": [
6
+ {
7
+ "name": "parallelize-async-operations",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1388
11
+ ],
12
+ "summary": "treat route transitions as an ordering barrier (closes #1388)"
13
+ }
14
+ ]
15
+ },
2
16
  {
3
17
  "version": "1.20.17",
4
18
  "date": "2026-07-29T10:47:56.581Z",