@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
@@ -13,6 +13,7 @@ const PRIMITIVE_TYPES = new Set([
13
13
  'undefined',
14
14
  'GeoPoint',
15
15
  ]);
16
+ const UNKNOWN_FIELD_LABEL = '<unknown field>';
16
17
  const isInFirestoreTypesDirectory = (filename) => {
17
18
  if (!filename || filename === '<input>')
18
19
  return false;
@@ -74,6 +75,56 @@ const unwrapArrayElementType = (node) => {
74
75
  }
75
76
  return current;
76
77
  };
78
+ const literalToString = (node) => {
79
+ if (!node || node.type !== utils_1.AST_NODE_TYPES.Literal)
80
+ return null;
81
+ const value = node.value;
82
+ if (typeof value === 'string' ||
83
+ typeof value === 'number' ||
84
+ typeof value === 'bigint') {
85
+ return String(value);
86
+ }
87
+ return null;
88
+ };
89
+ /**
90
+ * Derives a field label for diagnostics by walking ancestors.
91
+ * Prefers TSPropertySignature keys and returns identifier or string/number/bigint literal keys when present.
92
+ * Falls back to the surrounding TSTypeAliasDeclaration or TSInterfaceDeclaration name when no property key is available.
93
+ * This ensures declaration-level array aliases (for example, `type Foo = Object[]`) surface a meaningful label and avoid unlabeled diagnostics.
94
+ * Returns a placeholder when resolution fails so downstream messaging remains readable.
95
+ */
96
+ const getFieldName = (node) => {
97
+ let current = node;
98
+ while (current) {
99
+ if (current.type === utils_1.AST_NODE_TYPES.TSPropertySignature) {
100
+ const key = current.key;
101
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier)
102
+ return key.name;
103
+ const literalKey = literalToString(key);
104
+ if (literalKey !== null)
105
+ return literalKey;
106
+ const computedKey = key;
107
+ if (computedKey.type === 'TSComputedPropertyName' &&
108
+ computedKey.expression) {
109
+ const expr = computedKey.expression;
110
+ if (expr.type === utils_1.AST_NODE_TYPES.Identifier)
111
+ return expr.name;
112
+ const literalExpr = literalToString(expr);
113
+ if (literalExpr !== null)
114
+ return literalExpr;
115
+ }
116
+ }
117
+ if (current.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
118
+ current.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
119
+ const id = current.id;
120
+ if (id.type === utils_1.AST_NODE_TYPES.Identifier) {
121
+ return id.name;
122
+ }
123
+ }
124
+ current = current.parent;
125
+ }
126
+ return UNKNOWN_FIELD_LABEL;
127
+ };
77
128
  // Determine whether this type node appears in a Firestore model type context
78
129
  // i.e., within an interface or type alias declaration, and not within variable/function annotations
79
130
  const isInModelTypeContext = (node) => {
@@ -110,15 +161,14 @@ exports.noFirestoreObjectArrays = (0, createRule_1.createRule)({
110
161
  },
111
162
  schema: [],
112
163
  messages: {
113
- noObjectArrays: `Arrays of objects are problematic in Firestore:
114
- - Not queryable
115
- - Destructive updates
116
- - Concurrency risks
117
-
118
- Prefer:
119
- - Record<string, T> keyed by id with an index field for order (use toMap/toArr)
120
- - Subcollections
121
- - Arrays of IDs where appropriate`,
164
+ noObjectArrays: [
165
+ // What's wrong
166
+ "What's wrong: {{fieldName}} stores an array of objects in a Firestore document.",
167
+ // Why it matters
168
+ 'Why it matters: Firestore cannot query inside object arrays, and updating one item rewrites the whole array; concurrent writes can overwrite each other and lose data.',
169
+ // How to fix
170
+ 'How to fix: Store items as Record<string, T> keyed by id with an index field for ordering (use toMap/toArr helpers), or move items into a subcollection or store only an array of IDs.',
171
+ ].join('\n'),
122
172
  },
123
173
  },
124
174
  defaultOptions: [],
@@ -126,7 +176,7 @@ Prefer:
126
176
  if (!isInFirestoreTypesDirectory(context.getFilename())) {
127
177
  return {};
128
178
  }
129
- const sourceCode = context.getSourceCode();
179
+ const sourceCode = context.sourceCode;
130
180
  // Collect alias/interface/enum information within this file to refine object vs primitive classification
131
181
  const aliasNameToType = new Map();
132
182
  const interfaceNames = new Set();
@@ -331,8 +381,7 @@ Prefer:
331
381
  while (currentParent) {
332
382
  if (currentParent.type === utils_1.AST_NODE_TYPES.TSTypeOperator &&
333
383
  currentParent.operator === 'readonly') {
334
- currentParent = currentParent
335
- .parent;
384
+ currentParent = currentParent.parent;
336
385
  continue;
337
386
  }
338
387
  if (isParenthesizedType(currentParent)) {
@@ -369,6 +418,9 @@ Prefer:
369
418
  context.report({
370
419
  node,
371
420
  messageId: 'noObjectArrays',
421
+ data: {
422
+ fieldName: getFieldName(node),
423
+ },
372
424
  });
373
425
  }
374
426
  },
@@ -392,6 +444,9 @@ Prefer:
392
444
  context.report({
393
445
  node,
394
446
  messageId: 'noObjectArrays',
447
+ data: {
448
+ fieldName: getFieldName(node),
449
+ },
395
450
  });
396
451
  }
