@blumintinc/eslint-plugin-blumint 1.13.0 → 1.14.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 (186) hide show
  1. package/README.md +170 -139
  2. package/lib/index.js +190 -1
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  5. package/lib/rules/consistent-callback-naming.js +1 -1
  6. package/lib/rules/dynamic-https-errors.js +3 -4
  7. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  8. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
  9. package/lib/rules/enforce-callback-memo.js +1 -1
  10. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  11. package/lib/rules/enforce-console-error.js +35 -7
  12. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  13. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  14. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  15. package/lib/rules/enforce-early-destructuring.js +980 -0
  16. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  17. package/lib/rules/enforce-empty-object-check.js +502 -0
  18. package/lib/rules/enforce-exported-function-types.js +1 -1
  19. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  20. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  21. package/lib/rules/enforce-firestore-facade.js +4 -2
  22. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  23. package/lib/rules/enforce-global-constants.js +1 -1
  24. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  25. package/lib/rules/enforce-memoize-async.js +2 -6
  26. package/lib/rules/enforce-memoize-getters.js +39 -15
  27. package/lib/rules/enforce-microdiff.js +1 -1
  28. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  29. package/lib/rules/enforce-positive-naming.js +4 -0
  30. package/lib/rules/enforce-props-argument-name.js +2 -2
  31. package/lib/rules/enforce-safe-stringify.js +1 -1
  32. package/lib/rules/enforce-serializable-params.js +3 -3
  33. package/lib/rules/enforce-singular-type-names.js +1 -1
  34. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  35. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  36. package/lib/rules/enforce-storage-context.d.ts +9 -0
  37. package/lib/rules/enforce-storage-context.js +646 -0
  38. package/lib/rules/enforce-timestamp-now.js +23 -7
  39. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  40. package/lib/rules/enforce-transform-memoization.js +416 -0
  41. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  42. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  43. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  44. package/lib/rules/enforce-verb-noun-naming.js +5 -4
  45. package/lib/rules/ensure-pointer-events-none.js +28 -2
  46. package/lib/rules/export-if-in-doubt.js +27 -4
  47. package/lib/rules/extract-global-constants.js +40 -13
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  49. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  50. package/lib/rules/flatten-push-calls.d.ts +2 -0
  51. package/lib/rules/flatten-push-calls.js +428 -0
  52. package/lib/rules/global-const-style.js +50 -19
  53. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  54. package/lib/rules/jsdoc-above-field.js +208 -0
  55. package/lib/rules/key-only-outermost-element.js +21 -3
  56. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  57. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  58. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  59. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  60. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  61. package/lib/rules/memo-nested-react-components.js +410 -0
  62. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  63. package/lib/rules/no-always-true-false-conditions.js +68 -8
  64. package/lib/rules/no-array-length-in-deps.js +9 -3
  65. package/lib/rules/no-async-foreach.js +210 -12
  66. package/lib/rules/no-circular-references.js +18 -17
  67. package/lib/rules/no-class-instance-destructuring.js +55 -12
  68. package/lib/rules/no-complex-cloud-params.js +7 -3
  69. package/lib/rules/no-compositing-layer-props.js +2 -2
  70. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  71. package/lib/rules/no-console-error.d.ts +9 -0
  72. package/lib/rules/no-console-error.js +525 -0
  73. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  74. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  75. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  76. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  77. package/lib/rules/no-entire-object-hook-deps.js +4 -6
  78. package/lib/rules/no-excessive-parent-chain.js +1 -1
  79. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  80. package/lib/rules/no-explicit-return-type.js +215 -26
  81. package/lib/rules/no-filter-without-return.js +5 -1
  82. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  83. package/lib/rules/no-firestore-jest-mock.js +126 -8
  84. package/lib/rules/no-firestore-object-arrays.js +67 -12
  85. package/lib/rules/no-handler-suffix.d.ts +12 -0
  86. package/lib/rules/no-handler-suffix.js +305 -0
  87. package/lib/rules/no-hungarian.js +1 -1
  88. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  89. package/lib/rules/no-inline-component-prop.js +456 -0
  90. package/lib/rules/no-jsx-in-hooks.js +6 -1
  91. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  92. package/lib/rules/no-margin-properties.js +6 -6
  93. package/lib/rules/no-memoize-on-static.js +9 -1
  94. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  95. package/lib/rules/no-misused-switch-case.js +22 -1
  96. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  97. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  98. package/lib/rules/no-mock-firebase-admin.js +5 -2
  99. package/lib/rules/no-object-values-on-strings.js +10 -2
  100. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  101. package/lib/rules/no-passthrough-getters.js +38 -2
  102. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  103. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  104. package/lib/rules/no-redundant-param-types.js +16 -6
  105. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  106. package/lib/rules/no-redundant-this-params.js +459 -0
  107. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  108. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  109. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  110. package/lib/rules/no-restricted-properties-fix.js +11 -10
  111. package/lib/rules/no-separate-loading-state.js +7 -16
  112. package/lib/rules/no-stale-state-across-await.js +1 -1
  113. package/lib/rules/no-type-assertion-returns.js +43 -49
  114. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  115. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  116. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  117. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  118. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  119. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  120. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  121. package/lib/rules/no-unpinned-dependencies.js +36 -5
  122. package/lib/rules/no-unused-props.d.ts +2 -2
  123. package/lib/rules/no-unused-props.js +295 -91
  124. package/lib/rules/no-unused-usestate.js +6 -2
  125. package/lib/rules/no-useless-fragment.js +28 -2
  126. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  127. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  128. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  129. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  130. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  131. package/lib/rules/omit-index-html.d.ts +2 -1
  132. package/lib/rules/omit-index-html.js +62 -7
  133. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  134. package/lib/rules/parallelize-async-operations.js +24 -5
  135. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  136. package/lib/rules/prefer-batch-operations.js +32 -5
  137. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  138. package/lib/rules/prefer-clone-deep.js +3 -3
  139. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  140. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  141. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  142. package/lib/rules/prefer-docsetter-setall.js +243 -0
  143. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  144. package/lib/rules/prefer-fragment-component.js +1 -1
  145. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  146. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  147. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  148. package/lib/rules/prefer-global-router-state-key.js +88 -13
  149. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  150. package/lib/rules/prefer-memoized-props.js +445 -0
  151. package/lib/rules/prefer-next-dynamic.js +60 -69
  152. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
  153. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  154. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  155. package/lib/rules/prefer-params-over-parent-id.js +220 -171
  156. package/lib/rules/prefer-settings-object.js +2 -2
  157. package/lib/rules/prefer-type-over-interface.js +7 -2
  158. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  159. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  160. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  161. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  162. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  163. package/lib/rules/prevent-children-clobber.js +536 -0
  164. package/lib/rules/react-memoize-literals.d.ts +4 -0
  165. package/lib/rules/react-memoize-literals.js +495 -0
  166. package/lib/rules/react-usememo-should-be-component.js +2 -2
  167. package/lib/rules/require-hooks-default-params.js +17 -16
  168. package/lib/rules/require-https-error-cause.d.ts +4 -0
  169. package/lib/rules/require-https-error-cause.js +136 -0
  170. package/lib/rules/require-https-error.js +43 -21
  171. package/lib/rules/require-image-optimized.js +1 -1
  172. package/lib/rules/require-memo.js +1 -1
  173. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  174. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  175. package/lib/rules/require-usememo-object-literals.js +2 -3
  176. package/lib/rules/test-file-location-enforcement.js +1 -1
  177. package/lib/rules/use-latest-callback.js +6 -5
  178. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  179. package/lib/rules/vertically-group-related-functions.js +480 -0
  180. package/lib/utils/ASTHelpers.d.ts +13 -1
  181. package/lib/utils/ASTHelpers.js +24 -0
  182. package/lib/utils/getMethodName.d.ts +27 -0
  183. package/lib/utils/getMethodName.js +35 -0
  184. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  185. package/lib/utils/tsTypeClassifier.js +149 -0
  186. package/package.json +3 -1
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noUnusedProps = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
7
+ const PAREN_TYPE = utils_1.AST_NODE_TYPES.TSParenthesizedType ??
8
+ 'TSParenthesizedType';
6
9
  exports.noUnusedProps = (0, createRule_1.createRule)({
7
10
  name: 'no-unused-props',
8
11
  meta: {
@@ -13,7 +16,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
13
16
  },
14
17
  schema: [],
15
18
  messages: {
16
- unusedProp: 'Prop "{{propName}}" is defined in the Props type but not used in the component. Either use the prop in your component or remove it from the Props type. If you need to forward all props, use a rest spread operator: `const MyComponent = ({ usedProp, ...rest }: Props) => ...`',
19
+ unusedProp: 'Prop "{{propName}}" is declared in the component Props type but never used inside the component body. Unused props make the component API misleading: callers keep passing values that are ignored and reviewers assume behavior that is not implemented. Remove "{{propName}}" from the Props type, consume it in the component, or forward it with a rest spread (e.g., `const MyComponent = ({ usedProp, ...rest }: Props) => <Child {...rest} />`).',
17
20
  },
18
21
  fixable: 'code',
19
22
  },
@@ -22,8 +25,16 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
22
25
  const filename = context.filename ??
23
26
  context.getFilename();
24
27
  const ruleSettings = (context.settings && context.settings['no-unused-props']) ?? {};
25
- const reactLikeExtensions = (ruleSettings.reactLikeExtensions ??
26
- ['.tsx']).map((ext) => ext.toLowerCase());
28
+ const normalizeExtension = (ext) => {
29
+ const trimmed = ext.trim().toLowerCase();
30
+ if (!trimmed) {
31
+ return null;
32
+ }
33
+ return trimmed.startsWith('.') ? trimmed : `.${trimmed}`;
34
+ };
35
+ const reactLikeExtensions = Array.from(new Set((ruleSettings.reactLikeExtensions ?? ['.tsx'])
36
+ .map(normalizeExtension)
37
+ .filter((ext) => Boolean(ext))));
27
38
  const fileExtension = filename.includes('.')
28
39
  ? filename.slice(filename.lastIndexOf('.')).toLowerCase()
29
40
  : '';
@@ -43,14 +54,43 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
43
54
  'InstanceType',
44
55
  'ThisType',
45
56
  ]);
