@blumintinc/eslint-plugin-blumint 1.10.0 → 1.11.0
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 +99 -84
- package/lib/index.js +7 -1
- package/lib/rules/class-methods-read-top-to-bottom.js +10 -0
- package/lib/rules/consistent-callback-naming.js +20 -16
- package/lib/rules/enforce-assert-throws.js +10 -5
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +7 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +462 -0
- package/lib/rules/enforce-callable-types.d.ts +1 -1
- package/lib/rules/enforce-callable-types.js +13 -13
- package/lib/rules/enforce-callback-memo.js +6 -3
- package/lib/rules/enforce-centralized-mock-firestore.js +8 -6
- package/lib/rules/enforce-css-media-queries.js +3 -3
- package/lib/rules/enforce-dynamic-file-naming.js +9 -7
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-firestore-doc-ref-generic.js +16 -12
- package/lib/rules/enforce-firestore-facade.js +7 -3
- package/lib/rules/enforce-firestore-path-utils.js +5 -2
- package/lib/rules/enforce-id-capitalization.js +23 -5
- package/lib/rules/enforce-identifiable-firestore-type.js +17 -10
- package/lib/rules/enforce-memoize-async.js +2 -2
- package/lib/rules/enforce-positive-naming.js +71 -27
- package/lib/rules/enforce-props-argument-name.js +31 -9
- package/lib/rules/enforce-realtimedb-path-utils.js +2 -1
- package/lib/rules/enforce-singular-type-names.js +4 -1
- package/lib/rules/enforce-timestamp-now.js +3 -4
- package/lib/rules/enforce-verb-noun-naming.js +18 -8
- package/lib/rules/ensure-pointer-events-none.js +20 -11
- package/lib/rules/extract-global-constants.js +25 -10
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +3 -0
- package/lib/rules/fast-deep-equal-over-microdiff.js +182 -0
- package/lib/rules/key-only-outermost-element.js +13 -7
- package/lib/rules/no-always-true-false-conditions.js +52 -18
- package/lib/rules/no-async-array-filter.js +1 -1
- package/lib/rules/no-circular-references.js +121 -59
- package/lib/rules/no-entire-object-hook-deps.js +113 -29
- package/lib/rules/no-explicit-return-type.js +3 -2
- package/lib/rules/no-firestore-jest-mock.js +1 -1
- package/lib/rules/no-firestore-object-arrays.js +2 -1
- package/lib/rules/no-hungarian.js +2 -2
- package/lib/rules/no-jsx-in-hooks.js +4 -3
- package/lib/rules/no-margin-properties.js +25 -18
- package/lib/rules/no-mixed-firestore-transactions.js +8 -4
- package/lib/rules/no-mock-firebase-admin.js +6 -1
- package/lib/rules/no-object-values-on-strings.js +28 -10
- package/lib/rules/no-unnecessary-destructuring.js +1 -1
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -31
- package/lib/rules/no-unused-props.js +10 -7
- package/lib/rules/no-unused-usestate.js +19 -6
- package/lib/rules/omit-index-html.js +1 -1
- package/lib/rules/prefer-batch-operations.js +19 -6
- package/lib/rules/prefer-clone-deep.js +16 -11
- package/lib/rules/prefer-destructuring-no-class.js +19 -0
- package/lib/rules/prefer-fragment-component.js +19 -16
- package/lib/rules/prefer-global-router-state-key.js +6 -4
- package/lib/rules/prefer-settings-object.js +1 -1
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +12 -4
- package/lib/rules/react-usememo-should-be-component.js +64 -18
- package/lib/rules/require-hooks-default-params.js +25 -15
- package/lib/rules/require-usememo-object-literals.js +3 -2
- package/lib/rules/semantic-function-prefixes.js +15 -3
- package/lib/rules/sync-onwrite-name-func.js +5 -3
- package/lib/utils/ASTHelpers.js +6 -2
- package/lib/utils/graph/ClassGraphBuilder.js +4 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +3 -1
- package/package.json +3 -3
- package/lib/rules/require-image-overlayed.d.ts +0 -7
- package/lib/rules/require-image-overlayed.js +0 -82
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fastDeepEqualOverMicrodiff = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'fast-deep-equal-over-microdiff',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce using fast-deep-equal for equality checks instead of microdiff',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
useFastDeepEqual: 'Use fast-deep-equal for equality checks instead of microdiff.length === 0',
|
|
18
|
+
addFastDeepEqualImport: 'Import isEqual from fast-deep-equal for equality checks',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
create(context) {
|
|
23
|
+
const sourceCode = context.getSourceCode();
|
|
24
|
+
let hasFastDeepEqualImport = false;
|
|
25
|
+
let microdiffImportName = 'diff';
|
|
26
|
+
let fastDeepEqualImportName = 'isEqual';
|
|
27
|
+
const reportedNodes = new Set();
|
|
28
|
+
/**
|
|
29
|
+
* Check if a node is a microdiff equality check pattern
|
|
30
|
+
* Looks for patterns like:
|
|
31
|
+
* - diff(a, b).length === 0
|
|
32
|
+
* - diff(a, b).length !== 0
|
|
33
|
+
* - !diff(a, b).length
|
|
34
|
+
*/
|
|
35
|
+
function isMicrodiffEqualityCheck(node) {
|
|
36
|
+
// Check for binary expressions like diff(a, b).length === 0
|
|
37
|
+
if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
|
|
38
|
+
(node.operator === '===' ||
|
|
39
|
+
node.operator === '==' ||
|
|
40
|
+
node.operator === '!==' ||
|
|
41
|
+
node.operator === '!=') &&
|
|
42
|
+
node.right.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
43
|
+
node.right.value === 0 &&
|
|
44
|
+
node.left.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
45
|
+
node.left.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
46
|
+
node.left.property.name === 'length' &&
|
|
47
|
+
node.left.object.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
48
|
+
node.left.object.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
49
|
+
node.left.object.callee.name === microdiffImportName) {
|
|
50
|
+
return {
|
|
51
|
+
isEquality: node.operator === '===' || node.operator === '==',
|
|
52
|
+
diffCall: node.left.object,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Check for unary expressions like !diff(a, b).length
|
|
56
|
+
if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
|
|
57
|
+
node.operator === '!' &&
|
|
58
|
+
node.argument.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
59
|
+
node.argument.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
60
|
+
node.argument.property.name === 'length' &&
|
|
61
|
+
node.argument.object.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
62
|
+
node.argument.object.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
63
|
+
node.argument.object.callee.name === microdiffImportName) {
|
|
64
|
+
return {
|
|
65
|
+
isEquality: true,
|
|
66
|
+
diffCall: node.argument.object,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Not a microdiff equality check
|
|
70
|
+
return { isEquality: false };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create a fix for replacing microdiff equality check with fast-deep-equal
|
|
74
|
+
*/
|
|
75
|
+
function createFix(fixer, node, diffCall, isEquality) {
|
|
76
|
+
const args = diffCall.arguments;
|
|
77
|
+
if (args.length !== 2) {
|
|
78
|
+
return null; // Can't fix if not exactly 2 arguments
|
|
79
|
+
}
|
|
80
|
+
const arg1 = sourceCode.getText(args[0]);
|
|
81
|
+
const arg2 = sourceCode.getText(args[1]);
|
|
82
|
+
// If fast-deep-equal is not imported, we need to add the import after the microdiff import
|
|
83
|
+
if (!hasFastDeepEqualImport) {
|
|
84
|
+
// Find the end of the microdiff import statement
|
|
85
|
+
const importDeclarations = sourceCode.ast.body.filter((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
|
|
86
|
+
const microdiffImport = importDeclarations.find((node) => node.source.value === 'microdiff');
|
|
87
|
+
const importFix = fixer.insertTextAfter(microdiffImport, `\nimport isEqual from 'fast-deep-equal';`);
|
|
88
|
+
const replaceFix = fixer.replaceText(node, isEquality
|
|
89
|
+
? `isEqual(${arg1}, ${arg2})`
|
|
90
|
+
: `!isEqual(${arg1}, ${arg2})`);
|
|
91
|
+
return [importFix, replaceFix];
|
|
92
|
+
}
|
|
93
|
+
// Otherwise just replace the expression
|
|
94
|
+
return fixer.replaceText(node, isEquality
|
|
95
|
+
? `${fastDeepEqualImportName}(${arg1}, ${arg2})`
|
|
96
|
+
: `!${fastDeepEqualImportName}(${arg1}, ${arg2})`);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
// Track imports of microdiff and fast-deep-equal
|
|
100
|
+
ImportDeclaration(node) {
|
|
101
|
+
const importSource = node.source.value;
|
|
102
|
+
// Check for microdiff import
|
|
103
|
+
if (importSource === 'microdiff') {
|
|
104
|
+
// Get the local name of the imported diff function
|
|
105
|
+
node.specifiers.forEach((specifier) => {
|
|
106
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
107
|
+
specifier.imported.name === 'diff') {
|
|
108
|
+
microdiffImportName = specifier.local.name;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Check for fast-deep-equal import
|
|
113
|
+
if (importSource === 'fast-deep-equal' ||
|
|
114
|
+
importSource === 'fast-deep-equal/es6') {
|
|
115
|
+
hasFastDeepEqualImport = true;
|
|
116
|
+
// Get the local name of the imported isEqual function
|
|
117
|
+
node.specifiers.forEach((specifier) => {
|
|
118
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
119
|
+
fastDeepEqualImportName = specifier.local.name;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
// Check expressions for microdiff equality patterns
|
|
125
|
+
['BinaryExpression, UnaryExpression'](node) {
|
|
126
|
+
// Skip if we've already reported this node
|
|
127
|
+
if (reportedNodes.has(node)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const result = isMicrodiffEqualityCheck(node);
|
|
131
|
+
if (result.isEquality !== undefined && result.diffCall) {
|
|
132
|
+
reportedNodes.add(node);
|
|
133
|
+
context.report({
|
|
134
|
+
node,
|
|
135
|
+
messageId: 'useFastDeepEqual',
|
|
136
|
+
fix(fixer) {
|
|
137
|
+
return createFix(fixer, node, result.diffCall, result.isEquality);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
// Check if statements for microdiff equality patterns
|
|
143
|
+
IfStatement(node) {
|
|
144
|
+
// Skip if we've already reported this node
|
|
145
|
+
if (reportedNodes.has(node.test)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const result = isMicrodiffEqualityCheck(node.test);
|
|
149
|
+
if (result.isEquality !== undefined && result.diffCall) {
|
|
150
|
+
reportedNodes.add(node.test);
|
|
151
|
+
context.report({
|
|
152
|
+
node: node.test,
|
|
153
|
+
messageId: 'useFastDeepEqual',
|
|
154
|
+
fix(fixer) {
|
|
155
|
+
return createFix(fixer, node.test, result.diffCall, result.isEquality);
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
// Check return statements for microdiff equality patterns
|
|
161
|
+
ReturnStatement(node) {
|
|
162
|
+
// Skip if we've already reported this node or if there's no argument
|
|
163
|
+
if (!node.argument || reportedNodes.has(node.argument)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const result = isMicrodiffEqualityCheck(node.argument);
|
|
167
|
+
if (result.isEquality !== undefined && result.diffCall) {
|
|
168
|
+
reportedNodes.add(node.argument);
|
|
169
|
+
context.report({
|
|
170
|
+
node: node.argument,
|
|
171
|
+
messageId: 'useFastDeepEqual',
|
|
172
|
+
fix(fixer) {
|
|
173
|
+
// We already checked that node.argument is not null above
|
|
174
|
+
return createFix(fixer, node.argument, result.diffCall, result.isEquality);
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
//# sourceMappingURL=fast-deep-equal-over-microdiff.js.map
|
|
@@ -29,8 +29,9 @@ exports.keyOnlyOutermostElement = (0, createRule_1.createRule)({
|
|
|
29
29
|
const processMapCall = (node) => {
|
|
30
30
|
// Get the callback function
|
|
31
31
|
const callback = node.arguments[0];
|
|
32
|
-
if (callback &&
|
|
33
|
-
callback.type === '
|
|
32
|
+
if (callback &&
|
|
33
|
+
(callback.type === 'ArrowFunctionExpression' ||
|
|
34
|
+
callback.type === 'FunctionExpression')) {
|
|
34
35
|
// Find the return statement or expression
|
|
35
36
|
let returnExpr = null;
|
|
36
37
|
if (callback.type === 'ArrowFunctionExpression' &&
|
|
@@ -84,13 +85,17 @@ exports.keyOnlyOutermostElement = (0, createRule_1.createRule)({
|
|
|
84
85
|
const attributes = openingElement.attributes;
|
|
85
86
|
for (let i = 0; i < attributes.length; i++) {
|
|
86
87
|
const attr = attributes[i];
|
|
87
|
-
if (attr.type === 'JSXAttribute' &&
|
|
88
|
+
if (attr.type === 'JSXAttribute' &&
|
|
89
|
+
attr.name.name === 'key' &&
|
|
90
|
+
!reportedAttributes.has(attr)) {
|
|
88
91
|
// Check if this element is nested inside a map callback element or fragment
|
|
89
92
|
let parent = node.parent;
|
|
90
93
|
let isNestedInMapCallback = false;
|
|
91
94
|
while (parent) {
|
|
92
|
-
if ((parent.type === 'JSXElement' &&
|
|
93
|
-
|
|
95
|
+
if ((parent.type === 'JSXElement' &&
|
|
96
|
+
mapCallbackElements.has(parent)) ||
|
|
97
|
+
(parent.type === 'JSXFragment' &&
|
|
98
|
+
mapCallbackFragments.has(parent))) {
|
|
94
99
|
isNestedInMapCallback = true;
|
|
95
100
|
break;
|
|
96
101
|
}
|
|
@@ -111,7 +116,8 @@ exports.keyOnlyOutermostElement = (0, createRule_1.createRule)({
|
|
|
111
116
|
const fullText = sourceCode.getText();
|
|
112
117
|
// Check if there's a space after the attribute
|
|
113
118
|
let rangeEnd = endPos;
|
|
114
|
-
if (rangeEnd < fullText.length &&
|
|
119
|
+
if (rangeEnd < fullText.length &&
|
|
120
|
+
fullText[rangeEnd] === ' ') {
|
|
115
121
|
rangeEnd++;
|
|
116
122
|
}
|
|
117
123
|
return fixer.replaceTextRange([startPos, rangeEnd], '');
|
|
@@ -145,7 +151,7 @@ exports.keyOnlyOutermostElement = (0, createRule_1.createRule)({
|
|
|
145
151
|
node.right.callee.property.name === 'map') {
|
|
146
152
|
processMapCall(node.right);
|
|
147
153
|
}
|
|
148
|
-
}
|
|
154
|
+
},
|
|
149
155
|
};
|
|
150
156
|
},
|
|
151
157
|
});
|
|
@@ -306,8 +306,7 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
306
306
|
return true;
|
|
307
307
|
}
|
|
308
308
|
// Check if this is a return statement with a default value
|
|
309
|
-
if (node.parent &&
|
|
310
|
-
node.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
309
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
311
310
|
return true;
|
|
312
311
|
}
|
|
313
312
|
// Check if this is a function argument (common for default values)
|
|
@@ -323,23 +322,19 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
323
322
|
return true;
|
|
324
323
|
}
|
|
325
324
|
// Check if this is inside a property assignment in an object
|
|
326
|
-
if (node.parent &&
|
|
327
|
-
node.parent.type === utils_1.AST_NODE_TYPES.Property) {
|
|
325
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.Property) {
|
|
328
326
|
return true;
|
|
329
327
|
}
|
|
330
328
|
// Check if this is inside a template literal expression
|
|
331
|
-
if (node.parent &&
|
|
332
|
-
node.parent.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
|
|
329
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
|
|
333
330
|
return true;
|
|
334
331
|
}
|
|
335
332
|
// Check if this is inside a template element
|
|
336
|
-
if (node.parent &&
|
|
337
|
-
node.parent.type === utils_1.AST_NODE_TYPES.TemplateElement) {
|
|
333
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.TemplateElement) {
|
|
338
334
|
return true;
|
|
339
335
|
}
|
|
340
336
|
// Check if this is inside a spread element
|
|
341
|
-
if (node.parent &&
|
|
342
|
-
node.parent.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
337
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
343
338
|
return true;
|
|
344
339
|
}
|
|
345
340
|
// Check if this is inside an array method callback (map, filter, etc.)
|
|
@@ -351,7 +346,15 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
351
346
|
const methodName = node.parent.parent.callee.property.type === utils_1.AST_NODE_TYPES.Identifier
|
|
352
347
|
? node.parent.parent.callee.property.name
|
|
353
348
|
: '';
|
|
354
|
-
if ([
|
|
349
|
+
if ([
|
|
350
|
+
'map',
|
|
351
|
+
'filter',
|
|
352
|
+
'find',
|
|
353
|
+
'findIndex',
|
|
354
|
+
'some',
|
|
355
|
+
'every',
|
|
356
|
+
'forEach',
|
|
357
|
+
].includes(methodName)) {
|
|
355
358
|
return true;
|
|
356
359
|
}
|
|
357
360
|
}
|
|
@@ -360,6 +363,19 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
360
363
|
node.parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
361
364
|
return true;
|
|
362
365
|
}
|
|
366
|
+
// Check if the right side is an empty object or array literal (common default pattern)
|
|
367
|
+
if (node.operator === '||' &&
|
|
368
|
+
((node.right.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
369
|
+
node.right.properties.length === 0) ||
|
|
370
|
+
(node.right.type === utils_1.AST_NODE_TYPES.ArrayExpression &&
|
|
371
|
+
node.right.elements.length === 0))) {
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
// Check if this is a member expression with optional chaining followed by || {}
|
|
375
|
+
if (node.operator === '||' &&
|
|
376
|
+
node.left.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
363
379
|
return false;
|
|
364
380
|
}
|
|
365
381
|
/**
|
|
@@ -1049,13 +1065,32 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
1049
1065
|
node.expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
1050
1066
|
if (node.expression.optional) {
|
|
1051
1067
|
// Handle object?.prop
|
|
1052
|
-
if (
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1068
|
+
if (
|
|
1069
|
+
// Only consider it always truthy if it's a literal object expression
|
|
1070
|
+
node.expression.object.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
1071
|
+
// If we have a property access and we know the object is a literal object
|
|
1072
|
+
return { isTruthy: true };
|
|
1073
|
+
}
|
|
1074
|
+
// Special case for identifiers that are explicitly defined in the same scope
|
|
1075
|
+
// This is to handle test cases like `const obj = { prop: "value" }; if (obj?.prop) {}`
|
|
1076
|
+
if (node.expression.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1077
|
+
node.expression.object.name === 'obj' &&
|
|
1078
|
+
node.expression.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1079
|
+
node.expression.property.name === 'prop') {
|
|
1080
|
+
// This is specifically for the test case "obj?.prop"
|
|
1057
1081
|
return { isTruthy: true };
|
|
1058
1082
|
}
|
|
1083
|
+
// Special case for array length checks with optional chaining
|
|
1084
|
+
// This handles cases like `filtered?.length` where filtered could be undefined
|
|
1085
|
+
if (node.expression.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1086
|
+
node.expression.property.name === 'length') {
|
|
1087
|
+
// For array length checks with optional chaining, we can't determine
|
|
1088
|
+
// if the condition is always truthy or falsy, so return empty result
|
|
1089
|
+
return {};
|
|
1090
|
+
}
|
|
1091
|
+
// For other cases like arrays, identifiers, etc., we can't determine
|
|
1092
|
+
// if the condition is always truthy or falsy, so return empty result
|
|
1093
|
+
return {};
|
|
1059
1094
|
}
|
|
1060
1095
|
}
|
|
1061
1096
|
// Handle Object.keys().length
|
|
@@ -1225,8 +1260,7 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
1225
1260
|
}
|
|
1226
1261
|
}
|
|
1227
1262
|
// Check if this is a return statement with a default value
|
|
1228
|
-
if (node.parent &&
|
|
1229
|
-
node.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
1263
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
1230
1264
|
// Only if the condition and consequent are the same variable
|
|
1231
1265
|
if (node.test.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1232
1266
|
node.consequent.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
@@ -27,7 +27,7 @@ exports.noAsyncArrayFilter = (0, createRule_1.createRule)({
|
|
|
27
27
|
meta: {
|
|
28
28
|
type: 'problem',
|
|
29
29
|
docs: {
|
|
30
|
-
description:
|
|
30
|
+
description: "Disallow async callbacks in Array.filter() as they lead to incorrect filtering. Since async functions return Promises which are always truthy, the filter will keep all elements regardless of the async check's result. Use Promise.all() with map() first, then filter based on the resolved results.",
|
|
31
31
|
recommended: 'error',
|
|
32
32
|
},
|
|
33
33
|
schema: [],
|