@blumintinc/eslint-plugin-blumint 1.21.6 → 1.21.7

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
@@ -224,7 +224,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
224
224
  module.exports = {
225
225
  meta: {
226
226
  name: '@blumintinc/eslint-plugin-blumint',
227
- version: '1.21.6',
227
+ version: '1.21.7',
228
228
  },
229
229
  parseOptions: {
230
230
  ecmaVersion: 2020,
@@ -305,6 +305,34 @@ const isWriteTarget = (node) => {
305
305
  return false;
306
306
  }
307
307
  };
308
+ /**
309
+ * The composite literal a reference is STORED INTO — the object in
310
+ * `{ items: ITEMS }`, the array in `[ITEMS]` — or null for every other
311
+ * position.
312
+ *
313
+ * Storing a reference does not copy it: the same array stays reachable through
314
+ * the container, so `holder.items.push(3)` writes through to the binding
315
+ * exactly as a direct alias does, and freezing it raises the same TS2339. A
316
+ * `SpreadElement` is excluded because it genuinely builds a fresh value
317
+ * (`const COPY = [...ITEMS]`), and a computed key is excluded because it coerces
318
+ * the reference to a property name rather than retaining it.
319
+ */
320
+ const storageContainerOf = (node) => {
321
+ const parent = node.parent;
322
+ if (!parent) {
323
+ return null;
324
+ }
325
+ if (parent.type === utils_1.AST_NODE_TYPES.Property &&
326
+ parent.value === node &&
327
+ parent.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
328
+ return parent.parent;
329
+ }
330
+ if (parent.type === utils_1.AST_NODE_TYPES.ArrayExpression &&
331
+ parent.elements.some((element) => element === node)) {
332
+ return parent;
333
+ }
334
+ return null;
335
+ };
308
336
  /**
309
337
  * The declarator a reference initializes IN WHOLE — `OTHER` in
310
338
  * `const OTHER = ITEMS` — or null for every other position. Such a declaration
@@ -319,19 +347,27 @@ const isWriteTarget = (node) => {
319
347
  * tolerated — staying silent is the cheap error here, emitting a fix that stops
320
348
  * the file compiling is not.
321
349
  *
322
- * A reference that is only PART of an initializer builds a fresh value rather
323
- * than aliasing this one (`const COPY = [...ITEMS]`), and a destructuring id
324
- * extracts a member rather than the whole, so neither is an alias here.
350
+ * A reference STORED INTO a composite literal is followed through that
351
+ * container, since storing does not copy — see `storageContainerOf`. A
352
+ * destructuring id extracts a member rather than the whole, so it is not an
353
+ * alias here.
325
354
  */
326
355
  const aliasDeclaratorOf = (identifier) => {
327
- const value = outermostValueOf(identifier);
328
- const declarator = value.parent;
329
- if (declarator?.type !== utils_1.AST_NODE_TYPES.VariableDeclarator ||
330
- declarator.init !== value ||
331
- declarator.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
332
- return null;
356
+ // Ascends strictly, so reaching a node with no parent terminates the walk.
357
+ let value = outermostValueOf(identifier);
358
+ for (;;) {
359
+ const declarator = value.parent;
360
+ if (declarator?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
361
+ declarator.init === value &&
362
+ declarator.id.type === utils_1.AST_NODE_TYPES.Identifier) {
363
+ return declarator;
364
+ }
365
+ const container = storageContainerOf(value);
366
+ if (!container) {
367
+ return null;
368
+ }
369
+ value = outermostValueOf(container);
333
370
  }
334
- return declarator;
335
371
  };
336
372
  /**
337
373
  * Whether the binding is written through anywhere in the file, under its own
@@ -68,6 +68,28 @@ function isFirestoreModuleSource(source) {
68
68
  return (productSegment === product || !!productSegment?.startsWith(`${product}-`));
69
69
  });
70
70
  }
71
+ /**
72
+ * First segments that mark a path alias into the project's own tree rather than
73
+ * a published package. A scoped package's first segment is `@scope`, so a bare
74
+ * `@` can only be an alias.
75
+ */
76
+ const FIRST_PARTY_SOURCE_ROOTS = new Set(['@', '~', 'src', 'app', 'lib']);
77
+ /**
78
+ * Whether a specifier names a published package, which is the only thing that
79
+ * can REFUTE Firestore provenance.
80
+ *
81
+ * A relative or absolute path, or a path alias, resolves into first-party code
82
+ * this rule cannot follow, so such a source says nothing about which product
83
+ * the binding came from. Reading its failure to look like `firebase/firestore`
84
+ * as proof of a different product is what silenced the rule on every
85
+ * `import { db } from '../../config/firebaseAdmin'` re-export.
86
+ */
87
+ function isBarePackageSource(source) {
88
+ if (source.startsWith('.') || source.startsWith('/') || source === '') {
89
+ return false;
90
+ }
91
+ return !FIRST_PARTY_SOURCE_ROOTS.has(moduleSegments(source)[0]);
92
+ }
71
93
  /**
72
94
  * The module `name` is imported from, or null when the file declares the name
73
95
  * itself (a local helper, a parameter) or nothing declares it at all.
@@ -117,11 +139,13 @@ function provenanceIdentifier(callee) {
117
139
  /**
118
140
  * Whether a `runTransaction` call is the Firestore one this rule speaks about.
119
141
  *
120
- * The gate speaks only when it knows: a binding that resolves to an import is
121
- * judged by its module source, and anything else — a bare call, a parameter, a
122
- * local helper, a member call on an unresolvable receiver — keeps the rule's
123
- * posture of reporting, since a name with no traceable origin is far more often
124
- * Firestore (`db.runTransaction(...)`) than not.
142
+ * The gate speaks only when it knows: a binding that resolves to an import of a
143
+ * published package is judged by its module source, and anything else — a bare
144
+ * call, a parameter, a local helper, a member call on an unresolvable receiver,
145
+ * an import from the project's own tree — keeps the rule's posture of
146
+ * reporting, since a name with no traceable origin is far more often Firestore
147
+ * (`db.runTransaction(...)`) than not. Only a package specifier can refute
148
+ * Firestore; a first-party path merely fails to confirm it.
125
149
  */
126
150
  function isFirestoreTransactionCall(node, context) {
127
151
  if (!isRunTransactionCall(node)) {
@@ -132,7 +156,7 @@ function isFirestoreTransactionCall(node, context) {
132
156
  return true;
133
157
  }
134
158
  const source = importedSourceOf(ASTHelpers_1.ASTHelpers.getScope(context, node), carrier.name);
135
- if (source === null) {
159
+ if (source === null || !isBarePackageSource(source)) {
136
160
  return true;
137
161
  }
138
162
  return isFirestoreModuleSource(source);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.21.6",
3
+ "version": "1.21.7",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,26 @@
1
1
  [
2
+ {
3
+ "version": "1.21.7",
4
+ "date": "2026-09-04T21:52:00.765Z",
5
+ "rules": [
6
+ {
7
+ "name": "global-const-style",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 2327
11
+ ],
12
+ "summary": "follow the binding into a container (closes #2327)"
13
+ },
14
+ {
15
+ "name": "no-try-catch-already-exists-in-transaction",
16
+ "changeType": "fix",
17
+ "issues": [
18
+ 2326
19
+ ],
20
+ "summary": "report on first-party imports (closes #2326)"
21
+ }
22
+ ]
23
+ },
2
24
  {
3
25
  "version": "1.21.6",
4
26
  "date": "2026-09-04T20:52:21.747Z",