@blumintinc/eslint-plugin-blumint 1.12.6 → 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 (258) hide show
  1. package/README.md +171 -99
  2. package/lib/index.js +300 -17
  3. package/lib/rules/array-methods-this-context.js +20 -2
  4. package/lib/rules/avoid-utils-directory.js +11 -2
  5. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  6. package/lib/rules/consistent-callback-naming.js +1 -1
  7. package/lib/rules/dynamic-https-errors.d.ts +3 -1
  8. package/lib/rules/dynamic-https-errors.js +48 -11
  9. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  10. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +52 -96
  11. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  12. package/lib/rules/enforce-assert-throws.js +493 -51
  13. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  14. package/lib/rules/enforce-boolean-naming-prefixes.js +820 -39
  15. package/lib/rules/enforce-callable-types.js +4 -4
  16. package/lib/rules/enforce-callback-memo.js +143 -18
  17. package/lib/rules/enforce-centralized-mock-firestore.js +5 -2
  18. package/lib/rules/enforce-console-error.d.ts +3 -0
  19. package/lib/rules/enforce-console-error.js +278 -0
  20. package/lib/rules/enforce-css-media-queries.js +24 -46
  21. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  22. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  23. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  24. package/lib/rules/enforce-dynamic-imports.js +1 -1
  25. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  26. package/lib/rules/enforce-early-destructuring.js +980 -0
  27. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  28. package/lib/rules/enforce-empty-object-check.js +502 -0
  29. package/lib/rules/enforce-exported-function-types.js +4 -4
  30. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  31. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +262 -0
  32. package/lib/rules/enforce-firestore-doc-ref-generic.js +350 -3
  33. package/lib/rules/enforce-firestore-facade.js +349 -157
  34. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  35. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  36. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  37. package/lib/rules/enforce-global-constants.d.ts +4 -1
  38. package/lib/rules/enforce-global-constants.js +240 -12
  39. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  40. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  41. package/lib/rules/enforce-memoize-async.js +92 -27
  42. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  43. package/lib/rules/enforce-memoize-getters.js +147 -0
  44. package/lib/rules/enforce-microdiff.js +3 -2
  45. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  46. package/lib/rules/enforce-positive-naming.js +9 -7
  47. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  48. package/lib/rules/enforce-props-argument-name.js +165 -151
  49. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  50. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  51. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  52. package/lib/rules/enforce-querykey-ts.js +274 -0
  53. package/lib/rules/enforce-safe-stringify.js +1 -1
  54. package/lib/rules/enforce-serializable-params.js +3 -3
  55. package/lib/rules/enforce-singular-type-names.js +10 -6
  56. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  57. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  58. package/lib/rules/enforce-storage-context.d.ts +9 -0
  59. package/lib/rules/enforce-storage-context.js +646 -0
  60. package/lib/rules/enforce-timestamp-now.js +23 -7
  61. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  62. package/lib/rules/enforce-transform-memoization.js +416 -0
  63. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  64. package/lib/rules/enforce-typescript-markdown-code-blocks.js +100 -0
  65. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  66. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  67. package/lib/rules/enforce-verb-noun-naming.js +90 -14
  68. package/lib/rules/ensure-pointer-events-none.js +28 -2
  69. package/lib/rules/export-if-in-doubt.js +27 -4
  70. package/lib/rules/extract-global-constants.js +92 -53
  71. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  72. package/lib/rules/fast-deep-equal-over-microdiff.js +351 -83
  73. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  74. package/lib/rules/firestore-transaction-reads-before-writes.js +277 -0
  75. package/lib/rules/flatten-push-calls.d.ts +2 -0
  76. package/lib/rules/flatten-push-calls.js +428 -0
  77. package/lib/rules/generic-starts-with-t.js +8 -2
  78. package/lib/rules/global-const-style.js +50 -19
  79. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  80. package/lib/rules/jsdoc-above-field.js +208 -0
  81. package/lib/rules/key-only-outermost-element.js +21 -3
  82. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  83. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  84. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  85. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  86. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  87. package/lib/rules/memo-nested-react-components.js +410 -0
  88. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  89. package/lib/rules/memoize-root-level-hocs.js +242 -0
  90. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  91. package/lib/rules/no-always-true-false-conditions.js +68 -8
  92. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  93. package/lib/rules/no-array-length-in-deps.js +314 -0
  94. package/lib/rules/no-async-array-filter.js +5 -2
  95. package/lib/rules/no-async-foreach.js +210 -12
  96. package/lib/rules/no-circular-references.js +18 -17
  97. package/lib/rules/no-class-instance-destructuring.js +55 -12
  98. package/lib/rules/no-complex-cloud-params.js +22 -5
  99. package/lib/rules/no-compositing-layer-props.js +6 -4
  100. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  101. package/lib/rules/no-console-error.d.ts +9 -0
  102. package/lib/rules/no-console-error.js +525 -0
  103. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  104. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  105. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  106. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  107. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  108. package/lib/rules/no-entire-object-hook-deps.js +320 -68
  109. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  110. package/lib/rules/no-excessive-parent-chain.js +273 -0
  111. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  112. package/lib/rules/no-explicit-return-type.js +215 -26
  113. package/lib/rules/no-filter-without-return.js +5 -1
  114. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  115. package/lib/rules/no-firestore-jest-mock.js +126 -8
  116. package/lib/rules/no-firestore-object-arrays.js +398 -28
  117. package/lib/rules/no-handler-suffix.d.ts +12 -0
  118. package/lib/rules/no-handler-suffix.js +305 -0
  119. package/lib/rules/no-hungarian.js +14 -14
  120. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  121. package/lib/rules/no-inline-component-prop.js +456 -0
  122. package/lib/rules/no-jsx-in-hooks.js +6 -1
  123. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  124. package/lib/rules/no-margin-properties.d.ts +7 -1
  125. package/lib/rules/no-margin-properties.js +22 -9
  126. package/lib/rules/no-memoize-on-static.js +9 -1
  127. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  128. package/lib/rules/no-misleading-boolean-prefixes.js +331 -0
  129. package/lib/rules/no-misused-switch-case.js +22 -1
  130. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  131. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  132. package/lib/rules/no-mock-firebase-admin.js +5 -2
  133. package/lib/rules/no-object-values-on-strings.js +10 -2
  134. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  135. package/lib/rules/no-overridable-method-calls-in-constructor.js +272 -0
  136. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  137. package/lib/rules/no-passthrough-getters.js +206 -0
  138. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  139. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  140. package/lib/rules/no-redundant-param-types.js +16 -6
  141. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  142. package/lib/rules/no-redundant-this-params.js +459 -0
  143. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  144. package/lib/rules/no-redundant-usecallback-wrapper.js +328 -0
  145. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  146. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  147. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  148. package/lib/rules/no-restricted-properties-fix.js +173 -0
  149. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  150. package/lib/rules/no-separate-loading-state.js +133 -0
  151. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  152. package/lib/rules/no-stale-state-across-await.js +185 -0
  153. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  154. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  155. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  156. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  157. package/lib/rules/no-type-assertion-returns.js +49 -49
  158. package/lib/rules/no-undefined-null-passthrough.js +217 -40
  159. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  160. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  161. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  162. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  163. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  164. package/lib/rules/no-unnecessary-verb-suffix.js +2 -3
  165. package/lib/rules/no-unpinned-dependencies.js +36 -5
  166. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  167. package/lib/rules/no-unused-props.d.ts +5 -2
  168. package/lib/rules/no-unused-props.js +451 -112
  169. package/lib/rules/no-unused-usestate.js +6 -2
  170. package/lib/rules/no-useless-fragment.js +28 -2
  171. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  172. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  173. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  174. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  175. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  176. package/lib/rules/omit-index-html.d.ts +2 -1
  177. package/lib/rules/omit-index-html.js +62 -7
  178. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  179. package/lib/rules/optimize-object-boolean-conditions.js +236 -0
  180. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  181. package/lib/rules/parallelize-async-operations.js +394 -0
  182. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  183. package/lib/rules/prefer-batch-operations.js +228 -35
  184. package/lib/rules/prefer-block-comments-for-declarations.js +22 -7
  185. package/lib/rules/prefer-clone-deep.js +3 -3
  186. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  187. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  188. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  189. package/lib/rules/prefer-docsetter-setall.js +243 -0
  190. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  191. package/lib/rules/prefer-document-flattening.js +239 -0
  192. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  193. package/lib/rules/prefer-field-paths-in-transforms.js +251 -0
  194. package/lib/rules/prefer-fragment-component.js +4 -4
  195. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  196. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  197. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  198. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  199. package/lib/rules/prefer-global-router-state-key.js +305 -31
  200. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  201. package/lib/rules/prefer-memoized-props.js +445 -0
  202. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  203. package/lib/rules/prefer-next-dynamic.js +357 -0
  204. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  205. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +435 -0
  206. package/lib/rules/prefer-nullish-coalescing-override.d.ts +7 -0
  207. package/lib/rules/prefer-nullish-coalescing-override.js +188 -0
  208. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  209. package/lib/rules/prefer-params-over-parent-id.js +277 -0
  210. package/lib/rules/prefer-settings-object.js +23 -15
  211. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  212. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  213. package/lib/rules/prefer-type-over-interface.js +7 -2
  214. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  215. package/lib/rules/prefer-url-tostring-over-tojson.js +169 -0
  216. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  217. package/lib/rules/prefer-use-deep-compare-memo.js +431 -0
  218. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +25 -2
  219. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  220. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  221. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  222. package/lib/rules/prevent-children-clobber.js +536 -0
  223. package/lib/rules/react-memoize-literals.d.ts +4 -0
  224. package/lib/rules/react-memoize-literals.js +495 -0
  225. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  226. package/lib/rules/react-usememo-should-be-component.js +144 -78
  227. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  228. package/lib/rules/require-hooks-default-params.js +17 -16
  229. package/lib/rules/require-https-error-cause.d.ts +4 -0
  230. package/lib/rules/require-https-error-cause.js +136 -0
  231. package/lib/rules/require-https-error.js +43 -21
  232. package/lib/rules/require-image-optimized.js +2 -2
  233. package/lib/rules/require-memo.js +12 -2
  234. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  235. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  236. package/lib/rules/require-usememo-object-literals.js +9 -4
  237. package/lib/rules/semantic-function-prefixes.js +38 -18
  238. package/lib/rules/sync-onwrite-name-func.js +5 -1
  239. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  240. package/lib/rules/test-file-location-enforcement.js +57 -0
  241. package/lib/rules/use-custom-link.js +6 -1
  242. package/lib/rules/use-custom-router.js +6 -1
  243. package/lib/rules/use-latest-callback.d.ts +3 -0
  244. package/lib/rules/use-latest-callback.js +271 -0
  245. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  246. package/lib/rules/vertically-group-related-functions.js +480 -0
  247. package/lib/utils/ASTHelpers.d.ts +13 -1
  248. package/lib/utils/ASTHelpers.js +34 -0
  249. package/lib/utils/createRule.js +1 -1
  250. package/lib/utils/getMethodName.d.ts +27 -0
  251. package/lib/utils/getMethodName.js +35 -0
  252. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  253. package/lib/utils/ruleTester.d.ts +1 -0
  254. package/lib/utils/ruleTester.js +4 -1
  255. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  256. package/lib/utils/tsTypeClassifier.js +149 -0
  257. package/package.json +23 -14
  258. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
