@blumintinc/eslint-plugin-blumint 1.13.0 → 1.15.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 (199) hide show
  1. package/README.md +169 -139
  2. package/lib/index.js +200 -9
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/avoid-utils-directory.js +0 -4
  5. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  6. package/lib/rules/consistent-callback-naming.js +43 -4
  7. package/lib/rules/dynamic-https-errors.d.ts +1 -1
  8. package/lib/rules/dynamic-https-errors.js +134 -44
  9. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  10. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -235
  11. package/lib/rules/enforce-callback-memo.js +1 -1
  12. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  13. package/lib/rules/enforce-console-error.js +35 -7
  14. package/lib/rules/enforce-date-ttime.d.ts +1 -0
  15. package/lib/rules/enforce-date-ttime.js +156 -0
  16. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  17. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  18. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
  20. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  21. package/lib/rules/enforce-early-destructuring.js +980 -0
  22. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  23. package/lib/rules/enforce-empty-object-check.js +502 -0
  24. package/lib/rules/enforce-exported-function-types.js +1 -1
  25. package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
  26. package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
  27. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  28. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  29. package/lib/rules/enforce-firestore-facade.js +4 -2
  30. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  31. package/lib/rules/enforce-global-constants.js +4 -4
  32. package/lib/rules/enforce-id-capitalization.js +1 -1
  33. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  34. package/lib/rules/enforce-memoize-async.js +71 -21
  35. package/lib/rules/enforce-memoize-getters.js +39 -15
  36. package/lib/rules/enforce-microdiff.js +1 -1
  37. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  38. package/lib/rules/enforce-positive-naming.js +4 -0
  39. package/lib/rules/enforce-props-argument-name.js +44 -18
  40. package/lib/rules/enforce-safe-stringify.js +1 -1
  41. package/lib/rules/enforce-serializable-params.js +3 -3
  42. package/lib/rules/enforce-singular-type-names.js +1 -1
  43. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  44. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  45. package/lib/rules/enforce-storage-context.d.ts +9 -0
  46. package/lib/rules/enforce-storage-context.js +646 -0
  47. package/lib/rules/enforce-timestamp-now.js +23 -7
  48. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  49. package/lib/rules/enforce-transform-memoization.js +416 -0
  50. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  51. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  52. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  53. package/lib/rules/enforce-verb-noun-naming.js +3818 -4644
  54. package/lib/rules/ensure-pointer-events-none.js +28 -2
  55. package/lib/rules/export-if-in-doubt.js +27 -4
  56. package/lib/rules/extract-global-constants.js +40 -13
  57. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  58. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  59. package/lib/rules/flatten-push-calls.d.ts +2 -0
  60. package/lib/rules/flatten-push-calls.js +428 -0
  61. package/lib/rules/global-const-style.js +66 -23
  62. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  63. package/lib/rules/jsdoc-above-field.js +208 -0
  64. package/lib/rules/key-only-outermost-element.js +21 -3
  65. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  66. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  67. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  68. package/lib/rules/memo-compare-deeply-complex-props.js +801 -0
  69. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  70. package/lib/rules/memo-nested-react-components.js +415 -0
  71. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  72. package/lib/rules/no-always-true-false-conditions.js +68 -8
  73. package/lib/rules/no-array-length-in-deps.js +9 -3
  74. package/lib/rules/no-async-foreach.js +215 -12
  75. package/lib/rules/no-circular-references.d.ts +2 -1
  76. package/lib/rules/no-circular-references.js +31 -32
  77. package/lib/rules/no-class-instance-destructuring.js +55 -12
  78. package/lib/rules/no-complex-cloud-params.js +7 -3
  79. package/lib/rules/no-compositing-layer-props.js +2 -2
  80. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  81. package/lib/rules/no-console-error.d.ts +9 -0
  82. package/lib/rules/no-console-error.js +527 -0
  83. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  84. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  85. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  86. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  87. package/lib/rules/no-entire-object-hook-deps.js +52 -7
  88. package/lib/rules/no-excessive-parent-chain.js +4 -1
  89. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  90. package/lib/rules/no-explicit-return-type.js +215 -26
  91. package/lib/rules/no-filter-without-return.js +5 -1
  92. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  93. package/lib/rules/no-firestore-jest-mock.js +126 -8
  94. package/lib/rules/no-firestore-object-arrays.js +67 -12
  95. package/lib/rules/no-handler-suffix.d.ts +12 -0
  96. package/lib/rules/no-handler-suffix.js +305 -0
  97. package/lib/rules/no-hungarian.js +1 -1
  98. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  99. package/lib/rules/no-inline-component-prop.js +465 -0
  100. package/lib/rules/no-jsx-in-hooks.js +6 -1
  101. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  102. package/lib/rules/no-margin-properties.js +6 -6
  103. package/lib/rules/no-memoize-on-static.js +9 -1
  104. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  105. package/lib/rules/no-misused-switch-case.js +22 -1
  106. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  107. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  108. package/lib/rules/no-mock-firebase-admin.js +5 -2
  109. package/lib/rules/no-object-values-on-strings.js +10 -2
  110. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  111. package/lib/rules/no-passthrough-getters.d.ts +2 -2
  112. package/lib/rules/no-passthrough-getters.js +120 -2
  113. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  114. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  115. package/lib/rules/no-redundant-param-types.js +16 -6
  116. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  117. package/lib/rules/no-redundant-this-params.js +508 -0
  118. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  119. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  120. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  121. package/lib/rules/no-restricted-properties-fix.js +11 -10
  122. package/lib/rules/no-separate-loading-state.js +7 -16
  123. package/lib/rules/no-stale-state-across-await.js +1 -1
  124. package/lib/rules/no-type-assertion-returns.js +43 -49
  125. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  126. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  127. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  128. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  129. package/lib/rules/no-unnecessary-destructuring-rename.js +344 -0
  130. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  131. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  132. package/lib/rules/no-unpinned-dependencies.js +36 -5
  133. package/lib/rules/no-unused-props.d.ts +2 -2
  134. package/lib/rules/no-unused-props.js +295 -91
  135. package/lib/rules/no-unused-usestate.js +6 -2
  136. package/lib/rules/no-useless-fragment.js +28 -2
  137. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  138. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  139. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  140. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  141. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  142. package/lib/rules/omit-index-html.d.ts +2 -1
  143. package/lib/rules/omit-index-html.js +62 -7
  144. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  145. package/lib/rules/parallelize-async-operations.js +143 -59
  146. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  147. package/lib/rules/prefer-batch-operations.js +32 -5
  148. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  149. package/lib/rules/prefer-clone-deep.js +3 -3
  150. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  151. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  152. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  153. package/lib/rules/prefer-docsetter-setall.js +243 -0
  154. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  155. package/lib/rules/prefer-fragment-component.js +1 -1
  156. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  157. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  158. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  159. package/lib/rules/prefer-global-router-state-key.js +88 -13
  160. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  161. package/lib/rules/prefer-memoized-props.js +445 -0
  162. package/lib/rules/prefer-next-dynamic.js +60 -69
  163. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +118 -8
  164. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  165. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  166. package/lib/rules/prefer-params-over-parent-id.js +221 -172
  167. package/lib/rules/prefer-settings-object.js +27 -10
  168. package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
  169. package/lib/rules/prefer-type-over-interface.js +7 -2
  170. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  171. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  172. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  173. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
  174. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  175. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  176. package/lib/rules/prevent-children-clobber.js +540 -0
  177. package/lib/rules/react-memoize-literals.d.ts +4 -0
  178. package/lib/rules/react-memoize-literals.js +614 -0
  179. package/lib/rules/react-usememo-should-be-component.js +2 -2
  180. package/lib/rules/require-hooks-default-params.js +17 -16
  181. package/lib/rules/require-https-error-cause.d.ts +4 -0
  182. package/lib/rules/require-https-error-cause.js +155 -0
  183. package/lib/rules/require-https-error.js +43 -21
  184. package/lib/rules/require-image-optimized.js +1 -1
  185. package/lib/rules/require-memo.js +18 -10
  186. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  187. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  188. package/lib/rules/require-usememo-object-literals.js +2 -3
  189. package/lib/rules/test-file-location-enforcement.js +1 -1
  190. package/lib/rules/use-latest-callback.js +6 -5
  191. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  192. package/lib/rules/vertically-group-related-functions.js +480 -0
  193. package/lib/utils/ASTHelpers.d.ts +47 -2
  194. package/lib/utils/ASTHelpers.js +370 -112
  195. package/lib/utils/getMethodName.d.ts +27 -0
  196. package/lib/utils/getMethodName.js +35 -0
  197. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  198. package/lib/utils/tsTypeClassifier.js +149 -0
  199. package/package.json +3 -1
