@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
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noRestrictedPropertiesFix = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ const utils_1 = require("@typescript-eslint/utils");
6
+ /**
7
+ * This rule is a wrapper around the core ESLint no-restricted-properties rule
8
+ * that adds special handling for Object.keys() and Object.values() results.
9
+ * It prevents false positives when accessing standard array properties/methods
10
+ * on the arrays returned by Object.keys() and Object.values().
11
+ */
12
+ exports.noRestrictedPropertiesFix = (0, createRule_1.createRule)({
13
+ name: 'no-restricted-properties-fix',
14
+ meta: {
15
+ type: 'suggestion',
16
+ docs: {
17
+ description: 'Disallow certain properties on certain objects, with special handling for Object.keys() and Object.values()',
18
+ recommended: 'error',
19
+ },
20
+ fixable: 'code',
21
+ schema: [
22
+ {
23
+ type: 'array',
24
+ items: {
25
+ type: 'object',
26
+ properties: {
27
+ object: { type: 'string' },
28
+ property: { type: 'string' },
29
+ message: { type: 'string' },
30
+ allowObjects: {
31
+ type: 'array',
32
+ items: { type: 'string' },
33
+ },
34
+ },
35
+ additionalProperties: false,
36
+ },
37
+ },
38
+ ],
39
+ messages: {
40
+ restrictedProperty: 'Access to "{{objectName}}.{{propertyName}}" is restricted. {{restrictionReason}}Restricted properties often bypass safer APIs, hide side effects, or encourage patterns this codebase forbids. Use the allowed alternative from your rule configuration or remove this property access.',
41
+ },
42
+ },
43
+ defaultOptions: [[]],
44
+ create(context, [restrictedProperties]) {
45
+ if (!restrictedProperties || restrictedProperties.length === 0) {
46
+ return {};
47
+ }
48
+ const SAFE_ARRAY_PROPERTIES = new Set([
49
+ 'length',
50
+ 'sort',
51
+ 'filter',
52
+ 'map',
53
+ 'reduce',
54
+ 'forEach',
55
+ 'join',
56
+ 'slice',
57
+ 'concat',
58
+ ]);
59
+ /**
60
+ * Keeps the templated message readable by only adding a trailing space
61
+ * when a restriction reason is provided.
62
+ */
63
+ function formatRestrictionReason(message) {
64
+ return message ? `${message} ` : '';
65
+ }
66
+ /**
67
+ * Checks if the given node is a result of Object.keys() or Object.values()
68
+ * @param node The node to check
69
+ * @returns True if the node is a result of Object.keys() or Object.values()
70
+ */
71
+ function isObjectKeysOrValuesResult(node) {
72
+ if (node.type !== utils_1.AST_NODE_TYPES.CallExpression) {
73
+ return false;
74
+ }
75
+ const callee = node.callee;
76
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
77
+ return false;
78
+ }
79
+ if (callee.object.type !== utils_1.AST_NODE_TYPES.Identifier ||
80
+ callee.object.name !== 'Object') {
81
+ return false;
82
+ }
83
+ if (callee.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
84
+ (callee.property.name !== 'keys' && callee.property.name !== 'values')) {
85
+ return false;
86
+ }
87
+ return true;
88
+ }
89
+ return {
90
+ MemberExpression(node) {
91
+ // Skip if the object is a result of Object.keys() or Object.values()
92
+ if (isObjectKeysOrValuesResult(node.object)) {
93
+ if (node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
94
+ SAFE_ARRAY_PROPERTIES.has(node.property.name)) {
95
+ return;
96
+ }
97
+ }
98
+ // Apply the original rule logic
99
+ for (const restrictedProp of restrictedProperties) {
100
+ const objectMatches = restrictedProp.object &&
101
+ node.object.type === utils_1.AST_NODE_TYPES.Identifier &&
102
+ node.object.name === restrictedProp.object;
103
+ const propertyMatches = restrictedProp.property &&
104
+ ((node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
105
+ node.property.name === restrictedProp.property) ||
106
+ (node.property.type === utils_1.AST_NODE_TYPES.Literal &&
107
+ node.property.value === restrictedProp.property));
108
+ // If both object and property are restricted
109
+ if (restrictedProp.object &&
110
+ restrictedProp.property &&
111
+ objectMatches &&
112
+ propertyMatches) {
113
+ context.report({
114
+ node,
115
+ messageId: 'restrictedProperty',
116
+ data: {
117
+ objectName: restrictedProp.object,
118
+ propertyName: restrictedProp.property,
119
+ restrictionReason: formatRestrictionReason(restrictedProp.message),
120
+ },
121
+ fix: () => null,
122
+ });
123
+ }
124
+ // If only property is restricted (for any object)
125
+ else if (!restrictedProp.object &&
126
+ restrictedProp.property &&
127
+ propertyMatches) {
128
+ // Check if the object is in the allowObjects list
129
+ if (restrictedProp.allowObjects &&
130
+ node.object.type === utils_1.AST_NODE_TYPES.Identifier &&
131
+ restrictedProp.allowObjects.includes(node.object.name)) {
132
+ continue;
133
+ }
134
+ const objectName = node.object.type === utils_1.AST_NODE_TYPES.Identifier
135
+ ? node.object.name
136
+ : 'unknown';
137
+ context.report({
138
+ node,
139
+ messageId: 'restrictedProperty',
140
+ data: {
141
+ objectName,
142
+ propertyName: restrictedProp.property,
143
+ restrictionReason: formatRestrictionReason(restrictedProp.message),
144
+ },
145
+ fix: () => null,
146
+ });
147
+ }
148
+ // If only object is restricted (any property)
149
+ else if (restrictedProp.object &&
150
+ !restrictedProp.property &&
151
+ objectMatches) {
152
+ const propertyName = node.property.type === utils_1.AST_NODE_TYPES.Identifier
153
+ ? node.property.name
154
+ : node.property.type === utils_1.AST_NODE_TYPES.Literal
155
+ ? String(node.property.value)
156
+ : 'unknown';
157
+ context.report({
158
+ node,
159
+ messageId: 'restrictedProperty',
160
+ data: {
161
+ objectName: restrictedProp.object,
162
+ propertyName,
163
+ restrictionReason: formatRestrictionReason(restrictedProp.message),
164
+ },
165
+ fix: () => null,
166
+ });
167
+ }
168
+ }
169
+ },
170
+ };
171
+ },
172
+ });
173
+ //# sourceMappingURL=no-restricted-properties-fix.js.map
@@ -0,0 +1,8 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type Options = [
3
+ {
4
+ patterns?: string[];
5
+ }?
6
+ ];
7
+ export declare const noSeparateLoadingState: TSESLint.RuleModule<"separateLoadingState", Options, TSESLint.RuleListener>;
8
+ export {};
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noSeparateLoadingState = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const LOADING_PATTERNS = [
7
+ /^is.*Loading$/i,
8
+ /^isLoading.+/i, // isLoadingX pattern
9
+ ];
10
+ exports.noSeparateLoadingState = (0, createRule_1.createRule)({
11
+ name: 'no-separate-loading-state',
12
+ meta: {
13
+ type: 'suggestion',
14
+ docs: {
15
+ description: 'Disallow separate loading state variables that track the loading status of other state',
16
+ recommended: 'error',
17
+ },
18
+ fixable: undefined,
19
+ schema: [
20
+ {
21
+ type: 'object',
22
+ properties: {
23
+ patterns: {
24
+ type: 'array',
25
+ items: { type: 'string' },
26
+ },
27
+ },
28
+ additionalProperties: false,
29
+ },
30
+ ],
31
+ messages: {
32
+ separateLoadingState: 'Loading flag "{{stateName}}" splits the source of truth for data fetching. Boolean toggles drift from the actual data and add extra renders. Encode the loading phase inside the primary state instead (use a "loading" sentinel or discriminated union) so components read a single authoritative value.',
33
+ },
34
+ },
35
+ defaultOptions: [{}],
36
+ create(context, [options]) {
37
+ const effectivePatterns = options?.patterns?.map((p) => new RegExp(p, 'i')) ?? LOADING_PATTERNS;
38
+ const setterTrackers = [];
39
+ function isLoadingPattern(name) {
40
+ return effectivePatterns.some((pattern) => pattern.test(name));
41
+ }
42
+ function isUseStateCall(node) {
43
+ if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
44
+ node.callee.name === 'useState') {
45
+ return true;
46
+ }
47
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
48
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
49
+ node.callee.property.name === 'useState') {
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ function isTruthyValue(node) {
55
+ if (node.type === utils_1.AST_NODE_TYPES.Literal) {
56
+ return Boolean(node.value);
57
+ }
58
+ return false;
59
+ }
60
+ function isFalsyValue(node) {
61
+ if (node.type === utils_1.AST_NODE_TYPES.Literal) {
62
+ return !node.value;
63
+ }
64
+ return false;
65
+ }
66
+ return {
67
+ VariableDeclarator(node) {
68
+ // Check for useState destructuring patterns
69
+ if (node.id.type === utils_1.AST_NODE_TYPES.ArrayPattern &&
70
+ node.init?.type === utils_1.AST_NODE_TYPES.CallExpression &&
71
+ isUseStateCall(node.init)) {
72
+ const arrayPattern = node.id;
73
+ // Check if we have at least 2 elements (state and setter)
74
+ if (arrayPattern.elements.length >= 2) {
75
+ const stateElement = arrayPattern.elements[0];
76
+ const setterElement = arrayPattern.elements[1];
77
+ if (stateElement?.type === utils_1.AST_NODE_TYPES.Identifier &&
78
+ setterElement?.type === utils_1.AST_NODE_TYPES.Identifier &&
79
+ isLoadingPattern(stateElement.name)) {
80
+ const declared = context.getDeclaredVariables(node);
81
+ const setterVar = declared.find((v) => v.name === setterElement.name) ?? null;
82
+ if (!setterVar)
83
+ return;
84
+ setterTrackers.push({
85
+ declarator: node,
86
+ setterVar,
87
+ usage: { truthy: false, falsy: false },
88
+ stateName: stateElement.name,
89
+ });
90
+ }
91
+ }
92
+ }
93
+ },
94
+ CallExpression() {
95
+ // Setter usage is resolved via scope references in Program:exit
96
+ return;
97
+ },
98
+ 'Program:exit'() {
99
+ // Analyze collected data to determine violations
100
+ for (const tracker of setterTrackers) {
101
+ // Walk references of the exact setter variable to classify usages
102
+ for (const ref of tracker.setterVar.references) {
103
+ const parent = ref.identifier.parent;
104
+ if (parent &&
105
+ parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
106
+ parent.callee === ref.identifier &&
107
+ parent.arguments.length > 0) {
108
+ const argument = parent.arguments[0];
109
+ if (isTruthyValue(argument)) {
110
+ tracker.usage.truthy = true;
111
+ }
112
+ else if (isFalsyValue(argument)) {
113
+ tracker.usage.falsy = true;
114
+ }
115
+ }
116
+ }
117
+ const { declarator, usage, stateName } = tracker;
118
+ // If we have both truthy and falsy setter calls, it's likely a loading state pattern
119
+ if (usage.truthy && usage.falsy) {
120
+ context.report({
121
+ node: declarator,
122
+ messageId: 'separateLoadingState',
123
+ data: {
124
+ stateName,
125
+ },
126
+ });
127
+ }
128
+ }
129
+ },
130
+ };
131
+ },
132
+ });
133
+ //# sourceMappingURL=no-separate-loading-state.js.map
@@ -0,0 +1 @@
1
+ export declare const noStaleStateAcrossAwait: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"staleStateAcrossAwait", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noStaleStateAcrossAwait = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.noStaleStateAcrossAwait = (0, createRule_1.createRule)({
7
+ name: 'no-stale-state-across-await',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Prevent stale intermediate state by disallowing useState setter calls both before and after async boundaries (await, .then(), yield) within the same function',
12
+ recommended: 'error',
13
+ },
14
+ fixable: undefined,
15
+ schema: [],
16
+ messages: {
17
+ staleStateAcrossAwait: 'State setter "{{setterName}}" runs on both sides of {{boundaryType}}. Updates issued before the async boundary can resolve after later updates and overwrite fresher data, leaving the UI in a stale loading or placeholder state. Keep "{{setterName}}" updates on one side of the async boundary or consolidate into a single atomic update after the async work; if you intentionally use a sentinel value, document it with // eslint-disable-next-line @blumintinc/blumint/no-stale-state-across-await and a short comment explaining why.',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ return {
23
+ // Check functions for violations
24
+ 'FunctionDeclaration, FunctionExpression, ArrowFunctionExpression'(node) {
25
+ const sourceCode = context.sourceCode;
26
+ const scopeManager = sourceCode.scopeManager;
27
+ if (!scopeManager)
28
+ return;
29
+ const acquiredScope = scopeManager.acquire(node);
30
+ if (!acquiredScope)
31
+ return;
32
+ const scope = acquiredScope;
33
+ // Build local set of setter identifiers declared via useState in this scope
34
+ const localSetterNames = new Set();
35
+ let scopeCursor = scope;
36
+ while (scopeCursor) {
37
+ for (const variable of scopeCursor.variables) {
38
+ for (const def of variable.defs) {
39
+ if (def.type === 'Variable' &&
40
+ def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
41
+ def.node.id.type === utils_1.AST_NODE_TYPES.ArrayPattern &&
42
+ def.node.init?.type === utils_1.AST_NODE_TYPES.CallExpression &&
43
+ def.node.init.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
44
+ def.node.init.callee.name === 'useState') {
45
+ const arrayPattern = def.node.id;
46
+ if (arrayPattern.elements.length >= 2 &&
47
+ arrayPattern.elements[1]?.type === utils_1.AST_NODE_TYPES.Identifier) {
48
+ localSetterNames.add(arrayPattern.elements[1].name);
49
+ }
50
+ }
51
+ }
52
+ }
53
+ scopeCursor = scopeCursor.upper;
54
+ }
55
+ function isLocalSetter(identifier) {
56
+ return localSetterNames.has(identifier.name);
57
+ }
58
+ // Collect all setter calls and async boundaries in this function
59
+ const setterCalls = [];
60
+ const asyncBoundaries = [];
61
+ // Walk through the function body to find setter calls and async boundaries
62
+ function walkNode(n, skipNestedFunctions = true) {
63
+ if (n.type === utils_1.AST_NODE_TYPES.CallExpression) {
64
+ // Check for setter calls
65
+ if (n.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
66
+ isLocalSetter(n.callee)) {
67
+ setterCalls.push({
68
+ name: n.callee.name,
69
+ position: n.range[0],
70
+ });
71
+ }
72
+ // Check for .then()/.catch()/.finally() calls
73
+ if (n.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
74
+ n.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
75
+ (n.callee.property.name === 'then' ||
76
+ n.callee.property.name === 'catch' ||
77
+ n.callee.property.name === 'finally')) {
78
+ const methodName = n.callee.property.name;
79
+ if (methodName === 'then' || methodName === 'catch') {
80
+ asyncBoundaries.push({
81
+ position: n.range[0],
82
+ label: `a .${methodName}() callback`,
83
+ });
84
+ }
85
+ // Walk into callback arguments to find setter calls
86
+ if (n.arguments && n.arguments.length > 0) {
87
+ for (const arg of n.arguments) {
88
+ if (arg.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
89
+ arg.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
90
+ // Walk into the callback function body
91
+ walkNode(arg, false);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ if (n.type === utils_1.AST_NODE_TYPES.AwaitExpression) {
98
+ asyncBoundaries.push({
99
+ position: n.range[0],
100
+ label: 'an await boundary',
101
+ });
102
+ }
103
+ if (n.type === utils_1.AST_NODE_TYPES.YieldExpression) {
104
+ asyncBoundaries.push({
105
+ position: n.range[0],
106
+ label: 'a yield boundary',
107
+ });
108
+ }
109
+ if (n.type === utils_1.AST_NODE_TYPES.ForOfStatement &&
110
+ n.await === true) {
111
+ asyncBoundaries.push({
112
+ position: n.range[0],
113
+ label: 'a yield boundary',
114
+ });
115
+ }
116
+ // Recursively walk child nodes
117
+ for (const key in n) {
118
+ if (key === 'parent')
119
+ continue;
120
+ const value = n[key];
121
+ if (Array.isArray(value)) {
122
+ for (const item of value) {
123
+ if (item && typeof item === 'object' && item.type) {
124
+ // Skip nested functions unless we're explicitly walking into callbacks
125
+ if (skipNestedFunctions &&
126
+ (item.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
127
+ item.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
128
+ item.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
129
+ continue;
130
+ }
131
+ walkNode(item, skipNestedFunctions);
132
+ }
133
+ }
134
+ }
135
+ else if (value && typeof value === 'object' && value.type) {
136
+ // Skip nested functions unless we're explicitly walking into callbacks
137
+ if (skipNestedFunctions &&
138
+ (value.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
139
+ value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
140
+ value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
141
+ return;
142
+ }
143
+ walkNode(value, skipNestedFunctions);
144
+ }
145
+ }
146
+ }
147
+ // Walk the function body
148
+ if (node.body) {
149
+ walkNode(node.body);
150
+ }
151
+ // Check for violations
152
+ const setterCallsByName = new Map();
153
+ for (const call of setterCalls) {
154
+ if (!setterCallsByName.has(call.name)) {
155
+ setterCallsByName.set(call.name, []);
156
+ }
157
+ setterCallsByName.get(call.name).push(call);
158
+ }
159
+ // Check each setter for violations
160
+ for (const [setterName, calls] of setterCallsByName) {
161
+ if (calls.length < 2)
162
+ continue; // Need at least 2 calls to have a violation
163
+ // Check if there are calls both before and after any async boundary
164
+ for (const boundary of asyncBoundaries) {
165
+ const callsBefore = calls.filter((call) => call.position < boundary.position);
166
+ const callsAfter = calls.filter((call) => call.position > boundary.position);
167
+ if (callsBefore.length > 0 && callsAfter.length > 0) {
168
+ // Report violation on the function node
169
+ context.report({
170
+ node,
171
+ messageId: 'staleStateAcrossAwait',
172
+ data: {
173
+ setterName,
174
+ boundaryType: boundary.label,
175
+ },
176
+ });
177
+ break; // Only report once per setter
178
+ }
179
+ }
180
+ }
181
+ },
182
+ };
183
+ },
184
+ });
185
+ //# sourceMappingURL=no-stale-state-across-await.js.map
@@ -0,0 +1,3 @@
1
+ export declare const RULE_NAME = "no-static-constants-in-dynamic-files";
2
+ declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noStaticConstantInDynamicFile", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export default _default;
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.RULE_NAME = 'no-static-constants-in-dynamic-files';
7
+ const isDynamicFilename = (filename) => /\.dynamic\.tsx?$/.test(filename);
8
+ const isScreamingSnakeCase = (name) => /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/.test(name);
9
+ /**
10
+ * Records a leaf binding identifier so SCREAMING_SNAKE_CASE exports are checked.
11
+ */
12
+ const collectFromIdentifier = (identifier, identifiers) => {
13
+ identifiers.push(identifier);
14
+ };
15
+ /**
16
+ * Queues the rest element argument when it is a supported binding pattern so
17
+ * nested destructuring inside `...` is still inspected for exported constants.
18
+ */
19
+ const collectFromRestElement = (restElement, queue) => {
20
+ const argument = restElement.argument;
21
+ if (argument.type === utils_1.AST_NODE_TYPES.Identifier ||
22
+ argument.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
23
+ argument.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
24
+ queue.push(argument);
25
+ }
26
+ };
27
+ /**
28
+ * Walks object destructuring to capture identifiers and queue nested patterns
29
+ * (including defaults and rest spreads) so every bound name is inspected.
30
+ */
31
+ const collectFromObjectPattern = (pattern, identifiers, queue) => {
32
+ for (const property of pattern.properties) {
33
+ if (property.type === utils_1.AST_NODE_TYPES.Property) {
34
+ const value = property.value;
35
+ if (value.type === utils_1.AST_NODE_TYPES.Identifier) {
36
+ identifiers.push(value);
37
+ }
38
+ else if (value.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
39
+ queue.push(value.left);
40
+ }
41
+ else if (value.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
42
+ value.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
43
+ queue.push(value);
44
+ }
45
+ }
46
+ else if (property.type === utils_1.AST_NODE_TYPES.RestElement) {
47
+ collectFromRestElement(property, queue);
48
+ }
49
+ }
50
+ };
51
+ /**
52
+ * Walks array destructuring to collect identifiers and queue nested patterns
53
+ * so array defaults, nesting, and rest elements are not skipped.
54
+ */
55
+ const collectFromArrayPattern = (pattern, identifiers, queue) => {
56
+ for (const element of pattern.elements) {
57
+ if (!element) {
58
+ continue;
59
+ }
60
+ if (element.type === utils_1.AST_NODE_TYPES.Identifier) {
61
+ identifiers.push(element);
62
+ }
63
+ else if (element.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
64
+ queue.push(element.left);
65
+ }
66
+ else if (element.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
67
+ element.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
68
+ queue.push(element);
69
+ }
70
+ else if (element.type === utils_1.AST_NODE_TYPES.RestElement) {
71
+ collectFromRestElement(element, queue);
72
+ }
73
+ }
74
+ };
75
+ /**
76
+ * Traverses a binding pattern depth-first and returns every leaf Identifier
77
+ * so exported destructuring binds are all checked for SCREAMING_SNAKE_CASE.
78
+ */
79
+ const collectBindingIdentifiers = (pattern) => {
80
+ const identifiers = [];
81
+ const queue = [pattern];
82
+ while (queue.length > 0) {
83
+ const current = queue.pop();
84
+ if (!current) {
85
+ continue;
86
+ }
87
+ switch (current.type) {
88
+ case utils_1.AST_NODE_TYPES.Identifier:
89
+ collectFromIdentifier(current, identifiers);
90
+ break;
91
+ case utils_1.AST_NODE_TYPES.ObjectPattern:
92
+ collectFromObjectPattern(current, identifiers, queue);
93
+ break;
94
+ case utils_1.AST_NODE_TYPES.ArrayPattern:
95
+ collectFromArrayPattern(current, identifiers, queue);
96
+ break;
97
+ default:
98
+ break;
99
+ }
100
+ }
101
+ return identifiers;
102
+ };
103
+ exports.default = (0, createRule_1.createRule)({
104
+ name: exports.RULE_NAME,
105
+ meta: {
106
+ type: 'problem',
107
+ docs: {
108
+ description: 'Disallow exporting SCREAMING_SNAKE_CASE constants from .dynamic.ts/.dynamic.tsx files; move static constants to non-dynamic modules instead.',
109
+ recommended: 'error',
110
+ },
111
+ fixable: undefined,
112
+ schema: [],
113
+ messages: {
114
+ noStaticConstantInDynamicFile: 'Global constant "{{name}}" is exported from a .dynamic file. .dynamic files are reserved for runtime-only behavior; move this constant to a non-dynamic module (e.g., config.ts) and import it so static values do not live alongside dynamic code paths.',
115
+ },
116
+ },
117
+ defaultOptions: [],
118
+ create(context) {
119
+ const filename = context.filename ?? context.getFilename();
120
+ if (!isDynamicFilename(filename)) {
121
+ return {};
122
+ }
123
+ return {
124
+ ExportNamedDeclaration(node) {
125
+ const declaration = node.declaration;
126
+ if (!declaration ||
127
+ declaration.type !== utils_1.AST_NODE_TYPES.VariableDeclaration ||
128
+ declaration.kind !== 'const') {
129
+ return;
130
+ }
131
+ declaration.declarations.forEach((declarator) => {
132
+ const { id } = declarator;
133
+ const identifiers = collectBindingIdentifiers(id);
134
+ for (const identifier of identifiers) {
135
+ if (!isScreamingSnakeCase(identifier.name)) {
136
+ continue;
137
+ }
138
+ context.report({
139
+ node: identifier,
140
+ messageId: 'noStaticConstantInDynamicFile',
141
+ data: { name: identifier.name },
142
+ });
143
+ }
144
+ });
145
+ },
146
+ };
147
+ },
148
+ });
149
+ //# sourceMappingURL=no-static-constants-in-dynamic-files.js.map
@@ -0,0 +1 @@
1
+ export declare const noTryCatchAlreadyExistsInTransaction: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noAlreadyExistsCatchInTransaction", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;