@blumintinc/eslint-plugin-blumint 1.19.23 → 1.19.25

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/README.md CHANGED
@@ -128,7 +128,7 @@ full closed loop is documented in agora's `.claude/skills/eslint-autonomy/SKILL.
128
128
  | [enforce-react-type-naming](docs/rules/enforce-react-type-naming.md) | Enforce naming conventions for React types | ✅ | | | 🔧 | | |
129
129
  | [enforce-realtimedb-path-utils](docs/rules/enforce-realtimedb-path-utils.md) | Enforce usage of utility functions for Realtime Database paths | ✅ | | | | | |
130
130
  | [enforce-render-hits-memoization](docs/rules/enforce-render-hits-memoization.md) | Enforce proper memoization and usage of useRenderHits and renderHits | ✅ | | | | | |
131
- | [enforce-safe-stringify](docs/rules/enforce-safe-stringify.md) | Enforce using safe-stable-stringify instead of JSON.stringify to handle circular references and ensure deterministic output. JSON.stringify can throw errors on circular references and produce inconsistent output for objects with the same properties in different orders. safe-stable-stringify handles these cases safely. | ✅ | | | 🔧 | | |
131
+ | [enforce-safe-stringify](docs/rules/enforce-safe-stringify.md) | Enforce using safe-stable-stringify instead of JSON.stringify to handle circular references and ensure deterministic output. JSON.stringify can throw errors on circular references and produce inconsistent output for objects with the same properties in different orders. safe-stable-stringify handles these cases safely. | ✅ | | | | 💡 | |
132
132
  | [enforce-serializable-params](docs/rules/enforce-serializable-params.md) | Enforce serializable parameters for Firebase callable/HTTPS functions. | ✅ | | | | | |
133
133
  | [enforce-single-exported-unit-per-file](docs/rules/enforce-single-exported-unit-per-file.md) | Enforce that a .tsx/.jsx file exports at most one React component and any file exports at most one class | ✅ | | | | | |
134
134
  | [enforce-singular-type-names](docs/rules/enforce-singular-type-names.md) | Enforce TypeScript type names to be singular | ✅ | | | | | |