@@ -5,8 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.enforceIdentifiableFirestoreType = void 0;
7
7
  const utils_1 = require("@typescript-eslint/utils");
8
- const createRule_1 = require("../utils/createRule");
9
8
  const path_1 = __importDefault(require("path"));
9
+ const createRule_1 = require("../utils/createRule");
10
+ const TRANSPARENT_TYPE_NAMES = new Set(['Readonly', 'Resolve']);
10
11
  exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
11
12
  name: 'enforce-identifiable-firestore-type',
12
13
  meta: {
@@ -63,94 +64,143 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
63
64
  TSTypeAliasDeclaration(node) {
64
65
  if (node.id.name === folderName) {
65
66
  hasExpectedType = true;
66
- // Find Identifiable in type's dependencies
67
+ const findTypeAliasAnnotation = (typeName) => {
68
+ let scope = context.getScope();
69
+ while (scope) {
70
+ const variable = scope.variables.find((variableNode) => variableNode.name === typeName);
71
+ if (variable) {
72
+ const definition = variable.defs.find((definition) => definition.node.type ===
73
+ utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
74
+ if (definition &&
75
+ definition.node.type ===
76
+ utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration &&
77
+ definition.node.typeAnnotation) {
78
+ return definition.node.typeAnnotation;
79
+ }
80
+ }
81
+ scope = scope.upper;
82
+ }
83
+ return null;
84
+ };
85
+ const isParenthesizedType = (node) => node?.type === 'TSParenthesizedType';
86
+ const isReadonlyTypeOperator = (node) => node?.type === utils_1.AST_NODE_TYPES.TSTypeOperator &&
87
+ node.operator === 'readonly';
88
+ const unwrapTransparentType = (typeNode) => {
89
+ let current = typeNode;
90
+ while (current) {
91
+ if (isParenthesizedType(current)) {
92
+ const parenthesized = current;
93
+ current = parenthesized.typeAnnotation ?? null;
94
+ continue;
95
+ }
96
+ if (isReadonlyTypeOperator(current)) {
97
+ current = current.typeAnnotation ?? null;
98
+ continue;
99
+ }
100
+ break;
101
+ }
102
+ return current ?? null;
103
+ };
67
104
  const findIdentifiable = (type, checkedTypes = new Set()) => {
68
- if (!type)
105
+ const resolvedType = unwrapTransparentType(type);
106
+ if (!resolvedType) {
69
107
  return false;
70
- if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
71
- type.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
72
- const typeName = type.typeName.name;
108
+ }
109
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
110
+ resolvedType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
111
+ const typeName = resolvedType.typeName.name;
73
112
  if (typeName === 'Identifiable') {
74
113
  return true;
75
114
  }
76
- if (!checkedTypes.has(typeName)) {
77
- checkedTypes.add(typeName);
78
- // Look for the type in all scopes
79
- const scope = context.getScope();
80
- const variable = scope.variables.find((v) => v.name === typeName);
81
- if (variable) {
82
- const def = variable.defs.find((d) => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
83
- if (def &&
84
- 'typeAnnotation' in def.node &&
85
- def.node.typeAnnotation) {
86
- return findIdentifiable(def.node.typeAnnotation, checkedTypes);
87
- }
88
- }
89
- // Try looking in the parent scope
90
- if (scope.upper) {
91
- const parentVariable = scope.upper.variables.find((v) => v.name === typeName);
92
- if (parentVariable) {
93
- const def = parentVariable.defs.find((d) => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
94
- if (def &&
95
- 'typeAnnotation' in def.node &&
96
- def.node.typeAnnotation) {
97
- return findIdentifiable(def.node.typeAnnotation, checkedTypes);
98
- }
99
- }
100
- }
115
+ if (TRANSPARENT_TYPE_NAMES.has(typeName) &&
116
+ resolvedType.typeParameters?.params?.some((param) => findIdentifiable(param, checkedTypes))) {
117
+ return true;
118
+ }
119
+ if (checkedTypes.has(typeName)) {
120
+ return false;
101
121
  }
122
+ checkedTypes.add(typeName);
123
+ const aliasAnnotation = findTypeAliasAnnotation(typeName);
124
+ return findIdentifiable(aliasAnnotation, checkedTypes);
102
125
  }
103
- else if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
104
- // For intersection types, check each part
105
- return type.types.some((part) => findIdentifiable(part, checkedTypes));
126
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
127
+ return resolvedType.types.some((part) => findIdentifiable(part, new Set(checkedTypes)));
106
128
  }
107
129
  return false;
108
130
  };
109
- // Check if type extends Identifiable
110
- const checkIdentifiableExtension = (type) => {
111
- if (!type)
112
- return false;
113
- return findIdentifiable(type);
114
- };
115
131
  // Check if type has id: string field
116
- const checkIdField = (type) => {
117
- // Check for id: string field in type literal
118
- if (type.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
119
- return type.members.some((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
132
+ const checkIdField = (type, visitedTypes = new Set()) => {
133
+ const resolvedType = unwrapTransparentType(type);
134
+ if (!resolvedType) {
135
+ return false;
136
+ }
137
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
138
+ return resolvedType.members.some((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
120
139
  member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
121
140
  member.key.name === 'id' &&
122
141
  member.typeAnnotation?.typeAnnotation.type ===
123
142
  utils_1.AST_NODE_TYPES.TSStringKeyword);
124
143
  }
125
- // Check intersection types
126
- if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
127
- return type.types.some(checkIdField);
144
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
145
+ return resolvedType.types.some((part) => checkIdField(part, new Set(visitedTypes)));
146
+ }
147
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
148
+ if (resolvedType.typeName.type !== utils_1.AST_NODE_TYPES.Identifier) {
149
+ return false;
150
+ }
151
+ const typeName = resolvedType.typeName.name;
152
+ if (typeName === 'Identifiable') {
153
+ return true;
154
+ }
155
+ if (TRANSPARENT_TYPE_NAMES.has(typeName) &&
156
+ resolvedType.typeParameters?.params?.some((param) => checkIdField(param, visitedTypes))) {
157
+ return true;
158
+ }
159
+ if (visitedTypes.has(typeName)) {
160
+ return false;
161
+ }
162
+ visitedTypes.add(typeName);
163
+ const referencedType = findTypeAliasAnnotation(typeName);
164
+ return checkIdField(referencedType, visitedTypes);
128
165
  }
129
166
  return false;
130
167
  };
131
168
  // Check if type is wrapped in a utility type
132
169
  const isUtilityType = (type) => {
170
+ if (!type) {
171
+ return false;
172
+ }
133
173
  return (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
134
174
  type.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
135
175
  type.typeName.name === 'Resolve');
136
176
  };
137
177
  // Recursively check the type and its parameters
138
- const checkType = (type) => {
139
- // Check if type extends Identifiable
140
- if (checkIdentifiableExtension(type)) {
178
+ const checkType = (type, visitedTypes = new Set()) => {
179
+ const resolvedType = unwrapTransparentType(type);
180
+ if (!resolvedType) {
181
+ return false;
182
+ }
183
+ if (findIdentifiable(resolvedType)) {
141
184
  return true;
142
185
  }
143
- // Check if type has id: string field (only for utility types)
144
- if (isUtilityType(type) &&
145
- checkIdField(type.typeParameters.params[0])) {
186
+ if (isUtilityType(resolvedType) &&
187
+ resolvedType.typeParameters?.params?.[0] &&
188
+ checkIdField(resolvedType.typeParameters.params[0])) {
146
189
  return true;
147
190
  }
148
- // Check if type is wrapped in a utility type
149
- if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
150
- type.typeParameters?.params?.[0]) {
151
- return checkType(type.typeParameters.params[0]);
191
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
192
+ resolvedType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
193
+ const typeName = resolvedType.typeName.name;
194
+ if (visitedTypes.has(typeName)) {
195
+ return false;
196
+ }
197
+ visitedTypes.add(typeName);
198
+ const aliasAnnotation = findTypeAliasAnnotation(typeName);
199
+ return checkType(aliasAnnotation, visitedTypes);
200
+ }
201
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
202
+ return resolvedType.types.some((part) => checkType(part, new Set(visitedTypes)));
152
203
  }
153
- // For direct type definitions, require extending Identifiable
154
204
  return false;
155
205
  };
156
206
  typeHasIdentifiable = checkType(node.typeAnnotation);
@@ -3,7 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enforceMemoizeAsync = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
- const MEMOIZE_MODULE = 'typescript-memoize';
6
+ const MEMOIZE_MODULE = '@blumintinc/typescript-memoize';
7
+ const ALLOWED_MEMOIZE_MODULES = new Set([
8
+ MEMOIZE_MODULE,
9
+ 'typescript-memoize',
10
+ ]);
7
11
  /**
8
12
  * Matches a memoize decorator in supported syntaxes:
9
13
  * - @Alias()
@@ -38,33 +42,34 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
38
42
  meta: {
39
43
  type: 'suggestion',
40
44
  docs: {
41
- description: 'Enforce @Memoize() decorator on async methods with 0-1 parameters to cache results and prevent redundant API calls or expensive computations. This improves performance by reusing previous results when the same parameters are provided, particularly useful for data fetching methods.',
45
+ description: 'Enforce @Memoize() decorator on async methods with 0-1 parameters to cache results and prevent redundant API calls or expensive computations. Without memoization, repeated calls trigger redundant requests or expensive computations, increasing latency. @Memoize() caches results by parameter, ensuring subsequent calls with identical inputs return immediately.',
42
46
  recommended: 'error',
43
47
  },
44
48
  fixable: 'code',
45
49
  schema: [],
46
50
  messages: {
47
- requireMemoize: 'Async methods with 0-1 parameters should be decorated with @Memoize() to cache results and improve performance. Instead of `async getData(id?: string)`, use `@Memoize()\nasync getData(id?: string)`. Import Memoize from "typescript-memoize".',
51
+ requireMemoize: 'Async methods with 0-1 parameters should be decorated with @Memoize(). Without memoization, repeated calls trigger redundant network requests or expensive computations, increasing latency and risk of rate-limiting. @Memoize() caches results by parameter, ensuring subsequent calls with identical inputs return immediately. Fix: add @Memoize() above the method and import Memoize from "@blumintinc/typescript-memoize".',
48
52
  },
49
53
  },
50
54
  defaultOptions: [],
51
55
  create(context) {
52
56
  let hasMemoizeImport = false;
53
- let memoizeAlias = 'Memoize';
54
- let memoizeNamespace = null;
57
+ const memoizeAliases = new Map(); // alias -> source module
58
+ const memoizeNamespaces = new Map(); // namespace -> source module
55
59
  let scheduledImportFix = false;
56
60
  return {
57
61
  ImportDeclaration(node) {
58
- if (node.source.value === MEMOIZE_MODULE) {
62
+ if (ALLOWED_MEMOIZE_MODULES.has(String(node.source.value))) {
59
63
  node.specifiers.forEach((spec) => {
60
64
  if (spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
65
+ spec.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
61
66
  spec.imported.name === 'Memoize') {
62
67
  hasMemoizeImport = true;
63
- memoizeAlias = spec.local?.name ?? memoizeAlias;
68
+ memoizeAliases.set(spec.local.name, String(node.source.value));
64
69
  }
65
70
  else if (spec.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
66
71
  hasMemoizeImport = true;
67
- memoizeNamespace = spec.local.name;
72
+ memoizeNamespaces.set(spec.local.name, String(node.source.value));
68
73
  }
69
74
  });
70
75
  }
@@ -81,7 +86,39 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
81
86
  return;
82
87
  }
83
88
  // Check if method already has @Memoize or @Memoize() decorator
84
- const hasDecorator = node.decorators?.some((decorator) => isMemoizeDecorator(decorator, memoizeAlias));
89
+ const hasDecorator = node.decorators?.some((decorator) => {
90
+ // If no named imports were found, we assume 'Memoize' is the intended name (for legacy/global support)
91
+ const aliasesToCheck = memoizeAliases.size === 0
92
+ ? ['Memoize']
93
+ : Array.from(memoizeAliases.keys());
94
+ // Check against all known aliases
95
+ for (const alias of aliasesToCheck) {
96
+ if (isMemoizeDecorator(decorator, alias)) {
97
+ return true;
98
+ }
99
+ }
100
+ // Also check against namespaces
101
+ const expression = decorator.expression;
102
+ if (expression.type === utils_1.AST_NODE_TYPES.MemberExpression &&
103
+ !expression.computed &&
104
+ expression.property.type === utils_1.AST_NODE_TYPES.Identifier &&
105
+ expression.property.name === 'Memoize' &&
106
+ expression.object.type === utils_1.AST_NODE_TYPES.Identifier &&
107
+ memoizeNamespaces.has(expression.object.name)) {
108
+ return true;
109
+ }
110
+ // Handle namespace call: @ns.Memoize()
111
+ if (expression.type === utils_1.AST_NODE_TYPES.CallExpression &&
112
+ expression.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
113
+ !expression.callee.computed &&
114
+ expression.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
115
+ expression.callee.property.name === 'Memoize' &&
116
+ expression.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
117
+ memoizeNamespaces.has(expression.callee.object.name)) {
118
+ return true;
119
+ }
120
+ return false;
121
+ });
85
122
  if (hasDecorator) {
86
123
  return;
87
124
  }
@@ -90,16 +127,33 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
90
127
  messageId: 'requireMemoize',
91
128
  fix(fixer) {
92
129
  const fixes = [];
93
- const sourceCode = context.getSourceCode();
94
- const decoratorIdent = memoizeNamespace
95
- ? `${memoizeNamespace}.Memoize`
96
- : memoizeAlias;
130
+ const sourceCode = context.sourceCode;
131
+ // Determine which identifier to use for the decorator
132
+ let decoratorIdent = 'Memoize';
133
+ if (hasMemoizeImport) {
134
+ // Prefer 'Memoize' from the new package if available
135
+ if (memoizeAliases.has('Memoize') &&
136
+ memoizeAliases.get('Memoize') === MEMOIZE_MODULE) {
137
+ decoratorIdent = 'Memoize';
138
+ }
139
+ else if (memoizeAliases.size > 0) {
140
+ // Find first alias from new package, fallback to any alias
141
+ const newPackageAlias = Array.from(memoizeAliases.entries()).find(([_, pkg]) => pkg === MEMOIZE_MODULE)?.[0];
142
+ decoratorIdent =
143
+ newPackageAlias || Array.from(memoizeAliases.keys())[0];
144
+ }
145
+ else if (memoizeNamespaces.size > 0) {
146
+ // Prefer namespace from the new package if available
147
+ const newPackageNs = Array.from(memoizeNamespaces.entries()).find(([_, pkg]) => pkg === MEMOIZE_MODULE)?.[0];
148
+ const selectedNs = newPackageNs || Array.from(memoizeNamespaces.keys())[0];
149
+ decoratorIdent = `${selectedNs}.Memoize`;
150
+ }
151
+ }
97
152
  const importStatement = `import { Memoize } from '${MEMOIZE_MODULE}';`;
98
153
  // Add import if it's not already present; ensure we only add once per file
99
154
  if (!hasMemoizeImport &&
100
- !memoizeNamespace &&
101
- !scheduledImportFix &&
102
- !sourceCode.text.includes(importStatement)) {
155
+ memoizeNamespaces.size === 0 &&
156
+ !scheduledImportFix) {
103
157
  const programBody = sourceCode.ast.body;
104
158
  const firstImport = programBody.find((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
105
159
  const anchorNode = (firstImport ?? programBody[0]);
@@ -122,11 +176,7 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
122
176
  const insertionTarget = node.decorators && node.decorators.length > 0
123
177
  ? node.decorators[0]
124
178
  : node;
125
- const insertionStart = insertionTarget.range
126
- ? insertionTarget.range[0]
127
- : node.range
128
- ? node.range[0]
129
- : 0;
179
+ const insertionStart = insertionTarget.range?.[0] ?? node.range?.[0] ?? 0;
130
180
  const text = sourceCode.text;
131
181
  const lineStart = text.lastIndexOf('\n', insertionStart - 1) + 1;
132
182
  const leadingWhitespace = text.slice(lineStart, insertionStart).match(/^[ \t]*/)?.[0] ?? '';
@@ -42,7 +42,7 @@ exports.enforceMemoizeGetters = (0, createRule_1.createRule)({
42
42
  fixable: 'code',
43
43
  schema: [],
44
44
  messages: {
45
- requireMemoizeGetter: 'Private getters should be decorated with @Memoize() to preserve instance state and avoid redundant instantiation. Import Memoize from "@blumintinc/typescript-memoize".',
45
+ requireMemoizeGetter: 'Private getter "{{name}}" should use @Memoize() so repeated accesses reuse the same instance instead of re-instantiating and losing internal state. Add @Memoize() and import Memoize from "@blumintinc/typescript-memoize" to avoid redundant setup work.',
46
46
  },
47
47
  },
48
48
  defaultOptions: [],
@@ -52,17 +52,28 @@ exports.enforceMemoizeGetters = (0, createRule_1.createRule)({
52
52
  if (!/\.tsx?$/i.test(filename)) {
53
53
  return {};
54
54
  }
55
+ const sourceCode = context.getSourceCode();
55
56
  let hasMemoizeImport = false;
56
57
  let memoizeAlias = 'Memoize';
58
+ let memoizeNamespace = null;
59
+ let hasNamedImport = false;
57
60
  let scheduledImportFix = false;
58
61
  return {
59
62
  ImportDeclaration(node) {
60
63
  if (MEMOIZE_MODULES.has(String(node.source.value))) {
61
- const spec = node.specifiers.find((s) => s.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
62
- s.imported.name === 'Memoize');
63
- if (spec) {
64
- hasMemoizeImport = true;
65
- memoizeAlias = spec.local?.name ?? 'Memoize';
64
+ for (const spec of node.specifiers) {
65
+ if (spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
66
+ spec.imported.name === 'Memoize') {
67
+ hasMemoizeImport = true;
68
+ hasNamedImport = true;
69
+ memoizeAlias = spec.local.name;
70
+ }
71
+ else if (spec.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
72
+ hasMemoizeImport = true;
73
+ if (!hasNamedImport) {
74
+ memoizeNamespace = spec.local.name;
75
+ }
76
+ }
66
77
  }
67
78
  }
68
79
  },
@@ -76,16 +87,29 @@ exports.enforceMemoizeGetters = (0, createRule_1.createRule)({
76
87
  // enforce only "private" accessibility (undefined => public)
77
88
  if (node.accessibility !== 'private')
78
89
  return;
79
- // Already memoized?
80
- const hasDecorator = node.decorators?.some((d) => isMemoizeDecorator(d, memoizeAlias));
90
+ const decoratorAliases = memoizeAlias === 'Memoize' ? ['Memoize'] : ['Memoize', memoizeAlias];
91
+ const hasDecorator = node.decorators?.some((decorator) => decoratorAliases.some((alias) => isMemoizeDecorator(decorator, alias)));
81
92
  if (hasDecorator)
82
93
  return;
94
+ const propertyName = node.computed
95
+ ? '[computed]'
96
+ : sourceCode.getText(node.key);
83
97
  context.report({
84
98
  node,
85
99
  messageId: 'requireMemoizeGetter',
100
+ data: { name: propertyName },
86
101
  fix(fixer) {
87
102
  const fixes = [];
88
- const sourceCode = context.getSourceCode();
103
+ const getDecoratorIdent = () => {
104
+ if (hasNamedImport) {
105
+ return memoizeAlias;
106
+ }
107
+ if (memoizeNamespace) {
108
+ return `${memoizeNamespace}.Memoize`;
109
+ }
110
+ return memoizeAlias;
111
+ };
112
+ const decoratorIdent = getDecoratorIdent();
89
113
  // Insert import if needed, at the top alongside other imports
90
114
  if (!hasMemoizeImport && !scheduledImportFix) {
91
115
  const programBody = sourceCode.ast.body;
@@ -106,12 +130,12 @@ exports.enforceMemoizeGetters = (0, createRule_1.createRule)({
106
130
  scheduledImportFix = true;
107
131
  }
108
132
  // Insert decorator above the getter (or before the first decorator), preserving indentation
109
- const indent = ' '.repeat(node.loc.start.column);
110
- const lineStart = node.range
111
- ? sourceCode.text.lastIndexOf('\n', node.range[0] - 1) + 1
112
- : 0;
113
- const insertPosition = Math.max(0, lineStart);
114
- fixes.push(fixer.insertTextBeforeRange([insertPosition, insertPosition], `${indent}@${memoizeAlias}()\n`));
133
+ const insertionTarget = node.decorators?.[0] ?? node;
134
+ const insertionStart = insertionTarget.range[0];
135
+ const text = sourceCode.text;
136
+ const lineStart = text.lastIndexOf('\n', insertionStart - 1) + 1;
137
+ const leadingWhitespace = text.slice(lineStart, insertionStart).match(/^[ \t]*/)?.[0] ?? '';
138
+ fixes.push(fixer.insertTextBeforeRange([lineStart, lineStart], `${leadingWhitespace}@${decoratorIdent}()\n`));
115
139
  return fixes;
116
140
  },
117
141
  });
@@ -20,7 +20,7 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
20
20
  },
21
21
  defaultOptions: [],
22
22
  create(context) {
23
- const sourceCode = context.getSourceCode();
23
+ const sourceCode = context.sourceCode;
24
24
  const importedDiffLibraries = new Map();
25
25
  const importedFunctions = new Map(); // Map of imported function names to their sources
26
26
  let hasMicrodiffImport = false;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enforceObjectLiteralAsConst = void 0;
4
4
  const createRule_1 = require("../utils/createRule");
5
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
5
6
  exports.enforceObjectLiteralAsConst = (0, createRule_1.createRule)({
6
7
  name: 'enforce-object-literal-as-const',
7
8
  meta: {
@@ -55,10 +56,9 @@ exports.enforceObjectLiteralAsConst = (0, createRule_1.createRule)({
55
56
  return;
56
57
  }
57
58
  // Check if the return statement is inside a function
58
- const sourceCode = context.getSourceCode();
59
- // Use context.getAncestors() for now, but note it's deprecated
60
- // We'll need to update this when upgrading to ESLint v9
61
- const ancestors = context.getAncestors();
59
+ const sourceCode = context.sourceCode;
60
+ // Use ASTHelpers.getAncestors for ESLint v8/v9 compatibility
61
+ const ancestors = ASTHelpers_1.ASTHelpers.getAncestors(context, node);
62
62
  const isInFunction = ancestors.some((ancestor) => ancestor.type === 'FunctionDeclaration' ||
63
63
  ancestor.type === 'ArrowFunctionExpression' ||
64
64
  ancestor.type === 'FunctionExpression' ||
@@ -847,6 +847,10 @@ const DIS_EXCEPTIONS = [
847
847
  'dismissal',
848
848
  'disk',
849
849
  'disks',
850
+ 'dispute',
851
+ 'disputed',
852
+ 'disputes',
853
+ 'disputing',
850
854
  ];
851
855
  // Words that contain negative prefixes but should be treated as valid
852
856
  const EXCEPTION_WORDS_SET = new Set([...IN_EXCEPTIONS, ...NO_EXCEPTIONS, ...UN_EXCEPTIONS, ...DIS_EXCEPTIONS].map((word) => word.toLowerCase()));
@@ -34,14 +34,48 @@ exports.enforcePropsArgumentName = (0, createRule_1.createRule)({
34
34
  function endsWithProps(typeName) {
35
35
  return typeName.endsWith('Props');
36
36
  }
37
+ // Get all parameters that have a "Props" type
38
+ function getPropsParams(params) {
39
+ return params
40
+ .map((param) => {
41
+ const id = getIdFromParam(param);
42
+ if (id && id.typeAnnotation && id.typeAnnotation.typeAnnotation) {
43
+ const paramTypeName = getTypeName(id.typeAnnotation.typeAnnotation);
44
+ if (paramTypeName && endsWithProps(paramTypeName)) {
45
+ return { id, typeName: paramTypeName };
46
+ }
47
+ }
48
+ return null;
49
+ })
50
+ .filter((p) => !!p);
51
+ }
37
52
  // Generate suggested parameter name for Props types
38
- function getSuggestedParameterName(typeName, allParams, currentParam) {
53
+ function getSuggestedParameterName(typeName, allParams, currentParam, propsParams) {
39
54
  const currentId = getIdFromParam(currentParam);
40
55
  const currentName = currentId?.name;
41
56
  const existingNames = new Set(allParams
42
57
  .map((p) => getIdFromParam(p))
43
58
  .filter((id) => !!id && id.name !== currentName)
44
59
  .map((id) => id.name));
60
+ // If multiple parameters have the exact same Props type, allow the current names.
61
+ // This handles cases like (prevProps: TProps, nextProps: TProps) where
62
+ // forcing a single name based on the type would cause a collision or
63
+ // break descriptive naming.
64
+ const sameTypeParams = propsParams.filter((p) => p.typeName === typeName);
65
+ if (sameTypeParams.length > 1) {
66
+ // Keep the current name when multiple params share the same Props type.
67
+ // This preserves descriptive names like prevProps/nextProps.
68
+ if (!currentName) {
69
+ // Fallback should not occur since destructured params are filtered upstream.
70
+ let candidate = 'props';
71
+ let suffix = 2;
72
+ while (existingNames.has(candidate)) {
73
+ candidate = `props${suffix++}`;
74
+ }
75
+ return candidate;
76
+ }
77
+ return currentName;
78
+ }
45
79
  if (typeName === 'Props') {
46
80
  let candidate = 'props';
47
81
  let suffix = 2;
@@ -50,17 +84,7 @@ exports.enforcePropsArgumentName = (0, createRule_1.createRule)({
50
84
  }
51
85
  return candidate;
52
86
  }
53
- // Count how many parameters have Props types
54
- const propsParams = allParams.filter((param) => {
55
- if (param.type === utils_1.AST_NODE_TYPES.Identifier &&
56
- param.typeAnnotation &&
57
- param.typeAnnotation.typeAnnotation) {
58
- const paramTypeName = getTypeName(param.typeAnnotation.typeAnnotation);
59
- return paramTypeName && endsWithProps(paramTypeName);
60
- }
61
- return false;
62
- });
63
- // If there's only one Props parameter, suggest "props"
87
+ // If there's only one Props parameter (regardless of type name), suggest "props"
64
88
  if (propsParams.length <= 1) {
65
89
  let candidate = 'props';
66
90
  let suffix = 2;
@@ -69,8 +93,8 @@ exports.enforcePropsArgumentName = (0, createRule_1.createRule)({
69
93
  }
70
94
  return candidate;
71
95
  }
72
- // If there are multiple Props parameters, use prefixed names
73
- // For types like "UserProps", suggest "userProps"
96
+ // If there are multiple Props parameters with different types, use prefixed names.
97
+ // For types like "UserProps", suggest "userProps".
74
98
  const baseName = typeName.slice(0, -5); // Remove "Props"
75
99
  const camelCaseBase = baseName.charAt(0).toLowerCase() + baseName.slice(1);
76
100
  let candidate = `${camelCaseBase}Props`;
@@ -137,7 +161,8 @@ exports.enforcePropsArgumentName = (0, createRule_1.createRule)({
137
161
  node.parent.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
138
162
  return;
139
163
  }
140
- const sourceCode = context.getSourceCode();
164
+ const sourceCode = context.sourceCode;
165
+ const propsParams = getPropsParams(node.params);
141
166
  node.params.forEach((param) => {
142
167
  if (isDestructuredParameter(param)) {
143
168
  return;
@@ -146,7 +171,7 @@ exports.enforcePropsArgumentName = (0, createRule_1.createRule)({
146
171
  if (id && id.typeAnnotation && id.typeAnnotation.typeAnnotation) {
147
172
  const typeName = getTypeName(id.typeAnnotation.typeAnnotation);
148
173
  if (typeName && endsWithProps(typeName)) {
149
- const suggestedName = getSuggestedParameterName(typeName, node.params, param);
174
+ const suggestedName = getSuggestedParameterName(typeName, node.params, param, propsParams);
150
175
  if (id.name !== suggestedName) {
151
176
  const messageId = suggestedName === 'props'
152
177
  ? 'usePropsParameterName'
@@ -173,7 +198,8 @@ exports.enforcePropsArgumentName = (0, createRule_1.createRule)({
173
198
  // Check class method parameters (including constructors)
174
199
  function checkClassMethod(node) {
175
200
  const method = node.value;
176
- const sourceCode = context.getSourceCode();
201
+ const sourceCode = context.sourceCode;
202
+ const propsParams = getPropsParams(method.params);
177
203
  method.params.forEach((param) => {
178
204
  if (isDestructuredParameter(param)) {
179
205
  return;
@@ -182,7 +208,7 @@ exports.enforcePropsArgumentName = (0, createRule_1.createRule)({
182
208
  if (id && id.typeAnnotation && id.typeAnnotation.typeAnnotation) {
183
209
  const typeName = getTypeName(id.typeAnnotation.typeAnnotation);
184
210
  if (typeName && endsWithProps(typeName)) {
185
- const suggestedName = getSuggestedParameterName(typeName, method.params, param);
211
+ const suggestedName = getSuggestedParameterName(typeName, method.params, param, propsParams);
186
212
  if (id.name !== suggestedName) {
187
213
  const messageId = suggestedName === 'props'
188
214
  ? 'usePropsParameterName'
@@ -40,7 +40,7 @@ exports.enforceStableStringify = (0, createRule_1.createRule)({
40
40
  const fixes = [];
41
41
  // Add import if not present
42
42
  if (!hasStringifyImport) {
43
- const program = context.getSourceCode().ast;
43
+ const program = context.sourceCode.ast;
44
44
  const firstImport = program.body.find((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
45
45
  const importStatement = "import stringify from 'safe-stable-stringify';\n";
46
46
  if (firstImport) {