@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
@@ -1,8 +1,33 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.noPassthroughGetters = void 0;
4
27
  const createRule_1 = require("../utils/createRule");
5
28
  const utils_1 = require("@typescript-eslint/utils");
29
+ const getMethodName_1 = require("../utils/getMethodName");
30
+ const ts = __importStar(require("typescript"));
6
31
  const COMPLEX_EXPRESSION_TYPES = new Set([
7
32
  utils_1.AST_NODE_TYPES.CallExpression,
8
33
  utils_1.AST_NODE_TYPES.TemplateLiteral,
@@ -13,6 +38,7 @@ const COMPLEX_EXPRESSION_TYPES = new Set([
13
38
  ]);
14
39
  exports.noPassthroughGetters = (0, createRule_1.createRule)({
15
40
  create(context) {
41
+ const sourceCode = context.sourceCode;
16
42
  return {
17
43
  // Target getter methods in classes
18
44
  MethodDefinition(node) {
@@ -24,6 +50,10 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
24
50
  if (node.decorators && node.decorators.length > 0) {
25
51
  return;
26
52
  }
53
+ // Check if this getter satisfies an interface or overrides a base class member
54
+ if (isRequiredByInterfaceOrBaseClass(node, sourceCode)) {
55
+ return;
56
+ }
27
57
  const methodBody = node.value.body;
28
58
  if (!methodBody) {
29
59
  return;
@@ -55,14 +85,77 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
55
85
  }
56
86
  // Check if the return statement is accessing a property from a constructor parameter
57
87
  if (isConstructorParameterPropertyAccess(returnStatement.argument)) {
88
+ const getterName = (0, getMethodName_1.getMethodName)(node, sourceCode, {
89
+ computedFallbackToText: false,
90
+ }) || 'getter';
91
+ const propertyPath = getMemberPath(returnStatement.argument) ??
92
+ 'nested property on a constructor-injected object';
58
93
  context.report({
59
94
  node,
60
95
  messageId: 'noPassthroughGetter',
96
+ data: {
97
+ getterName,
98
+ propertyPath,
99
+ },
61
100
  });
62
101
  }
63
102
  }
64
103
  },
65
104
  };
105
+ /**
106
+ * Check if the getter is required by an implemented interface or overrides a base class member
107
+ */
108
+ function isRequiredByInterfaceOrBaseClass(node, sourceCode) {
109
+ const parserServices = sourceCode.parserServices;
110
+ if (!parserServices ||
111
+ !parserServices.program ||
112
+ !parserServices.esTreeNodeToTSNodeMap) {
113
+ return false;
114
+ }
115
+ const checker = parserServices.program.getTypeChecker();
116
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
117
+ if (!tsNode) {
118
+ return false;
119
+ }
120
+ const symbol = checker.getSymbolAtLocation(tsNode.name);
121
+ if (!symbol ||
122
+ !tsNode.parent ||
123
+ !(ts.isClassDeclaration(tsNode.parent) ||
124
+ ts.isClassExpression(tsNode.parent))) {
125
+ return false;
126
+ }
127
+ const classNode = tsNode.parent;
128
+ const classType = checker.getTypeAtLocation(classNode);
129
+ const classSymbol = classType.getSymbol();
130
+ if (!classSymbol) {
131
+ return false;
132
+ }
133
+ const instanceType = checker.getDeclaredTypeOfSymbol(classSymbol);
134
+ const name = symbol.getName();
135
+ // Check base classes
136
+ const baseTypes = instanceType.getBaseTypes() || [];
137
+ for (const baseType of baseTypes) {
138
+ if (baseType.getProperty(name)) {
139
+ return true;
140
+ }
141
+ }
142
+ // Check interfaces
143
+ if (classNode.heritageClauses) {
144
+ for (const clause of classNode.heritageClauses) {
145
+ // Only check implemented interfaces as base classes are already checked via getBaseTypes()
146
+ if (clause.token !== ts.SyntaxKind.ImplementsKeyword) {
147
+ continue;
148
+ }
149
+ for (const typeNode of clause.types) {
150
+ const type = checker.getTypeAtLocation(typeNode);
151
+ if (type.getProperty(name)) {
152
+ return true;
153
+ }
154
+ }
155
+ }
156
+ }
157
+ return false;
158
+ }
66
159
  /**
67
160
  * Check if the node is a simple property access from a constructor parameter
68
161
  * like this.settings.property or this.settings['property'] or this.settings.nested.deep.property
@@ -103,6 +196,31 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
103
196
  }
104
197
  return false;
105
198
  }
199
+ function getMemberPath(node) {
200
+ if (node.type === 'ThisExpression') {
201
+ return 'this';
202
+ }
203
+ if (node.type === 'Identifier') {
204
+ return node.name;
205
+ }
206
+ if (node.type !== 'MemberExpression') {
207
+ return null;
208
+ }
209
+ const objectPath = getMemberPath(node.object);
210
+ if (!objectPath) {
211
+ return null;
212
+ }
213
+ if (node.computed && node.property.type === 'Literal') {
214
+ const literalValue = typeof node.property.value === 'string'
215
+ ? JSON.stringify(node.property.value)
216
+ : String(node.property.value);
217
+ return `${objectPath}[${literalValue}]`;
218
+ }
219
+ if (node.property.type === 'Identifier') {
220
+ return `${objectPath}.${node.property.name}`;
221
+ }
222
+ return null;
223
+ }
106
224
  /**
107
225
  * Check if the node has a type assertion or casting
108
226
  */
@@ -157,12 +275,12 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
157
275
  meta: {
158
276
  type: 'suggestion',
159
277
  docs: {
160
- description: 'Avoid unnecessary getter methods that simply return properties from constructor parameters',
278
+ description: 'Avoid getter methods that only re-expose nested properties on constructor-injected objects without adding behavior',
161
279
  recommended: 'error',
162
280
  },
163
281
  schema: [],
164
282
  messages: {
165
- noPassthroughGetter: 'Avoid unnecessary getter methods that simply return properties from constructor parameters. Access the property directly instead.',
283
+ noPassthroughGetter: 'Getter "{{getterName}}" only forwards nested property "{{propertyPath}}" from an object provided via the constructor. This indirection hides the real source of state and expands the class API without adding behavior. Read the constructor-injected object directly or add logic that justifies a getter (validation, memoization, fallback).',
166
284
  },
167
285
  },
168
286
  defaultOptions: [],
@@ -0,0 +1,2 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noRedundantAnnotationAssertion: TSESLint.RuleModule<"redundantAnnotationAndAssertion", [], TSESLint.RuleListener>;
@@ -0,0 +1,402 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.noRedundantAnnotationAssertion = void 0;
27
+ const utils_1 = require("@typescript-eslint/utils");
28
+ const visitor_keys_1 = require("@typescript-eslint/visitor-keys");
29
+ const ts = __importStar(require("typescript"));
30
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
31
+ const createRule_1 = require("../utils/createRule");
32
+ /**
33
+ * Type string formatting flags chosen to keep comparisons stable and predictable.
34
+ * - NoTruncation avoids `...` elisions that can hide important differences.
35
+ * - WriteArrayAsGenericType normalizes arrays to `Array<T>` for consistent output.
36
+ * - UseFullyQualifiedType reduces ambiguity from locally-imported type names.
37
+ * - UseStructuralFallback keeps output meaningful when a nominal name is unavailable.
38
+ */
39
+ const TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation |
40
+ ts.TypeFormatFlags.WriteArrayAsGenericType |
41
+ ts.TypeFormatFlags.UseFullyQualifiedType |
42
+ ts.TypeFormatFlags.UseStructuralFallback;
43
+ function extractAssertionTypeNode(expression) {
44
+ if (!expression)
45
+ return null;
46
+ if (expression.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
47
+ return expression.typeAnnotation;
48
+ }
49
+ if (expression.type === utils_1.AST_NODE_TYPES.TSTypeAssertion) {
50
+ return expression.typeAnnotation;
51
+ }
52
+ return null;
53
+ }
54
+ function isTraversalBoundary(node) {
55
+ /**
56
+ * Nested functions/classes have their own return semantics; inner returns should not
57
+ * influence the outer function's return-type check.
58
+ */
59
+ return (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
60
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
61
+ node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
62
+ node.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
63
+ node.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
64
+ node.type === utils_1.AST_NODE_TYPES.ClassExpression);
65
+ }
66
+ function recordReturnStatement(node, assertions) {
67
+ if (!node.argument)
68
+ return;
69
+ const assertion = extractAssertionTypeNode(node.argument);
70
+ if (assertion)
71
+ assertions.push(assertion);
72
+ }
73
+ function addChildNodesToStack(node, stack) {
74
+ const keys = visitor_keys_1.visitorKeys[node.type];
75
+ if (!keys)
76
+ return;
77
+ for (const key of keys) {
78
+ const value = node[key];
79
+ if (Array.isArray(value)) {
80
+ for (const element of value) {
81
+ if (ASTHelpers_1.ASTHelpers.isNode(element)) {
82
+ stack.push(element);
83
+ }
84
+ }
85
+ continue;
86
+ }
87
+ if (ASTHelpers_1.ASTHelpers.isNode(value)) {
88
+ stack.push(value);
89
+ }
90
+ }
91
+ }
92
+ function collectReturnInfo(body) {
93
+ const assertions = [];
94
+ let returnCount = 0;
95
+ const stack = [...body.body];
96
+ while (stack.length) {
97
+ const current = stack.pop();
98
+ if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
99
+ returnCount += 1;
100
+ recordReturnStatement(current, assertions);
101
+ continue;
102
+ }
103
+ if (isTraversalBoundary(current))
104
+ continue;
105
+ addChildNodesToStack(current, stack);
106
+ }
107
+ return { assertions, returnCount };
108
+ }
109
+ function findTypeAnnotationStart(typeAnnotation, sourceCode) {
110
+ const start = typeAnnotation.range[0];
111
+ const text = sourceCode.getText();
112
+ let removalStart = start;
113
+ /**
114
+ * Scan backward to remove any whitespace before `: Type`, but avoid consuming tokens
115
+ * that belong to the declared name (e.g. `!` definite assignment assertions or `?`
116
+ * optional markers).
117
+ */
118
+ for (let i = start - 1; i >= 0; i -= 1) {
119
+ const char = text.charAt(i);
120
+ if (char === '\n' || char === '\r')
121
+ break;
122
+ if (char === '?' || char === '!') {
123
+ removalStart = i + 1;
124
+ break;
125
+ }
126
+ if (!/\s/.test(char)) {
127
+ removalStart = i + 1;
128
+ break;
129
+ }
130
+ removalStart = i;
131
+ }
132
+ return removalStart;
133
+ }
134
+ function removeTypeAnnotation(fixer, typeAnnotation, sourceCode) {
135
+ const end = typeAnnotation.range[1];
136
+ const removalStart = findTypeAnnotationStart(typeAnnotation, sourceCode);
137
+ return fixer.removeRange([removalStart, end]);
138
+ }
139
+ function typeText(type, checker) {
140
+ return checker.typeToString(type, undefined, TYPE_FORMAT_FLAGS);
141
+ }
142
+ function unwrapAlias(type, checker) {
143
+ const aliasSymbol = type
144
+ .aliasSymbol;
145
+ if (!aliasSymbol)
146
+ return type;
147
+ if ((aliasSymbol.flags & ts.SymbolFlags.Alias) !== 0) {
148
+ const target = checker.getAliasedSymbol(aliasSymbol);
149
+ return checker.getDeclaredTypeOfSymbol(target);
150
+ }
151
+ if ((aliasSymbol.flags & ts.SymbolFlags.TypeAlias) !== 0) {
152
+ const aliasTypeArguments = type.aliasTypeArguments;
153
+ if (aliasTypeArguments?.length) {
154
+ return checker.getApparentType(type);
155
+ }
156
+ return checker.getDeclaredTypeOfSymbol(aliasSymbol);
157
+ }
158
+ return type;
159
+ }
160
+ function formatPropertySignature(prop, parentType, checker) {
161
+ const declaration = prop.valueDeclaration ??
162
+ prop.declarations?.[0] ??
163
+ parentType.symbol?.valueDeclaration ??
164
+ parentType.symbol?.declarations?.[0];
165
+ if (!declaration)
166
+ return null;
167
+ const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
168
+ const text = typeText(unwrapAlias(propType, checker), checker);
169
+ const isOptional = (prop.getFlags() & ts.SymbolFlags.Optional) !== 0;
170
+ return `${prop.getName()}${isOptional ? '?' : ''}:${text}`;
171
+ }
172
+ function getFormattedTypeProperties(type, checker) {
173
+ return checker
174
+ .getPropertiesOfType(type)
175
+ .map((prop) => formatPropertySignature(prop, type, checker))
176
+ .filter((entry) => Boolean(entry))
177
+ .sort();
178
+ }
179
+ function getFormattedCallSignatures(type, checker) {
180
+ return checker
181
+ .getSignaturesOfType(type, ts.SignatureKind.Call)
182
+ .map((sig) => checker.signatureToString(sig, undefined, TYPE_FORMAT_FLAGS))
183
+ .sort();
184
+ }
185
+ function structuralKey(type, checker) {
186
+ const apparent = checker.getApparentType(type);
187
+ const properties = getFormattedTypeProperties(apparent, checker);
188
+ const signatures = getFormattedCallSignatures(apparent, checker);
189
+ return `${properties.join('|')}::${signatures.join('|')}`;
190
+ }
191
+ function getComparableType(typeNode, checker, services) {
192
+ const tsNode = services.esTreeNodeToTSNodeMap.get(typeNode);
193
+ if (!ts.isTypeNode(tsNode))
194
+ return null;
195
+ const type = checker.getTypeFromTypeNode(tsNode);
196
+ const unwrapped = unwrapAlias(type, checker);
197
+ return unwrapped;
198
+ }
199
+ /**
200
+ * Generate multiple string representations of both types for comparison.
201
+ * Multiple strategies are needed because TypeScript types can be semantically
202
+ * identical yet have different string representations depending on:
203
+ * - Type aliases (MyType vs the underlying type) via canonical/NoTypeReduction
204
+ * - Type formatting options (fully qualified vs relative) via default text
205
+ * - Structural equivalence (different names, same shape) via structural key
206
+ */
207
+ function getTypeRepresentations(annotationType, assertionType, checker) {
208
+ return {
209
+ annotationText: typeText(annotationType, checker),
210
+ assertionText: typeText(assertionType, checker),
211
+ annotationCanonical: checker.typeToString(annotationType, undefined, TYPE_FORMAT_FLAGS | ts.TypeFormatFlags.NoTypeReduction),
212
+ assertionCanonical: checker.typeToString(assertionType, undefined, TYPE_FORMAT_FLAGS | ts.TypeFormatFlags.NoTypeReduction),
213
+ annotationStructural: structuralKey(annotationType, checker),
214
+ assertionStructural: structuralKey(assertionType, checker),
215
+ };
216
+ }
217
+ function areTypesIdentical(annotationType, assertionType, checker) {
218
+ if (annotationType === assertionType)
219
+ return true;
220
+ const isTypeIdenticalTo = checker.isTypeIdenticalTo;
221
+ /** Guard the internal TypeScript helper; newer compiler versions may drop or change it. */
222
+ if (typeof isTypeIdenticalTo !== 'function') {
223
+ return false;
224
+ }
225
+ return isTypeIdenticalTo(annotationType, assertionType) === true;
226
+ }
227
+ function areTypesAssignableBothWays(annotationType, assertionType, checker) {
228
+ const isTypeAssignableTo = checker.isTypeAssignableTo;
229
+ /** Guard for checker.isTypeAssignableTo which may be absent from some d.ts versions. */
230
+ if (typeof isTypeAssignableTo !== 'function') {
231
+ return false;
232
+ }
233
+ return (isTypeAssignableTo(annotationType, assertionType) &&
234
+ isTypeAssignableTo(assertionType, annotationType));
235
+ }
236
+ function doTypeTextsMatch(representations) {
237
+ const { annotationText, assertionText, annotationCanonical, assertionCanonical, annotationStructural, assertionStructural, } = representations;
238
+ return (annotationText === assertionText ||
239
+ annotationCanonical === assertionCanonical ||
240
+ annotationStructural === assertionStructural);
241
+ }
242
+ function selectMatchingTypeRepresentation(representations) {
243
+ return representations.annotationText;
244
+ }
245
+ /**
246
+ * Checks if two types are effectively equal for the purpose of identifying redundant assertions.
247
+ * Types are effectively equal if they are:
248
+ * 1. Identical according to TypeScript's internal identity check.
249
+ * 2. Or, they are assignable both ways AND share at least one string representation
250
+ * (canonical, structural, or default formatting).
251
+ */
252
+ function areTypesEffectivelyEqual(annotationType, assertionType, representations, checker) {
253
+ const identical = areTypesIdentical(annotationType, assertionType, checker);
254
+ if (identical) {
255
+ return true;
256
+ }
257
+ const assignableBothWays = areTypesAssignableBothWays(annotationType, assertionType, checker);
258
+ const textMatches = doTypeTextsMatch(representations);
259
+ return assignableBothWays && textMatches;
260
+ }
261
+ /**
262
+ * Compare annotation and assertion types across identity, assignability, and
263
+ * multiple textual forms so equivalent aliases still count as redundant.
264
+ * @param annotation The type annotation node to compare.
265
+ * @param assertion The type assertion node to compare.
266
+ * @param checker The TypeScript type checker.
267
+ * @param services The parser services.
268
+ * @returns The matching type string if the types are effectively equal, null otherwise.
269
+ */
270
+ function haveMatchingTypes(annotation, assertion, checker, services) {
271
+ const annotationType = getComparableType(annotation, checker, services);
272
+ const assertionType = getComparableType(assertion, checker, services);
273
+ if (!annotationType || !assertionType)
274
+ return null;
275
+ const representations = getTypeRepresentations(annotationType, assertionType, checker);
276
+ if (!areTypesEffectivelyEqual(annotationType, assertionType, representations, checker)) {
277
+ return null;
278
+ }
279
+ return selectMatchingTypeRepresentation(representations);
280
+ }
281
+ function getReturnAssertion(node) {
282
+ const value = node.type === utils_1.AST_NODE_TYPES.MethodDefinition ? node.value : node;
283
+ const body = value.body;
284
+ if (body?.type === utils_1.AST_NODE_TYPES.BlockStatement) {
285
+ const { assertions, returnCount } = collectReturnInfo(body);
286
+ // Skip functions with multiple returns because different branches can assert different types.
287
+ if (returnCount !== 1)
288
+ return null;
289
+ // Ensure the single return is the last statement in the function body
290
+ // to avoid cases where an implicit undefined return exists.
291
+ const lastStatement = body.body[body.body.length - 1];
292
+ if (lastStatement?.type !== utils_1.AST_NODE_TYPES.ReturnStatement) {
293
+ return null;
294
+ }
295
+ return assertions[0] ?? null;
296
+ }
297
+ if (!body)
298
+ return null;
299
+ return extractAssertionTypeNode(body);
300
+ }
301
+ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
302
+ name: 'no-redundant-annotation-assertion',
303
+ meta: {
304
+ type: 'suggestion',
305
+ docs: {
306
+ description: 'Disallow combining a type annotation with an identical type assertion on the same value. Keep a single source of truth to avoid redundant type declarations that can drift apart.',
307
+ recommended: 'error',
308
+ requiresTypeChecking: true,
309
+ },
310
+ fixable: 'code',
311
+ schema: [],
312
+ messages: {
313
+ redundantAnnotationAndAssertion: 'What\'s wrong: Type "{{type}}" is declared twice: once as an annotation and again as an assertion. \u2192 Why it matters: If you later update the assertion but forget the annotation (or vice-versa), they can drift apart and cause TypeScript to silently bypass type checks or lead to incorrect runtime assumptions. \u2192 How to fix: Remove the annotation and keep the assertion to maintain a single source of truth.',
314
+ },
315
+ },
316
+ defaultOptions: [],
317
+ create(context) {
318
+ const sourceCode = context
319
+ .sourceCode ?? context.getSourceCode?.();
320
+ const parserServices = sourceCode?.parserServices ?? context.parserServices;
321
+ if (!sourceCode ||
322
+ !parserServices?.program ||
323
+ !parserServices.esTreeNodeToTSNodeMap ||
324
+ !parserServices.tsNodeToESTreeNodeMap) {
325
+ /* istanbul ignore next -- without type information the rule cannot run */
326
+ return {};
327
+ }
328
+ const checker = parserServices.program.getTypeChecker();
329
+ function reportIfRedundant(annotation, assertion, reportNode, fixerTarget) {
330
+ const matchingType = haveMatchingTypes(annotation.typeAnnotation, assertion, checker, parserServices);
331
+ if (!matchingType)
332
+ return;
333
+ context.report({
334
+ node: reportNode,
335
+ messageId: 'redundantAnnotationAndAssertion',
336
+ data: { type: matchingType },
337
+ fix: (fixer) => removeTypeAnnotation(fixer, fixerTarget, sourceCode),
338
+ });
339
+ }
340
+ return {
341
+ VariableDeclarator(node) {
342
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier ||
343
+ !node.id.typeAnnotation ||
344
+ node.id.optional ||
345
+ node.definite) {
346
+ return;
347
+ }
348
+ const assertionType = extractAssertionTypeNode(node.init);
349
+ if (!assertionType)
350
+ return;
351
+ reportIfRedundant(node.id.typeAnnotation, assertionType, node.id, node.id.typeAnnotation);
352
+ },
353
+ PropertyDefinition(node) {
354
+ if (!node.typeAnnotation ||
355
+ !node.value ||
356
+ node.optional ||
357
+ node.definite)
358
+ return;
359
+ const assertionType = extractAssertionTypeNode(node.value);
360
+ if (!assertionType)
361
+ return;
362
+ reportIfRedundant(node.typeAnnotation, assertionType, node.key, node.typeAnnotation);
363
+ },
364
+ FunctionDeclaration(node) {
365
+ if (!node.returnType)
366
+ return;
367
+ const assertionType = getReturnAssertion(node);
368
+ if (!assertionType)
369
+ return;
370
+ reportIfRedundant(node.returnType, assertionType, node.id ?? node, node.returnType);
371
+ },
372
+ FunctionExpression(node) {
373
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
374
+ return;
375
+ }
376
+ if (!node.returnType)
377
+ return;
378
+ const assertionType = getReturnAssertion(node);
379
+ if (!assertionType)
380
+ return;
381
+ reportIfRedundant(node.returnType, assertionType, node, node.returnType);
382
+ },
383
+ ArrowFunctionExpression(node) {
384
+ if (!node.returnType)
385
+ return;
386
+ const assertionType = getReturnAssertion(node);
387
+ if (!assertionType)
388
+ return;
389
+ reportIfRedundant(node.returnType, assertionType, node, node.returnType);
390
+ },
391
+ MethodDefinition(node) {
392
+ if (!node.value.returnType)
393
+ return;
394
+ const assertionType = getReturnAssertion(node);
395
+ if (!assertionType)
396
+ return;
397
+ reportIfRedundant(node.value.returnType, assertionType, node.key, node.value.returnType);
398
+ },
399
+ };
400
+ },
401
+ });
402
+ //# sourceMappingURL=no-redundant-annotation-assertion.js.map
@@ -64,7 +64,7 @@ exports.noRedundantParamTypes = (0, createRule_1.createRule)({
64
64
  fixable: 'code',
65
65
  schema: [],
66
66
  messages: {
67
- redundantParamType: 'Parameter type annotation is redundant',
67
+ redundantParamType: 'Parameter "{{paramText}}" repeats a type that the contextual function type already provides. Duplicate annotations drift out of sync and obscure the single source of truth for the signature. Remove the inline parameter annotation and rely on the variable or property type so the function stays aligned.',
68
68
  },
69
69
  },
70
70
  defaultOptions: [],
@@ -74,13 +74,19 @@ exports.noRedundantParamTypes = (0, createRule_1.createRule)({
74
74
  if (!hasRedundantTypeAnnotation(node))
75
75
  return;
76
76
  const params = node.params;
77
+ const sourceCode = context.getSourceCode();
77
78
  params.forEach((param) => {
79
+ const paramText = sourceCode
80
+ .getText(param)
81
+ .replace(/\s+/g, ' ')
82
+ .trim();
78
83
  if (isIdentifierWithTypeAnnotation(param)) {
79
84
  context.report({
80
85
  node: param,
81
86
  messageId: 'redundantParamType',
87
+ data: { paramText },
82
88
  fix(fixer) {
83
- return removeTypeAnnotation(fixer, param.typeAnnotation, context.getSourceCode());
89
+ return removeTypeAnnotation(fixer, param.typeAnnotation, sourceCode);
84
90
  },
85
91
  });
86
92
  }
@@ -88,8 +94,9 @@ exports.noRedundantParamTypes = (0, createRule_1.createRule)({
88
94
  context.report({
89
95
  node: param,
90
96
  messageId: 'redundantParamType',
97
+ data: { paramText },
91
98
  fix(fixer) {
92
- return removeTypeAnnotation(fixer, param.typeAnnotation, context.getSourceCode());
99
+ return removeTypeAnnotation(fixer, param.typeAnnotation, sourceCode);
93
100
  },
94
101
  });
95
102
  }
@@ -97,8 +104,9 @@ exports.noRedundantParamTypes = (0, createRule_1.createRule)({
97
104
  context.report({
98
105
  node: param,
99
106
  messageId: 'redundantParamType',
107
+ data: { paramText },
100
108
  fix(fixer) {
101
- return removeTypeAnnotation(fixer, param.typeAnnotation, context.getSourceCode());
109
+ return removeTypeAnnotation(fixer, param.typeAnnotation, sourceCode);
102
110
  },
103
111
  });
104
112
  }
@@ -106,8 +114,9 @@ exports.noRedundantParamTypes = (0, createRule_1.createRule)({
106
114
  context.report({
107
115
  node: param,
108
116
  messageId: 'redundantParamType',
117
+ data: { paramText },
109
118
  fix(fixer) {
110
- return removeTypeAnnotation(fixer, param.typeAnnotation, context.getSourceCode());
119
+ return removeTypeAnnotation(fixer, param.typeAnnotation, sourceCode);
111
120
  },
112
121
  });
113
122
  }
@@ -116,8 +125,9 @@ exports.noRedundantParamTypes = (0, createRule_1.createRule)({
116
125
  context.report({
117
126
  node: param,
118
127
  messageId: 'redundantParamType',
128
+ data: { paramText },
119
129
  fix(fixer) {
120
- return removeTypeAnnotation(fixer, left.typeAnnotation, context.getSourceCode());
130
+ return removeTypeAnnotation(fixer, left.typeAnnotation, sourceCode);
121
131
  },
122
132
  });
123
133
  }
@@ -0,0 +1,3 @@
1
+ type MessageIds = 'redundantInstanceArg' | 'redundantInstanceValueInObject';
2
+ export declare const noRedundantThisParams: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export {};