@blumintinc/eslint-plugin-blumint 1.10.0 → 1.11.1

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 (70) hide show
  1. package/README.md +99 -84
  2. package/lib/index.js +10 -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.js +9 -7
  14. package/lib/rules/enforce-dynamic-imports.js +1 -1
  15. package/lib/rules/enforce-firestore-doc-ref-generic.js +16 -12
  16. package/lib/rules/enforce-firestore-facade.js +7 -3
  17. package/lib/rules/enforce-firestore-path-utils.js +5 -2
  18. package/lib/rules/enforce-global-constants.d.ts +1 -0
  19. package/lib/rules/enforce-global-constants.js +77 -0
  20. package/lib/rules/enforce-id-capitalization.js +23 -5
  21. package/lib/rules/enforce-identifiable-firestore-type.js +17 -10
  22. package/lib/rules/enforce-memoize-async.js +2 -2
  23. package/lib/rules/enforce-positive-naming.js +71 -27
  24. package/lib/rules/enforce-props-argument-name.js +31 -9
  25. package/lib/rules/enforce-realtimedb-path-utils.js +2 -1
  26. package/lib/rules/enforce-singular-type-names.js +4 -1
  27. package/lib/rules/enforce-timestamp-now.js +3 -4
  28. package/lib/rules/enforce-verb-noun-naming.js +18 -8
  29. package/lib/rules/ensure-pointer-events-none.js +20 -11
  30. package/lib/rules/extract-global-constants.js +25 -10
  31. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +3 -0
  32. package/lib/rules/fast-deep-equal-over-microdiff.js +182 -0
  33. package/lib/rules/key-only-outermost-element.js +13 -7
  34. package/lib/rules/no-always-true-false-conditions.js +52 -18
  35. package/lib/rules/no-async-array-filter.js +1 -1
  36. package/lib/rules/no-circular-references.js +121 -59
  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 +3 -3
  42. package/lib/rules/no-jsx-in-hooks.js +4 -3
  43. package/lib/rules/no-margin-properties.js +25 -18
  44. package/lib/rules/no-mixed-firestore-transactions.js +8 -4
  45. package/lib/rules/no-mock-firebase-admin.js +6 -1
  46. package/lib/rules/no-object-values-on-strings.js +28 -10
  47. package/lib/rules/no-type-assertion-returns.js +13 -1
  48. package/lib/rules/no-unnecessary-destructuring.js +1 -1
  49. package/lib/rules/no-unnecessary-verb-suffix.js +0 -31
  50. package/lib/rules/no-unused-props.js +10 -7
  51. package/lib/rules/no-unused-usestate.js +19 -6
  52. package/lib/rules/omit-index-html.js +1 -1
  53. package/lib/rules/prefer-batch-operations.js +19 -6
  54. package/lib/rules/prefer-clone-deep.js +16 -11
  55. package/lib/rules/prefer-destructuring-no-class.js +19 -0
  56. package/lib/rules/prefer-fragment-component.js +19 -16
  57. package/lib/rules/prefer-global-router-state-key.js +6 -4
  58. package/lib/rules/prefer-settings-object.js +1 -1
  59. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +12 -4
  60. package/lib/rules/react-usememo-should-be-component.js +64 -18
  61. package/lib/rules/require-hooks-default-params.js +25 -15
  62. package/lib/rules/require-usememo-object-literals.js +3 -2
  63. package/lib/rules/semantic-function-prefixes.js +15 -3
  64. package/lib/rules/sync-onwrite-name-func.js +5 -3
  65. package/lib/utils/ASTHelpers.js +6 -2
  66. package/lib/utils/graph/ClassGraphBuilder.js +4 -1
  67. package/lib/utils/graph/ClassGraphSorterReadability.js +3 -1
  68. package/package.json +3 -3
  69. package/lib/rules/require-image-overlayed.d.ts +0 -7
  70. 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
  });
@@ -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 (['map', 'filter', 'find', 'findIndex', 'some', 'every', 'forEach'].includes(methodName)) {
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 (node.expression.object.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
1053
- (node.expression.object.type === utils_1.AST_NODE_TYPES.Identifier &&
1054
- node.expression.object.name !== 'undefined' &&
1055
- node.expression.object.name !== 'null')) {
1056
- // 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"
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: '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: [],