57
+ // Marks spread types whose members cannot be enumerated so unknown used props
58
+ // can still count as "using something" from the imported/spread type.
59
+ const UNKNOWN_SPREAD_PROP = '__unknown_spread_prop__';
60
+ const markUnknownSpreadType = (spreadTypeProps, spreadTypeName) => {
61
+ spreadTypeProps[spreadTypeName] ??= [];
62
+ if (!spreadTypeProps[spreadTypeName].includes(UNKNOWN_SPREAD_PROP)) {
63
+ spreadTypeProps[spreadTypeName].push(UNKNOWN_SPREAD_PROP);
64
+ }
65
+ };
66
+ const isTypeAliasDeclaration = (node) => node?.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration;
67
+ const findTypeAliasDeclaration = (typeName) => {
68
+ let scope = context.getScope();
69
+ while (scope) {
70
+ const variable = scope.set?.get(typeName) ??
71
+ scope.variables.find((v) => v.name === typeName);
72
+ if (variable) {
73
+ const typeAliasDef = variable.defs.find((def) => def.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
74
+ const typeAliasNode = typeAliasDef?.node;
75
+ if (isTypeAliasDeclaration(typeAliasNode)) {
76
+ return typeAliasNode;
77
+ }
78
+ }
79
+ scope = scope.upper;
80
+ }
81
+ return null;
82
+ };
46
83
  const propsTypes = new Map();
47
- // Track which spread types have been used in a component
48
- const usedSpreadTypes = new Map();
84
+ const componentToReferencedSpreadTypeNames = new Map();
85
+ const spreadTypeToPropNames = new Map();
86
+ const processingTypeAliases = new Set();
49
87
  const componentsToCheck = [];
50
88
  let currentComponent = null;
51
89
  const clearState = () => {
52
90
  propsTypes.clear();
53
- usedSpreadTypes.clear();
91
+ componentToReferencedSpreadTypeNames.clear();
92
+ spreadTypeToPropNames.clear();
93
+ processingTypeAliases.clear();
54
94
  componentsToCheck.length = 0;
55
95
  currentComponent = null;
56
96
  };
@@ -66,19 +106,44 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
66
106
  }
