@blumintinc/eslint-plugin-blumint 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +99 -84
  2. package/lib/index.js +7 -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-id-capitalization.js +23 -5
  19. package/lib/rules/enforce-identifiable-firestore-type.js +17 -10
  20. package/lib/rules/enforce-memoize-async.js +2 -2
  21. package/lib/rules/enforce-positive-naming.js +71 -27
  22. package/lib/rules/enforce-props-argument-name.js +31 -9
  23. package/lib/rules/enforce-realtimedb-path-utils.js +2 -1
  24. package/lib/rules/enforce-singular-type-names.js +4 -1
  25. package/lib/rules/enforce-timestamp-now.js +3 -4
  26. package/lib/rules/enforce-verb-noun-naming.js +18 -8
  27. package/lib/rules/ensure-pointer-events-none.js +20 -11
  28. package/lib/rules/extract-global-constants.js +25 -10
  29. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +3 -0
  30. package/lib/rules/fast-deep-equal-over-microdiff.js +182 -0
  31. package/lib/rules/key-only-outermost-element.js +13 -7
  32. package/lib/rules/no-always-true-false-conditions.js +52 -18
  33. package/lib/rules/no-async-array-filter.js +1 -1
  34. package/lib/rules/no-circular-references.js +121 -59
  35. package/lib/rules/no-entire-object-hook-deps.js +113 -29
  36. package/lib/rules/no-explicit-return-type.js +3 -2
  37. package/lib/rules/no-firestore-jest-mock.js +1 -1
  38. package/lib/rules/no-firestore-object-arrays.js +2 -1
  39. package/lib/rules/no-hungarian.js +2 -2
  40. package/lib/rules/no-jsx-in-hooks.js +4 -3
  41. package/lib/rules/no-margin-properties.js +25 -18
  42. package/lib/rules/no-mixed-firestore-transactions.js +8 -4
  43. package/lib/rules/no-mock-firebase-admin.js +6 -1
  44. package/lib/rules/no-object-values-on-strings.js +28 -10
  45. package/lib/rules/no-unnecessary-destructuring.js +1 -1
  46. package/lib/rules/no-unnecessary-verb-suffix.js +0 -31
  47. package/lib/rules/no-unused-props.js +10 -7
  48. package/lib/rules/no-unused-usestate.js +19 -6
  49. package/lib/rules/omit-index-html.js +1 -1
  50. package/lib/rules/prefer-batch-operations.js +19 -6
  51. package/lib/rules/prefer-clone-deep.js +16 -11
  52. package/lib/rules/prefer-destructuring-no-class.js +19 -0
  53. package/lib/rules/prefer-fragment-component.js +19 -16
  54. package/lib/rules/prefer-global-router-state-key.js +6 -4
  55. package/lib/rules/prefer-settings-object.js +1 -1
  56. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +12 -4
  57. package/lib/rules/react-usememo-should-be-component.js +64 -18
  58. package/lib/rules/require-hooks-default-params.js +25 -15
  59. package/lib/rules/require-usememo-object-literals.js +3 -2
  60. package/lib/rules/semantic-function-prefixes.js +15 -3
  61. package/lib/rules/sync-onwrite-name-func.js +5 -3
  62. package/lib/utils/ASTHelpers.js +6 -2
  63. package/lib/utils/graph/ClassGraphBuilder.js +4 -1
  64. package/lib/utils/graph/ClassGraphSorterReadability.js +3 -1
  65. package/package.json +3 -3
  66. package/lib/rules/require-image-overlayed.d.ts +0 -7
  67. package/lib/rules/require-image-overlayed.js +0 -82
@@ -12,6 +12,15 @@ const isJsxElement = (node) => {
12
12
  if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
13
13
  return isJsxElement(node.consequent) || isJsxElement(node.alternate);
14
14
  }
15
+ // For logical expressions like '&&', the result can be a non-JSX value
16
+ if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
17
+ // If it's a logical AND (&&), it can return the left operand which might be non-JSX
18
+ if (node.operator === '&&') {
19
+ return false;
20
+ }
21
+ // For other logical operators, check both sides
22
+ return isJsxElement(node.left) || isJsxElement(node.right);
23
+ }
15
24
  return (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
16
25
  node.type === utils_1.AST_NODE_TYPES.JSXFragment);
17
26
  };
