@blumintinc/eslint-plugin-blumint 1.9.1 → 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 -83
- package/lib/index.js +16 -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.d.ts +3 -0
- package/lib/rules/enforce-dynamic-file-naming.js +82 -0
- 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 +59 -0
- 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 +189 -5
- package/lib/rules/no-async-array-filter.js +1 -1
- package/lib/rules/no-circular-references.js +121 -59
- package/lib/rules/no-complex-cloud-params.js +4 -2
- 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 +18 -1
- package/lib/rules/no-jsx-in-hooks.js +4 -3
- package/lib/rules/no-margin-properties.d.ts +1 -0
- package/lib/rules/no-margin-properties.js +314 -0
- 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-type-assertion-returns.js +47 -6
- 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.d.ts +8 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +180 -0
- package/lib/rules/react-usememo-should-be-component.js +139 -17
- 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
|
});
|
|
@@ -276,6 +276,108 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
276
276
|
node.typeAnnotation.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
277
277
|
node.typeAnnotation.typeName.name === 'const');
|
|
278
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Check if a logical expression is being used for default value assignment
|
|
281
|
+
* This includes patterns like:
|
|
282
|
+
* - variable || defaultValue
|
|
283
|
+
* - variable ?? defaultValue
|
|
284
|
+
* - variable && expression
|
|
285
|
+
*/
|
|
286
|
+
function isDefaultValueAssignment(node) {
|
|
287
|
+
// Check if this is a nullish coalescing operator (always used for defaults)
|
|
288
|
+
if (node.operator === '??') {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
// Check if this is in a JSX attribute
|
|
292
|
+
if (node.parent &&
|
|
293
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.JSXAttribute ||
|
|
294
|
+
node.parent.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer)) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
// Check if this is a variable assignment with a default value
|
|
298
|
+
if (node.parent &&
|
|
299
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
300
|
+
node.parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression)) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
// Check if this is a function parameter default value
|
|
304
|
+
if (node.parent &&
|
|
305
|
+
node.parent.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
// Check if this is a return statement with a default value
|
|
309
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
// Check if this is a function argument (common for default values)
|
|
313
|
+
if (node.parent &&
|
|
314
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.CallExpression ||
|
|
315
|
+
node.parent.type === utils_1.AST_NODE_TYPES.NewExpression)) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
// Check if this is part of an object or array expression (common for default values)
|
|
319
|
+
if (node.parent &&
|
|
320
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
|
|
321
|
+
node.parent.type === utils_1.AST_NODE_TYPES.ArrayExpression)) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
// Check if this is inside a property assignment in an object
|
|
325
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.Property) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
// Check if this is inside a template literal expression
|
|
329
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
// Check if this is inside a template element
|
|
333
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.TemplateElement) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
// Check if this is inside a spread element
|
|
337
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
// Check if this is inside an array method callback (map, filter, etc.)
|
|
341
|
+
if (node.parent &&
|
|
342
|
+
node.parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
343
|
+
node.parent.parent &&
|
|
344
|
+
node.parent.parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
345
|
+
node.parent.parent.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
346
|
+
const methodName = node.parent.parent.callee.property.type === utils_1.AST_NODE_TYPES.Identifier
|
|
347
|
+
? node.parent.parent.callee.property.name
|
|
348
|
+
: '';
|
|
349
|
+
if ([
|
|
350
|
+
'map',
|
|
351
|
+
'filter',
|
|
352
|
+
'find',
|
|
353
|
+
'findIndex',
|
|
354
|
+
'some',
|
|
355
|
+
'every',
|
|
356
|
+
'forEach',
|
|
357
|
+
].includes(methodName)) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Check if this is inside an arrow function body
|
|
362
|
+
if (node.parent &&
|
|
363
|
+
node.parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
364
|
+
return true;
|
|
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
|
+
}
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
279
381
|
/**
|
|
280
382
|
* Check logical expressions (&&, ||)
|
|
281
383
|
*/
|
|
@@ -285,6 +387,12 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
285
387
|
// Add children to evaluated list to prevent duplicate evaluations
|
|
286
388
|
evaluatedParentNodes.add(node.left);
|
|
287
389
|
evaluatedParentNodes.add(node.right);
|
|
390
|
+
// Check if this is a default value assignment pattern
|
|
391
|
+
const isDefaultValuePattern = isDefaultValueAssignment(node);
|
|
392
|
+
if (isDefaultValuePattern) {
|
|
393
|
+
// Don't flag default value assignments as always true/false conditions
|
|
394
|
+
return {};
|
|
395
|
+
}
|
|
288
396
|
// For &&: if either side is always falsy, the whole expression is falsy
|
|
289
397
|
if (node.operator === '&&') {
|
|
290
398
|
if (leftResult.isFalsy) {
|
|
@@ -724,6 +832,16 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
724
832
|
}
|
|
725
833
|
// Template literals
|
|
726
834
|
if (node.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
|
|
835
|
+
// Skip template literals that are likely used for string interpolation
|
|
836
|
+
// rather than conditions
|
|
837
|
+
if (node.parent &&
|
|
838
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
839
|
+
node.parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression ||
|
|
840
|
+
node.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement ||
|
|
841
|
+
node.parent.type === utils_1.AST_NODE_TYPES.Property ||
|
|
842
|
+
node.parent.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer)) {
|
|
843
|
+
return {};
|
|
844
|
+
}
|
|
727
845
|
return checkTemplateLiteral(node);
|
|
728
846
|
}
|
|
729
847
|
// Type checking (typeof x === 'string')
|
|
@@ -947,13 +1065,32 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
947
1065
|
node.expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
948
1066
|
if (node.expression.optional) {
|
|
949
1067
|
// Handle object?.prop
|
|
950
|
-
if (
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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"
|
|
955
1081
|
return { isTruthy: true };
|
|
956
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 {};
|
|
957
1094
|
}
|
|
958
1095
|
}
|
|
959
1096
|
// Handle Object.keys().length
|
|
@@ -1092,11 +1229,58 @@ exports.noAlwaysTrueFalseConditions = (0, createRule_1.createRule)({
|
|
|
1092
1229
|
/**
|
|
1093
1230
|
* Check if a condition is always truthy or falsy and report it
|
|
1094
1231
|
*/
|
|
1232
|
+
/**
|
|
1233
|
+
* Check if a ternary expression is being used for default value assignment
|
|
1234
|
+
* This includes patterns like:
|
|
1235
|
+
* - variable ? variable : defaultValue
|
|
1236
|
+
*/
|
|
1237
|
+
function isDefaultValueTernary(node) {
|
|
1238
|
+
// Check if the condition and the consequent are the same variable
|
|
1239
|
+
// This is a common pattern for default values: status ? status : 'offline'
|
|
1240
|
+
if (node.test.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1241
|
+
node.consequent.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1242
|
+
node.test.name === node.consequent.name) {
|
|
1243
|
+
return true;
|
|
1244
|
+
}
|
|
1245
|
+
// Check if this is in a JSX attribute
|
|
1246
|
+
if (node.parent &&
|
|
1247
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.JSXAttribute ||
|
|
1248
|
+
node.parent.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer)) {
|
|
1249
|
+
return true;
|
|
1250
|
+
}
|
|
1251
|
+
// Check if this is a variable assignment with a default value
|
|
1252
|
+
if (node.parent &&
|
|
1253
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
1254
|
+
node.parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression)) {
|
|
1255
|
+
// Only if the condition and consequent are the same variable
|
|
1256
|
+
if (node.test.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1257
|
+
node.consequent.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1258
|
+
node.test.name === node.consequent.name) {
|
|
1259
|
+
return true;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
// Check if this is a return statement with a default value
|
|
1263
|
+
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
1264
|
+
// Only if the condition and consequent are the same variable
|
|
1265
|
+
if (node.test.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1266
|
+
node.consequent.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
1267
|
+
node.test.name === node.consequent.name) {
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
return false;
|
|
1272
|
+
}
|
|
1095
1273
|
function checkCondition(node) {
|
|
1096
1274
|
// Skip if already reported or if it's a part of a larger expression that's been reported
|
|
1097
1275
|
if (reportedNodes.has(node) || evaluatedParentNodes.has(node)) {
|
|
1098
1276
|
return;
|
|
1099
1277
|
}
|
|
1278
|
+
// Check if this is a ternary expression used for default values
|
|
1279
|
+
if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression &&
|
|
1280
|
+
isDefaultValueTernary(node)) {
|
|
1281
|
+
// Skip checking ternaries used for default values
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1100
1284
|
// We should NOT clear evaluatedParentNodes here as that can lead to duplicate evaluations
|
|
1101
1285
|
// and miss detection of conditions in nested expressions
|
|
1102
1286
|
// Check for nested expressions to avoid multiple errors
|
|
@@ -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: [],
|