@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
@@ -14,7 +14,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- requireDefaultParams: 'React hooks with all optional parameters should default to an empty object',
17
+ requireDefaultParams: 'Hook "{{hookName}}" accepts an options object where every property is optional, but the parameter is not defaulted. When callers omit the argument the hook receives undefined, so destructuring or property access throws even though the fields are optional. Default the parameter to an empty object (e.g., "({ option } = {})") so the hook stays safe to call with no arguments.',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
@@ -81,8 +81,10 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
81
81
  'ArrowFunctionExpression, FunctionDeclaration'(node) {
82
82
  // Check if it's a hook function
83
83
  let isHook = false;
84
+ let hookName;
84
85
  if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
85
- isHook = node.id ? isHookName(node.id.name) : false;
86
+ hookName = node.id?.name;
87
+ isHook = hookName ? isHookName(hookName) : false;
86
88
  }
87
89
  else {
88
90
  const parent = node.parent;
@@ -90,12 +92,14 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
90
92
  parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
91
93
  parent.id &&
92
94
  parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
95
+ hookName = parent.id.name;
93
96
  isHook = isHookName(parent.id.name);
94
97
  }
95
98
  }
96
99
  if (!isHook) {
97
100
  return;
98
101
  }
102
+ const messageData = { hookName: hookName ?? 'this hook' };
99
103
  // Check if it has exactly one parameter
100
104
  if (node.params.length !== 1) {
101
105
  return;
@@ -121,10 +125,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
121
125
  context.report({
122
126
  node: param,
123
127
  messageId: 'requireDefaultParams',
128
+ data: messageData,
124
129
  fix(fixer) {
125
- const paramText = context
126
- .getSourceCode()
127
- .getText(param);
130
+ const paramText = context.sourceCode.getText(param);
128
131
  return fixer.replaceText(param, `${paramText} = {}`);
129
132
  },
130
133
  });
@@ -140,10 +143,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
140
143
  context.report({
141
144
  node: param,
142
145
  messageId: 'requireDefaultParams',
146
+ data: messageData,
143
147
  fix(fixer) {
144
- const paramText = context
145
- .getSourceCode()
146
- .getText(param);
148
+ const paramText = context.sourceCode.getText(param);
147
149
  return fixer.replaceText(param, `${paramText} = {}`);
148
150
  },
149
151
  });
@@ -152,7 +154,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
152
154
  }
