@blumintinc/eslint-plugin-blumint 1.20.121 → 1.20.123
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
|
@@ -8,6 +8,36 @@ const FIRESTORE_PATHS = [
|
|
|
8
8
|
'firebase-admin',
|
|
9
9
|
'firebase-admin/firestore',
|
|
10
10
|
];
|
|
11
|
+
/**
|
|
12
|
+
* A `jest.mock` factory produces the same module shape whether it is written as
|
|
13
|
+
* a concise arrow, a block-bodied arrow, or a `function` expression. Matching
|
|
14
|
+
* the factory argument itself would only ever see the concise arrow, letting an
|
|
15
|
+
* identical mock evade the rule on a body-form choice alone. Resolving to the
|
|
16
|
+
* produced object keeps every spelling on one matching path.
|
|
17
|
+
*
|
|
18
|
+
* A body with more than a lone `return` is deliberately unresolved: the object
|
|
19
|
+
* reaching the caller can no longer be read off a single expression.
|
|
20
|
+
*/
|
|
21
|
+
const resolveFactoryReturn = (factory) => {
|
|
22
|
+
if (!factory ||
|
|
23
|
+
(factory.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
24
|
+
factory.type !== utils_1.AST_NODE_TYPES.FunctionExpression)) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
// A concise arrow body is the produced expression itself. Parentheses around
|
|
28
|
+
// an object body are not part of the AST, so the node is matched directly.
|
|
29
|
+
if (factory.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
30
|
+
return factory.body;
|
|
31
|
+
}
|
|
32
|
+
const statements = factory.body.body;
|
|
33
|
+
if (statements.length !== 1) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const [statement] = statements;
|
|
37
|
+
return statement.type === utils_1.AST_NODE_TYPES.ReturnStatement
|
|
38
|
+
? statement.argument ?? undefined
|
|
39
|
+
: undefined;
|
|
40
|
+
};
|
|
11
41
|
exports.enforceFirestoreMock = (0, createRule_1.createRule)({
|
|
12
42
|
name: 'enforce-mock-firestore',
|
|
13
43
|
meta: {
|
|
@@ -39,21 +69,18 @@ exports.enforceFirestoreMock = (0, createRule_1.createRule)({
|
|
|
39
69
|
typeof node.arguments[0].value === 'string' &&
|
|
40
70
|
node.arguments[0].value?.includes(path))) {
|
|
41
71
|
// Check if the mock includes Firestore-related properties
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
prop.key.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
messageId: 'noManualFirestoreMock',
|
|
55
|
-
});
|
|
56
|
-
}
|
|
72
|
+
const mockedModule = resolveFactoryReturn(node.arguments[1]);
|
|
73
|
+
if (mockedModule &&
|
|
74
|
+
mockedModule.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
75
|
+
mockedModule.properties.some((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
76
|
+
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
77
|
+
(prop.key.name === 'db' ||
|
|
78
|
+
prop.key.name === 'firestore' ||
|
|
79
|
+
prop.key.name === 'getFirestore'))) {
|
|
80
|
+
context.report({
|
|
81
|
+
node,
|
|
82
|
+
messageId: 'noManualFirestoreMock',
|
|
83
|
+
});
|
|
57
84
|
}
|
|
58
85
|
}
|
|
59
86
|
},
|
|
@@ -103,11 +103,17 @@ const EVENT_SUPPRESSION_METHODS = new Set([
|
|
|
103
103
|
* arguments. Such a wrapper is not redundant, so the receiver is deliberately
|
|
104
104
|
* unconstrained — deleting `x.preventDefault()` changes behaviour whether `x`
|
|
105
105
|
* is a parameter, a captured value or a nested member.
|
|
106
|
+
*
|
|
107
|
+
* The question is asked of the call itself rather than of a statement, because
|
|
108
|
+
* the behaviour that makes the wrapper load-bearing is identical whether the
|
|
109
|
+
* author spelled it as a concise arrow body, a `return` or an expression
|
|
110
|
+
* statement — and a predicate that recognizes only one of those spellings
|
|
111
|
+
* reports the other two while prescribing a remedy that does not exist.
|
|
106
112
|
*/
|
|
107
|
-
function isEventSuppressionCall(
|
|
108
|
-
if (
|
|
113
|
+
function isEventSuppressionCall(expression) {
|
|
114
|
+
if (!expression)
|
|
109
115
|
return false;
|
|
110
|
-
const expr = unwrapChainExpression(
|
|
116
|
+
const expr = unwrapChainExpression(expression);
|
|
111
117
|
if (!expr || expr.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
112
118
|
return false;
|
|
113
119
|
const callee = unwrapChainExpression(expr.callee);
|
|
@@ -124,6 +130,19 @@ function isEventSuppressionCall(stmt) {
|
|
|
124
130
|
}
|
|
125
131
|
return false;
|
|
126
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* The statement carrying a wrapper's effective call, so the suppression test
|
|
135
|
+
* reads the same expression from either statement kind.
|
|
136
|
+
*/
|
|
137
|
+
function isEventSuppressionStatement(stmt) {
|
|
138
|
+
if (stmt.type === utils_1.AST_NODE_TYPES.ExpressionStatement) {
|
|
139
|
+
return isEventSuppressionCall(stmt.expression);
|
|
140
|
+
}
|
|
141
|
+
if (stmt.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
142
|
+
return isEventSuppressionCall(stmt.argument);
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
127
146
|
function isIdentifierOrMemberOn(obj, nameSet) {
|
|
128
147
|
if (obj.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
129
148
|
return nameSet.has(obj.name);
|
|
@@ -379,6 +398,11 @@ exports.noRedundantUseCallbackWrapper = (0, createRule_1.createRule)({
|
|
|
379
398
|
// Handle implicit return: () => memoizedFn()
|
|
380
399
|
if (fn.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
381
400
|
fn.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
401
|
+
// An event-suppression call disqualifies the wrapper outright: no
|
|
402
|
+
// spelling of "pass the callback directly" keeps it, so reporting
|
|
403
|
+
// here would prescribe a remedy that does not exist.
|
|
404
|
+
if (isEventSuppressionCall(fn.body))
|
|
405
|
+
return;
|
|
382
406
|
const bodyExpr = unwrapChainExpression(fn.body);
|
|
383
407
|
if (bodyExpr && bodyExpr.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
384
408
|
const callee = unwrapChainExpression(bodyExpr.callee);
|
|
@@ -426,8 +450,10 @@ exports.noRedundantUseCallbackWrapper = (0, createRule_1.createRule)({
|
|
|
426
450
|
const stmts = fn.body.body.filter(Boolean);
|
|
427
451
|
// An event-suppression call disqualifies the wrapper outright: no
|
|
428
452
|
// spelling of "pass the callback directly" keeps it, so reporting
|
|
429
|
-
// here would prescribe a remedy that does not exist.
|
|
430
|
-
|
|
453
|
+
// here would prescribe a remedy that does not exist. Any statement
|
|
454
|
+
// carrying one is enough — the call is load-bearing wherever in
|
|
455
|
+
// the body it sits.
|
|
456
|
+
if (stmts.some(isEventSuppressionStatement))
|
|
431
457
|
return;
|
|
432
458
|
// Exactly one statement. A wrapper that sequences a second call is
|
|
433
459
|
// doing work the delegate alone does not, so collapsing it would
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.123",
|
|
4
|
+
"date": "2026-08-06T11:53:46.588Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-mock-firestore",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1798
|
|
11
|
+
],
|
|
12
|
+
"summary": "resolve the jest.mock factory's returned object in every body form (closes #1798)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.20.122",
|
|
18
|
+
"date": "2026-08-06T11:06:53.595Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "no-redundant-usecallback-wrapper",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
1796
|
|
25
|
+
],
|
|
26
|
+
"summary": "exempt a suppression wrapper in every body spelling (closes #1796)"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
2
30
|
{
|
|
3
31
|
"version": "1.20.121",
|
|
4
32
|
"date": "2026-08-06T10:37:08.150Z",
|