@blumintinc/eslint-plugin-blumint 1.21.5 → 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.5',
227
+ version: '1.21.7',
228
228
  },
229
229
  parseOptions: {
230
230
  ecmaVersion: 2020,
@@ -306,16 +306,122 @@ const isWriteTarget = (node) => {
306
306
  }
307
307
  };
308
308
  /**
309
- * Whether the binding is written through anywhere in the file. Answered from
310
- * the scope manager's reference list rather than a textual search for the
311
- * name, so a same-named binding in another scope (`const arr` shadowed inside a
312
- * callback) contributes nothing, and a same-named method on an unrelated
313
- * receiver (`other.push(1)`) is never even visited.
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.
314
319
  */
315
- const isBindingMutated = (variable) => variable.references.some((reference) => {
316
- const path = accessPathOf(reference.identifier);
317
- return path !== null && (isMutatingMethodCall(path) || isWriteTarget(path));
318
- });
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
+ };
336
+ /**
337
+ * The declarator a reference initializes IN WHOLE — `OTHER` in
338
+ * `const OTHER = ITEMS` — or null for every other position. Such a declaration
339
+ * introduces a second name for one value, so whatever is done to that name is
340
+ * done to this binding.
341
+ *
342
+ * Type wrappers are climbed because they annotate a value without replacing it:
343
+ * `const OTHER = ITEMS!` and `const OTHER = ITEMS satisfies T` denote the same
344
+ * array as the bare form, and each breaks the same way once it is frozen. A
345
+ * cast that erases the element type (`ITEMS as any`) is climbed on the same
346
+ * terms, which withholds the assertion from a mutation the compiler would have
347
+ * tolerated — staying silent is the cheap error here, emitting a fix that stops
348
+ * the file compiling is not.
349
+ *
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.
354
+ */
355
+ const aliasDeclaratorOf = (identifier) => {
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);
370
+ }
371
+ };
372
+ /**
373
+ * Whether the binding is written through anywhere in the file, under its own
374
+ * name or through an alias of it. Answered from the scope manager's reference
375
+ * list rather than a textual search for the name, so a same-named binding in
376
+ * another scope (`const arr` shadowed inside a callback) contributes nothing,
377
+ * and a same-named method on an unrelated receiver (`other.push(1)`) is never
378
+ * even visited.
379
+ *
380
+ * The walk follows aliases because a binding's own reference list is not where
381
+ * a mutation through one is recorded: in
382
+ * `const OTHER = ITEMS; OTHER.push(3);` the mutating call references `OTHER`, a
383
+ * separate variable this one never enrols, and reading only `ITEMS`'s
384
+ * references sees a plain read. Appending `as const` there emits TS2339 for an
385
+ * input that compiled (Issue #2324). Following is transitive — every hop names
386
+ * the one value — and `visited` keeps a chain that leads back on itself, which
387
+ * a redeclared `var` can build, from looping forever.
388
+ *
389
+ * The declaring KEYWORD is deliberately not screened. `as const` types the
390
+ * value `readonly`, and a binding takes its declared type from its initializer,
391
+ * so `let other = ITEMS; other.push(3);` is the same TS2339 as the `const`
392
+ * spelling; reassigning such a `let` does not recover mutability either,
393
+ * because the reassignment is then rejected against that same frozen type. A
394
+ * check keyed on `const` would leave the `let` spelling breaking builds under
395
+ * `--fix`.
396
+ */
397
+ const isBindingMutated = (variable, declaredVariablesOf) => {
398
+ // Grown in place and walked by index: an alias found mid-walk is appended and
399
+ // reached by the same loop, so the traversal needs no recursion of its own.
400
+ const pending = [variable];
401
+ const visited = new Set(pending);
402
+ for (let index = 0; index < pending.length; index += 1) {
403
+ for (const reference of pending[index].references) {
404
+ const path = accessPathOf(reference.identifier);
405
+ if (path !== null) {
406
+ if (isMutatingMethodCall(path) || isWriteTarget(path)) {
407
+ return true;
408
+ }
409
+ continue;
410
+ }
411
+ const declarator = aliasDeclaratorOf(reference.identifier);
412
+ if (!declarator) {
413
+ continue;
414
+ }
415
+ for (const alias of declaredVariablesOf(declarator)) {
416
+ if (!visited.has(alias)) {
417
+ visited.add(alias);
418
+ pending.push(alias);
419
+ }
420
+ }
421
+ }
422
+ }
423
+ return false;
424
+ };
319
425
  /**
320
426
  * Walks the scope chain upward from `scope` (inclusive) and reports whether
321
427
  * `targetName` is bound anywhere between `scope` and `stopScope` (inclusive).
@@ -531,6 +637,14 @@ exports.default = (0, createRule_1.createRule)({
531
637
  return (target.type === utils_1.AST_NODE_TYPES.Identifier &&
532
638
  !PRIMITIVE_VALUE_GLOBALS.has(target.name));
533
639
  };
640
+ /**
641
+ * The bindings a declaration node introduces, as the scope manager records
642
+ * them. The mutation walk resolves an alias declarator through this rather
643
+ * than looking its name up the scope chain: the scope manager already holds
644
+ * the exact answer, while a name lookup would have to guess which scope a
645
+ * `var` was hoisted into.
646
+ */
647
+ const declaredVariablesOf = (node) => context.getDeclaredVariables(node);
534
648
  const describeValueKind = (node) => {
535
649
  const target = unwrapValueWrappers(node);
536
650
  if (target.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
@@ -722,7 +836,8 @@ exports.default = (0, createRule_1.createRule)({
722
836
  const declaredVariable = context
723
837
  .getDeclaredVariables(declaration)
724
838
  .find((variable) => variable.name === name);
725
- return !declaredVariable || !isBindingMutated(declaredVariable);
839
+ return (!declaredVariable ||
840
+ !isBindingMutated(declaredVariable, declaredVariablesOf));
726
841
  };
727
842
  if (shouldHaveAsConst(init)) {
728
843
  context.report({
@@ -1 +1,2 @@
1
- export declare const noTryCatchAlreadyExistsInTransaction: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noAlreadyExistsCatchInTransaction", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noTryCatchAlreadyExistsInTransaction: TSESLint.RuleModule<"noAlreadyExistsCatchInTransaction", [], TSESLint.RuleListener>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noTryCatchAlreadyExistsInTransaction = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
5
6
  const createRule_1 = require("../utils/createRule");
6
7
  const ALREADY_EXISTS_STRINGS = new Set(['already-exists', 'ALREADY_EXISTS']);
7
8
  const ALREADY_EXISTS_NUMBERS = new Set([6, '6']);
@@ -24,6 +25,142 @@ function isRunTransactionCall(node) {
24
25
  }
25
26
  return false;
26
27
  }
28
+ /**
29
+ * The package surfaces whose `runTransaction` is the Firestore one.
30
+ *
31
+ * The bare name is not unique to Firestore: `firebase/database` exports a
32
+ * `runTransaction` for the Realtime Database, which re-applies its update
33
+ * function locally on conflict and carries no gRPC status codes, so
34
+ * `ALREADY_EXISTS` is not part of its error model and neither remedy this rule
35
+ * offers exists there — `runCreateForgivenessTransaction` is backend-Firestore
36
+ * only. Reporting an RTDB transaction leaves a developer with no way to comply.
37
+ */
38
+ const FIRESTORE_MODULE_ROOTS = [
39
+ { packageSegments: ['firebase'], product: 'firestore' },
40
+ { packageSegments: ['firebase-admin'], product: 'firestore' },
41
+ { packageSegments: ['@firebase'], product: 'firestore' },
42
+ { packageSegments: ['@google-cloud'], product: 'firestore' },
43
+ ];
44
+ /**
45
+ * Split a module source into path segments with any version suffix dropped, so
46
+ * a pinned specifier (`firebase@10/firestore`) reduces to the same root as the
47
+ * plain one. A `@` at the start of a segment marks a scope, not a version.
48
+ */
49
+ function moduleSegments(source) {
50
+ return source.split('/').map((segment) => {
51
+ const versionIndex = segment.indexOf('@', 1);
52
+ return versionIndex === -1 ? segment : segment.slice(0, versionIndex);
53
+ });
54
+ }
55
+ /**
56
+ * Match the package root structurally rather than against one spelling: a deep
57
+ * entry point (`firebase/firestore/lite`), a build variant
58
+ * (`@firebase/firestore-compat`) and a pinned version all name the same
59
+ * product, and a trailing segment must not defeat the check.
60
+ */
61
+ function isFirestoreModuleSource(source) {
62
+ const segments = moduleSegments(source);
63
+ return FIRESTORE_MODULE_ROOTS.some(({ packageSegments, product }) => {
64
+ if (!packageSegments.every((segment, index) => segments[index] === segment)) {
65
+ return false;
66
+ }
67
+ const productSegment = segments[packageSegments.length];
68
+ return (productSegment === product || !!productSegment?.startsWith(`${product}-`));
69
+ });
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
+ }
93
+ /**
94
+ * The module `name` is imported from, or null when the file declares the name
95
+ * itself (a local helper, a parameter) or nothing declares it at all.
96
+ */
97
+ function importedSourceOf(scope, name) {
98
+ const variable = ASTHelpers_1.ASTHelpers.findVariableInScope(scope, name);
99
+ if (!variable) {
100
+ return null;
101
+ }
102
+ for (const def of variable.defs) {
103
+ const specifier = def.node;
104
+ if (specifier.type !== utils_1.AST_NODE_TYPES.ImportSpecifier &&
105
+ specifier.type !== utils_1.AST_NODE_TYPES.ImportDefaultSpecifier &&
106
+ specifier.type !== utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
107
+ continue;
108
+ }
109
+ const declaration = specifier.parent;
110
+ if (declaration?.type !== utils_1.AST_NODE_TYPES.ImportDeclaration ||
111
+ typeof declaration.source.value !== 'string') {
112
+ continue;
113
+ }
114
+ return declaration.source.value;
115
+ }
116
+ return null;
117
+ }
118
+ /**
119
+ * The identifier whose binding carries the call's provenance: the callee for
120
+ * `runTransaction(...)`, and the root of the member chain for
121
+ * `database.runTransaction(...)`, since the receiver is what an import names
122
+ * and the property alone matches every `<anything>.runTransaction`.
123
+ */
124
+ function provenanceIdentifier(callee) {
125
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
126
+ return callee;
127
+ }
128
+ let current = callee;
129
+ while (current.type === utils_1.AST_NODE_TYPES.MemberExpression ||
130
+ current.type === utils_1.AST_NODE_TYPES.ChainExpression ||
131
+ current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
132
+ current =
133
+ current.type === utils_1.AST_NODE_TYPES.MemberExpression
134
+ ? current.object
135
+ : current.expression;
136
+ }
137
+ return current.type === utils_1.AST_NODE_TYPES.Identifier ? current : null;
138
+ }
139
+ /**
140
+ * Whether a `runTransaction` call is the Firestore one this rule speaks about.
141
+ *
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.
149
+ */
150
+ function isFirestoreTransactionCall(node, context) {
151
+ if (!isRunTransactionCall(node)) {
152
+ return false;
153
+ }
154
+ const carrier = provenanceIdentifier(unwrapChainExpression(node.callee));
155
+ if (!carrier) {
156
+ return true;
157
+ }
158
+ const source = importedSourceOf(ASTHelpers_1.ASTHelpers.getScope(context, node), carrier.name);
159
+ if (source === null || !isBarePackageSource(source)) {
160
+ return true;
161
+ }
162
+ return isFirestoreModuleSource(source);
163
+ }
27
164
  function getCallbackArgument(args) {
28
165
  for (const arg of args) {
29
166
  if (arg.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
@@ -301,7 +438,7 @@ exports.noTryCatchAlreadyExistsInTransaction = (0, createRule_1.createRule)({
301
438
  }
302
439
  return {
303
440
  CallExpression(node) {
304
- if (!isRunTransactionCall(node)) {
441
+ if (!isFirestoreTransactionCall(node, context)) {
305
442
  return;
306
443
  }
307
444
  const callback = getCallbackArgument(node.arguments);
@@ -310,7 +447,7 @@ exports.noTryCatchAlreadyExistsInTransaction = (0, createRule_1.createRule)({
310
447
  }
311
448
  },
312
449
  'CallExpression:exit'(node) {
313
- if (!isRunTransactionCall(node)) {
450
+ if (!isFirestoreTransactionCall(node, context)) {
314
451
  return;
315
452
  }
316
453
  const callback = getCallbackArgument(node.arguments);
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.preferSxPropOverSystemProps = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
5
6
  const createRule_1 = require("../utils/createRule");
6
7
  /**
7
8
  * Matches Prettier's own default. The autofix rewrites JSX that a formatter
@@ -111,7 +112,12 @@ const MUI_SYSTEM_PROPS = new Set([
111
112
  'textTransform',
112
113
  ]);
113
114
  /**
114
- * Default MUI component names to check. The user can extend this via options.
115
+ * The MUI components this rule covers.
116
+ *
117
+ * The list narrows what provenance has already selected: an element is
118
+ * inspected only when it resolves to an `@mui/*` import AND names one of these,
119
+ * so a name here can never be the whole reason an element is rewritten. The
120
+ * `components` option replaces the list.
115
121
  */
116
122
  const DEFAULT_MUI_COMPONENTS = new Set([
117
123
  'Box',
@@ -229,6 +235,78 @@ function isUpperCase(name) {
229
235
  name[0] === name[0].toUpperCase() &&
230
236
  name[0] !== name[0].toLowerCase());
231
237
  }
238
+ /**
239
+ * The package namespace every MUI distribution publishes under: `@mui/material`,
240
+ * `@mui/joy`, `@mui/system`, `@mui/lab` and their deep entry points
241
+ * (`@mui/material/Box`).
242
+ */
243
+ const MUI_PACKAGE_PREFIX = '@mui/';
244
+ const isMuiSource = (source) => source.startsWith(MUI_PACKAGE_PREFIX);
245
+ /**
246
+ * The import that introduces `name`, or null when the file declares it itself
247
+ * (a local component, a parameter) or nothing declares it at all.
248
+ */
249
+ function importBindingOf(scope, name) {
250
+ const variable = ASTHelpers_1.ASTHelpers.findVariableInScope(scope, name);
251
+ if (!variable) {
252
+ return null;
253
+ }
254
+ for (const def of variable.defs) {
255
+ const specifier = def.node;
256
+ if (specifier.type !== utils_1.AST_NODE_TYPES.ImportSpecifier &&
257
+ specifier.type !== utils_1.AST_NODE_TYPES.ImportDefaultSpecifier &&
258
+ specifier.type !== utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
259
+ continue;
260
+ }
261
+ const declaration = specifier.parent;
262
+ if (declaration?.type !== utils_1.AST_NODE_TYPES.ImportDeclaration ||
263
+ typeof declaration.source.value !== 'string') {
264
+ continue;
265
+ }
266
+ return {
267
+ source: declaration.source.value,
268
+ // A default or namespace import has no exported name to read, so the
269
+ // local name is the only thing that names the component.
270
+ exportedName: specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier
271
+ ? specifier.imported.name
272
+ : name,
273
+ };
274
+ }
275
+ return null;
276
+ }
277
+ /**
278
+ * The MUI export a JSX element names, or null when the element does not come
279
+ * from MUI.
280
+ *
281
+ * Provenance, not spelling, is what makes an element MUI: `Box`, `Button`,
282
+ * `Card` and `Avatar` are ordinary words that design systems, third-party
283
+ * packages and first-party wrappers use too, and this rule ships a fixer that
284
+ * moves props into an `sx` slot a non-MUI component has no reading for. On a
285
+ * wrapper forwarding `width`/`height` to an `<img>`, that rewrite type-checks,
286
+ * lints clean and silently drops the attributes.
287
+ *
288
+ * `<Ns.Box>` resolves through `Ns`, the namespace: the object carries the
289
+ * provenance, so reading the property alone matches every `<Anything.Box>`.
290
+ */
291
+ function muiExportOf(node, scope) {
292
+ const { name } = node;
293
+ if (name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
294
+ const binding = importBindingOf(scope, name.name);
295
+ return binding && isMuiSource(binding.source) ? binding.exportedName : null;
296
+ }
297
+ if (name.type === utils_1.AST_NODE_TYPES.JSXMemberExpression) {
298
+ let object = name.object;
299
+ while (object.type === utils_1.AST_NODE_TYPES.JSXMemberExpression) {
300
+ object = object.object;
301
+ }
302
+ if (object.type !== utils_1.AST_NODE_TYPES.JSXIdentifier) {
303
+ return null;
304
+ }
305
+ const binding = importBindingOf(scope, object.name);
306
+ return binding && isMuiSource(binding.source) ? name.property.name : null;
307
+ }
308
+ return null;
309
+ }
232
310
  /**
233
311
  * Convert a string value to a single-quoted JS string literal.
234
312
  * Used when building sx property values from JSX string attributes.
@@ -1023,9 +1101,14 @@ exports.preferSxPropOverSystemProps = (0, createRule_1.createRule)({
1023
1101
  },
1024
1102
  defaultOptions: [{}],
1025
1103
  create(context, [options]) {
1026
- const componentSet = options.components
1104
+ // Naming a component here is the documented opt-in for a first-party
1105
+ // wrapper that forwards its props to MUI. Such a wrapper is defined by
1106
+ // living outside `@mui/*`, so the names the user lists are honored whatever
1107
+ // introduced them.
1108
+ const explicitComponents = options.components
1027
1109
  ? new Set(options.components)
1028
- : DEFAULT_MUI_COMPONENTS;
1110
+ : null;
1111
+ const componentSet = explicitComponents ?? DEFAULT_MUI_COMPONENTS;
1029
1112
  const extraAllowed = options.allowedProps
1030
1113
  ? new Set(options.allowedProps)
1031
1114
  : new Set();
@@ -1052,14 +1135,31 @@ exports.preferSxPropOverSystemProps = (0, createRule_1.createRule)({
1052
1135
  }
1053
1136
  return MUI_SYSTEM_PROPS.has(name) && !isAllowedProp(name);
1054
1137
  }
1138
+ /**
1139
+ * The MUI component this element is, or null when the rule leaves it alone.
1140
+ * An element qualifies on two counts: it resolves to a component MUI
1141
+ * exports, and that component is one the rule covers.
1142
+ */
1143
+ function targetedComponentOf(node) {
1144
+ const writtenName = getComponentName(node);
1145
+ if (!writtenName || !isUpperCase(writtenName)) {
1146
+ return null;
1147
+ }
1148
+ if (explicitComponents?.has(writtenName)) {
1149
+ return writtenName;
1150
+ }
1151
+ const muiExport = muiExportOf(node, ASTHelpers_1.ASTHelpers.getScope(context, node));
1152
+ if (muiExport === null || !componentSet.has(muiExport)) {
1153
+ return null;
1154
+ }
1155
+ // The export name, not the local one: an aliased `Box as MuiBox` is still
1156
+ // MUI's `Box` for the covered-component and owned-prop lookups.
1157
+ return muiExport;
1158
+ }
1055
1159
  return {
1056
1160
  JSXOpeningElement(node) {
1057
- const componentName = getComponentName(node);
1058
- if (!componentName)
1059
- return;
1060
- if (!isUpperCase(componentName))
1061
- return;
1062
- if (!componentSet.has(componentName))
1161
+ const componentName = targetedComponentOf(node);
1162
+ if (componentName === null)
1063
1163
  return;
1064
1164
  const systemPropAttrs = [];
1065
1165
  let sxAttr = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.21.5",
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,56 @@
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
+ },
24
+ {
25
+ "version": "1.21.6",
26
+ "date": "2026-09-04T20:52:21.747Z",
27
+ "rules": [
28
+ {
29
+ "name": "global-const-style",
30
+ "changeType": "fix",
31
+ "issues": [
32
+ 2324
33
+ ],
34
+ "summary": "follow alias chains when detecting mutation (closes #2324)"
35
+ },
36
+ {
37
+ "name": "no-try-catch-already-exists-in-transaction",
38
+ "changeType": "fix",
39
+ "issues": [
40
+ 2325
41
+ ],
42
+ "summary": "gate on Firestore provenance (closes #2325)"
43
+ },
44
+ {
45
+ "name": "prefer-sx-prop-over-system-props",
46
+ "changeType": "fix",
47
+ "issues": [
48
+ 2323
49
+ ],
50
+ "summary": "gate the rewrite on MUI provenance (closes #2323)"
51
+ }
52
+ ]
53
+ },
2
54
  {
3
55
  "version": "1.21.5",
4
56
  "date": "2026-09-04T15:01:41.191Z",