@blumintinc/eslint-plugin-blumint 1.12.1 → 1.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -103,10 +103,12 @@ const enforce_dynamic_file_naming_1 = __importDefault(require("./rules/enforce-d
103
103
  const prefer_usecallback_over_usememo_for_functions_1 = __importDefault(require("./rules/prefer-usecallback-over-usememo-for-functions"));
104
104
  const no_margin_properties_1 = require("./rules/no-margin-properties");
105
105
  const enforce_boolean_naming_prefixes_1 = require("./rules/enforce-boolean-naming-prefixes");
106
+ const prefer_block_comments_for_declarations_1 = require("./rules/prefer-block-comments-for-declarations");
107
+ const no_undefined_null_passthrough_1 = require("./rules/no-undefined-null-passthrough");
106
108
  module.exports = {
107
109
  meta: {
108
110
  name: '@blumintinc/eslint-plugin-blumint',
109
- version: '1.12.1',
111
+ version: '1.12.3',
110
112
  },
111
113
  parseOptions: {
112
114
  ecmaVersion: 2020,
@@ -115,6 +117,7 @@ module.exports = {
115
117
  recommended: {
116
118
  plugins: ['@blumintinc/blumint'],
117
119
  rules: {
120
+ '@blumintinc/blumint/prefer-block-comments-for-declarations': 'error',
118
121
  '@blumintinc/blumint/key-only-outermost-element': 'error',
119
122
  '@blumintinc/blumint/avoid-utils-directory': 'error',
120
123
  '@blumintinc/blumint/enforce-firestore-path-utils': 'error',
@@ -221,10 +224,12 @@ module.exports = {
221
224
  '@blumintinc/blumint/prefer-usecallback-over-usememo-for-functions': 'error',
222
225
  '@blumintinc/blumint/no-margin-properties': 'error',
223
226
  '@blumintinc/blumint/enforce-boolean-naming-prefixes': 'error',
227
+ '@blumintinc/blumint/no-undefined-null-passthrough': 'error',
224
228
  },
225
229
  },
226
230
  },
227
231
  rules: {
232
+ 'prefer-block-comments-for-declarations': prefer_block_comments_for_declarations_1.preferBlockCommentsForDeclarations,
228
233
  'key-only-outermost-element': key_only_outermost_element_1.keyOnlyOutermostElement,
229
234
  'array-methods-this-context': array_methods_this_context_1.arrayMethodsThisContext,
230
235
  'class-methods-read-top-to-bottom': class_methods_read_top_to_bottom_1.classMethodsReadTopToBottom,
@@ -325,6 +330,7 @@ module.exports = {
325
330
  'prefer-usecallback-over-usememo-for-functions': prefer_usecallback_over_usememo_for_functions_1.default,
326
331
  'no-margin-properties': no_margin_properties_1.noMarginProperties,
327
332
  'enforce-boolean-naming-prefixes': enforce_boolean_naming_prefixes_1.enforceBooleanNamingPrefixes,
333
+ 'no-undefined-null-passthrough': no_undefined_null_passthrough_1.noUndefinedNullPassthrough,
328
334
  },
329
335
  };
330
336
  //# sourceMappingURL=index.js.map
@@ -20,6 +20,7 @@ const DEFAULT_BOOLEAN_PREFIXES = [
20
20
  'supports',
21
21
  'needs',
22
22
  'asserts',
23
+ 'includes',
23
24
  ];
24
25
  exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
25
26
  name: 'enforce-boolean-naming-prefixes',
@@ -119,9 +120,48 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
119
120
  ['===', '!==', '==', '!=', '>', '<', '>=', '<='].includes(node.init.operator)) {
120
121
  return true;
121
122
  }
122
- // Check for logical expressions (&&, ||)
123
+ // Check for logical expressions (&&)
123
124
  if (node.init.type === utils_1.AST_NODE_TYPES.LogicalExpression &&
124
125
  node.init.operator === '&&') {
126
+ // Check if the right side is a method call that might return a non-boolean value
127
+ const rightSide = node.init.right;
128
+ if (rightSide.type === utils_1.AST_NODE_TYPES.CallExpression) {
129
+ // If the method name doesn't suggest it returns a boolean, don't flag it
130
+ if (rightSide.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
131
+ rightSide.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
132
+ const methodName = rightSide.callee.property.name;
133
+ // Check if the method name suggests it returns a boolean
134
+ const isBooleanMethod = approvedPrefixes.some((prefix) => methodName.toLowerCase().startsWith(prefix.toLowerCase()));
135
+ // If the method name suggests it returns a boolean (starts with a boolean prefix or contains 'boolean' or 'enabled'),
136
+ // then the variable should be treated as a boolean
137
+ if (isBooleanMethod ||
138
+ methodName.toLowerCase().includes('boolean') ||
139
+ methodName.toLowerCase().includes('enabled') ||
140
+ methodName.toLowerCase().includes('auth') ||
141
+ methodName.toLowerCase().includes('valid') ||
142
+ methodName.toLowerCase().includes('check')) {
143
+ return true;
144
+ }
145
+ // For methods like getVolume(), getData(), etc., assume they return non-boolean values
146
+ if (methodName.toLowerCase().startsWith('get') ||
147
+ methodName.toLowerCase().startsWith('fetch') ||
148
+ methodName.toLowerCase().startsWith('retrieve') ||
149
+ methodName.toLowerCase().startsWith('load') ||
150
+ methodName.toLowerCase().startsWith('read')) {
151
+ return false;
152
+ }
153
+ }
154
+ }
155
+ // For property access like user.isAuthenticated, treat as boolean
156
+ if (rightSide.type === utils_1.AST_NODE_TYPES.MemberExpression &&
157
+ rightSide.property.type === utils_1.AST_NODE_TYPES.Identifier) {
158
+ const propertyName = rightSide.property.name;
159
+ const isBooleanProperty = approvedPrefixes.some((prefix) => propertyName.toLowerCase().startsWith(prefix.toLowerCase()));
160
+ if (isBooleanProperty) {
161
+ return true;
162
+ }
163
+ }
164
+ // Default to true for other cases with && to avoid false negatives
125
165
  return true;
126
166
  }
127
167
  // Special case for logical OR (||) - only consider it boolean if:
@@ -7,6 +7,68 @@ const FIRESTORE_METHODS = new Set(['get', 'set', 'update', 'delete']);
7
7
  const isMemberExpression = (node) => {
8
8
  return node.type === utils_1.AST_NODE_TYPES.MemberExpression;
9
9
  };
10
+ // Track variables that are assigned realtimeDb references
11
+ const realtimeDbRefVariables = new Set();
12
+ const realtimeDbChildVariables = new Set();
13
+ const isRealtimeDbRefAssignment = (node) => {
14
+ if (node.type !== utils_1.AST_NODE_TYPES.VariableDeclarator)
15
+ return false;
16
+ const init = node.init;
17
+ if (!init)
18
+ return false;
19
+ // Check for direct realtimeDb.ref() assignments
20
+ // e.g., const ref = realtimeDb.ref(path);
21
+ if (init.type === utils_1.AST_NODE_TYPES.CallExpression &&
22
+ isMemberExpression(init.callee) &&
23
+ isIdentifier(init.callee.property) &&
24
+ init.callee.property.name === 'ref' &&
25
+ isIdentifier(init.callee.object) &&
26
+ (init.callee.object.name === 'realtimeDb' || init.callee.object.name.includes('realtimeDb'))) {
27
+ if (isIdentifier(node.id)) {
28
+ realtimeDbRefVariables.add(node.id.name);
29
+ return true;
30
+ }
31
+ }
32
+ // Check for child() method calls on realtimeDb refs
33
+ // e.g., const childRef = parentRef.child('path');
34
+ if (init.type === utils_1.AST_NODE_TYPES.CallExpression &&
35
+ isMemberExpression(init.callee) &&
36
+ isIdentifier(init.callee.property) &&
37
+ init.callee.property.name === 'child' &&
38
+ isIdentifier(init.callee.object) &&
39
+ realtimeDbRefVariables.has(init.callee.object.name)) {
40
+ if (isIdentifier(node.id)) {
41
+ realtimeDbChildVariables.add(node.id.name);
42
+ return true;
43
+ }
44
+ }
45
+ return false;
46
+ };
47
+ const isRealtimeDbReference = (node) => {
48
+ // Check if it's a direct realtimeDb.ref() call
49
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
50
+ isMemberExpression(node.callee) &&
51
+ isIdentifier(node.callee.property) &&
52
+ node.callee.property.name === 'ref' &&
53
+ isIdentifier(node.callee.object) &&
54
+ (node.callee.object.name === 'realtimeDb' || node.callee.object.name.includes('realtimeDb'))) {
55
+ return true;
56
+ }
57
+ // Check if it's a variable that holds a realtimeDb reference
58
+ if (isIdentifier(node)) {
59
+ return realtimeDbRefVariables.has(node.name) || realtimeDbChildVariables.has(node.name);
60
+ }
61
+ // Check if it's a child() call on a realtimeDb reference
62
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
63
+ isMemberExpression(node.callee) &&
64
+ isIdentifier(node.callee.property) &&
65
+ node.callee.property.name === 'child' &&
66
+ isIdentifier(node.callee.object) &&
67
+ (realtimeDbRefVariables.has(node.callee.object.name) || realtimeDbChildVariables.has(node.callee.object.name))) {
68
+ return true;
69
+ }
70
+ return false;
71
+ };
10
72
  const isFirestoreMethodCall = (node) => {
11
73
  if (!isMemberExpression(node.callee))
12
74
  return false;
@@ -24,11 +86,19 @@ const isFirestoreMethodCall = (node) => {
24
86
  name.includes('Tx')) {
25
87
  return false;
26
88
  }
89
+ // Skip if it's a realtimeDb reference variable
90
+ if (realtimeDbRefVariables.has(name) || realtimeDbChildVariables.has(name)) {
91
+ return false;
92
+ }
27
93
  // Check for batch or transaction
28
94
  if (/batch|transaction/i.test(name)) {
29
95
  return true;
30
96
  }
31
97
  }
98
+ // Check if the method is called on a realtimeDb reference
99
+ if (isRealtimeDbReference(object)) {
100
+ return false;
101
+ }
32
102
  // Handle type assertions (as in the bug report)
33
103
  if (object.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
34
104
  return true;
@@ -41,10 +111,12 @@ const isFirestoreMethodCall = (node) => {
41
111
  const callee = current.callee;
42
112
  if (isMemberExpression(callee)) {
43
113
  const property = callee.property;
44
- if (isIdentifier(property) &&
45
- (property.name === 'doc' || property.name === 'collection')) {
46
- foundDocOrCollection = true;
47
- break;
114
+ if (isIdentifier(property)) {
115
+ // Check for Firestore methods
116
+ if (property.name === 'doc' || property.name === 'collection') {
117
+ foundDocOrCollection = true;
118
+ break;
119
+ }
48
120
  }
49
121
  }
50
122
  }
@@ -63,8 +135,11 @@ const isFirestoreMethodCall = (node) => {
63
135
  if (!foundDocOrCollection && isIdentifier(object)) {
64
136
  const name = object.name;
65
137
  // If the variable name contains 'doc' or 'ref', it's likely a Firestore reference
66
- if (name.toLowerCase().includes('doc') ||
67
- name.toLowerCase().includes('ref')) {
138
+ // But exclude realtimeDb references
139
+ if ((name.toLowerCase().includes('doc') || name.toLowerCase().includes('ref')) &&
140
+ !name.includes('realtimeDb') &&
141
+ !realtimeDbRefVariables.has(name) &&
142
+ !realtimeDbChildVariables.has(name)) {
68
143
  return true;
69
144
  }
70
145
  }
@@ -94,7 +169,14 @@ exports.enforceFirestoreFacade = (0, createRule_1.createRule)({
94
169
  },
95
170
  defaultOptions: [],
96
171
  create(context) {
172
+ // Clear the sets at the beginning of each file analysis
173
+ realtimeDbRefVariables.clear();
174
+ realtimeDbChildVariables.clear();
97
175
  return {
176
+ // Track variable declarations that are assigned realtimeDb references
177
+ VariableDeclarator(node) {
178
+ isRealtimeDbRefAssignment(node);
179
+ },
98
180
  CallExpression(node) {
99
181
  if (!isFirestoreMethodCall(node))
100
182
  return;
@@ -71,8 +71,8 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
71
71
  'fast-diff',
72
72
  'diff',
73
73
  'deep-object-diff',
74
- 'fast-deep-equal',
75
- 'fast-deep-equal/es6',
74
+ // Removed 'fast-deep-equal' and 'fast-deep-equal/es6' from this list
75
+ // as they are allowed alternatives to microdiff
76
76
  ].includes(importSource)) {
77
77
  // Track imported function names and their sources
78
78
  node.specifiers.forEach((specifier) => {
@@ -84,13 +84,7 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
84
84
  const localName = specifier.local.name;
85
85
  importedFunctions.set(localName, importSource);
86
86
  }
87
- else if (importSource === 'fast-deep-equal' ||
88
- importSource === 'fast-deep-equal/es6') {
89
- // Handle default imports for fast-deep-equal
90
- if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
91
- importedFunctions.set(specifier.local.name, importSource);
92
- }
93
- }
87
+ // Removed the fast-deep-equal handling since it's now allowed
94
88
  });
95
89
  // Report all competing diffing libraries right away
96
90
  context.report({
@@ -117,12 +111,22 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
117
111
  }
118
112
  return false;
119
113
  });
120
- if (hasDiffImport ||
121
- importSource === 'fast-deep-equal' ||
122
- importSource === 'fast-deep-equal/es6') {
114
+ if (hasDiffImport) {
123
115
  importedDiffLibraries.set(importSource, node);
124
116
  }
125
117
  }
118
+ // Special handling for fast-deep-equal: track it but don't report it
119
+ if (importSource === 'fast-deep-equal' ||
120
+ importSource === 'fast-deep-equal/es6') {
121
+ // Track imported function names for later reference
122
+ node.specifiers.forEach((specifier) => {
123
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
124
+ importedFunctions.set(specifier.local.name, importSource);
125
+ }
126
+ });
127
+ // Add to importedDiffLibraries for tracking but don't report
128
+ importedDiffLibraries.set(importSource, node);
129
+ }
126
130
  },
127
131
  // Check for usage of other diffing libraries
128
132
  CallExpression(node) {
@@ -137,7 +141,13 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
137
141
  // Check if this is a function we specifically imported from a diff library
138
142
  if (importedFunctions.has(name)) {
139
143
  usedImportNames.add(name);
140
- // Always report it if it's from a tracked library
144
+ // Get the source of the imported function
145
+ const importSource = importedFunctions.get(name);
146
+ // Skip reporting if it's from fast-deep-equal
147
+ if (importSource === 'fast-deep-equal' || importSource === 'fast-deep-equal/es6') {
148
+ return;
149
+ }
150
+ // Report it if it's from any other tracked library
141
151
  reportedNodes.add(node);
142
152
  context.report({
143
153
  node,
@@ -154,8 +164,7 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
154
164
  'fastDiff',
155
165
  'diffArrays',
156
166
  'detailedDiff',
157
- 'fastDeepEqual',
158
- 'isEqual',
167
+ // Removed 'fastDeepEqual' and 'isEqual' as they are allowed alternatives
159
168
  ].includes(name);
160
169
  if (isDiffFunction) {
161
170
  // Track this import name as used
@@ -824,6 +824,26 @@ const DIS_EXCEPTIONS = [
824
824
  'disaster',
825
825
  'disasters',
826
826
  'disastrous',
827
+ // Added common technical terms containing 'dis' that are not negative
828
+ 'display',
829
+ 'displayed',
830
+ 'displaying',
831
+ 'displays',
832
+ 'dispatch',
833
+ 'dispatched',
834
+ 'dispatches',
835
+ 'dispatching',
836
+ 'discover',
837
+ 'discovered',
838
+ 'discovering',
839
+ 'discovers',
840
+ 'discovery',
841
+ 'dismiss',
842
+ 'dismissed',
843
+ 'dismissing',
844
+ 'dismissal',
845
+ 'disk',
846
+ 'disks',
827
847
  ];
828
848
  // Words that contain negative prefixes but should be treated as valid
829
849
  const EXCEPTION_WORDS = [
@@ -920,6 +940,10 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
920
940
  * Check if a name has boolean negative naming
921
941
  */
922
942
  function hasBooleanNegativeNaming(name) {
943
+ // Safety check: if name is undefined or null, return not negative
944
+ if (!name) {
945
+ return { isNegative: false, alternatives: [] };
946
+ }
923
947
  // Check for exact matches in our alternatives map first
924
948
  if (BOOLEAN_POSITIVE_ALTERNATIVES[name]) {
925
949
  return {
@@ -930,9 +954,12 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
930
954
  // Split the name into words
931
955
  const words = splitNameIntoWords(name);
932
956
  // Check if this follows the pattern IS_NOT_SOMETHING or HAS_NO_SOMETHING
933
- const secondWord = words[1].toLowerCase();
934
- if (EXCEPTION_WORDS.some((exception) => secondWord === exception.toLowerCase())) {
935
- return { isNegative: false, alternatives: [] };
957
+ // Make sure words[1] exists before trying to access it
958
+ if (words.length > 1) {
959
+ const secondWord = words[1].toLowerCase();
960
+ if (EXCEPTION_WORDS.some((exception) => secondWord === exception.toLowerCase())) {
961
+ return { isNegative: false, alternatives: [] };
962
+ }
936
963
  }
937
964
  const nameLowercase = name.toLowerCase();
938
965
  // Check for negative prefixes in boolean-like variables
@@ -1193,6 +1220,7 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
1193
1220
  * Check TSPropertySignature for negative naming (in interfaces)
1194
1221
  */
1195
1222
  function checkPropertySignature(node) {
1223
+ // Skip non-identifier keys (like computed properties)
1196
1224
  if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
1197
1225
  return;
1198
1226
  // Only check boolean properties
@@ -1200,7 +1228,10 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
1200
1228
  !(node.typeAnnotation?.typeAnnotation.type ===
1201
1229
  utils_1.AST_NODE_TYPES.TSBooleanKeyword))
1202
1230
  return;
1231
+ // Ensure we have a valid property name
1203
1232
  const propertyName = node.key.name;
1233
+ if (!propertyName)
1234
+ return;
1204
1235
  const { isNegative, alternatives } = hasBooleanNegativeNaming(propertyName);
1205
1236
  if (isNegative) {
1206
1237
  context.report({
@@ -210,8 +210,9 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
210
210
  MethodDefinition(node) {
211
211
  if (!node.value.returnType)
212
212
  return;
213
- if (mergedOptions.allowAbstractMethodSignatures &&
214
- isInterfaceOrAbstractMethodSignature(node)) {
213
+ if (isTypeGuardFunction(node.value) ||
214
+ (mergedOptions.allowAbstractMethodSignatures &&
215
+ isInterfaceOrAbstractMethodSignature(node))) {
215
216
  return;
216
217
  }
217
218
  context.report({
@@ -45,6 +45,12 @@ const ALLOWED_SUFFIXES = [
45
45
  'Display',
46
46
  'Displayed',
47
47
  ];
48
+ // Common compound nouns that should not be flagged as Hungarian notation
49
+ const ALLOWED_COMPOUND_NOUNS = [
50
+ 'PhoneNumber',
51
+ 'EmailAddress',
52
+ 'PostalCode',
53
+ ];
48
54
  // Common built-in JavaScript prototype methods
49
55
  const BUILT_IN_METHODS = new Set([
50
56
  // String methods
@@ -208,6 +214,25 @@ exports.noHungarian = (0, createRule_1.createRule)({
208
214
  const checkedIdentifiers = new Set();
209
215
  // Check if a variable name contains a type marker with proper word boundaries
210
216
  function hasTypeMarker(variableName) {
217
+ // Check if the variable name is exactly one of the allowed compound nouns
218
+ // or if it contains one of the allowed compound nouns but is not a prefix like "strPhoneNumber"
219
+ for (const compoundNoun of ALLOWED_COMPOUND_NOUNS) {
220
+ // If the variable name is exactly the compound noun (case-insensitive)
221
+ if (variableName.toLowerCase() === compoundNoun.toLowerCase()) {
222
+ return false;
223
+ }
224
+ // If the variable name contains the compound noun
225
+ if (variableName.includes(compoundNoun)) {
226
+ // Check if it's a prefix like "strPhoneNumber" (which should be flagged)
227
+ const prefix = variableName.substring(0, variableName.indexOf(compoundNoun));
228
+ if (TYPE_MARKERS.some(marker => prefix.toLowerCase() === marker.toLowerCase())) {
229
+ // This is a type marker prefix + compound noun, so it should be flagged
230
+ return true;
231
+ }
232
+ // Otherwise, it's a valid use of the compound noun
233
+ return false;
234
+ }
235
+ }
211
236
  // Check if the variable name ends with one of the allowed descriptive suffixes
212
237
  if (ALLOWED_SUFFIXES.some((suffix) => variableName.endsWith(suffix) &&
213
238
  variableName.length > suffix.length &&
@@ -216,8 +241,12 @@ exports.noHungarian = (0, createRule_1.createRule)({
216
241
  }
217
242
  const normalizedVarName = variableName.toLowerCase();
218
243
  // Handle SCREAMING_SNAKE_CASE separately
219
- if (variableName === variableName.toUpperCase() &&
220
- variableName.includes('_')) {
244
+ if (variableName === variableName.toUpperCase()) {
245
+ // Special case for all-caps variables without underscores (like BREAKPOINTS)
246
+ // These should not be flagged as Hungarian notation
247
+ if (!variableName.includes('_')) {
248
+ return false;
249
+ }
221
250
  return TYPE_MARKERS.some((marker) => {
222
251
  const markerUpper = marker.toUpperCase();
223
252
  // Check if it's a prefix (PREFIX_REST)
@@ -232,6 +261,7 @@ exports.noHungarian = (0, createRule_1.createRule)({
232
261
  }
233
262
  // Check if it's in the middle (PART_MARKER_PART)
234
263
  const parts = variableName.split('_');
264
+ // Only consider exact matches for parts, not substrings
235
265
  return parts.some((part) => part === markerUpper);
236
266
  });
237
267
  }
@@ -254,6 +284,8 @@ exports.noHungarian = (0, createRule_1.createRule)({
254
284
  /[A-Z0-9]/.test(variableName[variableName.length - normalizedMarker.length - 1])) {
255
285
  return true;
256
286
  }
287
+ // Check for word boundaries to avoid matching substrings
288
+ // For example, avoid matching "int" in "points" or "str" in "stream"
257
289
  const markerIndex = normalizedVarName.indexOf(normalizedMarker);
258
290
  if (markerIndex === -1) {
259
291
  return false;
@@ -261,10 +293,15 @@ exports.noHungarian = (0, createRule_1.createRule)({
261
293
  const markerPrefix = variableName.at(markerIndex);
262
294
  const preMarkerPrefix = variableName.at(markerIndex - 1);
263
295
  const suffix = variableName.at(markerIndex + normalizedMarker.length);
264
- return ((!markerPrefix ||
265
- preMarkerPrefix === '_' ||
266
- /[A-Z]/.test(markerPrefix)) &&
267
- (!suffix || suffix === '_' || /[A-Z]/.test(suffix)));
296
+ // Ensure we have proper word boundaries
297
+ // A word boundary is defined by:
298
+ // 1. Start of string OR underscore OR capital letter before the marker
299
+ // 2. End of string OR underscore OR capital letter after the marker
300
+ const hasStartBoundary = markerIndex === 0 || preMarkerPrefix === '_' || /[A-Z]/.test(markerPrefix || '');
301
+ const hasEndBoundary = markerIndex + normalizedMarker.length === normalizedVarName.length ||
302
+ suffix === '_' ||
303
+ /[A-Z]/.test(suffix || '');
304
+ return hasStartBoundary && hasEndBoundary;
268
305
  });
269
306
  }
270
307
  // Check if the identifier is a built-in method or imported from an external module
@@ -193,12 +193,23 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
193
193
  }
194
194
  return false;
195
195
  }
196
+ /**
197
+ * Check if the current file is a .f.ts file
198
+ */
199
+ function isFunctionFile(context) {
200
+ const filename = context.getFilename();
201
+ return filename.endsWith('.f.ts');
202
+ }
196
203
  /**
197
204
  * Common function to check function return types
198
205
  */
199
206
  function checkFunctionReturnType(node) {
200
207
  if (!node.returnType)
201
208
  return;
209
+ // Skip checking return types in .f.ts files
210
+ if (isFunctionFile(context)) {
211
+ return;
212
+ }
202
213
  // Allow type predicates if configured
203
214
  if (mergedOptions.allowTypePredicates &&
204
215
  isTypePredicate(node.returnType)) {
@@ -283,6 +294,10 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
283
294
  if (node.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
284
295
  // Check for explicit return type
285
296
  if (node.returnType) {
297
+ // Skip checking return types in .f.ts files
298
+ if (isFunctionFile(context)) {
299
+ return;
300
+ }
286
301
  // Allow type predicates if configured
287
302
  if (mergedOptions.allowTypePredicates &&
288
303
  isTypePredicate(node.returnType)) {
@@ -0,0 +1,2 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noUndefinedNullPassthrough: TSESLint.RuleModule<'unexpected', never[]>;
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noUndefinedNullPassthrough = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ exports.noUndefinedNullPassthrough = (0, createRule_1.createRule)({
6
+ create(context) {
7
+ return {
8
+ FunctionDeclaration(node) {
9
+ // Only apply to functions with exactly one parameter
10
+ if (node.params.length !== 1) {
11
+ return;
12
+ }
13
+ // Skip React hooks (functions starting with 'use')
14
+ if (node.id &&
15
+ node.id.type === 'Identifier' &&
16
+ node.id.name.startsWith('use')) {
17
+ return;
18
+ }
19
+ checkFunctionBody(node.body, node.params[0], context);
20
+ },
21
+ ArrowFunctionExpression(node) {
22
+ // Only apply to arrow functions with exactly one parameter
23
+ if (node.params.length !== 1) {
24
+ return;
25
+ }
26
+ // Skip if the function is part of a variable declaration that starts with 'use' (React hook)
27
+ const parent = node.parent;
28
+ if (parent &&
29
+ parent.type === 'VariableDeclarator' &&
30
+ parent.id.type === 'Identifier' &&
31
+ parent.id.name.startsWith('use')) {
32
+ return;
33
+ }
34
+ // For arrow functions with block body
35
+ if (node.body.type === 'BlockStatement') {
36
+ checkFunctionBody(node.body, node.params[0], context);
37
+ }
38
+ else {
39
+ // For arrow functions with expression body (implicit return)
40
+ checkImplicitReturn(node, context);
41
+ }
42
+ },
43
+ FunctionExpression(node) {
44
+ // Only apply to functions with exactly one parameter
45
+ if (node.params.length !== 1) {
46
+ return;
47
+ }
48
+ // Skip if the function is part of a variable declaration that starts with 'use' (React hook)
49
+ const parent = node.parent;
50
+ if (parent &&
51
+ parent.type === 'VariableDeclarator' &&
52
+ parent.id.type === 'Identifier' &&
53
+ parent.id.name.startsWith('use')) {
54
+ return;
55
+ }
56
+ checkFunctionBody(node.body, node.params[0], context);
57
+ },
58
+ };
59
+ },
60
+ name: 'no-undefined-null-passthrough',
61
+ meta: {
62
+ type: 'suggestion',
63
+ docs: {
64
+ description: 'Avoid functions that return undefined or null when their single argument is undefined or null',
65
+ recommended: 'error',
66
+ },
67
+ schema: [],
68
+ messages: {
69
+ unexpected: 'Avoid functions that return undefined or null when their single argument is undefined or null. Move the null/undefined check to the caller instead.',
70
+ },
71
+ },
72
+ defaultOptions: [],
73
+ });
74
+ /**
75
+ * Check function body for early returns when parameter is null/undefined
76
+ */
77
+ function checkFunctionBody(body, param, context) {
78
+ // Get the parameter name
79
+ let paramName = null;
80
+ if (param.type === 'Identifier') {
81
+ paramName = param.name;
82
+ }
83
+ else if (param.type === 'AssignmentPattern' &&
84
+ param.left.type === 'Identifier') {
85
+ paramName = param.left.name;
86
+ }
87
+ if (!paramName)
88
+ return;
89
+ // Look for early returns based on parameter being null/undefined
90
+ for (const statement of body.body) {
91
+ if (statement.type === 'IfStatement') {
92
+ const test = statement.test;
93
+ // Check for patterns like: if (!param) return;
94
+ // or if (param === null) return;
95
+ // or if (param === undefined) return;
96
+ if (isNullUndefinedCheck(test, paramName)) {
97
+ // Check if the consequent is a block statement with a return
98
+ if (statement.consequent.type === 'BlockStatement') {
99
+ for (const consequentStmt of statement.consequent.body) {
100
+ if (consequentStmt.type === 'ReturnStatement' &&
101
+ (!consequentStmt.argument ||
102
+ isNullOrUndefinedLiteral(consequentStmt.argument))) {
103
+ // Check if there's a transformation in the function
104
+ const hasTransformation = checkForTransformation(body, paramName);
105
+ if (!hasTransformation) {
106
+ context.report({
107
+ node: statement,
108
+ messageId: 'unexpected',
109
+ });
110
+ }
111
+ return;
112
+ }
113
+ }
114
+ }
115
+ // Check if the consequent is a direct return statement
116
+ else if (statement.consequent.type === 'ReturnStatement' &&
117
+ (!statement.consequent.argument ||
118
+ isNullOrUndefinedLiteral(statement.consequent.argument))) {
119
+ // Check if there's a transformation in the function
120
+ const hasTransformation = checkForTransformation(body, paramName);
121
+ if (!hasTransformation) {
122
+ context.report({
123
+ node: statement,
124
+ messageId: 'unexpected',
125
+ });
126
+ }
127
+ return;
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ /**
134
+ * Check if the function body contains a transformation of the parameter
135
+ */
136
+ function checkForTransformation(body, paramName) {
137
+ if (!paramName)
138
+ return false;
139
+ // Look for return statements that call functions with the parameter
140
+ for (const statement of body.body) {
141
+ if (statement.type === 'ReturnStatement' && statement.argument) {
142
+ // Check for return transformData(data) pattern
143
+ if (statement.argument.type === 'CallExpression' &&
144
+ statement.argument.arguments.some(arg => arg.type === 'Identifier' && arg.name === paramName)) {
145
+ return true;
146
+ }
147
+ }
148
+ }
149
+ return false;
150
+ }
151
+ /**
152
+ * Check arrow functions with expression bodies (implicit returns)
153
+ */
154
+ function checkImplicitReturn(node, context) {
155
+ // Get the parameter name
156
+ let paramName = null;
157
+ if (node.params[0].type === 'Identifier') {
158
+ paramName = node.params[0].name;
159
+ }
160
+ else if (node.params[0].type === 'AssignmentPattern' &&
161
+ node.params[0].left.type === 'Identifier') {
162
+ paramName = node.params[0].left.name;
163
+ }
164
+ if (!paramName)
165
+ return;
166
+ // Check for patterns like: (param) => param ? param.value : null
167
+ if (node.body.type === 'ConditionalExpression') {
168
+ const test = node.body.test;
169
+ if (isParameterReference(test, paramName) &&
170
+ isNullOrUndefinedLiteral(node.body.alternate)) {
171
+ context.report({
172
+ node,
173
+ messageId: 'unexpected',
174
+ });
175
+ }
176
+ }
177
+ else if (node.body.type === 'LogicalExpression') {
178
+ // Check for (param) => param && doSomething(param)
179
+ if (node.body.operator === '&&' &&
180
+ isParameterReference(node.body.left, paramName)) {
181
+ context.report({
182
+ node,
183
+ messageId: 'unexpected',
184
+ });
185
+ }
186
+ }
187
+ else if (node.body.type === 'Identifier' && node.body.name === paramName) {
188
+ // Check for (param) => param
189
+ context.report({
190
+ node,
191
+ messageId: 'unexpected',
192
+ });
193
+ }
194
+ }
195
+ /**
196
+ * Check if an expression is testing if a parameter is null or undefined
197
+ */
198
+ function isNullUndefinedCheck(node, paramName) {
199
+ // Check for !param
200
+ if (node.type === 'UnaryExpression' &&
201
+ node.operator === '!' &&
202
+ node.argument.type === 'Identifier' &&
203
+ node.argument.name === paramName) {
204
+ return true;
205
+ }
206
+ // Check for param === null, param === undefined, etc.
207
+ if (node.type === 'BinaryExpression' &&
208
+ (node.operator === '===' || node.operator === '==' ||
209
+ node.operator === '!==' || node.operator === '!=')) {
210
+ const left = node.left;
211
+ const right = node.right;
212
+ // param === null/undefined
213
+ if (left.type === 'Identifier' &&
214
+ left.name === paramName &&
215
+ isNullOrUndefinedLiteral(right)) {
216
+ return true;
217
+ }
218
+ // null/undefined === param
219
+ if (right.type === 'Identifier' &&
220
+ right.name === paramName &&
221
+ left.type !== 'PrivateIdentifier' && // Ensure left is not a PrivateIdentifier
222
+ isNullOrUndefinedLiteral(left)) {
223
+ return true;
224
+ }
225
+ }
226
+ // Check for param === null || param === undefined
227
+ if (node.type === 'LogicalExpression' &&
228
+ node.operator === '||') {
229
+ return (isNullUndefinedCheck(node.left, paramName) ||
230
+ isNullUndefinedCheck(node.right, paramName));
231
+ }
232
+ return false;
233
+ }
234
+ /**
235
+ * Check if a node is a null or undefined literal
236
+ */
237
+ function isNullOrUndefinedLiteral(node) {
238
+ if (node.type === 'Literal' && node.value === null) {
239
+ return true;
240
+ }
241
+ if (node.type === 'Identifier' && node.name === 'undefined') {
242
+ return true;
243
+ }
244
+ return false;
245
+ }
246
+ /**
247
+ * Check if a node is a reference to the parameter
248
+ */
249
+ function isParameterReference(node, paramName) {
250
+ if (!paramName)
251
+ return false;
252
+ return node.type === 'Identifier' && node.name === paramName;
253
+ }
254
+ //# sourceMappingURL=no-undefined-null-passthrough.js.map
@@ -111,6 +111,14 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
111
111
  }
112
112
  else if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
113
113
  if (typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
114
+ // List of TypeScript utility types that transform other types
115
+ const utilityTypes = ['Pick', 'Omit', 'Partial', 'Required', 'Record', 'Exclude', 'Extract', 'NonNullable', 'ReturnType', 'InstanceType', 'ThisType'];
116
+ // Skip checking for utility type parameters (T, K, etc.) as they're not actual props
117
+ if (typeNode.typeName.name.length === 1 && /^[A-Z]$/.test(typeNode.typeName.name)) {
118
+ // This is likely a generic type parameter (T, K, etc.), not a real type
119
+ // Skip it to avoid false positives
120
+ return;
121
+ }
114
122
  if (typeNode.typeName.name === 'Pick' &&
115
123
  typeNode.typeParameters) {
116
124
  // Handle Pick utility type
@@ -149,6 +157,23 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
149
157
  }
150
158
  }
151
159
  }
160
+ else if (
161
+ // Handle other utility types like Required, Partial, etc.
162
+ utilityTypes.includes(typeNode.typeName.name) &&
163
+ typeNode.typeParameters) {
164
+ // For utility types like Required<T, K>, we need to handle the base type
165
+ const baseType = typeNode.typeParameters.params[0];
166
+ if (baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
167
+ baseType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
168
+ // Mark the base type as used via the utility type
169
+ const baseTypeName = baseType.typeName.name;
170
+ props[`...${baseTypeName}`] = baseType.typeName;
171
+ // For utility types, we need to track individual properties that might be used
172
+ if (!spreadTypeProps[baseTypeName]) {
173
+ spreadTypeProps[baseTypeName] = [];
174
+ }
175
+ }
176
+ }
152
177
  else {
153
178
  // For referenced types like FormControlLabelProps, we need to track that these props should be forwarded
154
179
  const spreadTypeName = typeNode.typeName.name;
@@ -234,7 +259,12 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
234
259
  // 2. It's a property from a spread type and any property from that spread type is used, OR
235
260
  // 3. It's a spread type and any of its properties are used in the component
236
261
  let shouldReport = true;
237
- if (prop.startsWith('...') && hasRestSpread) {
262
+ // Skip reporting for generic type parameters (T, K, etc.)
263
+ if (prop.startsWith('...') && prop.length === 4 && /^\.\.\.([A-Z])$/.test(prop)) {
264
+ // This is a generic type parameter like ...T, ...K, etc.
265
+ shouldReport = false;
266
+ }
267
+ else if (prop.startsWith('...') && hasRestSpread) {
238
268
  shouldReport = false;
239
269
  }
240
270
  else if (prop.startsWith('...')) {
@@ -0,0 +1,6 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ /**
3
+ * Rule to enforce the use of block comments (/** *\/) instead of single-line comments (//)
4
+ * for all declarations, including type declarations, variable declarations, and function declarations.
5
+ */
6
+ export declare const preferBlockCommentsForDeclarations: TSESLint.RuleModule<'preferBlockComment', never[]>;
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferBlockCommentsForDeclarations = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ /**
6
+ * Rule to enforce the use of block comments (/** *\/) instead of single-line comments (//)
7
+ * for all declarations, including type declarations, variable declarations, and function declarations.
8
+ */
9
+ exports.preferBlockCommentsForDeclarations = (0, createRule_1.createRule)({
10
+ create(context) {
11
+ /**
12
+ * Check if a comment is a line comment that should be converted to a block comment
13
+ */
14
+ const isLineCommentBeforeDeclaration = (comment, node) => {
15
+ // Only process line comments
16
+ if (comment.type !== 'Line') {
17
+ return false;
18
+ }
19
+ // Check if the comment is directly before the node
20
+ const commentLine = comment.loc.end.line;
21
+ const nodeLine = node.loc.start.line;
22
+ return commentLine === nodeLine - 1;
23
+ };
24
+ /**
25
+ * Check if a node is inside a function body
26
+ */
27
+ const isInsideFunctionBody = (node) => {
28
+ let parent = node.parent;
29
+ while (parent) {
30
+ if (parent.type === 'BlockStatement' &&
31
+ (parent.parent?.type === 'FunctionDeclaration' ||
32
+ parent.parent?.type === 'FunctionExpression' ||
33
+ parent.parent?.type === 'ArrowFunctionExpression' ||
34
+ parent.parent?.type === 'MethodDefinition')) {
35
+ return true;
36
+ }
37
+ parent = parent.parent;
38
+ }
39
+ return false;
40
+ };
41
+ /**
42
+ * Process a node that might have a declaration comment
43
+ */
44
+ const checkNodeForLineComments = (node) => {
45
+ // Skip nodes inside function bodies
46
+ if (isInsideFunctionBody(node)) {
47
+ return;
48
+ }
49
+ const sourceCode = context.getSourceCode();
50
+ const comments = sourceCode.getCommentsBefore(node);
51
+ // Find the closest comment to the node
52
+ const lastComment = comments[comments.length - 1];
53
+ if (lastComment && isLineCommentBeforeDeclaration(lastComment, node)) {
54
+ context.report({
55
+ loc: lastComment.loc,
56
+ messageId: 'preferBlockComment',
57
+ fix: (fixer) => {
58
+ const commentText = lastComment.value.trim();
59
+ return fixer.replaceText(lastComment, `/** ${commentText} */`);
60
+ },
61
+ });
62
+ }
63
+ };
64
+ return {
65
+ // Check function declarations
66
+ FunctionDeclaration(node) {
67
+ checkNodeForLineComments(node);
68
+ },
69
+ // Check variable declarations
70
+ VariableDeclaration(node) {
71
+ checkNodeForLineComments(node);
72
+ },
73
+ // Check type declarations
74
+ TSTypeAliasDeclaration(node) {
75
+ checkNodeForLineComments(node);
76
+ },
77
+ // Check interface declarations
78
+ TSInterfaceDeclaration(node) {
79
+ checkNodeForLineComments(node);
80
+ },
81
+ // Check class declarations
82
+ ClassDeclaration(node) {
83
+ checkNodeForLineComments(node);
84
+ },
85
+ // Check property declarations in interfaces and classes
86
+ TSPropertySignature(node) {
87
+ checkNodeForLineComments(node);
88
+ },
89
+ // Check class properties
90
+ PropertyDefinition(node) {
91
+ checkNodeForLineComments(node);
92
+ },
93
+ // Check method declarations
94
+ MethodDefinition(node) {
95
+ checkNodeForLineComments(node);
96
+ },
97
+ // Check enum declarations
98
+ TSEnumDeclaration(node) {
99
+ checkNodeForLineComments(node);
100
+ },
101
+ };
102
+ },
103
+ name: 'prefer-block-comments-for-declarations',
104
+ meta: {
105
+ type: 'suggestion',
106
+ docs: {
107
+ description: 'Enforce the use of block comments for declarations',
108
+ recommended: 'error',
109
+ },
110
+ fixable: 'code',
111
+ schema: [],
112
+ messages: {
113
+ preferBlockComment: 'Use block comments (/** */) instead of line comments (//) for declarations to improve IDE support.',
114
+ },
115
+ },
116
+ defaultOptions: [],
117
+ });
118
+ //# sourceMappingURL=prefer-block-comments-for-declarations.js.map
@@ -55,18 +55,36 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
55
55
  // For destructured parameters, use the type annotation name
56
56
  const typeNode = param.typeAnnotation.typeAnnotation;
57
57
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
58
- return typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
58
+ // Include type parameters in the type signature to differentiate generic types
59
+ const typeName = typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
59
60
  ? typeNode.typeName.name
60
61
  : 'unknown';
62
+ // If there are type parameters, include them in the type signature
63
+ if (typeNode.typeParameters && typeNode.typeParameters.params.length > 0) {
64
+ const typeParams = typeNode.typeParameters.params
65
+ .map(param => param.type)
66
+ .join('_');
67
+ return `${typeName}<${typeParams}>`;
68
+ }
69
+ return typeName;
61
70
  }
62
71
  return typeNode.type;
63
72
  }
64
73
  if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation) {
65
74
  const typeNode = param.typeAnnotation.typeAnnotation;
66
75
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
67
- return typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
76
+ // Include type parameters in the type signature to differentiate generic types
77
+ const typeName = typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
68
78
  ? typeNode.typeName.name
69
79
  : 'unknown';
80
+ // If there are type parameters, include them in the type signature
81
+ if (typeNode.typeParameters && typeNode.typeParameters.params.length > 0) {
82
+ const typeParams = typeNode.typeParameters.params
83
+ .map(param => param.type)
84
+ .join('_');
85
+ return `${typeName}<${typeParams}>`;
86
+ }
87
+ return typeName;
70
88
  }
71
89
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSStringKeyword)
72
90
  return 'string';
@@ -258,6 +276,24 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
258
276
  if (hasABPattern(node.params))
259
277
  return true;
260
278
  }
279
+ // Check if the function is a handler with transaction parameter
280
+ // This is a common pattern in Firebase/Firestore handlers
281
+ if ((node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
282
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
283
+ node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) &&
284
+ node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
285
+ node.parent.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
286
+ const functionName = node.parent.id.name;
287
+ // Check if the function name or its type annotation suggests it's a handler with transaction
288
+ if (functionName.includes('Transaction') ||
289
+ functionName.includes('WithTransaction') ||
290
+ (node.parent.id.typeAnnotation?.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
291
+ node.parent.id.typeAnnotation.typeAnnotation.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
292
+ (node.parent.id.typeAnnotation.typeAnnotation.typeName.name.includes('Transaction') ||
293
+ node.parent.id.typeAnnotation.typeAnnotation.typeName.name.includes('WithTransaction')))) {
294
+ return true;
295
+ }
296
+ }
261
297
  return false;
262
298
  }
263
299
  function checkFunction(node) {
@@ -73,6 +73,31 @@ exports.preferUseMemoOverUseEffectUseState = (0, createRule_1.createRule)({
73
73
  // Allow basic expressions and literals
74
74
  return true;
75
75
  };
76
+ // Helper to check if a node is a reference to a prop or variable
77
+ const isIdentifierReference = (node) => {
78
+ return node.type === 'Identifier';
79
+ };
80
+ // Helper to check if this is a state synchronization pattern
81
+ const isStateSynchronization = (initialValue, setterArgument) => {
82
+ // If the initial value is a reference to a prop/variable and the setter argument
83
+ // is the same reference, this is likely state synchronization
84
+ if (initialValue &&
85
+ isIdentifierReference(initialValue) &&
86
+ isIdentifierReference(setterArgument) &&
87
+ initialValue.name === setterArgument.name) {
88
+ return true;
89
+ }
90
+ // If the initial value is a function that references a prop and the setter argument
91
+ // is that same prop, this is likely state synchronization
92
+ if (initialValue &&
93
+ initialValue.type === 'ArrowFunctionExpression' &&
94
+ initialValue.body.type === 'Identifier' &&
95
+ isIdentifierReference(setterArgument) &&
96
+ initialValue.body.name === setterArgument.name) {
97
+ return true;
98
+ }
99
+ return false;
100
+ };
76
101
  return {
77
102
  // Track useState declarations
78
103
  VariableDeclarator(node) {
@@ -110,6 +135,10 @@ exports.preferUseMemoOverUseEffectUseState = (0, createRule_1.createRule)({
110
135
  const stateInfo = stateSetters.get(setterName);
111
136
  if (stateInfo && statement.expression.arguments.length === 1) {
112
137
  const computation = statement.expression.arguments[0];
138
+ // Skip if this is a state synchronization pattern
139
+ if (isStateSynchronization(stateInfo.initialValue, computation)) {
140
+ return;
141
+ }
113
142
  // Check if the computation is pure
114
143
  if (isPureComputation(computation)) {
115
144
  // Report the issue but without autofixing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.12.1",
3
+ "version": "1.12.3",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",