@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.
Files changed (72) hide show
  1. package/README.md +99 -83
  2. package/lib/index.js +16 -1
  3. package/lib/rules/class-methods-read-top-to-bottom.js +10 -0
  4. package/lib/rules/consistent-callback-naming.js +20 -16
  5. package/lib/rules/enforce-assert-throws.js +10 -5
  6. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +7 -0
  7. package/lib/rules/enforce-boolean-naming-prefixes.js +462 -0
  8. package/lib/rules/enforce-callable-types.d.ts +1 -1
  9. package/lib/rules/enforce-callable-types.js +13 -13
  10. package/lib/rules/enforce-callback-memo.js +6 -3
  11. package/lib/rules/enforce-centralized-mock-firestore.js +8 -6
  12. package/lib/rules/enforce-css-media-queries.js +3 -3
  13. package/lib/rules/enforce-dynamic-file-naming.d.ts +3 -0
  14. package/lib/rules/enforce-dynamic-file-naming.js +82 -0
  15. package/lib/rules/enforce-dynamic-imports.js +1 -1
  16. package/lib/rules/enforce-firestore-doc-ref-generic.js +16 -12
  17. package/lib/rules/enforce-firestore-facade.js +7 -3
  18. package/lib/rules/enforce-firestore-path-utils.js +5 -2
  19. package/lib/rules/enforce-id-capitalization.js +59 -0
  20. package/lib/rules/enforce-identifiable-firestore-type.js +17 -10
  21. package/lib/rules/enforce-memoize-async.js +2 -2
  22. package/lib/rules/enforce-positive-naming.js +71 -27
  23. package/lib/rules/enforce-props-argument-name.js +31 -9
  24. package/lib/rules/enforce-realtimedb-path-utils.js +2 -1
  25. package/lib/rules/enforce-singular-type-names.js +4 -1
  26. package/lib/rules/enforce-timestamp-now.js +3 -4
  27. package/lib/rules/enforce-verb-noun-naming.js +18 -8
  28. package/lib/rules/ensure-pointer-events-none.js +20 -11
  29. package/lib/rules/extract-global-constants.js +25 -10
  30. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +3 -0
  31. package/lib/rules/fast-deep-equal-over-microdiff.js +182 -0
  32. package/lib/rules/key-only-outermost-element.js +13 -7
  33. package/lib/rules/no-always-true-false-conditions.js +189 -5
  34. package/lib/rules/no-async-array-filter.js +1 -1
  35. package/lib/rules/no-circular-references.js +121 -59
  36. package/lib/rules/no-complex-cloud-params.js +4 -2
  37. package/lib/rules/no-entire-object-hook-deps.js +113 -29
  38. package/lib/rules/no-explicit-return-type.js +3 -2
  39. package/lib/rules/no-firestore-jest-mock.js +1 -1
  40. package/lib/rules/no-firestore-object-arrays.js +2 -1
  41. package/lib/rules/no-hungarian.js +18 -1
  42. package/lib/rules/no-jsx-in-hooks.js +4 -3
  43. package/lib/rules/no-margin-properties.d.ts +1 -0
  44. package/lib/rules/no-margin-properties.js +314 -0
  45. package/lib/rules/no-mixed-firestore-transactions.js +8 -4
  46. package/lib/rules/no-mock-firebase-admin.js +6 -1
  47. package/lib/rules/no-object-values-on-strings.js +28 -10
  48. package/lib/rules/no-type-assertion-returns.js +47 -6
  49. package/lib/rules/no-unnecessary-destructuring.js +1 -1
  50. package/lib/rules/no-unnecessary-verb-suffix.js +0 -31
  51. package/lib/rules/no-unused-props.js +10 -7
  52. package/lib/rules/no-unused-usestate.js +19 -6
  53. package/lib/rules/omit-index-html.js +1 -1
  54. package/lib/rules/prefer-batch-operations.js +19 -6
  55. package/lib/rules/prefer-clone-deep.js +16 -11
  56. package/lib/rules/prefer-destructuring-no-class.js +19 -0
  57. package/lib/rules/prefer-fragment-component.js +19 -16
  58. package/lib/rules/prefer-global-router-state-key.js +6 -4
  59. package/lib/rules/prefer-settings-object.js +1 -1
  60. package/lib/rules/prefer-usecallback-over-usememo-for-functions.d.ts +8 -0
  61. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +180 -0
  62. package/lib/rules/react-usememo-should-be-component.js +139 -17
  63. package/lib/rules/require-hooks-default-params.js +25 -15
  64. package/lib/rules/require-usememo-object-literals.js +3 -2
  65. package/lib/rules/semantic-function-prefixes.js +15 -3
  66. package/lib/rules/sync-onwrite-name-func.js +5 -3
  67. package/lib/utils/ASTHelpers.js +6 -2
  68. package/lib/utils/graph/ClassGraphBuilder.js +4 -1
  69. package/lib/utils/graph/ClassGraphSorterReadability.js +3 -1
  70. package/package.json +3 -3
  71. package/lib/rules/require-image-overlayed.d.ts +0 -7
  72. 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 && (callback.type === 'ArrowFunctionExpression' ||
33
- callback.type === 'FunctionExpression')) {
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' && attr.name.name === 'key' && !reportedAttributes.has(attr)) {
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' && mapCallbackElements.has(parent)) ||
93
- (parent.type === 'JSXFragment' && mapCallbackFragments.has(parent))) {
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 && fullText[rangeEnd] === ' ') {
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 (node.expression.object.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
951
- (node.expression.object.type === utils_1.AST_NODE_TYPES.Identifier &&
952
- node.expression.object.name !== 'undefined' &&
953
- node.expression.object.name !== 'null')) {
954
- // If we have a property access and we know the object is defined
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: '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.',
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: [],