@blumintinc/eslint-plugin-blumint 1.8.0 → 1.8.2

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 (41) hide show
  1. package/README.md +84 -39
  2. package/lib/index.js +50 -2
  3. package/lib/rules/enforce-assertSafe-object-key.js +1 -1
  4. package/lib/rules/enforce-css-media-queries.d.ts +5 -0
  5. package/lib/rules/enforce-css-media-queries.js +87 -0
  6. package/lib/rules/enforce-dynamic-imports.d.ts +9 -0
  7. package/lib/rules/enforce-dynamic-imports.js +84 -0
  8. package/lib/rules/enforce-id-capitalization.d.ts +6 -0
  9. package/lib/rules/enforce-id-capitalization.js +118 -0
  10. package/lib/rules/enforce-mui-rounded-icons.d.ts +1 -0
  11. package/lib/rules/enforce-mui-rounded-icons.js +54 -0
  12. package/lib/rules/enforce-object-literal-as-const.js +37 -0
  13. package/lib/rules/enforce-positive-naming.js +116 -168
  14. package/lib/rules/enforce-react-type-naming.d.ts +3 -0
  15. package/lib/rules/enforce-react-type-naming.js +191 -0
  16. package/lib/rules/enforce-singular-type-names.d.ts +2 -0
  17. package/lib/rules/enforce-singular-type-names.js +112 -0
  18. package/lib/rules/enforce-verb-noun-naming.js +5 -2
  19. package/lib/rules/ensure-pointer-events-none.d.ts +1 -0
  20. package/lib/rules/ensure-pointer-events-none.js +211 -0
  21. package/lib/rules/key-only-outermost-element.d.ts +3 -0
  22. package/lib/rules/key-only-outermost-element.js +152 -0
  23. package/lib/rules/no-always-true-false-conditions.js +19 -0
  24. package/lib/rules/no-circular-references.d.ts +1 -0
  25. package/lib/rules/no-circular-references.js +523 -0
  26. package/lib/rules/no-hungarian.d.ts +5 -0
  27. package/lib/rules/no-hungarian.js +223 -0
  28. package/lib/rules/no-object-values-on-strings.d.ts +2 -0
  29. package/lib/rules/no-object-values-on-strings.js +294 -0
  30. package/lib/rules/no-type-assertion-returns.js +143 -118
  31. package/lib/rules/no-unnecessary-destructuring.d.ts +5 -0
  32. package/lib/rules/no-unnecessary-destructuring.js +69 -0
  33. package/lib/rules/no-unused-props.js +109 -11
  34. package/lib/rules/no-unused-usestate.d.ts +8 -0
  35. package/lib/rules/no-unused-usestate.js +84 -0
  36. package/lib/rules/omit-index-html.d.ts +7 -0
  37. package/lib/rules/omit-index-html.js +91 -0
  38. package/lib/rules/prefer-usememo-over-useeffect-usestate.d.ts +5 -0
  39. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +129 -0
  40. package/lib/rules/react-usememo-should-be-component.js +369 -2
  41. package/package.json +7 -4
