@blumintinc/eslint-plugin-blumint 1.20.189 → 1.20.190

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.189',
226
+ version: '1.20.190',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -1255,6 +1255,70 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
1255
1255
  return typeInfo;
1256
1256
  }
1257
1257
  const sourceCode = context.getSourceCode();
1258
+ /**
1259
+ * The variable each reference resolves to, keyed by the referencing
1260
+ * identifier NODE rather than by name.
1261
+ *
1262
+ * why: identity keying is what makes the orphan check below immune to
1263
+ * shadowing — a name lookup would resolve an inner `hydrated` against an
1264
+ * outer binding of the same name and read its uses as survivors.
1265
+ */
1266
+ let variableByReference = null;
1267
+ function resolveBinding(identifier) {
1268
+ if (!variableByReference) {
1269
+ variableByReference = new Map();
1270
+ for (const scope of sourceCode.scopeManager?.scopes ?? []) {
1271
+ for (const variable of scope.variables) {
1272
+ for (const reference of variable.references) {
1273
+ variableByReference.set(reference.identifier, variable);
1274
+ }
1275
+ }
1276
+ }
1277
+ }
1278
+ return variableByReference.get(identifier) ?? null;
1279
+ }
1280
+ /**
1281
+ * Whether deleting `element` from the dependency array would leave its
1282
+ * binding with no reader left in the file.
1283
+ *
1284
+ * why: a value declared and then read ONLY inside a dependency array is by
1285
+ * construction load-bearing — the declaration would be pointless otherwise
1286
+ * — so removing the entry both discards a deliberate recompute trigger and
1287
+ * strands the declaration. The consumer runs `no-unused-vars` as an error
1288
+ * and builds with `noUnusedLocals`, so the rewrite turns a green file red
1289
+ * on their machine while staying green here. Every sibling instance of this
1290
+ * class was fixed by deleting the stranded declaration too, but that remedy
1291
+ * is unavailable here: the declaration is a hook CALL, and dropping it
1292
+ * changes the component's hook order. Declining the edit is the only safe
1293
+ * remedy, so the report stands and the autofix steps aside.
1294
+ *
1295
+ * Parameters are deliberately exempt. An unread parameter is not an unused
1296
+ * BINDING to either instrument — `no-unused-vars` runs with `args: 'none'`
1297
+ * and `noUnusedLocals` does not cover parameters — so declining there would
1298
+ * withhold a fix without preventing any breakage, and it would silently
1299
+ * settle the reporting question #1621 defers.
1300
+ */
1301
+ function wouldStrandBinding(element) {
1302
+ const identifier = unwrapExpression(element);
1303
+ if (identifier.type !== utils_1.AST_NODE_TYPES.Identifier)
1304
+ return false;
1305
+ const variable = resolveBinding(identifier);
1306
+ if (!variable || variable.defs.length === 0)
1307
+ return false;
1308
+ if (variable.defs.some((def) => def.type === 'Parameter')) {
1309
+ return false;
1310
+ }
1311
+ const [start, end] = element.range;
1312
+ // why: a declarator's own initializer counts as a WRITE reference, so a
1313
+ // survivor test that accepts any reference never fires for `const x = …`
1314
+ // — the shape this check exists for (#1868 is the same trap).
1315
+ return !variable.references.some((reference) => {
1316
+ if (!reference.isRead())
1317
+ return false;
1318
+ const [from, to] = reference.identifier.range;
1319
+ return from < start || to > end;
1320
+ });
1321
+ }
1258
1322
  // why: scanning every comment once per file rather than once per hook call
1259
1323
  // keeps the check off the hot path of files with many hooks.
1260
1324
  let manuallyManagedLines = null;
@@ -1373,6 +1437,10 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
1373
1437
  const elementIndex = depsArg.elements.indexOf(element);
1374
1438
  if (elementIndex === -1)
1375
1439
  return null;
1440
+ // The report stands either way; only the rewrite is withheld.
1441
+ if (wouldStrandBinding(element)) {
1442
+ return null;
1443
+ }
1376
1444
  // If this is the only element, just remove it
1377
1445
  if (depsArg.elements.length === 1) {
1378
1446
  return fixer.remove(element);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.189",
3
+ "version": "1.20.190",
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.190",
4
+ "date": "2026-08-29T13:41:20.482Z",
5
+ "rules": [
6
+ {
7
+ "name": "no-entire-object-hook-deps",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 2209
11
+ ],
12
+ "summary": "decline a removal that strands its binding (closes #2209)"
13
+ }
14
+ ]
15
+ },
2
16
  {
3
17
  "version": "1.20.189",
4
18
  "date": "2026-08-29T11:57:05.586Z",