@@ -57,7 +66,7 @@ const isUsedMultipleTimes = (variableName, node) => {
57
66
  const child = searchNode[key];
58
67
  if (child && typeof child === 'object') {
59
68
  if (Array.isArray(child)) {
60
- child.forEach(item => {
69
+ child.forEach((item) => {
61
70
  if (item && typeof item === 'object') {
62
71
  findReferences(item);
63
72
  }
@@ -105,7 +114,8 @@ const containsJsxInObject = (node) => {
105
114
  const containsJsxInSwitchStatement = (node) => {
106
115
  for (const switchCase of node.cases) {
107
116
  for (const statement of switchCase.consequent) {
108
- if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement && statement.argument) {
117
+ if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
118
+ statement.argument) {
109
119
  if (isJsxElement(statement.argument)) {
110
120
  return true;
111
121
  }
@@ -144,8 +154,17 @@ const containsJsxInFunction = (node) => {
144
154
  if (body.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
145
155
  body.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
146
156
  // Check array methods like map, filter, find, etc.
147
- const arrayMethods = ['map', 'filter', 'find', 'findIndex', 'some', 'every', 'reduce'];
148
- if (arrayMethods.includes(body.callee.property.name) && body.arguments.length > 0) {
157
+ const arrayMethods = [
158
+ 'map',
159
+ 'filter',
160
+ 'find',
161
+ 'findIndex',
162
+ 'some',
163
+ 'every',
164
+ 'reduce',
165
+ ];
166
+ if (arrayMethods.includes(body.callee.property.name) &&
167
+ body.arguments.length > 0) {
149
168
  const callback = body.arguments[0];
150
169
  if ((callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
151
170
  callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
@@ -183,9 +202,17 @@ const containsJsxInExpression = (node) => {
183
202
  }
184
203
  switch (node.type) {
185
204
  case utils_1.AST_NODE_TYPES.ConditionalExpression:
186
- return containsJsxInExpression(node.consequent) || containsJsxInExpression(node.alternate);
205
+ return (containsJsxInExpression(node.consequent) ||
206
+ containsJsxInExpression(node.alternate));
187
207
  case utils_1.AST_NODE_TYPES.LogicalExpression:
188
- return containsJsxInExpression(node.left) || containsJsxInExpression(node.right);
208
+ // For logical AND (&&) expressions, if the left side can be falsy,
209
+ // then the expression can return a non-JSX value, so we should not flag it
210
+ if (node.operator === '&&') {
211
+ // If the left side is a boolean expression or can be falsy, this can return a non-JSX value
212
+ return false;
213
+ }
214
+ return (containsJsxInExpression(node.left) ||
215
+ containsJsxInExpression(node.right));
189
216
  case utils_1.AST_NODE_TYPES.ObjectExpression:
190
217
  // Special case: If this is an object with both JSX and non-JSX properties,
191
218
  // it's likely a data structure that happens to contain JSX, not a component
@@ -218,8 +245,8 @@ const containsJsxInExpression = (node) => {
218
245
  node.callee.property.name === 'map' &&
219
246
  node.arguments.length > 0) {
220
247
  const callback = node.arguments[0];
221
- if ((callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
222
- callback.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
248
+ if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
249
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
223
250
  // Check if the callback returns an object with both JSX and non-JSX properties
224
251
  if (callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement &&
225
252
  callback.body.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
@@ -252,18 +279,27 @@ const containsJsxInExpression = (node) => {
252
279
  // Check array methods
253
280
  if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
254
281
  node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
255
- const arrayMethods = ['filter', 'find', 'findIndex', 'some', 'every', 'reduce'];
256
- if (arrayMethods.includes(node.callee.property.name) && node.arguments.length > 0) {
282
+ const arrayMethods = [
283
+ 'filter',
284
+ 'find',
285
+ 'findIndex',
286
+ 'some',
287
+ 'every',
288
+ 'reduce',
289
+ ];
290
+ if (arrayMethods.includes(node.callee.property.name) &&
291
+ node.arguments.length > 0) {
257
292
  const callback = node.arguments[0];
258
- if ((callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
259
- callback.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
293
+ if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
294
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
260
295
  return containsJsxInFunction(callback);
261
296
  }
262
297
  }
263
298
  }
264
299
  // Check arguments for JSX
265
300
  for (const arg of node.arguments) {
266
- if (arg.type !== utils_1.AST_NODE_TYPES.SpreadElement && containsJsxInExpression(arg)) {
301
+ if (arg.type !== utils_1.AST_NODE_TYPES.SpreadElement &&
302
+ containsJsxInExpression(arg)) {
267
303
  return true;
268
304
  }
269
305
  }
@@ -281,7 +317,8 @@ const containsJsxInExpression = (node) => {
281
317
  const containsJsxInBlockStatement = (node) => {
282
318
  for (const statement of node.body) {
283
319
  // Check return statements
284
- if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement && statement.argument) {
320
+ if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
321
+ statement.argument) {
285
322
  if (containsJsxInExpression(statement.argument)) {
286
323
  return true;
287
324
  }
@@ -289,7 +326,8 @@ const containsJsxInBlockStatement = (node) => {
289
326
  // Check if statements
290
327
  if (statement.type === utils_1.AST_NODE_TYPES.IfStatement) {
291
328
  if (statement.consequent.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
292
- statement.consequent.argument && containsJsxInExpression(statement.consequent.argument)) {
329
+ statement.consequent.argument &&
330
+ containsJsxInExpression(statement.consequent.argument)) {
293
331
  return true;
294
332
  }
295
333
  if (statement.consequent.type === utils_1.AST_NODE_TYPES.BlockStatement &&
@@ -298,7 +336,8 @@ const containsJsxInBlockStatement = (node) => {
298
336
  }
299
337
  if (statement.alternate) {
300
338
  if (statement.alternate.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
301
- statement.alternate.argument && containsJsxInExpression(statement.alternate.argument)) {
339
+ statement.alternate.argument &&
340
+ containsJsxInExpression(statement.alternate.argument)) {
302
341
  return true;
303
342
  }
304
343
  if (statement.alternate.type === utils_1.AST_NODE_TYPES.BlockStatement &&
@@ -311,10 +350,12 @@ const containsJsxInBlockStatement = (node) => {
311
350
  return true;
312
351
  }
313
352
  if (statement.alternate.consequent &&
314
- ((statement.alternate.consequent.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
353
+ ((statement.alternate.consequent.type ===
354
+ utils_1.AST_NODE_TYPES.ReturnStatement &&
315
355
  statement.alternate.consequent.argument &&
316
356
  containsJsxInExpression(statement.alternate.consequent.argument)) ||
317
- (statement.alternate.consequent.type === utils_1.AST_NODE_TYPES.BlockStatement &&
357
+ (statement.alternate.consequent.type ===
358
+ utils_1.AST_NODE_TYPES.BlockStatement &&
318
359
  containsJsxInBlockStatement(statement.alternate.consequent)))) {
319
360
  return true;
320
361
  }
@@ -378,6 +419,11 @@ const containsJsxInUseMemo = (node) => {
378
419
  if (isJsxElement(callback.body)) {
379
420
  return true;
380
421
  }
422
+ // Special case for logical expressions that can return non-JSX values
423
+ if (callback.body.type === utils_1.AST_NODE_TYPES.LogicalExpression &&
424
+ callback.body.operator === '&&') {
425
+ return false;
426
+ }
381
427
  // For non-JSX expressions, we need to check if they contain JSX
382
428
  return containsJsxInExpression(callback.body);
383
429
  }
@@ -25,7 +25,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
25
25
  function hasAllOptionalProperties(typeNode) {
26
26
  // Handle type literals directly
27
27
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
28
- return typeNode.members.every(member => {
28
+ return typeNode.members.every((member) => {
29
29
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
30
30
  return false;
31
31
  }
@@ -39,7 +39,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
39
39
  return false;
40
40
  }
41
41
  const scope = context.getScope();
42
- const variable = scope.variables.find(v => v.name === typeName.name);
42
+ const variable = scope.variables.find((v) => v.name === typeName.name);
43
43
  if (!variable || !variable.defs[0]?.node) {
44
44
  // If we can't find the type definition, assume it's a type with required properties
45
45
  // This handles cases where the type is imported from another module
@@ -50,7 +50,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
50
50
  return hasAllOptionalProperties(def.typeAnnotation);
51
51
  }
52
52
  else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
53
- return def.body.body.every(member => {
53
+ return def.body.body.every((member) => {
54
54
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
55
55
  return false;
56
56
  }
@@ -68,7 +68,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
68
68
  }
69
69
  // Handle interface declarations
70
70
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
71
- return typeNode.body.body.every(member => {
71
+ return typeNode.body.body.every((member) => {
72
72
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
73
73
  return false;
74
74
  }
@@ -106,13 +106,14 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
106
106
  return;
107
107
  }
108
108
  // Check if the parameter has a type annotation
109
- if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.typeAnnotation) {
109
+ if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
110
+ param.typeAnnotation) {
110
111
  const typeAnnotation = param.typeAnnotation.typeAnnotation;
111
112
  if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
112
113
  const typeName = typeAnnotation.typeName;
113
114
  if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
114
115
  const scope = context.getScope();
115
- const variable = scope.variables.find(v => v.name === typeName.name);
116
+ const variable = scope.variables.find((v) => v.name === typeName.name);
116
117
  if (variable && variable.defs[0]?.node) {
117
118
  const def = variable.defs[0].node;
118
119
  if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
@@ -121,14 +122,16 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
121
122
  node: param,
122
123
  messageId: 'requireDefaultParams',
123
124
  fix(fixer) {
124
- const paramText = context.getSourceCode().getText(param);
125
+ const paramText = context
126
+ .getSourceCode()
127
+ .getText(param);
125
128
  return fixer.replaceText(param, `${paramText} = {}`);
126
129
  },
127
130
  });
128
131
  }
129
132
  }
130
133
  else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
131
- if (def.body.body.every(member => {
134
+ if (def.body.body.every((member) => {
132
135
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
133
136
  return false;
134
137
  }
@@ -138,7 +141,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
138
141
  node: param,
139
142
  messageId: 'requireDefaultParams',
140
143
  fix(fixer) {
141
- const paramText = context.getSourceCode().getText(param);
144
+ const paramText = context
145
+ .getSourceCode()
146
+ .getText(param);
142
147
  return fixer.replaceText(param, `${paramText} = {}`);
143
148
  },
144
149
  });
@@ -148,8 +153,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
148
153
  else {
149
154
  // If we can't find the type definition, check if it's defined in the same file
150
155
  const program = context.getSourceCode().ast;
151
- const typeDefinitions = program.body.filter(node => {
152
- if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration || node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
156
+ const typeDefinitions = program.body.filter((node) => {
157
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
158
+ node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
153
159
  if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
154
160
  return node.id.name === typeName.name;
155
161
  }
@@ -167,14 +173,16 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
167
173
  node: param,
168
174
  messageId: 'requireDefaultParams',
169
175
  fix(fixer) {
170
- const paramText = context.getSourceCode().getText(param);
176
+ const paramText = context
177
+ .getSourceCode()
178
+ .getText(param);
171
179
  return fixer.replaceText(param, `${paramText} = {}`);
172
180
  },
173
181
  });
174
182
  }
175
183
  }
176
184
  else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
177
- if (def.body.body.every(member => {
185
+ if (def.body.body.every((member) => {
178
186
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
179
187
  return false;
180
188
  }
@@ -184,7 +192,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
184
192
  node: param,
185
193
  messageId: 'requireDefaultParams',
186
194
  fix(fixer) {
187
- const paramText = context.getSourceCode().getText(param);
195
+ const paramText = context
196
+ .getSourceCode()
197
+ .getText(param);
188
198
  return fixer.replaceText(param, `${paramText} = {}`);
189
199
  },
190
200
  });
@@ -195,7 +205,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
195
205
  }
196
206
  }
197
207
  else if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
198
- if (typeAnnotation.members.every(member => {
208
+ if (typeAnnotation.members.every((member) => {
199
209
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
200
210
  return false;
201
211
  }
@@ -7,7 +7,7 @@ exports.requireUseMemoObjectLiterals = (0, createRule_1.createRule)({
7
7
  meta: {
8
8
  type: 'suggestion',
9
9
  docs: {
10
- description: 'Enforce using useMemo for inline object/array literals passed as props to JSX components to prevent unnecessary re-renders. When object/array literals are defined inline in JSX, they create new references on every render, causing child components to re-render even if the values haven\'t changed. Wrap them in useMemo to maintain referential equality.',
10
+ description: "Enforce using useMemo for inline object/array literals passed as props to JSX components to prevent unnecessary re-renders. When object/array literals are defined inline in JSX, they create new references on every render, causing child components to re-render even if the values haven't changed. Wrap them in useMemo to maintain referential equality.",
11
11
  recommended: 'error',
12
12
  },
13
13
  messages: {
@@ -25,7 +25,8 @@ exports.requireUseMemoObjectLiterals = (0, createRule_1.createRule)({
25
25
  }
26
26
  // Skip if the prop name is 'sx' or ends with 'Sx'
27
27
  const propName = node.name.name;
28
- if (typeof propName === 'string' && (propName === 'sx' || propName.endsWith('Sx'))) {
28
+ if (typeof propName === 'string' &&
29
+ (propName === 'sx' || propName.endsWith('Sx'))) {
29
30
  return;
30
31
  }
31
32
  const { expression } = node.value;
@@ -3,8 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.semanticFunctionPrefixes = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
- const DISALLOWED_PREFIXES = new Set(['get', 'update', 'check', 'manage', 'process', 'do']);
7
- const NEXTJS_DATA_FUNCTIONS = new Set(['getServerSideProps', 'getStaticProps', 'getStaticPaths']);
6
+ const DISALLOWED_PREFIXES = new Set([
7
+ 'get',
8
+ 'update',
9
+ 'check',
10
+ 'manage',
11
+ 'process',
12
+ 'do',
13
+ ]);
14
+ const NEXTJS_DATA_FUNCTIONS = new Set([
15
+ 'getServerSideProps',
16
+ 'getStaticProps',
17
+ 'getStaticPaths',
18
+ ]);
8
19
  const SUGGESTED_ALTERNATIVES = {
9
20
  get: ['fetch', 'retrieve', 'compute', 'derive'],
10
21
  update: ['modify', 'set', 'apply'],
@@ -75,7 +86,8 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
75
86
  if (node.id) {
76
87
  functionName = node.id.name;
77
88
  }
78
- else if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator && node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
89
+ else if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
90
+ node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
79
91
  functionName = node.parent.id.name;
80
92
  }
81
93
  if (!functionName)
@@ -40,14 +40,16 @@ exports.syncOnwriteNameFunc = (0, createRule_1.createRule)({
40
40
  if (!nameProperty || !funcProperty) {
41
41
  return;
42
42
  }
43
- const nameValue = nameProperty.value.value;
43
+ const nameValue = nameProperty.value
44
+ .value;
44
45
  let funcName;
45
46
  // Handle variable references
46
47
  if (funcProperty.value.type === utils_1.AST_NODE_TYPES.Identifier) {
47
48
  const funcIdentifier = funcProperty.value;
48
49
  const scope = context.getScope();
49
- const variable = scope.references.find(ref => ref.identifier === funcIdentifier)?.resolved;
50
- if (variable?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
50
+ const variable = scope.references.find((ref) => ref.identifier === funcIdentifier)?.resolved;
51
+ if (variable?.defs[0]?.node.type ===
52
+ utils_1.AST_NODE_TYPES.VariableDeclarator &&
51
53
  variable.defs[0].node.init?.type === utils_1.AST_NODE_TYPES.Identifier) {
52
54
  // If the variable is initialized with another identifier, use that name
53
55
  funcName = variable.defs[0].node.init.name;
@@ -262,9 +262,13 @@ class ASTHelpers {
262
262
  default:
263
263
  break;
264
264
  }
265
- // Removing duplicates
265
+ // Removing duplicates and ensuring exact matches only
266
266
  return [
267
- ...new Set(dependencies.filter((dep) => graph?.[dep]?.type !== 'property')),
267
+ ...new Set(dependencies.filter((dep) => {
268
+ // Only include dependencies that exist exactly in the graph
269
+ // This prevents substring matches (e.g., 'nextMatches' vs 'nextMatchesWithResults')
270
+ return (graph?.[dep] !== undefined && graph?.[dep]?.type !== 'property');
271
+ })),
268
272
  ];
269
273
  }
270
274
  static isNode(value) {
@@ -63,7 +63,10 @@ class ClassGraphBuilder {
63
63
  return node.type === 'MethodDefinition';
64
64
  }
65
65
  addDependencies(node, methodName) {
66
- const newDependencies = ASTHelpers_1.ASTHelpers.classMethodDependenciesOf(node, this.graph, this.className).filter((name) => !!this.graph[name] && name !== methodName);
66
+ const newDependencies = ASTHelpers_1.ASTHelpers.classMethodDependenciesOf(node, this.graph, this.className).filter((name) => {
67
+ // Only include dependencies that exist exactly in the graph and aren't self-references
68
+ return !!this.graph[name] && name !== methodName;
69
+ });
67
70
  if (this.graph[methodName]) {
68
71
  // Ensure dependencies is initialized as an array
69
72
  if (!Array.isArray(this.graph[methodName].dependencies)) {
@@ -72,7 +72,9 @@ class ClassGraphSorterReadability extends ClassGraphSorter_1.ClassGraphSorter {
72
72
  visited.add(node.name);
73
73
  dfsSortedNodes.push(node);
74
74
  // Ensure node.dependencies exists and is an array before iterating
75
- const dependencies = Array.isArray(node.dependencies) ? node.dependencies : [];
75
+ const dependencies = Array.isArray(node.dependencies)
76
+ ? node.dependencies
77
+ : [];
76
78
  for (const dep of dependencies) {
77
79
  const depNode = this.graph[String(dep)];
78
80
  if (depNode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -30,11 +30,11 @@
30
30
  "lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"",
31
31
  "lint:js": "eslint ./src",
32
32
  "lint:shell": "shellcheck .devcontainer/git-flow-completion.bash",
33
- "lint:fix": "tsc && eslint ./**/* --quiet --fix",
33
+ "lint:fix": "tsc && eslint \"./src/**/*\" --quiet --fix",
34
34
  "test": "jest --passWithNoTests --reporters=default --reporters=jest-junit",
35
35
  "test:ci": "jest --ci --passWithNoTests --reporters=default --reporters=jest-junit",
36
36
  "docs": "./scripts/make-docs.sh && npm run update:eslint-docs",
37
- "update:eslint-docs": "eslint-doc-generator",
37
+ "update:eslint-docs": "eslint-doc-generator --init-rule-docs",
38
38
  "build": "tsc",
39
39
  "prepare": "husky install && npm run build",
40
40
  "version": "git add -A src",
@@ -1,7 +0,0 @@
1
- declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"useImageOverlayed", {
2
- componentPath: string;
3
- }[], {
4
- JSXElement(node: any): void;
5
- ImportDeclaration(node: any): void;
6
- }>;
7
- export = _default;
@@ -1,82 +0,0 @@
1
- "use strict";
2
- const createRule_1 = require("../utils/createRule");
3
- module.exports = (0, createRule_1.createRule)({
4
- name: 'require-image-overlayed',
5
- meta: {
6
- type: 'problem',
7
- docs: {
8
- description: 'Enforce using ImageOverlayed component instead of next/image or img tags',
9
- recommended: 'error',
10
- requiresTypeChecking: false,
11
- },
12
- fixable: 'code',
13
- schema: [
14
- {
15
- type: 'object',
16
- properties: {
17
- componentPath: {
18
- type: 'string',
19
- description: 'The import path for the ImageOverlayed component',
20
- default: 'src/components/ImageOverlayed',
21
- },
22
- },
23
- additionalProperties: false,
24
- },
25
- ],
26
- messages: {
27
- useImageOverlayed: 'Use ImageOverlayed component from {{ componentPath }} instead of {{ component }}',
28
- },
29
- },
30
- defaultOptions: [{ componentPath: 'src/components/ImageOverlayed' }],
31
- create(context) {
32
- const options = context.options[0] || {
33
- componentPath: 'src/components/ImageOverlayed',
34
- };
35
- const sourceCode = context.getSourceCode();
36
- return {
37
- // Handle JSX img elements
38
- JSXElement(node) {
39
- if (node.openingElement.name.name === 'img') {
40
- context.report({
41
- node,
42
- messageId: 'useImageOverlayed',
43
- data: {
44
- componentPath: options.componentPath,
45
- component: 'img tag',
46
- },
47
- fix(fixer) {
48
- const attributes = node.openingElement.attributes
49
- .map((attr) => sourceCode.getText(attr))
50
- .join(' ');
51
- return fixer.replaceText(node, `<ImageOverlayed ${attributes} />`);
52
- },
53
- });
54
- }
55
- },
56
- // Handle next/image imports and usage
57
- ImportDeclaration(node) {
58
- if (node.source.value === 'next/image' && node.specifiers.length > 0) {
59
- const imageSpecifier = node.specifiers.find((spec) => (spec.type === 'ImportDefaultSpecifier' ||
60
- spec.type === 'ImportSpecifier') &&
61
- (spec.local.name === 'Image' || spec.imported?.name === 'Image'));
62
- if (imageSpecifier) {
63
- const localName = imageSpecifier.local.name;
64
- // Report the import
65
- context.report({
66
- node,
67
- messageId: 'useImageOverlayed',
68
- data: {
69
- componentPath: options.componentPath,
70
- component: 'next/image',
71
- },
72
- fix(fixer) {
73
- return fixer.replaceText(node, `import ${localName} from '${options.componentPath}';`);
74
- },
75
- });
76
- }
77
- }
78
- },
79
- };
80
- },
81
- });
82
- //# sourceMappingURL=require-image-overlayed.js.map