397
452
  }
@@ -0,0 +1,12 @@
1
+ type Options = [
2
+ {
3
+ ignoreClassMethods?: boolean;
4
+ ignoreInterfaceImplementations?: boolean;
5
+ interfaceAllowlist?: string[];
6
+ allowNames?: string[];
7
+ allowPatterns?: string[];
8
+ allowFilePatterns?: string[];
9
+ }
10
+ ];
11
+ export declare const noHandlerSuffix: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"handlerSuffix", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
12
+ export {};
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noHandlerSuffix = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const minimatch_1 = require("minimatch");
6
+ const createRule_1 = require("../utils/createRule");
7
+ const DEFAULT_OPTIONS = {
8
+ ignoreClassMethods: false,
9
+ ignoreInterfaceImplementations: false,
10
+ interfaceAllowlist: [],
11
+ allowNames: [],
12
+ allowPatterns: [],
13
+ allowFilePatterns: [],
14
+ };
15
+ function toEntityName(entity) {
16
+ if (entity.type === utils_1.AST_NODE_TYPES.Identifier) {
17
+ return entity.name;
18
+ }
19
+ if (entity.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
20
+ return `${toEntityName(entity.left)}.${entity.right.name}`;
21
+ }
22
+ return '';
23
+ }
24
+ function isUnsafeAllowPattern(pattern) {
25
+ // Flag nested quantifiers that commonly lead to catastrophic backtracking
26
+ const nestedQuantifierPattern = /\((?:[^()\\]|\\.)*[+*{][^)]*\)\s*[+*{]/;
27
+ return nestedQuantifierPattern.test(pattern);
28
+ }
29
+ function getStaticKeyName(key) {
30
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
31
+ return key.name;
32
+ }
33
+ if (key.type === utils_1.AST_NODE_TYPES.Literal && typeof key.value === 'string') {
34
+ return key.value;
35
+ }
36
+ return null;
37
+ }
38
+ function getHandlerSuffix(name) {
39
+ const match = name.match(/^(.*?)(Handlers?)$/i);
40
+ if (!match)
41
+ return null;
42
+ const [, baseName, suffix] = match;
43
+ if (!suffix)
44
+ return null;
45
+ const normalizedSuffix = suffix.toLowerCase() === 'handlers' ? 'Handlers' : 'Handler';
46
+ return {
47
+ baseName,
48
+ suffix: normalizedSuffix,
49
+ };
50
+ }
51
+ function isInAllowedFile(filename, allowFilePatterns) {
52
+ return allowFilePatterns.some((pattern) => (0, minimatch_1.minimatch)(filename, pattern));
53
+ }
54
+ function toMemberExpressionName(expr) {
55
+ if (expr.computed || expr.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
56
+ return null;
57
+ }
58
+ const objectName = expr.object.type === utils_1.AST_NODE_TYPES.Identifier
59
+ ? expr.object.name
60
+ : expr.object.type === utils_1.AST_NODE_TYPES.MemberExpression
61
+ ? toMemberExpressionName(expr.object)
62
+ : null;
63
+ return objectName ? `${objectName}.${expr.property.name}` : null;
64
+ }
65
+ function getImplementedInterfaces(classNode) {
66
+ return (classNode.implements?.flatMap((impl) => {
67
+ const expr = impl.expression;
68
+ const name = expr.type === utils_1.AST_NODE_TYPES.Identifier
69
+ ? expr.name
70
+ : expr.type === utils_1.AST_NODE_TYPES.MemberExpression
71
+ ? toMemberExpressionName(expr)
72
+ : null;
73
+ return name ? [name] : [];
74
+ }) ?? []);
75
+ }
76
+ function getClassFromMember(node) {
77
+ const classBody = node.parent;
78
+ if (classBody &&
79
+ classBody.type === utils_1.AST_NODE_TYPES.ClassBody &&
80
+ classBody.parent &&
81
+ (classBody.parent.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
82
+ classBody.parent.type === utils_1.AST_NODE_TYPES.ClassExpression)) {
83
+ return classBody.parent;
84
+ }
85
+ return null;
86
+ }
87
+ function getTypeAnnotationName(annotation) {
88
+ if (!annotation)
89
+ return null;
90
+ if (annotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
91
+ const typeName = annotation.typeAnnotation.typeName;
92
+ if (typeName.type === utils_1.AST_NODE_TYPES.Identifier ||
93
+ typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
94
+ return toEntityName(typeName);
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+ exports.noHandlerSuffix = (0, createRule_1.createRule)({
100
+ name: 'no-handler-suffix',
101
+ meta: {
102
+ type: 'suggestion',
103
+ docs: {
104
+ description: 'Disallow the generic "handler" suffix in callback names so names explain the action they perform',
105
+ recommended: 'error',
106
+ },
107
+ schema: [
108
+ {
109
+ type: 'object',
110
+ properties: {
111
+ ignoreClassMethods: { type: 'boolean' },
112
+ ignoreInterfaceImplementations: { type: 'boolean' },
113
+ interfaceAllowlist: {
114
+ type: 'array',
115
+ items: { type: 'string' },
116
+ },
117
+ allowNames: { type: 'array', items: { type: 'string' } },
118
+ allowPatterns: { type: 'array', items: { type: 'string' } },
119
+ allowFilePatterns: { type: 'array', items: { type: 'string' } },
120
+ },
121
+ additionalProperties: false,
122
+ },
123
+ ],
124
+ messages: {
125
+ handlerSuffix: 'Function "{{name}}" ends with the generic {{suffix}} suffix. Handler names hide the outcome of the callback and make call sites indistinguishable. Rename it to describe the effect (e.g., "{{suggestedName}}" or another action phrase) so readers know what the function actually does.',
126
+ },
127
+ },
128
+ defaultOptions: [DEFAULT_OPTIONS],
129
+ create(context, [options]) {
130
+ const filename = context.getFilename();
131
+ const resolvedOptions = { ...DEFAULT_OPTIONS, ...(options ?? {}) };
132
+ const allowNames = new Set(resolvedOptions.allowNames);
133
+ const interfaceAllowlist = new Set(resolvedOptions.interfaceAllowlist);
134
+ const invalidAllowPatterns = [];
135
+ const unsafeAllowPatterns = [];
136
+ const allowPatterns = (resolvedOptions.allowPatterns ?? []).flatMap((pattern) => {
137
+ try {
138
+ if (isUnsafeAllowPattern(pattern)) {
139
+ unsafeAllowPatterns.push(pattern);
140
+ return [];
141
+ }
142
+ return [new RegExp(pattern)];
143
+ }
144
+ catch (error) {
145
+ const reason = error && typeof error === 'object' && 'message' in error
146
+ ? ` (${String(error.message)})`
147
+ : '';
148
+ invalidAllowPatterns.push(`${pattern}${reason}`);
149
+ return [];
150
+ }
151
+ });
152
+ if (invalidAllowPatterns.length > 0 || unsafeAllowPatterns.length > 0) {
153
+ const errorParts = [];
154
+ if (invalidAllowPatterns.length > 0) {
155
+ errorParts.push(`invalid allowPatterns: ${invalidAllowPatterns.join(', ')}`);
156
+ }
157
+ if (unsafeAllowPatterns.length > 0) {
158
+ errorParts.push(`unsafe allowPatterns (avoid nested quantifiers that risk catastrophic backtracking): ${unsafeAllowPatterns.join(', ')}`);
159
+ }
160
+ throw new Error(`no-handler-suffix: ${errorParts.join('; ')}`);
161
+ }
162
+ if (isInAllowedFile(filename, resolvedOptions.allowFilePatterns ?? [])) {
163
+ return {};
164
+ }
165
+ function isAllowedName(name) {
166
+ if (allowNames.has(name))
167
+ return true;
168
+ return allowPatterns.some((regex) => regex.test(name));
169
+ }
170
+ function shouldIgnoreForInterfaces(node) {
171
+ const { ignoreInterfaceImplementations } = resolvedOptions;
172
+ if ((node.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
173
+ node.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
174
+ !node.computed &&
175
+ (node.key.type === utils_1.AST_NODE_TYPES.Identifier ||
176
+ node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier ||
177
+ (node.key.type === utils_1.AST_NODE_TYPES.Literal &&
178
+ typeof node.key.value === 'string'))) {
179
+ const classNode = getClassFromMember(node);
180
+ if (!classNode)
181
+ return false;
182
+ const implemented = getImplementedInterfaces(classNode);
183
+ if (implemented.length === 0)
184
+ return false;
185
+ if (ignoreInterfaceImplementations) {
186
+ return true;
187
+ }
188
+ if (interfaceAllowlist.size > 0) {
189
+ return implemented.some((iface) => interfaceAllowlist.has(iface));
190
+ }
191
+ }
192
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
193
+ node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
194
+ const annotationName = getTypeAnnotationName(node.id.typeAnnotation);
195
+ if (annotationName &&
196
+ (interfaceAllowlist.has(annotationName) ||
197
+ resolvedOptions.ignoreInterfaceImplementations)) {
198
+ return true;
199
+ }
200
+ }
201
+ return false;
202
+ }
203
+ function getParentName(parent) {
204
+ if (!parent)
205
+ return null;
206
+ if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
207
+ parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
208
+ return parent.id.name;
209
+ }
210
+ if ((parent.type === utils_1.AST_NODE_TYPES.Property ||
211
+ parent.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
212
+ parent.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
213
+ !parent.computed &&
214
+ parent.key.type !== utils_1.AST_NODE_TYPES.PrivateIdentifier) {
215
+ return getStaticKeyName(parent.key);
216
+ }
217
+ return null;
218
+ }
219
+ function reportIfHandlerName(name, identifierNode, owner) {
220
+ if (!name || !identifierNode)
221
+ return;
222
+ const suffixInfo = getHandlerSuffix(name);
223
+ if (!suffixInfo)
224
+ return;
225
+ if (isAllowedName(name))
226
+ return;
227
+ if ((owner.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
228
+ owner.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
229
+ resolvedOptions.ignoreClassMethods) {
230
+ return;
231
+ }
232
+ if (owner.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
233
+ owner.type === utils_1.AST_NODE_TYPES.PropertyDefinition ||
234
+ owner.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
235
+ owner.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
236
+ if (shouldIgnoreForInterfaces(owner)) {
237
+ return;
238
+ }
239
+ }
240
+ const suggestedName = suffixInfo.baseName || 'a verb phrase that states the outcome';
241
+ context.report({
242
+ node: identifierNode,
243
+ messageId: 'handlerSuffix',
244
+ data: {
245
+ name,
246
+ suffix: suffixInfo.suffix,
247
+ suggestedName,
248
+ },
249
+ });
250
+ }
251
+ return {
252
+ FunctionDeclaration(node) {
253
+ if (node.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
254
+ reportIfHandlerName(node.id.name, node.id, node);
255
+ }
256
+ },
257
+ VariableDeclarator(node) {
258
+ if (node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
259
+ (node.init?.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
260
+ node.init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
261
+ reportIfHandlerName(node.id.name, node.id, node);
262
+ }
263
+ },
264
+ MethodDefinition(node) {
265
+ if (!node.computed) {
266
+ const name = getStaticKeyName(node.key);
267
+ if (name) {
268
+ reportIfHandlerName(name, node.key, node);
269
+ }
270
+ }
271
+ },
272
+ PropertyDefinition(node) {
273
+ if (!node.computed &&
274
+ (node.value?.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
275
+ node.value?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
276
+ const name = getStaticKeyName(node.key);
277
+ if (name) {
278
+ reportIfHandlerName(name, node.key, node);
279
+ }
280
+ }
281
+ },
282
+ Property(node) {
283
+ if (!node.computed &&
284
+ (node.value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
285
+ node.value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
286
+ const name = getStaticKeyName(node.key);
287
+ if (name) {
288
+ reportIfHandlerName(name, node.key, node);
289
+ }
290
+ }
291
+ },
292
+ FunctionExpression(node) {
293
+ if (!node.id)
294
+ return;
295
+ // Avoid double-reporting when the parent already names the function
296
+ const parentName = getParentName(node.parent);
297
+ if (parentName && parentName === node.id.name) {
298
+ return;
299
+ }
300
+ reportIfHandlerName(node.id.name, node.id, node);
301
+ },
302
+ };
303
+ },
304
+ });
305
+ //# sourceMappingURL=no-handler-suffix.js.map
@@ -201,7 +201,7 @@ exports.noHungarian = (0, createRule_1.createRule)({
201
201
  fixable: undefined,
202
202
  schema: [],
203
203
  messages: {
204
- noHungarian: 'Avoid Hungarian notation in variable name "{{name}}"',
204
+ noHungarian: 'Identifier "{{name}}" encodes its type through a prefix or suffix (Hungarian notation). Type-coded names hide the domain concept and become misleading when the underlying type changes. Rename it to a domain-focused name without the type marker and rely on TypeScript for type information.',
205
205
  },
206
206
  },
207
207
  defaultOptions: [],
@@ -0,0 +1,10 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type Options = [
3
+ {
4
+ props?: string[];
5
+ allowRenderProps?: boolean;
6
+ allowModuleScopeFactories?: boolean;
7
+ }
8
+ ];
9
+ export declare const noInlineComponentProp: TSESLint.RuleModule<"inlineComponentProp", Options, TSESLint.RuleListener>;
10
+ export {};