@@ -12,6 +12,7 @@ exports.ARRAY_METHODS = [
12
12
  ];
13
13
  exports.arrayMethodsThisContext = (0, createRule_1.createRule)({
14
14
  create(context) {
15
+ const sourceCode = context.sourceCode;
15
16
  return {
16
17
  CallExpression(node) {
17
18
  // Array method called with a class method reference
@@ -21,9 +22,23 @@ exports.arrayMethodsThisContext = (0, createRule_1.createRule)({
21
22
  node.arguments.length > 0 &&
22
23
  node.arguments[0].type === 'MemberExpression' &&
23
24
  node.arguments[0].object.type === 'ThisExpression') {
25
+ const methodProperty = node.arguments[0].property;
26
+ const methodAccessor = node.arguments[0].computed
27
+ ? `[${sourceCode.getText(methodProperty)}]`
28
+ : methodProperty.type === 'Identifier'
29
+ ? `.${methodProperty.name}`
30
+ : methodProperty.type === 'PrivateIdentifier'
31
+ ? `.#${methodProperty.name}`
32
+ : `.${sourceCode.getText(methodProperty)}`;
33
+ const methodReference = `this${methodAccessor}`;
24
34
  context.report({
25
35
  node: node.arguments[0],
26
36
  messageId: 'unexpected',
37
+ data: {
38
+ arrayMethod: node.callee.property.name,
39
+ methodAccessor,
40
+ methodReference,
41
+ },
27
42
  });
28
43
  }
29
44
  // Function expression bound to `this` in array method
@@ -41,6 +56,9 @@ exports.arrayMethodsThisContext = (0, createRule_1.createRule)({
41
56
  context.report({
42
57
  node: node.arguments[0],
43
58
  messageId: 'preferArrow',
59
+ data: {
60
+ arrayMethod: node.callee.property.name,
61
+ },
44
62
  });
45
63
  }
46
64
  },
@@ -55,8 +73,8 @@ exports.arrayMethodsThisContext = (0, createRule_1.createRule)({
55
73
  },
56
74
  schema: [],
57
75
  messages: {
58
- unexpected: 'Use an arrow function to preserve "this" context in array methods. Instead of `array.map(this.method)`, use `array.map((x) => this.method(x))`.',
59
- preferArrow: 'Use an arrow function instead of binding "this". Instead of `array.map(function(x) {}.bind(this))`, use `array.map((x) => {...})`.',
76
+ unexpected: 'Array method "{{arrayMethod}}" receives the class method reference {{methodReference}}, which strips its "this" binding when the callback runs. Use an arrow callback so the class instance remains available, e.g. {{arrayMethod}}((item) => this{{methodAccessor}}(item)).',
77
+ preferArrow: 'Array method "{{arrayMethod}}" binds a callback with ".bind(this)", which allocates a new function and hides that the code depends on the class instance. Prefer an arrow callback that captures "this" without rebinding, e.g. {{arrayMethod}}((item) => this.handleItem(item)).',
60
78
  },
61
79
  },
62
80
  defaultOptions: [],
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.avoidUtilsDirectory = void 0;
7
+ const path_1 = __importDefault(require("path"));
4
8
  const createRule_1 = require("../utils/createRule");
5
9
  exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
6
10
  name: 'avoid-utils-directory',
@@ -10,10 +14,9 @@ exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
10
14
  description: 'Enforce using util/ instead of utils/ directory',
11
15
  recommended: 'error',
12
16
  },
13
- fixable: 'code',
14
17
  schema: [],
15
18
  messages: {
16
- avoidUtils: 'Use "util/" directory instead of "utils/"',
19
+ avoidUtils: 'Path "{{path}}" lives under a "utils/" directory. Generic "utils" folders become grab bags where unrelated helpers accumulate, which makes imports unpredictable and hides ownership. Move this file into a focused "util/" folder (e.g., "util/date" or "util/string") so callers know where to find it and understand its responsibility.',
17
20
  },
18
21
  },
19
22
  defaultOptions: [],
@@ -21,6 +24,9 @@ exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
21
24
  return {
22
25
  Program(node) {
23
26
  const filename = context.getFilename();
27
+ const relativePath = path_1.default.isAbsolute(filename)
28
+ ? path_1.default.relative(process.cwd(), filename) || filename
29
+ : filename;
24
30
  // Skip files in node_modules
25
31
  if (filename.includes('node_modules')) {
26
32
  return;
@@ -31,6 +37,9 @@ exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
31
37
  context.report({
32
38
  node,
33
39
  messageId: 'avoidUtils',
40
+ data: {
41
+ path: relativePath,
42
+ },
34
43
  fix() {
35
44
  // We can't provide an auto-fix since directory renaming is beyond ESLint's scope
36
45
  return null;
@@ -15,23 +15,31 @@ exports.classMethodsReadTopToBottom = (0, createRule_1.createRule)({
15
15
  meta: {
16
16
  type: 'suggestion',
17
17
  docs: {
18
- description: 'Ensures classes read linearly from top to bottom.',
18
+ description: 'Enforces a top-to-bottom class layout so callers lead into the helpers they rely on.',
19
19
  recommended: 'warn',
20
20
  },
21
21
  schema: [],
22
22
  messages: {
23
- classMethodsReadTopToBottom: 'Methods should be ordered for top-down readability.',
23
+ classMethodsReadTopToBottom: [
24
+ "What's wrong: In {{className}}, {{actualMember}} appears before {{expectedMember}}.",
25
+ 'Why it matters: Top-down flow enables local reasoning: you can verify each caller without scrolling back. Upward jumps make code reviews harder (must verify call chains in reverse), obscure which fields a helper assumes are initialized, and increase the risk of calling helpers before state is ready (leading to null reference errors or accessing uninitialized fields).',
26
+ 'How to fix: Move {{expectedMember}} above {{actualMember}} so the class reads top-to-bottom (fields to constructor to callers to helpers).',
27
+ ].join('\n'),
24
28
  },
25
29
  fixable: 'code', // To allow ESLint to autofix issues.
26
30
  },
27
31
  defaultOptions: [],
28
32
  create(context) {
29
- let className;
33
+ const classNames = new WeakMap();
30
34
  return {
31
35
  ClassDeclaration(node) {
32
- className = node.id?.name || '';
36
+ classNames.set(node.body, node.id?.name || '');
37
+ },
38
+ ClassExpression(node) {
39
+ classNames.set(node.body, node.id?.name || '');
33
40
  },
34
41
  'ClassBody:exit'(node) {
42
+ const className = classNames.get(node) || '';
35
43
  const graphBuilder = new ClassGraphBuilder_1.ClassGraphBuilder(className, node);
36
44
  const sortedOrder = graphBuilder.memberNamesSorted;
37
45
  const actualOrder = node.body
@@ -51,7 +59,13 @@ exports.classMethodsReadTopToBottom = (0, createRule_1.createRule)({
51
59
  return; // Skip if there are actual duplicates
52
60
  }
53
61
  for (let i = 0; i < actualOrder.length; i++) {
54
- if (actualOrder[i] !== sortedOrder[i]) {
62
+ const actualMember = actualOrder[i];
63
+ const expectedMember = sortedOrder[i];
64
+ if (!actualMember || !expectedMember) {
65
+ throw new Error(`class-methods-read-top-to-bottom invariant violated while comparing members in ${className || 'an unnamed class'} at position ${i}: actualMember=${String(actualMember)}, expectedMember=${String(expectedMember)}, actualOrder.length=${actualOrder.length}, sortedOrder.length=${sortedOrder.length}`);
66
+ }
67
+ if (actualMember !== expectedMember) {
68
+ const classNameReport = className || 'this class';
55
69
  const sourceCode = context.getSourceCode();
56
70
  const newClassBody = sortedOrder
57
71
  .map((n) => {
@@ -75,6 +89,11 @@ exports.classMethodsReadTopToBottom = (0, createRule_1.createRule)({
75
89
  return context.report({
76
90
  node,
77
91
  messageId: 'classMethodsReadTopToBottom',
92
+ data: {
93
+ className: classNameReport,
94
+ actualMember,
95
+ expectedMember,
96
+ },
78
97
  fix(fixer) {
79
98
  return fixer.replaceTextRange([node.range[0] + 1, node.range[1] - 1], // Exclude the curly braces
80
99
  newClassBody);
@@ -210,7 +210,7 @@ module.exports = (0, createRule_1.createRule)({
210
210
  }
211
211
  }
212
212
  // Get references from global scope
213
- const sourceCode = context.getSourceCode();
213
+ const sourceCode = context.sourceCode;
214
214
  if (sourceCode.scopeManager?.globalScope) {
215
215
  const globalVar = sourceCode.scopeManager.globalScope.variables.find((v) => v.name === functionName);
216
216
  if (globalVar) {
@@ -1,2 +1,4 @@
1
1
  import { TSESLint } from '@typescript-eslint/utils';
2
- export declare const dynamicHttpsErrors: TSESLint.RuleModule<'dynamicHttpsErrors', never[]>;
2
+ type MessageIds = 'dynamicHttpsErrors' | 'missingThirdArgument';
3
+ export declare const dynamicHttpsErrors: TSESLint.RuleModule<MessageIds, never[]>;
4
+ export {};
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dynamicHttpsErrors = void 0;
4
4
  const createRule_1 = require("../utils/createRule");
5
+ const utils_1 = require("@typescript-eslint/utils");
5
6
  const isHttpsErrorCall = (callee) => {
6
7
  if (callee.type === 'MemberExpression') {
7
8
  return (callee.object.type === 'Identifier' &&
@@ -19,12 +20,13 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
19
20
  meta: {
20
21
  type: 'suggestion',
21
22
  docs: {
22
- description: 'Dynamic error details should only be in the third argument of the HttpsError constructor. The second argument is hashed to produce a unique id.',
23
+ description: 'Keep HttpsError messages static and move request-specific details to the third argument so error identifiers remain stable and debugging context is preserved.',
23
24
  recommended: 'error',
24
25
  },
25
26
  schema: [],
26
27
  messages: {
27
- dynamicHttpsErrors: 'Found dynamic error details in the second argument of the HttpsError constructor - the "message" field. This field is hashed to produce a unique id for error monitoring. Move any dynamic details to the third argument - the "details" field - to preserve the unique id and to monitor the error correctly.',
28
+ dynamicHttpsErrors: 'The HttpsError message (second argument) must stay static. Template expressions here change the hashed message and explode the number of error ids for the same failure. Keep this argument constant and move interpolated values into the third "details" argument so monitoring groups the error while still capturing request context.',
29
+ missingThirdArgument: 'HttpsError calls must include a third "details" argument. The message (second argument) is hashed into a stable identifier, so omitting details leaves errors hard to debug and encourages packing variables into the hashed message. Provide a third argument with the request-specific context (object or string) to keep identifiers stable and diagnostics useful.',
28
30
  },
29
31
  },
30
32
  defaultOptions: [],
@@ -32,23 +34,58 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
32
34
  const checkForHttpsError = (node) => {
33
35
  const callee = node.callee;
34
36
  if (isHttpsErrorCall(callee)) {
37
+ // Check for missing third argument
38
+ if (node.arguments.length < 3) {
39
+ context.report({
40
+ node,
41
+ messageId: 'missingThirdArgument',
42
+ });
43
+ }
44
+ // Check for dynamic content in second argument (existing functionality)
35
45
  const secondArg = node.arguments[1];
36
- if (secondArg && secondArg.type === 'TemplateLiteral') {
37
- if (secondArg.expressions.length > 0) {
38
- context.report({
39
- node: secondArg,
40
- messageId: 'dynamicHttpsErrors',
41
- });
42
- }
46
+ if (!secondArg)
47
+ return;
48
+ if (secondArg.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
49
+ secondArg.expressions.length > 0) {
50
+ context.report({
51
+ node: secondArg,
52
+ messageId: 'dynamicHttpsErrors',
53
+ });
54
+ return;
55
+ }
56
+ // Only string concatenation with "+" can be static; all other operators
57
+ // are treated as dynamic to avoid hashing non-literal message content.
58
+ const isDynamicBinaryExpression = (expression) => {
59
+ if (expression.operator !== '+')
60
+ return true;
61
+ const isStaticLiteral = (expr) => expr.type === utils_1.AST_NODE_TYPES.Literal &&
62
+ typeof expr.value === 'string';
63
+ const isSafe = (expr) => {
64
+ if (expr.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
65
+ return false;
66
+ }
67
+ if (expr.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
68
+ return !isDynamicBinaryExpression(expr);
69
+ }
70
+ return isStaticLiteral(expr);
71
+ };
72
+ return !(isSafe(expression.left) && isSafe(expression.right));
73
+ };
74
+ if (secondArg.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
75
+ isDynamicBinaryExpression(secondArg)) {
76
+ context.report({
77
+ node: secondArg,
78
+ messageId: 'dynamicHttpsErrors',
79
+ });
43
80
  }
44
81
  }
45
82
  };
46
83
  return {
47
84
  NewExpression(node) {
48
- return checkForHttpsError(node);
85
+ checkForHttpsError(node);
49
86
  },
50
87
  CallExpression(node) {
51
- return checkForHttpsError(node);
88
+ checkForHttpsError(node);
52
89
  },
53
90
  };
54
91
  },
@@ -0,0 +1,8 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type Options = [
3
+ {
4
+ readonly assertSafeImportPath?: string;
5
+ }
6
+ ];
7
+ export declare const enforceAssertSafeObjectKey: TSESLint.RuleModule<"useAssertSafe", Options, TSESLint.RuleListener>;
8
+ export {};
@@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enforceAssertSafeObjectKey = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const DEFAULT_IMPORT_PATH = 'functions/src/util/assertSafe';
6
7
  exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
7
- name: 'enforce-assertSafe-object-key',
8
+ name: 'enforce-assert-safe-object-key',
8
9
  meta: {
9
10
  type: 'problem',
10
11
  docs: {
@@ -12,21 +13,30 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
12
13
  recommended: 'error',
13
14
  },
14
15
  fixable: 'code',
15
- schema: [],
16
+ schema: [
17
+ {
18
+ type: 'object',
19
+ properties: {
20
+ assertSafeImportPath: { type: 'string' },
21
+ },
22
+ additionalProperties: false,
23
+ },
24
+ ],
16
25
  messages: {
17
- useAssertSafe: 'Use assertSafe() for object key access to ensure safe property access. Replace `obj[String(id)]` or `obj[`${id}`]` with `obj[assertSafe(id)]`.',
26
+ useAssertSafe: 'Dynamic object key "{{key}}" is used without assertSafe() validation. Unvalidated keys can resolve to unexpected properties (including prototype fields) and make lookups fragile or unsafe. Wrap the key with assertSafe({{key}}) before accessing the object.',
18
27
  },
19
28
  },
20
- defaultOptions: [],
21
- create(context) {
29
+ defaultOptions: [{}],
30
+ create(context, [options]) {
31
+ const importPath = options?.assertSafeImportPath || DEFAULT_IMPORT_PATH;
22
32
  let hasAssertSafeImport = false;
23
33
  /**
24
34
  * Helper function to add assertSafe import if needed
25
35
  */
26
36
  const addAssertSafeImport = (fixer) => {
27
- const program = context.getSourceCode().ast;
37
+ const program = context.sourceCode.ast;
28
38
  const firstImport = program.body.find((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
29
- const importStatement = "import { assertSafe } from 'functions/src/util/assertSafe';\n";
39
+ const importStatement = `import { assertSafe } from '${importPath}';\n`;
30
40
  if (firstImport) {
31
41
  return fixer.insertTextBefore(firstImport, importStatement);
32
42
  }
@@ -48,6 +58,14 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
48
58
  fixes.push(fixer.replaceText(node, `assertSafe(${argText})`));
49
59
  return fixes;
50
60
  };
61
+ const reportUseAssertSafe = (node, expressionText) => context.report({
62
+ node,
63
+ messageId: 'useAssertSafe',
64
+ data: { key: expressionText },
65
+ fix(fixer) {
66
+ return createFixes(fixer, node, expressionText);
67
+ },
68
+ });
51
69
  return {
52
70
  ImportDeclaration(node) {
53
71
  // Check if assertSafe is already imported
@@ -64,15 +82,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
64
82
  if (key.type === utils_1.AST_NODE_TYPES.CallExpression &&
65
83
  key.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
66
84
  key.callee.name === 'String') {
67
- context.report({
68
- node: key,
69
- messageId: 'useAssertSafe',
70
- fix(fixer) {
71
- const arg = key.arguments[0];
72
- const argText = context.getSourceCode().getText(arg);
73
- return createFixes(fixer, key, argText);
74
- },
75
- });
85
+ const arg = key.arguments[0];
86
+ const argText = context.sourceCode.getText(arg);
87
+ reportUseAssertSafe(key, argText);
76
88
  }
77
89
  // Check for template literals like `${id}`
78
90
  if (key.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
@@ -80,15 +92,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
80
92
  key.quasis.length === 2 &&
81
93
  key.quasis[0].value.raw === '' &&
82
94
  key.quasis[1].value.raw === '') {
83
- context.report({
84
- node: key,
85
- messageId: 'useAssertSafe',
86
- fix(fixer) {
87
- const expr = key.expressions[0];
88
- const exprText = context.getSourceCode().getText(expr);
89
- return createFixes(fixer, key, exprText);
90
- },
91
- });
95
+ const expr = key.expressions[0];
96
+ const exprText = context.sourceCode.getText(expr);
97
+ reportUseAssertSafe(key, exprText);
92
98
  }
93
99
  }
94
100
  },
@@ -100,15 +106,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
100
106
  if (left.type === utils_1.AST_NODE_TYPES.CallExpression &&
101
107
  left.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
102
108
  left.callee.name === 'String') {
103
- context.report({
104
- node: left,
105
- messageId: 'useAssertSafe',
106
- fix(fixer) {
107
- const arg = left.arguments[0];
108
- const argText = context.getSourceCode().getText(arg);
109
- return createFixes(fixer, left, argText);
110
- },
111
- });
109
+ const arg = left.arguments[0];
110
+ const argText = context.sourceCode.getText(arg);
111
+ reportUseAssertSafe(left, argText);
112
112
  }
113
113
  // Check for template literals like `${id}`
114
114
  if (left.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
@@ -116,15 +116,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
116
116
  left.quasis.length === 2 &&
117
117
  left.quasis[0].value.raw === '' &&
118
118
  left.quasis[1].value.raw === '') {
119
- context.report({
120
- node: left,
121
- messageId: 'useAssertSafe',
122
- fix(fixer) {
123
- const expr = left.expressions[0];
124
- const exprText = context.getSourceCode().getText(expr);
125
- return createFixes(fixer, left, exprText);
126
- },
127
- });
119
+ const expr = left.expressions[0];
120
+ const exprText = context.sourceCode.getText(expr);
121
+ reportUseAssertSafe(left, exprText);
128
122
  }
129
123
  }
130
124
  },
@@ -163,15 +157,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
163
157
  if (property.type === utils_1.AST_NODE_TYPES.CallExpression &&
164
158
  property.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
165
159
  property.callee.name === 'String') {
166
- context.report({
167
- node: property,
168
- messageId: 'useAssertSafe',
169
- fix(fixer) {
170
- const arg = property.arguments[0];
171
- const argText = context.getSourceCode().getText(arg);
172
- return createFixes(fixer, property, argText);
173
- },
174
- });
160
+ const arg = property.arguments[0];
161
+ const argText = context.sourceCode.getText(arg);
162
+ reportUseAssertSafe(property, argText);
175
163
  return;
176
164
  }
177
165
  // Check for template literals
@@ -190,16 +178,9 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
190
178
  // Complex template literals with additional text are fine
191
179
  return;
192
180
  }
193
- context.report({
194
- node: property,
195
- messageId: 'useAssertSafe',
196
- fix(fixer) {
197
- // Extract the expression from the template literal
198
- const expr = property.expressions[0];
199
- const exprText = context.getSourceCode().getText(expr);
200
- return createFixes(fixer, property, exprText);
201
- },
202
- });
181
+ const expr = property.expressions[0];
182
+ const exprText = context.sourceCode.getText(expr);
183
+ reportUseAssertSafe(property, exprText);
203
184
  return;
204
185
  }
205
186
  // Check for direct variable usage (identifiers)
@@ -212,15 +193,8 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
212
193
  if (isLikelyArray) {
213
194
  return;
214
195
  }
215
- context.report({
216
- node: property,
217
- messageId: 'useAssertSafe',
218
- fix(fixer) {
219
- // For direct variable use, just use the variable name
220
- const propText = context.getSourceCode().getText(property);
221
- return createFixes(fixer, property, propText);
222
- },
223
- });
196
+ const propText = context.sourceCode.getText(property);
197
+ reportUseAssertSafe(property, propText);
224
198
  return;
225
199
  }
226
200
  // Check for binary expressions (like index + 1)
@@ -229,14 +203,8 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
229
203
  if (isLikelyArray) {
230
204
  return;
231
205
  }
232
- context.report({
233
- node: property,
234
- messageId: 'useAssertSafe',
235
- fix(fixer) {
236
- const propText = context.getSourceCode().getText(property);
237
- return createFixes(fixer, property, propText);
238
- },
239
- });
206
+ const propText = context.sourceCode.getText(property);
207
+ reportUseAssertSafe(property, propText);
240
208
  return;
241
209
  }
242
210
  // Check for boolean expressions and other literals
@@ -247,14 +215,8 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
247
215
  if (isLikelyArray) {
248
216
  return;
249
217
  }
250
- context.report({
251
- node: property,
252
- messageId: 'useAssertSafe',
253
- fix(fixer) {
254
- const propText = context.getSourceCode().getText(property);
255
- return createFixes(fixer, property, propText);
256
- },
257
- });
218
+ const propText = context.sourceCode.getText(property);
219
+ reportUseAssertSafe(property, propText);
258
220
  return;
259
221
  }
260
222
  // Check for function calls (anything that isn't handled above)
@@ -266,14 +228,8 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
266
228
  if (isLikelyArray) {
267
229
  return;
268
230
  }
269
- context.report({
270
- node: property,
271
- messageId: 'useAssertSafe',
272
- fix(fixer) {
273
- const propText = context.getSourceCode().getText(property);
274
- return createFixes(fixer, property, propText);
275
- },
276
- });
231
+ const propText = context.sourceCode.getText(property);
232
+ reportUseAssertSafe(property, propText);
277
233
  return;
278
234
  }
279
235
  }
@@ -281,4 +237,4 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
281
237
  };
282
238
  },
283
239
  });
284
- //# sourceMappingURL=enforce-assertSafe-object-key.js.map
240
+ //# sourceMappingURL=enforce-assert-safe-object-key.js.map
@@ -1 +1,3 @@
1
- export declare const enforceAssertThrows: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"assertShouldThrow", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
1
+ type MessageIds = 'assertShouldThrow' | 'shouldBeAssertPrefixed';
2
+ export declare const enforceAssertThrows: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export {};