@blumintinc/eslint-plugin-blumint 1.20.196 → 1.20.197

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.196',
226
+ version: '1.20.197',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -1310,6 +1310,46 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
1310
1310
  dependencyEntryIdentifiers = entries;
1311
1311
  return entries;
1312
1312
  }
1313
+ /**
1314
+ * Whether a parameter's binding comes out of a DESTRUCTURING pattern - a
1315
+ * destructured prop rather than a positional parameter.
1316
+ *
1317
+ * why: this is the line `noUnusedParameters` draws, measured against the
1318
+ * consumer's own compiler. It reports a destructured property wherever it
1319
+ * sits, including a rest sibling and including an `_`-prefixed one; a
1320
+ * positional parameter it reports only when the name does not start with
1321
+ * `_`. Keying on the pattern rather than on the name is therefore the
1322
+ * accurate test for the destructured case, and `_`-prefixing must NOT be
1323
+ * read as an opt-out there - tsc ignores the name inside a pattern, so
1324
+ * honouring it would readmit the strand on a binding that merely looks
1325
+ * deliberate.
1326
+ *
1327
+ * The AssignmentPattern step is carried but not reachable from the report
1328
+ * path: measured, the rule emits nothing at all for a DEFAULTED
1329
+ * destructured prop (`({ label, revision = 0 })`), so the fixer never gets
1330
+ * to judge one. It stays because dropping it would classify such a prop as
1331
+ * positional the moment that reporting gap is closed, which is the strand
1332
+ * this function exists to prevent - not because a fixture exercises it.
1333
+ */
1334
+ function isDestructuredParameter(name) {
1335
+ if (name.type !== utils_1.AST_NODE_TYPES.Identifier)
1336
+ return false;
1337
+ let node = name.parent;
1338
+ while (node) {
1339
+ if (node.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
1340
+ node.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
1341
+ return true;
1342
+ }
1343
+ if (node.type === utils_1.AST_NODE_TYPES.Property ||
1344
+ node.type === utils_1.AST_NODE_TYPES.RestElement ||
1345
+ node.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
1346
+ node = node.parent;
1347
+ continue;
1348
+ }
1349
+ return false;
1350
+ }
1351
+ return false;
1352
+ }
1313
1353
  /**
1314
1354
  * Whether removing `element`'s binding from every dependency array that
1315
1355
  * lists it would leave the binding with no reader in the file.
@@ -1336,11 +1376,35 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
1336
1376
  * inference is safe in the conservative direction — if a sibling report is
1337
1377
  * suppressed the cost is a withheld fix, never a dangling reference.
1338
1378
  *
1339
- * Parameters are deliberately exempt. An unread parameter is not an unused
1340
- * BINDING to either instrument — `no-unused-vars` runs with `args: 'none'`
1341
- * and `noUnusedLocals` does not cover parameters so declining there would
1342
- * withhold a fix without preventing any breakage, and it would silently
1343
- * settle the reporting question #1621 defers.
1379
+ * A DESTRUCTURED parameter is not exempt; a positional one still is.
1380
+ *
1381
+ * why: the instrument that covers a parameter is `noUnusedParameters`, not
1382
+ * `noUnusedLocals`, and the consumer sets `noUnusedParameters: true` while
1383
+ * setting `noUnusedLocals: false` so a blanket parameter exemption reads
1384
+ * the one flag the consumer has turned OFF and misses the one it has turned
1385
+ * ON. It also cites `no-unused-vars` with `args: 'none'`, which is not the
1386
+ * consumer's setting either. Stranding a destructured prop is therefore a
1387
+ * red build there, from a `tsc --noEmit` gate, and `no-unused-props` cannot
1388
+ * clean up after this fixer because that rule is report-only.
1389
+ *
1390
+ * Nearly every dependency entry in a React component is a destructured
1391
+ * prop, so this is the common case rather than an edge: 21 composed
1392
+ * findings over 13 distinct fixture shapes, every one of them a
1393
+ * destructured prop this fixer stranded (#2236).
1394
+ *
1395
+ * The POSITIONAL parameter stays exempt, and deliberately so. tsc reports
1396
+ * one too, so this IS a residue — but the composed sweep over 23,785
1397
+ * fixtures reached zero of them on its own, and declining there would
1398
+ * settle the reporting question #1621 defers on unmeasured ground while
1399
+ * withholding fixes the corpus shows to be safe, including the #2208
1400
+ * margin-comment arm whose subject is a positional parameter. The residue
1401
+ * is carried deliberately and it is WITNESSED: the control fixture added
1402
+ * with this fix is now the sweep's only surviving stranded parameter, so
1403
+ * the cost of the exemption is visible in that guard's dump rather than
1404
+ * asserted here and forgotten.
1405
+ *
1406
+ * The report stands either way. Only the rewrite is withheld, which is the
1407
+ * conservative direction the rest of this function already takes.
1344
1408
  */