@@ -22,6 +22,54 @@ function isAsConstAssertion(node) {
22
22
  function isTypePredicate(node) {
23
23
  return node.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypePredicate;
24
24
  }
25
+ /**
26
+ * Checks if a node is inside a return statement
27
+ */
28
+ function isInsideReturnStatement(node) {
29
+ let current = node;
30
+ while (current?.parent) {
31
+ if (current.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
32
+ return true;
33
+ }
34
+ current = current.parent;
35
+ }
36
+ return false;
37
+ }
38
+ /**
39
+ * Checks if a node is inside a conditional statement
40
+ */
41
+ function isInsideConditionalStatement(node) {
42
+ let current = node;
43
+ while (current?.parent) {
44
+ if (current.parent.type === utils_1.AST_NODE_TYPES.IfStatement ||
45
+ current.parent.type === utils_1.AST_NODE_TYPES.WhileStatement ||
46
+ current.parent.type === utils_1.AST_NODE_TYPES.DoWhileStatement ||
47
+ current.parent.type === utils_1.AST_NODE_TYPES.ForStatement ||
48
+ current.parent.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
49
+ // If we're in a conditional statement but not in a return statement, it's valid
50
+ return !isInsideReturnStatement(current.parent);
51
+ }
52
+ current = current.parent;
53
+ }
54
+ return false;
55
+ }
56
+ /**
57
+ * Checks if a type assertion is used to access a property
58
+ */
59
+ function isPropertyAccess(node) {
60
+ return (node.parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
61
+ node.parent.object === node);
62
+ }
63
+ /**
64
+ * Checks if a type assertion is used within an array includes check
65
+ */
66
+ function isArrayIncludesCheck(node) {
67
+ return (node.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
68
+ node.parent.arguments.includes(node) &&
69
+ node.parent.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
70
+ node.parent.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
71
+ node.parent.callee.property.name === 'includes');
72
+ }
25
73
  exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
26
74
  name: 'no-type-assertion-returns',
27
75
  meta: {
@@ -50,6 +98,94 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
50
98
  defaultOptions: [defaultOptions],
51
99
  create(context, [options]) {
52
100
  const mergedOptions = { ...defaultOptions, ...options };
101
+ /**
102
+ * Common function to check if a type assertion should be allowed
103
+ */
104
+ function shouldAllowTypeAssertion(node) {
105
+ // If the parent is a return statement, we already handle it in ReturnStatement
106
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
107
+ return true;
108
+ }
109
+ // If the parent is an arrow function, we already handle it in ArrowFunctionExpression
110
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
111
+ return true;
112
+ }
113
+ // If the parent is a variable declarator, this is a variable declaration with type assertion
114
+ // which is a valid pattern and should not be flagged
115
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
116
+ return true;
117
+ }
118
+ // Allow type assertions within conditional statements (if, while, do-while, for)
119
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.IfStatement ||
120
+ node.parent?.type === utils_1.AST_NODE_TYPES.WhileStatement ||
121
+ node.parent?.type === utils_1.AST_NODE_TYPES.DoWhileStatement ||
122
+ node.parent?.type === utils_1.AST_NODE_TYPES.ForStatement) {
123
+ return true;
124
+ }
125
+ // Allow type assertions within logical expressions (which are often used in conditions)
126
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
127
+ return true;
128
+ }
129
+ // Allow type assertions within method calls like array.includes()
130
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
131
+ node.parent.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
132
+ return true;
133
+ }
134
+ // Allow type assertions within array includes checks
135
+ if (isArrayIncludesCheck(node)) {
136
+ return true;
137
+ }
138
+ // Allow type assertions within conditional expressions, but only if they're not part of a return statement
139
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.ConditionalExpression && !isInsideReturnStatement(node)) {
140
+ return true;
141
+ }
142
+ // Allow type assertions used to access properties in conditional contexts
143
+ if (isPropertyAccess(node) && isInsideConditionalStatement(node)) {
144
+ return true;
145
+ }
146
+ return false;
147
+ }
148
+ /**
149
+ * Common function to check function return types
150
+ */
151
+ function checkFunctionReturnType(node) {
152
+ if (!node.returnType)
153
+ return;
154
+ // Allow type predicates if configured
155
+ if (mergedOptions.allowTypePredicates && isTypePredicate(node.returnType)) {
156
+ return;
157
+ }
158
+ // If type predicates are not allowed, report them
159
+ if (!mergedOptions.allowTypePredicates && isTypePredicate(node.returnType)) {
160
+ context.report({
161
+ node: node.returnType,
162
+ messageId: 'useExplicitVariable',
163
+ });
164
+ return;
165
+ }
166
+ // Check if the function has a return statement with a direct value (not a variable)
167
+ if (node.body && node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
168
+ for (const statement of node.body.body) {
169
+ if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement && statement.argument) {
170
+ // If returning a variable reference, that's fine
171
+ if (statement.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
172
+ continue;
173
+ }
174
+ // If returning an object literal, array literal, or other complex expression
175
+ // without first assigning it to a typed variable, that's a problem
176
+ if (statement.argument.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
177
+ statement.argument.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
178
+ statement.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
179
+ context.report({
180
+ node: node.returnType,
181
+ messageId: 'useExplicitVariable',
182
+ });
183
+ break;
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
53
189
  return {
54
190
  // Check for return statements with type assertions
55
191
  ReturnStatement(node) {
@@ -84,81 +220,11 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
84
220
  },
85
221
  // Check functions with explicit return types
86
222
  FunctionDeclaration(node) {
87
- if (!node.returnType)
88
- return;
89
- // Allow type predicates if configured
90
- if (mergedOptions.allowTypePredicates && isTypePredicate(node.returnType)) {
91
- return;
92
- }
93
- // If type predicates are not allowed, report them
94
- if (!mergedOptions.allowTypePredicates && isTypePredicate(node.returnType)) {
95
- context.report({
96
- node: node.returnType,
97
- messageId: 'useExplicitVariable',
98
- });
99
- return;
100
- }
101
- // Check if the function has a return statement with a direct value (not a variable)
102
- if (node.body && node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
103
- for (const statement of node.body.body) {
104
- if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement && statement.argument) {
105
- // If returning a variable reference, that's fine
106
- if (statement.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
107
- continue;
108
- }
109
- // If returning an object literal, array literal, or other complex expression
110
- // without first assigning it to a typed variable, that's a problem
111
- if (statement.argument.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
112
- statement.argument.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
113
- statement.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
114
- context.report({
115
- node: node.returnType,
116
- messageId: 'useExplicitVariable',
117
- });
118
- break;
119
- }
120
- }
121
- }
122
- }
223
+ checkFunctionReturnType(node);
123
224
  },
124
225
  // Check function expressions with explicit return types
125
226
  FunctionExpression(node) {
126
- if (!node.returnType)
127
- return;
128
- // Allow type predicates if configured
129
- if (mergedOptions.allowTypePredicates && isTypePredicate(node.returnType)) {
130
- return;
131
- }
132
- // If type predicates are not allowed, report them
133
- if (!mergedOptions.allowTypePredicates && isTypePredicate(node.returnType)) {
134
- context.report({
135
- node: node.returnType,
136
- messageId: 'useExplicitVariable',
137
- });
138
- return;
139
- }
140
- // Check if the function has a return statement with a direct value (not a variable)
141
- if (node.body && node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
142
- for (const statement of node.body.body) {
143
- if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement && statement.argument) {
144
- // If returning a variable reference, that's fine
145
- if (statement.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
146
- continue;
147
- }
148
- // If returning an object literal, array literal, or other complex expression
149
- // without first assigning it to a typed variable, that's a problem
150
- if (statement.argument.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
151
- statement.argument.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
152
- statement.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
153
- context.report({
154
- node: node.returnType,
155
- messageId: 'useExplicitVariable',
156
- });
157
- break;
158
- }
159
- }
160
- }
161
- }
227
+ checkFunctionReturnType(node);
162
228
  },
163
229
  // Check arrow functions
164
230
  ArrowFunctionExpression(node) {
@@ -207,40 +273,7 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
207
273
  }
208
274
  else if (node.returnType) {
209
275
  // For arrow functions with block bodies
210
- // Allow type predicates if configured
211
- if (mergedOptions.allowTypePredicates && isTypePredicate(node.returnType)) {
212
- return;
213
- }
214
- // If type predicates are not allowed, report them
215
- if (!mergedOptions.allowTypePredicates && isTypePredicate(node.returnType)) {
216
- context.report({
217
- node: node.returnType,
218
- messageId: 'useExplicitVariable',
219
- });
220
- return;
221
- }
222
- // Check if the function has a return statement with a direct value (not a variable)
223
- if (node.body && node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
224
- for (const statement of node.body.body) {
225
- if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement && statement.argument) {
226
- // If returning a variable reference, that's fine
227
- if (statement.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
228
- continue;
229
- }
230
- // If returning an object literal, array literal, or other complex expression
231
- // without first assigning it to a typed variable, that's a problem
232
- if (statement.argument.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
233
- statement.argument.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
234
- statement.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
235
- context.report({
236
- node: node.returnType,
237
- messageId: 'useExplicitVariable',
238
- });
239
- break;
240
- }
241
- }
242
- }
243
- }
276
+ checkFunctionReturnType(node);
244
277
  }
245
278
  },
246
279
  // Check for array methods with type assertions
@@ -253,12 +286,8 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
253
286
  if (mergedOptions.allowAsConst && isAsConstAssertion(node)) {
254
287
  return;
255
288
  }
256
- // If the parent is a return statement, we already handle it in ReturnStatement
257
- if (node.parent?.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
258
- return;
259
- }
260
- // If the parent is an arrow function, we already handle it in ArrowFunctionExpression
261
- if (node.parent?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
289
+ // Use the common function to check if the type assertion should be allowed
290
+ if (shouldAllowTypeAssertion(node)) {
262
291
  return;
263
292
  }
264
293
  // For standalone type assertions in expressions
@@ -269,12 +298,8 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
269
298
  },
270
299
  // Check for type assertions using angle bracket syntax
271
300
  TSTypeAssertion(node) {
272
- // If the parent is a return statement, we already handle it in ReturnStatement
273
- if (node.parent?.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
274
- return;
275
- }
276
- // If the parent is an arrow function, we already handle it in ArrowFunctionExpression
277
- if (node.parent?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
301
+ // Use the common function to check if the type assertion should be allowed
302
+ if (shouldAllowTypeAssertion(node)) {
278
303
  return;
279
304
  }
280
305
  // For standalone type assertions in expressions
@@ -0,0 +1,5 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+ export declare const noUnnecessaryDestructuring: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noUnnecessaryDestructuring", never[], {
3
+ VariableDeclarator(node: TSESTree.VariableDeclarator): void;
4
+ AssignmentExpression(node: TSESTree.AssignmentExpression): void;
5
+ }>;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noUnnecessaryDestructuring = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ exports.noUnnecessaryDestructuring = (0, createRule_1.createRule)({
6
+ name: 'no-unnecessary-destructuring',
7
+ meta: {
8
+ type: 'suggestion',
9
+ docs: {
10
+ description: 'Avoid unnecessary object destructuring when there is only one property inside the destructured object',
11
+ recommended: 'error',
12
+ },
13
+ messages: {
14
+ noUnnecessaryDestructuring: 'Avoid unnecessary object destructuring with a single rest property. Use the object directly instead of `{ ...obj }`.',
15
+ },
16
+ schema: [],
17
+ fixable: 'code',
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ return {
22
+ // Handle variable declarations
23
+ VariableDeclarator(node) {
24
+ if (node.id.type === 'ObjectPattern' &&
25
+ node.id.properties.length === 1 &&
26
+ node.id.properties[0].type === 'RestElement') {
27
+ const restElement = node.id.properties[0];
28
+ // Report the issue
29
+ context.report({
30
+ node,
31
+ messageId: 'noUnnecessaryDestructuring',
32
+ fix(fixer) {
33
+ const sourceCode = context.getSourceCode();
34
+ const restName = sourceCode.getText(restElement.argument);
35
+ // Handle the case where init might be null
36
+ if (!node.init) {
37
+ return null;
38
+ }
39
+ const initText = sourceCode.getText(node.init);
40
+ // Replace the destructuring with direct assignment
41
+ return fixer.replaceText(node, `${restName} = ${initText}`);
42
+ },
43
+ });
44
+ }
45
+ },
46
+ // Handle assignments like { ...obj } = value
47
+ AssignmentExpression(node) {
48
+ if (node.operator === '=' &&
49
+ node.left.type === 'ObjectPattern' &&
50
+ node.left.properties.length === 1 &&
51
+ node.left.properties[0].type === 'RestElement') {
52
+ const restElement = node.left.properties[0];
53
+ context.report({
54
+ node,
55
+ messageId: 'noUnnecessaryDestructuring',
56
+ fix(fixer) {
57
+ const sourceCode = context.getSourceCode();
58
+ const restName = sourceCode.getText(restElement.argument);
59
+ const rightText = sourceCode.getText(node.right);
60
+ // Replace the destructuring with direct assignment
61
+ return fixer.replaceText(node, `${restName} = ${rightText}`);
62
+ },
63
+ });
64
+ }
65
+ }
66
+ };
67
+ },
68
+ });
69
+ //# sourceMappingURL=no-unnecessary-destructuring.js.map
@@ -21,11 +21,15 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
21
21
  create(context) {
22
22
  const propsTypes = new Map();
23
23
  const usedProps = new Map();
24
+ // Track which spread types have been used in a component
25
+ const usedSpreadTypes = new Map();
24
26
  let currentComponent = null;
25
27
  return {
26
28
  TSTypeAliasDeclaration(node) {
27
29
  if (node.id.name.endsWith('Props')) {
28
30
  const props = {};
31
+ // Track which properties come from which spread type
32
+ const spreadTypeProps = {};
29
33
  function extractProps(typeNode) {
30
34
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
31
35
  typeNode.members.forEach((member) => {
@@ -45,6 +49,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
45
49
  const [baseType, pickedProps] = type.typeParameters.params;
46
50
  if (baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
47
51
  baseType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
52
+ const baseTypeName = baseType.typeName.name;
48
53
  // Extract the picked properties from the union type
49
54
  if (pickedProps.type === utils_1.AST_NODE_TYPES.TSUnionType) {
50
55
  pickedProps.types.forEach((t) => {
@@ -52,7 +57,13 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
52
57
  t.literal.type === utils_1.AST_NODE_TYPES.Literal &&
53
58
  typeof t.literal.value === 'string') {
54
59
  // Add each picked property as a regular prop
55
- props[t.literal.value] = t.literal;
60
+ const propName = t.literal.value;
61
+ props[propName] = t.literal;
62
+ // Track that this prop comes from the base type
63
+ if (!spreadTypeProps[baseTypeName]) {
64
+ spreadTypeProps[baseTypeName] = [];
65
+ }
66
+ spreadTypeProps[baseTypeName].push(propName);
56
67
  }
57
68
  });
58
69
  }
@@ -60,7 +71,13 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
60
71
  pickedProps.literal.type === utils_1.AST_NODE_TYPES.Literal &&
61
72
  typeof pickedProps.literal.value === 'string') {
62
73
  // Single property pick
63
- props[pickedProps.literal.value] = pickedProps.literal;
74
+ const propName = pickedProps.literal.value;
75
+ props[propName] = pickedProps.literal;
76
+ // Track that this prop comes from the base type
77
+ if (!spreadTypeProps[baseTypeName]) {
78
+ spreadTypeProps[baseTypeName] = [];
79
+ }
80
+ spreadTypeProps[baseTypeName].push(propName);
64
81
  }
65
82
  }
66
83
  }
@@ -74,7 +91,13 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
74
91
  else {
75
92
  // If we can't find the type declaration, it's likely an imported type
76
93
  // Mark it as a forwarded prop
77
- props[`...${typeName.name}`] = typeName;
94
+ const spreadTypeName = typeName.name;
95
+ props[`...${spreadTypeName}`] = typeName;
96
+ // For imported types, we need to track individual properties that might be used
97
+ // from this spread type, even if we don't know what they are yet
98
+ if (!spreadTypeProps[spreadTypeName]) {
99
+ spreadTypeProps[spreadTypeName] = [];
100
+ }
78
101
  }
79
102
  }
80
103
  }
@@ -91,6 +114,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
91
114
  const [baseType, pickedProps] = typeNode.typeParameters.params;
92
115
  if (baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
93
116
  baseType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
117
+ const baseTypeName = baseType.typeName.name;
94
118
  // Extract the picked properties from the union type
95
119
  if (pickedProps.type === utils_1.AST_NODE_TYPES.TSUnionType) {
96
120
  pickedProps.types.forEach((type) => {
@@ -98,7 +122,13 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
98
122
  type.literal.type === utils_1.AST_NODE_TYPES.Literal &&
99
123
  typeof type.literal.value === 'string') {
100
124
  // Add each picked property as a regular prop
101
- props[type.literal.value] = type.literal;
125
+ const propName = type.literal.value;
126
+ props[propName] = type.literal;
127
+ // Track that this prop comes from the base type
128
+ if (!spreadTypeProps[baseTypeName]) {
129
+ spreadTypeProps[baseTypeName] = [];
130
+ }
131
+ spreadTypeProps[baseTypeName].push(propName);
102
132
  }
103
133
  });
104
134
  }
@@ -106,19 +136,44 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
106
136
  pickedProps.literal.type === utils_1.AST_NODE_TYPES.Literal &&
107
137
  typeof pickedProps.literal.value === 'string') {
108
138
  // Single property pick
109
- props[pickedProps.literal.value] = pickedProps.literal;
139
+ const propName = pickedProps.literal.value;
140
+ props[propName] = pickedProps.literal;
141
+ // Track that this prop comes from the base type
142
+ if (!spreadTypeProps[baseTypeName]) {
143
+ spreadTypeProps[baseTypeName] = [];
144
+ }
145
+ spreadTypeProps[baseTypeName].push(propName);
110
146
  }
111
147
  }
112
148
  }
113
149
  else {
114
150
  // For referenced types like FormControlLabelProps, we need to track that these props should be forwarded
115
- props[`...${typeNode.typeName.name}`] = typeNode.typeName;
151
+ const spreadTypeName = typeNode.typeName.name;
152
+ props[`...${spreadTypeName}`] = typeNode.typeName;
153
+ // For imported types, we need to track individual properties that might be used
154
+ // from this spread type, even if we don't know what they are yet
155
+ if (!spreadTypeProps[spreadTypeName]) {
156
+ spreadTypeProps[spreadTypeName] = [];
157
+ }
116
158
  }
117
159
  }