67
107
  return false;
68
108
  };
69
- const isAnyPropFromSpreadTypeUsed = (spreadTypeName, used) => {
70
- const spreadTypeProps = usedSpreadTypes.get(spreadTypeName);
71
- if (!spreadTypeProps) {
109
+ const isTSParenthesizedType = (node) => node.type === PAREN_TYPE &&
110
+ node.typeAnnotation != null;
111
+ const getStaticPropName = (key) => {
112
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
113
+ return key.name;
114
+ }
115
+ if (key.type === utils_1.AST_NODE_TYPES.Literal &&
116
+ typeof key.value === 'string') {
117
+ return key.value;
118
+ }
119
+ return null;
120
+ };
121
+ const isAnyPropFromSpreadTypeUsed = (spreadTypeName, used, knownProps) => {
122
+ const spreadTypeProps = spreadTypeToPropNames.get(spreadTypeName);
123
+ if (!spreadTypeProps || spreadTypeProps.size === 0) {
72
124
  return false;
73
125
  }
126
+ if (spreadTypeProps.has(UNKNOWN_SPREAD_PROP)) {
127
+ for (const usedProp of used) {
128
+ if (usedProp.startsWith('...')) {
129
+ continue;
130
+ }
131
+ if (!knownProps.has(usedProp)) {
132
+ return true;
133
+ }
134
+ }
135
+ }
74
136
  for (const spreadProp of spreadTypeProps) {
137
+ if (spreadProp === UNKNOWN_SPREAD_PROP) {
138
+ continue;
139
+ }
75
140
  if (used.has(spreadProp)) {
76
141
  return true;
77
142
  }
78
143
  }
79
144
  return false;
80
145
  };
81
- const shouldSkipSpreadType = (prop, hasRestSpread, used) => {
146
+ const shouldSkipSpreadType = (prop, hasRestSpread, used, knownProps) => {
82
147
  if (hasRestSpread) {
83
148
  return true;
84
149
  }
@@ -86,16 +151,18 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
86
151
  if (UTILITY_TYPES.has(spreadTypeName)) {
87
152
  return true;
88
153
  }
89
- return isAnyPropFromSpreadTypeUsed(spreadTypeName, used);
154
+ return isAnyPropFromSpreadTypeUsed(spreadTypeName, used, knownProps);
90
155
  };
91
156
  const shouldSkipPropFromSpreadType = (prop, currentTypeName, used) => {
92
- for (const [spreadType, props] of usedSpreadTypes.entries()) {
93
- if (spreadType === currentTypeName)
94
- continue;
95
- if (!props.has(prop)) {
157
+ const spreadTypes = componentToReferencedSpreadTypeNames.get(currentTypeName);
158
+ if (!spreadTypes)
159
+ return false;
160
+ for (const spreadType of spreadTypes) {
161
+ const spreadProps = spreadTypeToPropNames.get(spreadType);
162
+ if (!spreadProps || !spreadProps.has(prop)) {
96
163
  continue;
97
164
  }
98
- for (const spreadProp of props) {
165
+ for (const spreadProp of spreadProps) {
99
166
  if (used.has(spreadProp)) {
100
167
  return true;
101
168
  }
@@ -109,6 +176,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
109
176
  return;
110
177
  }
111
178
  const hasRestSpread = hasRestSpreadUsage(used, restUsed);
179
+ const knownProps = new Set(Object.keys(propsType));
112
180
  Object.keys(propsType).forEach((prop) => {
113
181
  if (!used.has(prop)) {
114
182
  // For imported types (props that start with '...'), only report if there's no rest spread operator
@@ -123,7 +191,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
123
191
  shouldReport = false;
124
192
  }
125
193
  else if (prop.startsWith('...')) {
126
- shouldReport = !shouldSkipSpreadType(prop, hasRestSpread, used);
194
+ shouldReport = !shouldSkipSpreadType(prop, hasRestSpread, used, knownProps);
127
195
  }
128
196
  else {
129
197
  shouldReport = !shouldSkipPropFromSpreadType(prop, typeName, used);
@@ -140,62 +208,206 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
140
208
  };
141
209
  return {
142
210
  TSTypeAliasDeclaration(node) {
143
- if (node.id.name.endsWith('Props')) {
211
+ if (!node.id.name.endsWith('Props')) {
212
+ return;
213
+ }
214
+ if (processingTypeAliases.has(node.id.name)) {
215
+ return;
216
+ }
217
+ processingTypeAliases.add(node.id.name);
218
+ try {
144
219
  const props = {};
145
220
  // Track which properties come from which spread type
146
221
  const spreadTypeProps = {};
147
- const addBaseTypeProps = (typeNode, shouldInclude = () => true) => {
148
- if (typeNode.type !== utils_1.AST_NODE_TYPES.TSTypeLiteral) {
149
- return;
222
+ const visitingTypeReferences = new Set();
223
+ const scope = context.getScope();
224
+ const withTypeVisitation = (visited, typeName, callback) => {
225
+ if (visited.has(typeName)) {
226
+ return null;
227
+ }
228
+ visited.add(typeName);
229
+ try {
230
+ return callback();
231
+ }
232
+ finally {
233
+ visited.delete(typeName);
150
234
  }
151
- typeNode.members.forEach((member) => {
152
- if (member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
153
- member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
154
- shouldInclude(member.key.name)) {
155
- props[member.key.name] = member.key;
235
+ };
236
+ const addBaseTypeProps = (typeNode, shouldInclude = () => true) => {
237
+ // Tracks type identifiers while descending to avoid infinite recursion on
238
+ // self-referential or mutually recursive type aliases; names are removed
239
+ // in the finally block so parallel branches still run.
240
+ const visitingTypeNames = new Set();
241
+ const visit = (node) => {
242
+ if (isTSParenthesizedType(node)) {
243
+ return visit(node.typeAnnotation);
156
244
  }
157
- });
245
+ switch (node.type) {
246
+ case utils_1.AST_NODE_TYPES.TSTypeLiteral: {
247
+ let added = false;
248
+ node.members.forEach((member) => {
249
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
250
+ return;
251
+ }
252
+ const propName = getStaticPropName(member.key);
253
+ if (propName && shouldInclude(propName)) {
254
+ props[propName] = member.key;
255
+ added = true;
256
+ }
257
+ });
258
+ return added;
259
+ }
260
+ case utils_1.AST_NODE_TYPES.TSIntersectionType:
261
+ case utils_1.AST_NODE_TYPES.TSUnionType: {
262
+ let added = false;
263
+ node.types.forEach((child) => {
264
+ if (visit(child)) {
265
+ added = true;
266
+ }
267
+ });
268
+ return added;
269
+ }
270
+ case utils_1.AST_NODE_TYPES.TSTypeReference: {
271
+ if (node.typeName.type !== utils_1.AST_NODE_TYPES.Identifier) {
272
+ return false;
273
+ }
274
+ const typeIdentifier = node.typeName;
275
+ return (withTypeVisitation(visitingTypeNames, typeIdentifier.name, () => {
276
+ const typeAliasNode = findTypeAliasDeclaration(typeIdentifier.name);
277
+ return typeAliasNode
278
+ ? visit(typeAliasNode.typeAnnotation)
279
+ : false;
280
+ }) ?? false);
281
+ }
282
+ default:
283
+ return false;
284
+ }
285
+ };
286
+ return visit(typeNode);
158
287
  };
159
- const handleOmitType = (baseType, omittedProps) => {
160
- if (baseType.type !== utils_1.AST_NODE_TYPES.TSTypeReference ||
161
- baseType.typeName.type !== utils_1.AST_NODE_TYPES.Identifier) {
162
- return;
163
- }
164
- const baseTypeName = baseType.typeName.name;
288
+ const handleOmitType = (baseType, omittedProps, currentScope) => {
289
+ const baseTypeName = baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
290
+ baseType.typeName.type === utils_1.AST_NODE_TYPES.Identifier
291
+ ? baseType.typeName.name
292
+ : null;
293
+ const sourceCode = context.getSourceCode();
294
+ const baseTypeRefText = baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference
295
+ ? sourceCode.getText(baseType.typeName)
296
+ : null;
297
+ const spreadKey = baseTypeName ?? baseTypeRefText;
298
+ const fallbackSpreadKey = spreadKey ?? sourceCode.getText(baseType);
299
+ const recordSpreadMarker = () => {
300
+ const markerKey = fallbackSpreadKey;
301
+ if (!markerKey) {
302
+ return;
303
+ }
304
+ props[`...${markerKey}`] =
305
+ baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference
306
+ ? baseType.typeName
307
+ : baseType;
308
+ markUnknownSpreadType(spreadTypeProps, markerKey);
309
+ };
165
310
  const omittedPropNames = new Set();
311
+ let canComputeOmittedProps = false;
166
312
  if (omittedProps.type === utils_1.AST_NODE_TYPES.TSUnionType) {
167
- omittedProps.types.forEach((type) => {
168
- if (type.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
169
- type.literal.type === utils_1.AST_NODE_TYPES.Literal &&
170
- typeof type.literal.value === 'string') {
171
- omittedPropNames.add(type.literal.value);
172
- }
173
- });
313
+ canComputeOmittedProps = omittedProps.types.every((type) => type.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
314
+ type.literal.type === utils_1.AST_NODE_TYPES.Literal &&
315
+ typeof type.literal.value === 'string');
316
+ if (canComputeOmittedProps) {
317
+ omittedProps.types.forEach((type) => {
318
+ if (type.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
319
+ type.literal.type === utils_1.AST_NODE_TYPES.Literal &&
320
+ typeof type.literal.value === 'string') {
321
+ omittedPropNames.add(type.literal.value);
322
+ }
323
+ });
324
+ }
174
325
  }
175
326
  else if (omittedProps.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
176
327
  omittedProps.literal.type === utils_1.AST_NODE_TYPES.Literal &&
177
328
  typeof omittedProps.literal.value === 'string') {
329
+ canComputeOmittedProps = true;
178
330
  omittedPropNames.add(omittedProps.literal.value);
179
331
  }
180
- const scope = context.getScope();
181
- const variable = scope.variables.find((v) => v.name === baseTypeName);
182
- if (variable &&
183
- variable.defs[0]?.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
184
- addBaseTypeProps(variable.defs[0].node.typeAnnotation, (name) => !omittedPropNames.has(name));
332
+ if (!canComputeOmittedProps) {
333
+ if (spreadKey) {
334
+ recordSpreadMarker();
335
+ return;
336
+ }
337
+ const added = addBaseTypeProps(baseType);
338
+ if (!added) {
339
+ recordSpreadMarker();
340
+ }
341
+ return;
185
342
  }
186
- else {
187
- props[`...${baseTypeName}`] = baseType.typeName;
188
- if (!spreadTypeProps[baseTypeName]) {
189
- spreadTypeProps[baseTypeName] = [];
343
+ const shouldInclude = (name) => !omittedPropNames.has(name);
344
+ let added = false;
345
+ if (currentScope && baseTypeName) {
346
+ const variable = ASTHelpers_1.ASTHelpers.findVariableInScope(currentScope, baseTypeName);
347
+ const definitionNode = variable?.defs[0]?.node;
348
+ if (definitionNode?.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
349
+ added = addBaseTypeProps(definitionNode.typeAnnotation, shouldInclude);
190
350
  }
351
+ else if (definitionNode?.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
352
+ added = addInterfaceProps(definitionNode, currentScope, shouldInclude);
353
+ }
354
+ }
355
+ if (!added) {
356
+ added = addBaseTypeProps(baseType, shouldInclude);
191
357
  }
358
+ if (added) {
359
+ return;
360
+ }
361
+ recordSpreadMarker();
192
362
  };
193
- function extractProps(typeNode) {
363
+ const addPropsFromMembers = (members, shouldInclude = () => true) => {
364
+ let added = false;
365
+ for (const member of members) {
366
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature &&
367
+ member.type !== utils_1.AST_NODE_TYPES.TSMethodSignature) {
368
+ continue;
369
+ }
370
+ const propName = getStaticPropName(member.key);
371
+ if (!propName || !shouldInclude(propName)) {
372
+ continue;
373
+ }
374
+ props[propName] = member.key;
375
+ added = true;
376
+ }
377
+ return added;
378
+ };
379
+ const addInterfaceProps = (declaration, currentScope, shouldInclude = () => true, visitingInterfaces = new Set()) => {
380
+ return (withTypeVisitation(visitingInterfaces, declaration.id.name, () => {
381
+ let added = addPropsFromMembers(declaration.body.body, shouldInclude);
382
+ declaration.extends?.forEach((extension) => {
383
+ if (extension.expression.type === utils_1.AST_NODE_TYPES.Identifier &&
384
+ extension.expression.name) {
385
+ const parentVariable = ASTHelpers_1.ASTHelpers.findVariableInScope(currentScope, extension.expression.name);
386
+ const parentDef = parentVariable?.defs[0]?.node;
387
+ if (parentDef?.type ===
388
+ utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
389
+ added =
390
+ addInterfaceProps(parentDef, currentScope, shouldInclude, visitingInterfaces) || added;
391
+ }
392
+ else if (parentDef?.type ===
393
+ utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
394
+ added =
395
+ addBaseTypeProps(parentDef.typeAnnotation, shouldInclude) || added;
396
+ }
397
+ }
398
+ });
399
+ return added;
400
+ }) ?? false);
401
+ };
402
+ function extractProps(typeNode, currentScope) {
194
403
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
195
404
  typeNode.members.forEach((member) => {
196
- if (member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
197
- member.key.type === utils_1.AST_NODE_TYPES.Identifier) {
198
- props[member.key.name] = member.key;
405
+ if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
406
+ return;
407
+ }
408
+ const propName = getStaticPropName(member.key);
409
+ if (propName) {
410
+ props[propName] = member.key;
199
411
  }
200
412
  });
201
413
  }
@@ -246,16 +458,15 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
246
458
  type.typeParameters.params.length === 2) {
247
459
  // Handle Omit utility type in intersection
248
460
  const [baseType, omittedProps] = type.typeParameters.params;
249
- handleOmitType(baseType, omittedProps);
461
+ handleOmitType(baseType, omittedProps, currentScope);
250
462
  }
251
463
  else {
252
464
  // For referenced types in intersections, we need to find their type declaration
253
- const scope = context.getScope();
254
- const variable = scope.variables.find((v) => v.name === typeName.name);
255
- if (variable &&
256
- variable.defs[0]?.node.type ===
257
- utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
258
- extractProps(variable.defs[0].node.typeAnnotation);
465
+ const typeAliasNode = findTypeAliasDeclaration(typeName.name);
466
+ if (typeAliasNode) {
467
+ withTypeVisitation(visitingTypeReferences, typeName.name, () => {
468
+ extractProps(typeAliasNode.typeAnnotation, currentScope);
469
+ });
259
470
  }
260
471
  else {
261
472
  // If we can't find the type declaration, it's likely an imported type
@@ -264,15 +475,13 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
264
475
  props[`...${spreadTypeName}`] = typeName;
265
476
  // For imported types, we need to track individual properties that might be used
266
477
  // from this spread type, even if we don't know what they are yet
267
- if (!spreadTypeProps[spreadTypeName]) {
268
- spreadTypeProps[spreadTypeName] = [];
269
- }
478
+ markUnknownSpreadType(spreadTypeProps, spreadTypeName);
270
479
  }
271
480
  }
272
481
  }
273
482
  }
274
483
  else {
275
- extractProps(type);
484
+ extractProps(type, currentScope);
276
485
  }
277
486
  });
278
487
  }
@@ -328,7 +537,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
328
537
  typeNode.typeParameters.params.length === 2) {
329
538
  // Handle Omit<T, K> utility type
330
539
  const [baseType, omittedProps] = typeNode.typeParameters.params;
331
- handleOmitType(baseType, omittedProps);
540
+ handleOmitType(baseType, omittedProps, currentScope);
332
541
  }
333
542
  else if (
334
543
  // Handle other utility types like Required, Partial, etc.
@@ -339,21 +548,19 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
339
548
  if (baseType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
340
549
  baseType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
341
550
  const baseTypeName = baseType.typeName.name;
342
- // Find the base type definition
343
- const scope = context.getScope();
344
- const variable = scope.variables.find((v) => v.name === baseTypeName);
345
- if (variable &&
346
- variable.defs[0]?.node.type ===
347
- utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
551
+ const typeAliasNode = findTypeAliasDeclaration(baseTypeName);
552
+ if (typeAliasNode) {
348
553
  // For Partial<T>, Required<T>, etc., add all properties from the base type
349
- addBaseTypeProps(variable.defs[0].node.typeAnnotation);
554
+ const added = addBaseTypeProps(typeAliasNode.typeAnnotation);
555
+ if (!added) {
556
+ props[`...${baseTypeName}`] = baseType.typeName;
557
+ markUnknownSpreadType(spreadTypeProps, baseTypeName);
558
+ }
350
559
  }
351
560
  else {
352
561
  // If we can't find the base type definition, treat it as a spread type
353
562
  props[`...${baseTypeName}`] = baseType.typeName;
354
- if (!spreadTypeProps[baseTypeName]) {
355
- spreadTypeProps[baseTypeName] = [];
356
- }
563
+ markUnknownSpreadType(spreadTypeProps, baseTypeName);
357
564
  }
358
565
  }
359
566
  }
@@ -363,29 +570,24 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
363
570
  props[`...${spreadTypeName}`] = typeNode.typeName;
364
571
  // For imported types, we need to track individual properties that might be used
365
572
  // from this spread type, even if we don't know what they are yet
366
- if (!spreadTypeProps[spreadTypeName]) {
367
- spreadTypeProps[spreadTypeName] = [];
368
- }
573
+ markUnknownSpreadType(spreadTypeProps, spreadTypeName);
369
574
  }
370
575
  }
371
576
  }
372
577
  }
373
- extractProps(node.typeAnnotation);
578
+ extractProps(node.typeAnnotation, scope);
374
579
  propsTypes.set(node.id.name, props);
375
- // Store the mapping of spread types to their properties
376
580
  const typeName = node.id.name;
377
- usedSpreadTypes.set(typeName, new Set(Object.keys(spreadTypeProps)));
378
- // Store the spread type properties for later reference
581
+ componentToReferencedSpreadTypeNames.set(typeName, new Set(Object.keys(spreadTypeProps)));
379
582
  for (const [spreadType, propNames] of Object.entries(spreadTypeProps)) {
380
- // Create a map entry for this spread type if it doesn't exist
381
- if (!usedSpreadTypes.has(spreadType)) {
382
- usedSpreadTypes.set(spreadType, new Set());
383
- }
384
- // Add the property names to the spread type's set
385
- const spreadTypeSet = usedSpreadTypes.get(spreadType);
583
+ const spreadTypeSet = spreadTypeToPropNames.get(spreadType) ?? new Set();
584
+ spreadTypeToPropNames.set(spreadType, spreadTypeSet);
386
585
  propNames.forEach((prop) => spreadTypeSet.add(prop));
387
586
  }
388
587
  }
588
+ finally {
589
+ processingTypeAliases.delete(node.id.name);
590
+ }
389
591
  },
390
592
  VariableDeclaration(node) {
391
593
  if (node.declarations.length === 1) {
@@ -402,9 +604,11 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
402
604
  const used = new Set();
403
605
  let restUsed = false;
404
606
  param.properties.forEach((prop) => {
405
- if (prop.type === utils_1.AST_NODE_TYPES.Property &&
406
- prop.key.type === utils_1.AST_NODE_TYPES.Identifier) {
407
- used.add(prop.key.name);
607
+ if (prop.type === utils_1.AST_NODE_TYPES.Property) {
608
+ const propName = getStaticPropName(prop.key);
609
+ if (propName) {
610
+ used.add(propName);
611
+ }
408
612
  }
409
613
  else if (prop.type === utils_1.AST_NODE_TYPES.RestElement &&
410
614
  prop.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
@@ -17,7 +17,7 @@ exports.noUnusedUseState = createRule({
17
17
  },
18
18
  fixable: 'code',
19
19
  messages: {
20
- unusedUseState: 'The state variable is ignored. Remove the unused useState hook.',
20
+ unusedUseState: 'State value "{{stateName}}" from useState is discarded. React still allocates state and re-renders for a value you never read, which misleads readers into thinking the component depends on that state. Remove the useState pair or switch to a ref/derived value when you only need the setter-style side effect.',
21
21
  },
22
22
  schema: [],
23
23
  },
@@ -41,12 +41,16 @@ exports.noUnusedUseState = createRule({
41
41
  utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
42
42
  (arrayPattern.elements[0].name === '_' ||
43
43
  arrayPattern.elements[0].name.startsWith('_'))) {
44
+ const stateIdentifier = arrayPattern.elements[0];
44
45
  context.report({
45
46
  node,
46
47
  messageId: 'unusedUseState',
48
+ data: {
49
+ stateName: stateIdentifier.name,
50
+ },
47
51
  fix: (fixer) => {
48
52
  // Remove the entire useState declaration
49
- const sourceCode = context.getSourceCode();
53
+ const sourceCode = context.sourceCode;
50
54
  const parentStatement = node.parent;
51
55
  if (parentStatement &&
52
56
  parentStatement.type ===
@@ -1,16 +1,42 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noUselessFragment = void 0;
4
+ /**
5
+ * Normalizes JSX child node types into short descriptors used inside lint messages.
6
+ * Keeps message phrasing consistent regardless of the specific child node shape.
7
+ * @param child - The JSX child node to describe.
8
+ * @returns Human-readable descriptor for the child type used in lint messages.
9
+ */
10
+ const describeChild = (child) => {
11
+ switch (child.type) {
12
+ case 'JSXElement':
13
+ return 'JSX element';
14
+ case 'JSXFragment':
15
+ return 'fragment';
16
+ case 'JSXExpressionContainer':
17
+ return 'expression result';
18
+ case 'JSXText':
19
+ return 'text node';
20
+ case 'JSXSpreadChild':
21
+ return 'spread child';
22
+ default:
23
+ return 'child node';
24
+ }
25
+ };
4
26
  exports.noUselessFragment = {
5
27
  create(context) {
6
28
  return {
7
29
  JSXFragment(node) {
8
30
  if (node.children.length === 1) {
31
+ const [child] = node.children;
9
32
  context.report({
10
33
  node,
11
34
  messageId: 'noUselessFragment',
35
+ data: {
36
+ childKind: describeChild(child),
37
+ },
12
38
  fix(fixer) {
13
- const sourceCode = context.getSourceCode();
39
+ const sourceCode = context.sourceCode;
14
40
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
15
41
  const openingFragment = sourceCode.getFirstToken(node);
16
42
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -38,7 +64,7 @@ exports.noUselessFragment = {
38
64
  recommended: 'error',
39
65
  },
40
66
  messages: {
41
- noUselessFragment: 'React fragment is unnecessary when wrapping a single child. Instead of `<>{"text"}</> or <Fragment>{"text"}</Fragment>`, use just `{"text"}`. Fragments are only needed when returning multiple elements.',
67
+ noUselessFragment: 'React fragment wraps a single {{childKind}} and does not provide grouping. Fragments exist to wrap multiple siblings; leaving this fragment adds extra syntax and a React tree node without changing the rendered output. Remove the fragment and return the child directly.',
42
68
  },
43
69
  schema: [],
44
70
  fixable: 'code',
@@ -0,0 +1,9 @@
1
+ type Options = [
2
+ {
3
+ ignoreCallExpressions?: boolean;
4
+ ignoreSymbol?: boolean;
5
+ tsOnly?: boolean;
6
+ }
7
+ ];
8
+ export declare const noUselessUsememoPrimitives: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"uselessUseMemoPrimitive", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
9
+ export default noUselessUsememoPrimitives;