package/lib/index.js CHANGED
@@ -222,7 +222,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
222
222
  module.exports = {
223
223
  meta: {
224
224
  name: '@blumintinc/eslint-plugin-blumint',
225
- version: '1.19.23',
225
+ version: '1.19.25',
226
226
  },
227
227
  parseOptions: {
228
228
  ecmaVersion: 2020,
@@ -171,7 +171,12 @@ const IN_EXCEPTIONS = [
171
171
  'infusion',
172
172
  'ingenious',
173
173
  'ingenuity',
174
+ 'ingest',
174
175
  'ingested',
176
+ 'ingestible',
177
+ 'ingesting',
178
+ 'ingestion',
179
+ 'ingests',
175
180
  'ingot',
176
181
  'ingrained',
177
182
  'ingredient',
@@ -1,2 +1,4 @@
1
1
  import { TSESLint } from '@typescript-eslint/utils';
2
- export declare const enforceStableStringify: TSESLint.RuleModule<"useStableStringify", [], TSESLint.RuleListener>;
2
+ type MessageIds = 'useStableStringify' | 'replaceWithStringify';
3
+ export declare const enforceStableStringify: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
4
+ export {};
@@ -11,10 +11,18 @@ exports.enforceStableStringify = (0, createRule_1.createRule)({
11
11
  description: 'Enforce using safe-stable-stringify instead of JSON.stringify to handle circular references and ensure deterministic output. JSON.stringify can throw errors on circular references and produce inconsistent output for objects with the same properties in different orders. safe-stable-stringify handles these cases safely.',
12
12
  recommended: 'error',
13
13
  },
14
- fixable: 'code',
14
+ // Offered as a suggestion, not an auto-fix: `stringify` returns
15
+ // `string | undefined` (its undefined overload fires for `any`/`unknown`/
16
+ // `... | undefined` arguments), whereas `JSON.stringify` returns `string`.
17
+ // An unconditional `--fix` therefore silently widens the return type and
18
+ // breaks `: string` contracts (TS2322/TS2345) at call sites — passing lint
19
+ // while failing tsc. A suggestion forces a conscious, per-site opt-in that
20
+ // handles the possible `undefined`.
21
+ hasSuggestions: true,
15
22
  schema: [],
16
23
  messages: {
17
- useStableStringify: 'Use safe-stable-stringify instead of JSON.stringify for safer serialization. Replace `JSON.stringify(obj)` with `stringify(obj)`. First import it: `import stringify from "safe-stable-stringify"`. This handles circular references and provides deterministic output.',
24
+ useStableStringify: 'Prefer safe-stable-stringify over JSON.stringify for circular-reference-safe, deterministic output. Left as a suggestion rather than an auto-fix because `stringify` returns `string | undefined` (not `string`), so a blind rewrite can silently break `: string` contracts apply it per call site and handle the possible `undefined`.',
25
+ replaceWithStringify: "Replace with safe-stable-stringify's `stringify` (import it as `import stringify from 'safe-stable-stringify'`, and handle its `string | undefined` return, e.g. `stringify(obj) ?? ''`).",
18
26
  },
19
27
  },
20
28
  defaultOptions: [],
@@ -36,25 +44,35 @@ exports.enforceStableStringify = (0, createRule_1.createRule)({
36
44
  context.report({
37
45
  node,
38
46
  messageId: 'useStableStringify',
39
- fix(fixer) {
40
- const fixes = [];
41
- // Add import if not present
42
- if (!hasStringifyImport) {
43
- const program = context.sourceCode.ast;
44
- const firstImport = program.body.find((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
45
- const importStatement = "import stringify from 'safe-stable-stringify';\n";
46
- if (firstImport) {
47
- fixes.push(fixer.insertTextBefore(firstImport, importStatement));
48
- }
49
- else {
50
- fixes.push(fixer.insertTextBefore(program.body[0], importStatement));
51
- }
52
- hasStringifyImport = true;
53
- }
54
- // Replace JSON.stringify with stringify
55
- fixes.push(fixer.replaceText(node, 'stringify'));
56
- return fixes;
57
- },
47
+ suggest: [
48
+ {
49
+ messageId: 'replaceWithStringify',
50
+ fix(fixer) {
51
+ const fixes = [];
52
+ // Add the import only when the file lacks it. Unlike the old
53
+ // batch auto-fix, suggestions are applied one at a time with a
54
+ // re-lint in between, so we must NOT flip a shared flag here:
55
+ // each suggestion is computed independently against the
56
+ // current file, and adding the import whenever it is absent
57
+ // keeps every single-suggestion application self-contained
58
+ // (the re-lint suppresses a duplicate for later call sites).
59
+ if (!hasStringifyImport) {
60
+ const program = context.sourceCode.ast;
61
+ const firstImport = program.body.find((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
62
+ const importStatement = "import stringify from 'safe-stable-stringify';\n";
63
+ if (firstImport) {
64
+ fixes.push(fixer.insertTextBefore(firstImport, importStatement));
65
+ }
66
+ else {
67
+ fixes.push(fixer.insertTextBefore(program.body[0], importStatement));
68
+ }
69
+ }
70
+ // Replace JSON.stringify with stringify
71
+ fixes.push(fixer.replaceText(node, 'stringify'));
72
+ return fixes;
73
+ },
74
+ },
75
+ ],
58
76
  });
59
77
  }
60
78
  },
@@ -605,9 +605,18 @@ exports.verticallyGroupRelatedFunctions = (0, createRule_1.createRule)({
605
605
  if (misplacedIndex === -1) {
606
606
  return;
607
607
  }
608
- const misplacedInfo = functions[misplacedIndex];
609
- const targetIndex = expectedNames.indexOf(misplacedInfo.name);
610
- const anchorName = targetIndex > 0 ? expectedNames[targetIndex - 1] : null;
608
+ // Report the function that BELONGS in the first mismatched slot
609
+ // (expected[i]), not the wrong occupant currently sitting there
610
+ // (actual[i]). Telling the reader to move expected[i] up to right after
611
+ // expected[i-1] fills the hole and extends the already-correct prefix
612
+ // by one, so obeying each emitted instruction verbatim strictly
613
+ // converges on the fixed point. Naming the displaced occupant instead
614
+ // sends it off to its own final home without filling this slot — the
615
+ // prefix stays mismatched and the next pass emits a different
616
+ // instruction anchored on a different sibling, the non-converging
617
+ // "anchor chase" on diamond/shared-leaf graphs (#1330).
618
+ const misplacedInfo = expectedOrderInfos[misplacedIndex];
619
+ const anchorName = misplacedIndex > 0 ? expectedNames[misplacedIndex - 1] : null;
611
620
  const dependencyReason = misplacedInfo.dependencies.length > 0
612
621
  ? normalizedOptions.dependencyDirection === 'callees-first'
613
622
  ? `it calls ${misplacedInfo.dependencies.join(', ')} and helpers should precede the callers that depend on them when dependencyDirection is "callees-first"`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.19.23",
3
+ "version": "1.19.25",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,40 @@
1
1
  [
2
+ {
3
+ "version": "1.19.25",
4
+ "date": "2026-07-22T21:39:17.787Z",
5
+ "rules": [
6
+ {
7
+ "name": "enforce-safe-stringify",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1332
11
+ ],
12
+ "summary": "offer JSON.stringify rewrite as a suggestion, not an unconditional auto-fix (closes #1332)"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "version": "1.19.24",
18
+ "date": "2026-07-22T21:30:58.024Z",
19
+ "rules": [
20
+ {
21
+ "name": "enforce-positive-naming",
22
+ "changeType": "fix",
23
+ "issues": [
24
+ 1331
25
+ ],
26
+ "summary": "exempt ingest word family from in- prefix heuristic (closes #1331)"
27
+ },
28
+ {
29
+ "name": "vertically-group-related-functions",
30
+ "changeType": "fix",
31
+ "issues": [
32
+ 1330
33
+ ],
34
+ "summary": "fill the mismatched slot instead of evicting its occupant (closes #1330)"
35
+ }
36
+ ]
37
+ },
2
38
  {
3
39
  "version": "1.19.23",
4
40
  "date": "2026-07-22T15:32:41.825Z",