118
160
  }
119
161
  }
120
162
  extractProps(node.typeAnnotation);
121
163
  propsTypes.set(node.id.name, props);
164
+ // Store the mapping of spread types to their properties
165
+ const typeName = node.id.name;
166
+ usedSpreadTypes.set(typeName, new Set(Object.keys(spreadTypeProps)));
167
+ // Store the spread type properties for later reference
168
+ for (const [spreadType, propNames] of Object.entries(spreadTypeProps)) {
169
+ // Create a map entry for this spread type if it doesn't exist
170
+ if (!usedSpreadTypes.has(spreadType)) {
171
+ usedSpreadTypes.set(spreadType, new Set());
172
+ }
173
+ // Add the property names to the spread type's set
174
+ const spreadTypeSet = usedSpreadTypes.get(spreadType);
175
+ propNames.forEach(prop => spreadTypeSet.add(prop));
176
+ }
122
177
  }
123
178
  },
124
179
  VariableDeclaration(node) {
@@ -168,11 +223,54 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
168
223
  if (propsType && used) {
169
224
  Object.keys(propsType).forEach((prop) => {
170
225
  if (!used.has(prop)) {
171
- context.report({
172
- node: propsType[prop],
173
- messageId: 'unusedProp',
174
- data: { propName: prop },
175
- });
226
+ // For imported types (props that start with '...'), only report if there's no rest spread operator
227
+ // This allows imported types to be used without being flagged when properly forwarded
228
+ const hasRestSpread = Array.from(used.values()).some(usedProp => usedProp.startsWith('...'));
229
+ // Don't report unused props if:
230
+ // 1. It's a spread type and there's a rest spread operator, OR
231
+ // 2. It's a property from a spread type and any property from that spread type is used, OR
232
+ // 3. It's a spread type and any of its properties are used in the component
233
+ let shouldReport = true;
234
+ if (prop.startsWith('...') && hasRestSpread) {
235
+ shouldReport = false;
236
+ }
237
+ else if (prop.startsWith('...')) {
238
+ // For spread types like "...GroupInfoBasic", check if any properties from this type are used
239
+ const spreadTypeName = prop.substring(3); // Remove the "..." prefix
240
+ // Get the properties that belong to this spread type
241
+ const spreadTypeProps = usedSpreadTypes.get(spreadTypeName);
242
+ if (spreadTypeProps) {
243
+ // Check if any property from this spread type is being used in the component
244
+ const anyPropFromSpreadTypeUsed = Array.from(spreadTypeProps).some(spreadProp => used.has(spreadProp));
245
+ if (anyPropFromSpreadTypeUsed) {
246
+ shouldReport = false;
247
+ }
248
+ }
249
+ }
250
+ else {
251
+ // Check if this prop might be from a spread type that has other properties being used
252
+ for (const [spreadType, props] of usedSpreadTypes.entries()) {
253
+ // Skip the current props type
254
+ if (spreadType === typeName)
255
+ continue;
256
+ // If this prop is from a spread type
257
+ if (props.has(prop)) {
258
+ // Check if any other prop from this spread type is being used
259
+ const anyPropFromSpreadTypeUsed = Array.from(props).some(spreadProp => used.has(spreadProp));
260
+ if (anyPropFromSpreadTypeUsed) {
261
+ shouldReport = false;
262
+ break;
263
+ }
264
+ }
265
+ }
266
+ }
267
+ if (shouldReport) {
268
+ context.report({
269
+ node: propsType[prop],
270
+ messageId: 'unusedProp',
271
+ data: { propName: prop },
272
+ });
273
+ }
176
274
  }
177
275
  });
178
276
  }
@@ -0,0 +1,8 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+ /**
3
+ * Rule to detect and remove unused useState hooks in React components
4
+ * This rule identifies cases where the state variable from useState is ignored (e.g., replaced with _)
5
+ */
6
+ export declare const noUnusedUseState: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"unusedUseState", never[], {
7
+ VariableDeclarator(node: TSESTree.VariableDeclarator): void;
8
+ }>;