153
155
  else {
154
156
  // If we can't find the type definition, check if it's defined in the same file
155
- const program = context.getSourceCode().ast;
157
+ const program = context.sourceCode.ast;
156
158
  const typeDefinitions = program.body.filter((node) => {
157
159
  if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
158
160
  node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
@@ -172,10 +174,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
172
174
  context.report({
173
175
  node: param,
174
176
  messageId: 'requireDefaultParams',
177
+ data: messageData,
175
178
  fix(fixer) {
176
- const paramText = context
177
- .getSourceCode()
178
- .getText(param);
179
+ const paramText = context.sourceCode.getText(param);
179
180
  return fixer.replaceText(param, `${paramText} = {}`);
180
181
  },
181
182
  });
@@ -191,10 +192,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
191
192
  context.report({
192
193
  node: param,
193
194
  messageId: 'requireDefaultParams',
195
+ data: messageData,
194
196
  fix(fixer) {
195
- const paramText = context
196
- .getSourceCode()
197
- .getText(param);
197
+ const paramText = context.sourceCode.getText(param);
198
198
  return fixer.replaceText(param, `${paramText} = {}`);
199
199
  },
200
200
  });
@@ -214,8 +214,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
214
214
  context.report({
215
215
  node: param,
216
216
  messageId: 'requireDefaultParams',
217
+ data: messageData,
217
218
  fix(fixer) {
218
- const paramText = context.getSourceCode().getText(param);
219
+ const paramText = context.sourceCode.getText(param);
219
220
  return fixer.replaceText(param, `${paramText} = {}`);
220
221
  },
221
222
  });
@@ -0,0 +1,4 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type MessageIds = 'missingCause' | 'causeNotCatchBinding' | 'missingCatchBinding';
3
+ export declare const requireHttpsErrorCause: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
4
+ export {};
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireHttpsErrorCause = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
6
+ const createRule_1 = require("../utils/createRule");
7
+ const isHttpsErrorCallee = (callee) => {
8
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
9
+ return callee.name === 'HttpsError';
10
+ }
11
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
12
+ return (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
13
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
14
+ callee.property.name === 'HttpsError');
15
+ }
16
+ return false;
17
+ };
18
+ const findVariableInScopeChain = (scope, name) => {
19
+ let currentScope = scope;
20
+ while (currentScope) {
21
+ const variable = currentScope.variables.find((item) => item.name === name);
22
+ if (variable) {
23
+ return variable;
24
+ }
25
+ currentScope = currentScope.upper;
26
+ }
27
+ return null;
28
+ };
29
+ exports.requireHttpsErrorCause = (0, createRule_1.createRule)({
30
+ name: 'require-https-error-cause',
31
+ meta: {
32
+ type: 'problem',
33
+ docs: {
34
+ description: 'Ensure HttpsError calls inside catch blocks pass the caught error as the fourth "cause" argument to preserve stack traces for monitoring.',
35
+ recommended: 'error',
36
+ },
37
+ schema: [],
38
+ messages: {
39
+ missingCause: 'HttpsError inside a catch block must pass the caught error as the fourth "cause" argument. Without the original error, the stack is lost and monitoring fingerprints degrade. Add {{catchName}} as the cause argument after the optional details parameter.',
40
+ causeNotCatchBinding: 'The fourth HttpsError argument should be the catch binding {{catchName}} so the original error stack is preserved for monitoring. Pass the catch variable instead of {{actual}}.',
41
+ missingCatchBinding: 'HttpsError inside a catch block needs a named catch binding so the original error can be forwarded as the fourth "cause" argument. Bind the error value (e.g., `catch (error)`) and pass that variable as the cause to keep the upstream stack trace.',
42
+ },
43
+ },
44
+ defaultOptions: [],
45
+ create(context) {
46
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
47
+ const catchStack = [];
48
+ const reportMissingCause = (node, catchName) => {
49
+ context.report({
50
+ node,
51
+ messageId: 'missingCause',
52
+ data: {
53
+ catchName,
54
+ },
55
+ });
56
+ };
57
+ const reportWrongCause = (node, catchName, actual) => {
58
+ context.report({
59
+ node,
60
+ messageId: 'causeNotCatchBinding',
61
+ data: {
62
+ catchName,
63
+ actual,
64
+ },
65
+ });
66
+ };
67
+ const isCatchBindingReference = (identifier, frame) => {
68
+ if (!frame.paramName || identifier.name !== frame.paramName) {
69
+ return false;
70
+ }
71
+ const variable = findVariableInScopeChain(ASTHelpers_1.ASTHelpers.getScope(context, identifier), identifier.name);
72
+ return (variable?.defs.some((definition) => definition.type === 'CatchClause' && definition.node === frame.node) ?? false);
73
+ };
74
+ const validateHttpsError = (node) => {
75
+ if (!catchStack.length) {
76
+ return;
77
+ }
78
+ if (!isHttpsErrorCallee(node.callee)) {
79
+ return;
80
+ }
81
+ const activeCatch = catchStack[catchStack.length - 1];
82
+ if (!activeCatch.paramName) {
83
+ context.report({
84
+ node,
85
+ messageId: 'missingCatchBinding',
86
+ });
87
+ return;
88
+ }
89
+ const catchName = activeCatch.paramName;
90
+ // HttpsError accepts both positional and object-based constructors. This signature
91
+ // is supported for compatibility with HttpsError overloads, ensuring the rule
92
+ // can validate the 'cause' property within a settings object to preserve
93
+ // error chaining for better diagnostics.
94
+ if (node.arguments.length === 1 &&
95
+ node.arguments[0].type === utils_1.AST_NODE_TYPES.ObjectExpression) {
96
+ const settingsObj = node.arguments[0];
97
+ const causeProp = settingsObj.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
98
+ !prop.computed &&
99
+ ((prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
100
+ prop.key.name === 'cause') ||
101
+ (prop.key.type === utils_1.AST_NODE_TYPES.Literal &&
102
+ prop.key.value === 'cause')));
103
+ if (!causeProp) {
104
+ reportMissingCause(node, catchName);
105
+ return;
106
+ }
107
+ const causeValue = causeProp.value;
108
+ if (causeValue.type !== utils_1.AST_NODE_TYPES.Identifier) {
109
+ reportWrongCause(causeValue, catchName, sourceCode.getText(causeValue));
110
+ return;
111
+ }
112
+ if (!isCatchBindingReference(causeValue, activeCatch)) {
113
+ reportWrongCause(causeValue, catchName, sourceCode.getText(causeValue));
114
+ }
115
+ return;
116
+ }
117
+ if (node.arguments.length < 4) {
118
+ reportMissingCause(node, catchName);
119
+ return;
120
+ }
121
+ const causeArg = node.arguments[3];
122
+ if (!causeArg || causeArg.type !== utils_1.AST_NODE_TYPES.Identifier) {
123
+ reportWrongCause(causeArg ?? node, catchName, causeArg
124
+ ? sourceCode.getText(causeArg)
125
+ : 'the missing cause argument');
126
+ return;
127
+ }
128
+ if (!isCatchBindingReference(causeArg, activeCatch)) {
129
+ reportWrongCause(causeArg, catchName, sourceCode.getText(causeArg));
130
+ }
131
+ };
132
+ return {
133
+ CatchClause(node) {
134
+ if (node.param?.type === utils_1.AST_NODE_TYPES.Identifier) {
135
+ catchStack.push({ paramName: node.param.name, node });
136
+ return;
137
+ }
138
+ // Destructured catch params (e.g., `catch ({ message })`) do not bind the
139
+ // full error object, so they cannot be forwarded as the HttpsError cause.
140
+ // Users should bind the error value (e.g., `catch (error)`) before rethrowing.
141
+ catchStack.push({ paramName: null, node });
142
+ },
143
+ 'CatchClause:exit'() {
144
+ catchStack.pop();
145
+ },
146
+ NewExpression(node) {
147
+ validateHttpsError(node);
148
+ },
149
+ CallExpression(node) {
150
+ validateHttpsError(node);
151
+ },
152
+ };
153
+ },
154
+ });
155
+ //# sourceMappingURL=require-https-error-cause.js.map
@@ -11,8 +11,8 @@ module.exports = (0, createRule_1.createRule)({
11
11
  },
12
12
  schema: [],
13
13
  messages: {
14
- useHttpsError: 'Use HttpsError instead of throw new Error in functions/src directory',
15
- useProprietaryHttpsError: 'Use our proprietary HttpsError instead of firebase-admin HttpsError',
14
+ useHttpsError: 'Throwing "{{constructorName}}" in Cloud Functions returns a generic 500 and drops the structured status code clients rely on. Throw the proprietary HttpsError instead so responses include the correct status, sanitized message, and logging context.',
15
+ useProprietaryHttpsError: '{{reference}} comes from {{source}} and bypasses our proprietary HttpsError wrapper, so responses skip standardized status codes, logging, and client-safe payloads. Import and throw HttpsError from @our-company/errors to keep errors consistent.',
16
16
  },
17
17
  },