1345
1409
  function wouldStrandBinding(element) {
1346
1410
  const identifier = unwrapExpression(element);
@@ -1349,7 +1413,9 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
1349
1413
  const variable = resolveBinding(identifier);
1350
1414
  if (!variable || variable.defs.length === 0)
1351
1415
  return false;
1352
- if (variable.defs.some((def) => def.type === 'Parameter')) {
1416
+ const parameterDefs = variable.defs.filter((def) => def.type === 'Parameter');
1417
+ if (parameterDefs.length === variable.defs.length &&
1418
+ !parameterDefs.some((def) => isDestructuredParameter(def.name))) {
1353
1419
  return false;
1354
1420
  }
1355
1421
  const entries = collectDependencyEntryIdentifiers();
@@ -256,8 +256,39 @@ export type DiagnosticsFn = (before: string[], after: string[]) => string[];
256
256
  * they run now match `tsconfig.json`.
257
257
  */
258
258
  export declare const introducedDiagnosticsIgnoringUnused: DiagnosticsFn;
259
+ export declare const multisetIntersect: (lists: string[][]) => string[];
260
+ /** The `TS####` prefix a corpus diagnostic is built with in `compileCorpus`. */
261
+ export declare const codeOf: (diagnostic: string) => string;
262
+ export declare const canonicalizeDiagnostic: (diagnostic: string) => string;
263
+ export type DiagnosticIntersection = {
264
+ /** The shared multiset, carrying the FIRST list's original message strings. */
265
+ common: string[];
266
+ /**
267
+ * Everything ANY mode saw that the intersection did not keep - what the
268
+ * oracle silenced. Taken over every list, not just the first: the artifact
269
+ * class this discount exists for is the STRICT-only diagnostic, which never
270
+ * appears in the default mode's list and so is invisible to a counter read
271
+ * off `lists[0]` alone.
272
+ */
273
+ dropped: string[];
274
+ /**
275
+ * The subset of `dropped` that a code-only intersection would have KEPT: the
276
+ * TS code is present in every list with the multiplicity to match, and only
277
+ * the message text diverged. A genuinely mode-specific diagnostic is not in
278
+ * here, so this counter isolates exactly the silent-divergence failure and a
279
+ * guard can assert it to zero.
280
+ *
281
+ * Measured zero across all three consuming guards. If one ever appears, the
282
+ * remedy is to extend `canonicalizeDiagnostic` when it is another print-order
283
+ * divergence, or to record that one shape by name in the guard's own baseline
284
+ * when the two modes genuinely produce different diagnostics under the same
285
+ * TS code. Widening the comparison back toward the code alone is not a
286
+ * remedy: it resumes silencing, which is the defect.
287
+ */
288
+ codeMatchedDrops: string[];
289
+ };
259
290
  /**
260
- * The multiset every list shares.
291
+ * The multiset every list shares, with an account of what it discarded.
261
292
  *
262
293
  * This is the mode discount, and it is the one place the cross-corpus oracles
263
294
  * deliberately differ from `fixer-type-safety`'s. That guard UNIONS the
@@ -286,8 +317,12 @@ export declare const introducedDiagnosticsIgnoringUnused: DiagnosticsFn;
286
317
  * The intersection only bites where both modes could judge. A pair whose input
287
318
  * compiles under one mode only has a single-element intersection, so for it
288
319
  * this is identical to the union.
320
+ *
321
+ * Because dropping is how this oracle produces a clean, every drop is counted
322
+ * rather than discarded in silence, and `codeMatchedDrops` separates "the modes
323
+ * disagree" from "the modes agree and the message merely printed differently".
289
324
  */
290
- export declare const multisetIntersect: (lists: string[][]) => string[];
325
+ export declare const intersectDiagnostics: (lists: string[][]) => DiagnosticIntersection;
291
326
  /**
292
327
  * `ts.createProgram` SILENTLY drops a root file whose name it does not
293
328
  * recognize as TypeScript: `corpus.ts-7` is filtered out with no diagnostic at
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.DECLARES_INTO_SHARED_SCOPE = exports.withSuffix = exports.multisetIntersect = exports.introducedDiagnosticsIgnoringUnused = exports.introducedDiagnostics = exports.isFragmentArtifact = exports.isUnusedDeclaration = exports.missingNameOf = exports.UNRESOLVED_NAME = exports.multisetDiff = exports.compileCorpus = exports.MODES = exports.STUBS = exports.SUBSTITUTION_PARTNER_STUBS = exports.FIRESTORE_CLIENT_STUB = exports.FIRESTORE_ADMIN_STUB = exports.FIRESTORE_COMMON = exports.TIMESTAMP_ADMIN = exports.REACT_STUB = void 0;
29
+ exports.DECLARES_INTO_SHARED_SCOPE = exports.withSuffix = exports.intersectDiagnostics = exports.canonicalizeDiagnostic = exports.codeOf = exports.multisetIntersect = exports.introducedDiagnosticsIgnoringUnused = exports.introducedDiagnostics = exports.isFragmentArtifact = exports.isUnusedDeclaration = exports.missingNameOf = exports.UNRESOLVED_NAME = exports.multisetDiff = exports.compileCorpus = exports.MODES = exports.STUBS = exports.SUBSTITUTION_PARTNER_STUBS = exports.FIRESTORE_CLIENT_STUB = exports.FIRESTORE_ADMIN_STUB = exports.FIRESTORE_COMMON = exports.TIMESTAMP_ADMIN = exports.REACT_STUB = void 0;
30
30
  const path_1 = __importDefault(require("path"));
31
31
  const ts = __importStar(require("typescript"));
32
32
  /**
@@ -725,8 +725,266 @@ exports.introducedDiagnostics = introducedDiagnostics;
725
725
  */
726
726
  const introducedDiagnosticsIgnoringUnused = (before, after) => (0, exports.introducedDiagnostics)(before, after).filter((d) => !(0, exports.isUnusedDeclaration)(d));
727
727
  exports.introducedDiagnosticsIgnoringUnused = introducedDiagnosticsIgnoringUnused;
728
+ const multisetIntersect = (lists) => intersectBy(lists, exports.canonicalizeDiagnostic);
729
+ exports.multisetIntersect = multisetIntersect;
730
+ /** The `TS####` prefix a corpus diagnostic is built with in `compileCorpus`. */
731
+ const codeOf = (diagnostic) => {
732
+ const colon = diagnostic.indexOf(':');
733
+ return colon < 0 ? diagnostic : diagnostic.slice(0, colon);
734
+ };
735
+ exports.codeOf = codeOf;
736
+ const OPENERS = '<([{';
737
+ const CLOSERS = '>)]}';
738
+ const isOpener = (char) => OPENERS.includes(char);
739
+ const isCloser = (char) => CLOSERS.includes(char);
740
+ /**
741
+ * Every scan below treats `=>` as ONE token. Its `>` is not a closing bracket,
742
+ * and letting it decrement the depth drives a function-typed member negative
743
+ * and splits the rest of the string in the wrong places.
744
+ */
745
+ const skipsArrow = (text, index) => text[index] === '=' && text[index + 1] === '>';
746
+ /** Splits on `separator` where it sits at bracket depth zero. */
747
+ const splitTopLevel = (text, separator) => {
748
+ const parts = [];
749
+ let depth = 0;
750
+ let start = 0;
751
+ for (let index = 0; index < text.length; index++) {
752
+ const char = text[index];
753
+ if (skipsArrow(text, index)) {
754
+ index++;
755
+ continue;
756
+ }
757
+ if (isOpener(char))
758
+ depth++;
759
+ else if (isCloser(char))
760
+ depth--;
761
+ else if (char === separator && depth === 0) {
762
+ parts.push(text.slice(start, index));
763
+ start = index + 1;
764
+ }
765
+ }
766
+ parts.push(text.slice(start));
767
+ return parts;
768
+ };
769
+ /**
770
+ * A bracketed group's contents are an element LIST - tuple elements, type
771
+ * arguments, parameters, object members - separated by `,` or `;`. Splitting
772
+ * one as if it were a union is how `{ a: A | B; }` canonicalizes to
773
+ * `{B; | a: A}`: the `|` belongs to the member's type, not to the body.
774
+ */
775
+ const splitListElements = (text) => {
776
+ const parts = [];
777
+ const separators = [];
778
+ let depth = 0;
779
+ let start = 0;
780
+ for (let index = 0; index < text.length; index++) {
781
+ const char = text[index];
782
+ if (skipsArrow(text, index)) {
783
+ index++;
784
+ continue;
785
+ }
786
+ if (isOpener(char))
787
+ depth++;
788
+ else if (isCloser(char))
789
+ depth--;
790
+ else if ((char === ',' || char === ';') && depth === 0) {
791
+ parts.push(text.slice(start, index));
792
+ separators.push(char);
793
+ start = index + 1;
794
+ }
795
+ }
796
+ parts.push(text.slice(start));
797
+ return { parts, separators };
798
+ };
799
+ /** Splits on a depth-zero `=>`, so `(x: A) => B | C` unions only `B | C`. */
800
+ const splitTopLevelArrow = (text) => {
801
+ const parts = [];
802
+ let depth = 0;
803
+ let start = 0;
804
+ for (let index = 0; index < text.length; index++) {
805
+ const char = text[index];
806
+ if (skipsArrow(text, index)) {
807
+ if (depth === 0) {
808
+ parts.push(text.slice(start, index));
809
+ start = index + 2;
810
+ }
811
+ index++;
812
+ continue;
813
+ }
814
+ if (isOpener(char))
815
+ depth++;
816
+ else if (isCloser(char))
817
+ depth--;
818
+ }
819
+ parts.push(text.slice(start));
820
+ return parts;
821
+ };
822
+ /** The depth-zero `:` separating a member's name from its type, or -1. */
823
+ const labelEnd = (text) => {
824
+ let depth = 0;
825
+ for (let index = 0; index < text.length; index++) {
826
+ const char = text[index];
827
+ if (skipsArrow(text, index)) {
828
+ index++;
829
+ continue;
830
+ }
831
+ if (isOpener(char))
832
+ depth++;
833
+ else if (isCloser(char))
834
+ depth--;
835
+ else if (char === ':' && depth === 0)
836
+ return index;
837
+ }
838
+ return -1;
839
+ };
840
+ /** The index of the bracket closing the one at `open`, or -1 if unbalanced. */
841
+ const matchingBracket = (text, open) => {
842
+ let depth = 0;
843
+ for (let index = open; index < text.length; index++) {
844
+ const char = text[index];
845
+ if (skipsArrow(text, index)) {
846
+ index++;
847
+ continue;
848
+ }
849
+ if (isOpener(char))
850
+ depth++;
851
+ else if (isCloser(char)) {
852
+ depth--;
853
+ if (depth === 0)
854
+ return index;
855
+ if (depth < 0)
856
+ return -1;
857
+ }
858
+ }
859
+ return -1;
860
+ };
861
+ /** Rewrites the contents of every bracketed group as an element list. */
862
+ const sortUnionsInside = (text) => {
863
+ let out = '';
864
+ let index = 0;
865
+ while (index < text.length) {
866
+ const char = text[index];
867
+ if (isOpener(char)) {
868
+ const close = matchingBracket(text, index);
869
+ if (close < 0) {
870
+ out += char;
871
+ index++;
872
+ continue;
873
+ }
874
+ out += char + sortUnionsList(text.slice(index + 1, close)) + text[close];
875
+ index = close + 1;
876
+ continue;
877
+ }
878
+ out += char;
879
+ index++;
880
+ }
881
+ return out;
882
+ };
883
+ const sortUnionsList = (text) => {
884
+ const { parts, separators } = splitListElements(text);
885
+ const sorted = parts.map((part) => sortUnionsMember(part.trim()));
886
+ return sorted.reduce((out, part, index) => index ? `${out}${separators[index - 1]} ${part}` : part, '');
887
+ };
888
+ /** `name: T` unions only `T`; the name is not a union member. */
889
+ const sortUnionsMember = (text) => {
890
+ const colon = labelEnd(text);
891
+ if (colon < 0)
892
+ return sortUnions(text);
893
+ return `${text.slice(0, colon)}: ${sortUnions(text.slice(colon + 1).trim())}`;
894
+ };
895
+ /**
896
+ * Only UNION members are reordered. Tuple elements, type arguments, parameters
897
+ * and object members print in DECLARATION order, which is a property of the
898
+ * source and stable across programs, so sorting those would erase a real
899
+ * difference rather than a spurious one - they are rebuilt in place.
900
+ */
901
+ const sortUnions = (text) => {
902
+ const arrowParts = splitTopLevelArrow(text);
903
+ if (arrowParts.length > 1) {
904
+ return arrowParts
905
+ .map((part, index) => index === arrowParts.length - 1
906
+ ? sortUnions(part.trim())
907
+ : sortUnionsInside(part.trim()))
908
+ .join(' => ');
909
+ }
910
+ if (splitListElements(text).separators.length)
911
+ return sortUnionsList(text);
912
+ const members = splitTopLevel(text, '|').map((member) => sortUnionsInside(member.trim()));
913
+ if (members.length === 1)
914
+ return members[0];
915
+ return [...members].sort().join(' | ');
916
+ };
728
917
  /**
729
- * The multiset every list shares.
918
+ * A diagnostic message with every printed union in a canonical member order.
919
+ *
920
+ * TypeScript orders a union's members by type ID - the order the checker
921
+ * happened to CREATE those types in - not by anything in the source, and a
922
+ * type ID is per-program. Two programs over the same files can therefore print
923
+ * one union two ways:
924
+ *
925
+ * TS2345: ... parameter of type 'Record<string, unknown> | unknown[]'.
926
+ * TS2345: ... parameter of type 'unknown[] | Record<string, unknown>'.
927
+ *
928
+ * That matters because `intersectDiagnostics` is a SILENCING oracle: what it
929
+ * drops becomes a clean. Comparing raw messages discarded a diagnostic present
930
+ * in BOTH modes as strict-only, which is why `cross-fixture-fixer-type-safety`
931
+ * read 0 findings while `fixer-type-safety` - which unions instead of
932
+ * intersecting - baselines the same 4 `enforce-microdiff` TS2345 pairs (#2235).
933
+ *
934
+ * Rewriting is confined to single-quoted spans because that is where and only
935
+ * where TypeScript prints a type; a string-literal type nested in one is
936
+ * printed with double quotes, so the spans do not nest. This is a COMPARISON
937
+ * KEY - every diagnostic reported to a maintainer is the original string.
938
+ */
939
+ const canonicalCache = new Map();
940
+ const canonicalizeDiagnostic = (diagnostic) => {
941
+ const cached = canonicalCache.get(diagnostic);
942
+ if (cached !== undefined)
943
+ return cached;
944
+ const canonical = diagnostic.replace(/'([^']*)'/g, (_match, inner) => `'${sortUnions(inner)}'`);
945
+ canonicalCache.set(diagnostic, canonical);
946
+ return canonical;
947
+ };
948
+ exports.canonicalizeDiagnostic = canonicalizeDiagnostic;
949
+ /** The entries of `list` that `kept` does not cover, compared by `keyOf`. */
950
+ const subtractBy = (kept, list, keyOf) => {
951
+ const counts = new Map();
952
+ for (const diagnostic of kept) {
953
+ const key = keyOf(diagnostic);
954
+ counts.set(key, (counts.get(key) || 0) + 1);
955
+ }
956
+ return list.filter((diagnostic) => {
957
+ const key = keyOf(diagnostic);
958
+ const remaining = counts.get(key) || 0;
959
+ if (remaining <= 0)
960
+ return true;
961
+ counts.set(key, remaining - 1);
962
+ return false;
963
+ });
964
+ };
965
+ const intersectBy = (lists, keyOf) => {
966
+ if (!lists.length)
967
+ return [];
968
+ let common = [...lists[0]];
969
+ for (const list of lists.slice(1)) {
970
+ const counts = new Map();
971
+ for (const diagnostic of list) {
972
+ const key = keyOf(diagnostic);
973
+ counts.set(key, (counts.get(key) || 0) + 1);
974
+ }
975
+ common = common.filter((diagnostic) => {
976
+ const key = keyOf(diagnostic);
977
+ const remaining = counts.get(key) || 0;
978
+ if (remaining <= 0)
979
+ return false;
980
+ counts.set(key, remaining - 1);
981
+ return true;
982
+ });
983
+ }
984
+ return common;
985
+ };
986
+ /**
987
+ * The multiset every list shares, with an account of what it discarded.
730
988
  *
731
989
  * This is the mode discount, and it is the one place the cross-corpus oracles
732
990
  * deliberately differ from `fixer-type-safety`'s. That guard UNIONS the
@@ -755,27 +1013,26 @@ exports.introducedDiagnosticsIgnoringUnused = introducedDiagnosticsIgnoringUnuse
755
1013
  * The intersection only bites where both modes could judge. A pair whose input
756
1014
  * compiles under one mode only has a single-element intersection, so for it
757
1015
  * this is identical to the union.
1016
+ *
1017
+ * Because dropping is how this oracle produces a clean, every drop is counted
1018
+ * rather than discarded in silence, and `codeMatchedDrops` separates "the modes
1019
+ * disagree" from "the modes agree and the message merely printed differently".
758
1020
  */
759
- const multisetIntersect = (lists) => {
760
- if (!lists.length)
761
- return [];
762
- let common = [...lists[0]];
763
- for (const list of lists.slice(1)) {
764
- const counts = new Map();
765
- for (const diagnostic of list) {
766
- counts.set(diagnostic, (counts.get(diagnostic) || 0) + 1);
767
- }
768
- common = common.filter((diagnostic) => {
769
- const remaining = counts.get(diagnostic) || 0;
770
- if (remaining <= 0)
771
- return false;
772
- counts.set(diagnostic, remaining - 1);
773
- return true;
774
- });
775
- }
776
- return common;
1021
+ const intersectDiagnostics = (lists) => {
1022
+ const common = intersectBy(lists, exports.canonicalizeDiagnostic);
1023
+ const byCode = intersectBy(lists, exports.codeOf);
1024
+ return {
1025
+ common,
1026
+ // Compared by the CANONICAL key, like `common` itself: subtracting by raw
1027
+ // string would report the other mode's spelling of a KEPT diagnostic as a
1028
+ // drop, which is the #2235 confusion inverted.
1029
+ dropped: lists.flatMap((list) => subtractBy(common, list, exports.canonicalizeDiagnostic)),
1030
+ // Canonical-key equality implies code equality, so `byCode` contains
1031
+ // `common` as a multiset and the difference is exactly the divergence.
1032
+ codeMatchedDrops: subtractBy(common, byCode, exports.canonicalizeDiagnostic),
1033
+ };
777
1034
  };
778
- exports.multisetIntersect = multisetIntersect;
1035
+ exports.intersectDiagnostics = intersectDiagnostics;
779
1036
  /**
780
1037
  * `ts.createProgram` SILENTLY drops a root file whose name it does not
781
1038
  * recognize as TypeScript: `corpus.ts-7` is filtered out with no diagnostic at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.196",
3
+ "version": "1.20.197",
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.197",
4
+ "date": "2026-08-31T08:50:13.198Z",
5
+ "rules": [
6
+ {
7
+ "name": "no-entire-object-hook-deps",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 2236
11
+ ],
12
+ "summary": "decline a removal that strands a destructured prop (closes #2236)"
13
+ }
14
+ ]
15
+ },
2
16
  {
3
17
  "version": "1.20.196",
4
18
  "date": "2026-08-31T07:14:16.368Z",