@blumintinc/eslint-plugin-blumint 1.19.24 → 1.19.26
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 +1 -1
- package/lib/index.js +2 -2
- package/lib/rules/enforce-safe-stringify.d.ts +3 -1
- package/lib/rules/enforce-safe-stringify.js +39 -21
- package/lib/rules/no-console-error.d.ts +1 -0
- package/lib/rules/no-console-error.js +22 -0
- package/package.json +1 -1
- package/release-manifest.json +28 -0
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.
|
|
225
|
+
version: '1.19.26',
|
|
226
226
|
},
|
|
227
227
|
parseOptions: {
|
|
228
228
|
ecmaVersion: 2020,
|
|
@@ -267,7 +267,7 @@ module.exports = {
|
|
|
267
267
|
'@blumintinc/blumint/no-async-foreach': 'error',
|
|
268
268
|
'@blumintinc/blumint/no-console-error': [
|
|
269
269
|
'error',
|
|
270
|
-
{ allowWithUseAlertDialog: true },
|
|
270
|
+
{ allowWithUseAlertDialog: true, allowErrorInstanceArgument: true },
|
|
271
271
|
],
|
|
272
272
|
'@blumintinc/blumint/no-conditional-literals-in-jsx': 'error',
|
|
273
273
|
'@blumintinc/blumint/no-filter-without-return': 'error',
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
},
|
|
@@ -113,6 +113,18 @@ const getLocalNameFromPattern = (target) => {
|
|
|
113
113
|
return null;
|
|
114
114
|
};
|
|
115
115
|
const findDeclaredVariableByName = (name, variables) => variables.find((variable) => variable.name === name);
|
|
116
|
+
/**
|
|
117
|
+
* A `new <Something>Error(...)` argument is the structured-error handoff the
|
|
118
|
+
* rule exists to encourage: on the backend, `console.error(new HttpsError(...))`
|
|
119
|
+
* is the monitored pipeline (spyOnConsoleErrors reports the Error among the
|
|
120
|
+
* args), not a bypass of it. Detection stays purely syntactic — the rule is not
|
|
121
|
+
* type-aware and runs on files outside any `parserOptions.project` — so a bare
|
|
122
|
+
* identifier (`console.error(error)`) is deliberately excluded because syntax
|
|
123
|
+
* alone cannot prove it holds an Error.
|
|
124
|
+
*/
|
|
125
|
+
const isStructuredErrorArgument = (arg) => arg.type === utils_1.AST_NODE_TYPES.NewExpression &&
|
|
126
|
+
arg.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
127
|
+
/Error$/.test(arg.callee.name);
|
|
116
128
|
/**
|
|
117
129
|
* Tracks aliases of `console` and `console.error` to detect indirect calls.
|
|
118
130
|
*/
|
|
@@ -451,6 +463,7 @@ exports.noConsoleError = (0, createRule_1.createRule)({
|
|
|
451
463
|
items: { type: 'string' },
|
|
452
464
|
},
|
|
453
465
|
allowWithUseAlertDialog: { type: 'boolean' },
|
|
466
|
+
allowErrorInstanceArgument: { type: 'boolean' },
|
|
454
467
|
},
|
|
455
468
|
additionalProperties: false,
|
|
456
469
|
},
|
|
@@ -467,6 +480,7 @@ exports.noConsoleError = (0, createRule_1.createRule)({
|
|
|
467
480
|
if (shouldIgnoreFile(filename))
|
|
468
481
|
return {};
|
|
469
482
|
const allowWithUseAlertDialog = options.allowWithUseAlertDialog === true;
|
|
483
|
+
const allowErrorInstanceArgument = options.allowErrorInstanceArgument === true;
|
|
470
484
|
const aliasTracker = new AliasTracker(context);
|
|
471
485
|
const dialogTracker = new UseAlertDialogTracker(context);
|
|
472
486
|
const pendingTracker = new PendingCallTracker();
|
|
@@ -511,6 +525,14 @@ exports.noConsoleError = (0, createRule_1.createRule)({
|
|
|
511
525
|
const scope = getScopeForNode(context, node);
|
|
512
526
|
if (!aliasTracker.isConsoleErrorCall(node, scope))
|
|
513
527
|
return;
|
|
528
|
+
// A structured-error argument is the sanctioned, monitored path, so the
|
|
529
|
+
// carve-out applies to every matched call shape (direct, aliased,
|
|
530
|
+
// destructured, computed) — unlike the useAlertDialog queueing below,
|
|
531
|
+
// which is restricted to the direct-global shape.
|
|
532
|
+
if (allowErrorInstanceArgument &&
|
|
533
|
+
node.arguments.some(isStructuredErrorArgument)) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
514
536
|
// Queueing is limited to direct global `console.error(...)` calls because deferral relies on
|
|
515
537
|
// linear control flow. Aliased or destructured references can escape the current scope
|
|
516
538
|
// (stored, passed, invoked later), so they are reported immediately to avoid missing non-local calls.
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.19.26",
|
|
4
|
+
"date": "2026-07-23T00:25:08.850Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "no-console-error",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1333
|
|
11
|
+
],
|
|
12
|
+
"summary": "allow structured Error argument via allowErrorInstanceArgument (closes #1333)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.19.25",
|
|
18
|
+
"date": "2026-07-22T21:39:17.787Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "enforce-safe-stringify",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
1332
|
|
25
|
+
],
|
|
26
|
+
"summary": "offer JSON.stringify rewrite as a suggestion, not an unconditional auto-fix (closes #1332)"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
2
30
|
{
|
|
3
31
|
"version": "1.19.24",
|
|
4
32
|
"date": "2026-07-22T21:30:58.024Z",
|