18
18
  defaultOptions: [],
@@ -22,12 +22,20 @@ module.exports = (0, createRule_1.createRule)({
22
22
  if (!filename.includes('functions/src')) {
23
23
  return {};
24
24
  }
25
- let httpsIdentifier = null;
26
- let httpsErrorIdentifier = null;
25
+ const httpsIdentifiers = new Map();
26
+ const httpsErrorIdentifiers = new Map();
27
+ const reportProprietary = (node, data) => context.report({
28
+ node,
29
+ messageId: 'useProprietaryHttpsError',
30
+ data,
31
+ });
27
32
  return {
28
33
  ImportDeclaration(node) {
29
34
  if (node.source.value === 'firebase-admin' ||
30
35
  node.source.value === 'firebase-admin/lib/https-error') {
36
+ const sourceModule = String(node.source.value);
37
+ // Report imports immediately so the forbidden dependency is blocked even
38
+ // when unused; throw sites below also report to cover runtime usage.
31
39
  // Check for direct HttpsError import
32
40
  const httpsErrorSpecifier = node.specifiers.find((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
33
41
  spec.imported.name === 'HttpsError');
@@ -35,17 +43,19 @@ module.exports = (0, createRule_1.createRule)({
35
43
  const httpsSpecifier = node.specifiers.find((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
36
44
  spec.imported.name === 'https');
37
45
  if (httpsErrorSpecifier && 'local' in httpsErrorSpecifier) {
38
- httpsErrorIdentifier = httpsErrorSpecifier.local.name;
39
- context.report({
40
- node,
41
- messageId: 'useProprietaryHttpsError',
46
+ const localName = httpsErrorSpecifier.local.name;
47
+ httpsErrorIdentifiers.set(localName, sourceModule);
48
+ reportProprietary(node, {
49
+ reference: localName,
50
+ source: sourceModule,
42
51
  });
43
52
  }
44
53
  if (httpsSpecifier && 'local' in httpsSpecifier) {
45
- httpsIdentifier = httpsSpecifier.local.name;
46
- context.report({
47
- node,
48
- messageId: 'useProprietaryHttpsError',
54
+ const localName = httpsSpecifier.local.name;
55
+ httpsIdentifiers.set(localName, sourceModule);
56
+ reportProprietary(node, {
57
+ reference: `${localName}.HttpsError`,
58
+ source: sourceModule,
49
59
  });
50
60
  }
51
61
  }
@@ -65,22 +75,34 @@ module.exports = (0, createRule_1.createRule)({
65
75
  context.report({
66
76
  node,
67
77
  messageId: 'useHttpsError',
78
+ data: {
79
+ constructorName: callee.name,
80
+ },
68
81
  });
69
82
  return;
70
83
  }
71
84
  // Check for firebase-admin HttpsError usage
72
- const isFirebaseHttpsError = callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
85
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
73
86
  callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
74
- callee.object.name === httpsIdentifier &&
75
87
  callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
76
- callee.property.name === 'HttpsError';
77
- const isDirectHttpsError = callee.type === utils_1.AST_NODE_TYPES.Identifier &&
78
- callee.name === httpsErrorIdentifier;
79
- if (isFirebaseHttpsError || isDirectHttpsError) {
80
- context.report({
81
- node,
82
- messageId: 'useProprietaryHttpsError',
88
+ callee.property.name === 'HttpsError') {
89
+ const objectName = callee.object.name;
90
+ const source = httpsIdentifiers.get(objectName);
91
+ if (!source) {
92
+ return;
93
+ }
94
+ reportProprietary(node, {
95
+ reference: `${objectName}.HttpsError`,
96
+ source,
83
97
  });
98
+ return;
99
+ }
100
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
101
+ const source = httpsErrorIdentifiers.get(callee.name);
102
+ if (!source) {
103
+ return;
104
+ }
105
+ reportProprietary(node, { reference: callee.name, source });
84
106
  }
85
107
  },
86
108
  };
@@ -32,7 +32,7 @@ module.exports = (0, createRule_1.createRule)({
32
32
  const options = context.options[0] || {
33
33
  componentPath: 'src/components/image/ImageOptimized',
34
34
  };
35
- const sourceCode = context.getSourceCode();
35
+ const sourceCode = context.sourceCode;
36
36
  return {
37
37
  // Handle JSX img elements
38
38
  JSXElement(node) {
@@ -8,21 +8,25 @@ const ASTHelpers_1 = require("../utils/ASTHelpers");
8
8
  const isComponentExplicitlyUnmemoized = (componentName) => componentName.toLowerCase().includes('unmemoized');
9
9
  function isFunction(node) {
10
10
  return (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
11
- node.type === utils_1.AST_NODE_TYPES.FunctionExpression);
11
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
12
+ node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration);
12
13
  }
13
- function isHigherOrderFunctionReturningJSX(node) {
14
+ function isHigherOrderFunctionReturningJSX(node, context) {
14
15
  if (isFunction(node)) {
15
- // Check if function takes another function as an argument
16
- const hasFunctionParam = 'params' in node && node.params.some(isFunction);
17
16
  if (node.body && node.body.type === 'BlockStatement') {
18
17
  for (const statement of node.body.body) {
19
18
  if (statement.type === 'ReturnStatement' && statement.argument) {
20
- const returnsJSX = ASTHelpers_1.ASTHelpers.returnsJSX(statement.argument);
19
+ const returnsJSX = ASTHelpers_1.ASTHelpers.returnsJSX(statement.argument, context);
21
20
  const returnsFunction = isFunction(statement.argument);
22
- return (hasFunctionParam || returnsFunction) && returnsJSX;
21
+ return returnsFunction && returnsJSX;
23
22
  }
24
23
  }
25
24
  }
25
+ else if (node.body && isFunction(node.body)) {
26
+ // Shorthand arrow HOC: (Comp) => (props) => <Comp {...props} />
27
+ // Here node.body is the inner function; check if it returns JSX.
28
+ return ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
29
+ }
26
30
  }
27
31
  return false;
28
32
  }
@@ -52,14 +56,15 @@ function checkFunction(context, node) {
52
56
  if (!fileName.endsWith('.tsx')) {
53
57
  return;
54
58
  }
55
- if (isHigherOrderFunctionReturningJSX(node)) {
59
+ if (isHigherOrderFunctionReturningJSX(node, context)) {
56
60
  return;
57
61
  }
58
62
  const parentNode = node.parent;
59
63
  if (node.parent.type === 'CallExpression') {
60
64
  return;
61
65
  }
62
- if (ASTHelpers_1.ASTHelpers.returnsJSX(node.body) && ASTHelpers_1.ASTHelpers.hasParameters(node)) {
66
+ if (ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context) &&
67
+ ASTHelpers_1.ASTHelpers.hasParameters(node)) {
63
68
  const results = [
64
69
  isUnmemoizedArrowFunction,
65
70
  isUnmemoizedFunctionComponent,
@@ -79,7 +84,10 @@ function checkFunction(context, node) {
79
84
  },
80
85
  fix: results[2] || results[1]
81
86
  ? function fix(fixer) {
82
- const sourceCode = context.getSourceCode();
87
+ if (node.async || node.generator) {
88
+ return null;
89
+ }
90
+ const sourceCode = context.sourceCode;
83
91
  let importFix = null;
84
92
  // Search for memo import statement
85
93
  const importDeclarations = sourceCode.ast.body.filter((node) => node.type === 'ImportDeclaration');
@@ -118,7 +126,7 @@ function checkFunction(context, node) {
118
126
  const functionNameReplacement = `function ${node.id.name}Unmemoized`;
119
127
  const fixes = [
120
128
  fixer.replaceTextRange(functionKeywordRange, functionKeywordReplacement),
121
- fixer.insertTextAfterRange([node.range[1], node.range[1]], ')'),
129
+ fixer.insertTextAfterRange([node.range[1], node.range[1]], ');'),
122
130
  fixer.replaceTextRange([node.id.range[0] - 1, node.id.range[1]], functionNameReplacement),
123
131
  ];
124
132
  if (importFix) {
@@ -0,0 +1,3 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const requireMemoizeJsxReturners: TSESLint.RuleModule<"requireMemoizeJsxReturner", [], TSESLint.RuleListener>;
3
+ export default requireMemoizeJsxReturners;