@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
@@ -34,6 +34,7 @@ const DEFAULT_OPTIONS = {
34
34
  prefixes: DEFAULT_BOOLEAN_PREFIXES,
35
35
  ignoreOverriddenGetters: false,
36
36
  };
37
+ const BOOLEANISH_BINARY_OPERATORS = new Set(['===', '!==', '==', '!=', '>', '<', '>=', '<=', 'in', 'instanceof']);
37
38
  exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
38
39
  name: 'enforce-boolean-naming-prefixes',
39
40
  meta: {
@@ -62,17 +63,16 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
62
63
  },
63
64
  ],
64
65
  messages: {
65
- missingBooleanPrefix: 'Boolean {{type}} "{{name}}" should start with a prefix like "is", "has", "can", or "should" (e.g., is{{capitalizedName}}, has{{capitalizedName}}). ' +
66
- 'Boolean prefixes make code self-documenting—readers immediately know the value is true/false without checking its declaration or type. ' +
67
- 'This improves readability at call sites: `if (user.{{name}})` is ambiguous, but `if (user.is{{capitalizedName}})` clearly signals a boolean check.',
66
+ missingBooleanPrefix: 'Boolean {{type}} "{{name}}" is missing a common approved boolean prefix ({{prefixes}}). ' +
67
+ 'Prefixes immediately communicate that the value is a true/false predicate; without one, checks like `if ({{name}})` read as generic truthiness guards and hide the boolean intent. ' +
68
+ 'Rename by prepending any approved prefix so the name becomes `<prefix>{{capitalizedName}}`, making the boolean contract obvious at call sites and API boundaries.',
68
69
  },
69
70
  },
70
71
  defaultOptions: [DEFAULT_OPTIONS],
71
72
  create(context, [options]) {
72
73
  const approvedPrefixes = options.prefixes || DEFAULT_OPTIONS.prefixes;
73
- const ignoreOverriddenGetters = options.ignoreOverriddenGetters ?? DEFAULT_OPTIONS.ignoreOverriddenGetters;
74
- const importStatusCache = new Map();
75
- const externalApiUsageCache = new Map();
74
+ const ignoreOverriddenGetters = options.ignoreOverriddenGetters ??
75
+ DEFAULT_OPTIONS.ignoreOverriddenGetters;
76
76
  function findVariableInScopes(name) {
77
77
  let currentScope = context.getScope();
78
78
  while (currentScope) {
@@ -127,8 +127,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
127
127
  ];
128
128
  return (hasApprovedPrefix(normalizedName, {
129
129
  treatLeadingUnderscoreAsApproved: false,
130
- }) ||
131
- suffixKeywords.some((keyword) => lowerName.endsWith(keyword)));
130
+ }) || suffixKeywords.some((keyword) => lowerName.endsWith(keyword)));
132
131
  }
133
132
  /**
134
133
  * Capitalize the first letter of a name for use in suggested alternatives
@@ -200,7 +199,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
200
199
  }
201
200
  // Check for logical expressions that typically return boolean
202
201
  if (node.init.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
203
- ['===', '!==', '==', '!=', '>', '<', '>=', '<=', 'in', 'instanceof'].includes(node.init.operator)) {
202
+ BOOLEANISH_BINARY_OPERATORS.has(node.init.operator)) {
204
203
  return true;
205
204
  }
206
205
  // Check for logical expressions (&&)
@@ -377,7 +376,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
377
376
  return true;
378
377
  }
379
378
  if (node.body.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
380
- ['===', '!==', '==', '!=', '>', '<', '>=', '<=', 'in', 'instanceof'].includes(node.body.operator)) {
379
+ BOOLEANISH_BINARY_OPERATORS.has(node.body.operator)) {
381
380
  return true;
382
381
  }
383
382
  if (node.body.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
@@ -434,9 +433,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
434
433
  const calleeName = callExpression.callee.name;
435
434
  const lowerCallee = calleeName.toLowerCase();
436
435
  if (lowerCallee.startsWith('assert')) {
437
- return identifierReturnsBoolean(calleeName)
438
- ? 'boolean'
439
- : 'unknown';
436
+ return identifierReturnsBoolean(calleeName) ? 'boolean' : 'unknown';
440
437
  }
441
438
  const matchesPrefix = approvedPrefixes.some((prefix) => prefix !== 'asserts' &&
442
439
  lowerCallee.startsWith(prefix.toLowerCase()));
@@ -516,18 +513,7 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
516
513
  return callExpressionLooksBoolean(currentExpression);
517
514
  }
518
515
  if (currentExpression.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
519
- if ([
520
- '===',
521
- '!==',
522
- '==',
523
- '!=',
524
- '>',
525
- '<',
526
- '>=',
527
- '<=',
528
- 'in',
529
- 'instanceof',
530
- ].includes(currentExpression.operator)) {
516
+ if (BOOLEANISH_BINARY_OPERATORS.has(currentExpression.operator)) {
531
517
  return 'boolean';
532
518
  }
533
519
  }
@@ -1084,215 +1070,6 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
1084
1070
  });
1085
1071
  }
1086
1072
  }
1087
- /**
1088
- * Check if an identifier is imported from an external module
1089
- */
1090
- function isImportedIdentifier(name) {
1091
- if (importStatusCache.has(name)) {
1092
- const cached = importStatusCache.get(name);
1093
- if (cached !== undefined)
1094
- return cached;
1095
- }
1096
- const variable = findVariableInScopes(name);
1097
- if (!variable) {
1098
- importStatusCache.set(name, false);
1099
- return false;
1100
- }
1101
- const isImport = variable.defs.some((def) => def.type === 'ImportBinding');
1102
- importStatusCache.set(name, isImport);
1103
- return isImport;
1104
- }
1105
- /**
1106
- * Check if a variable is used with an external API
1107
- */
1108
- function isVariableUsedWithExternalApi(variableName) {
1109
- if (externalApiUsageCache.has(variableName)) {
1110
- const cached = externalApiUsageCache.get(variableName);
1111
- if (cached !== undefined)
1112
- return cached;
1113
- }
1114
- const variable = findVariableInScopes(variableName);
1115
- if (!variable) {
1116
- externalApiUsageCache.set(variableName, false);
1117
- return false;
1118
- }
1119
- for (const reference of variable.references) {
1120
- if (reference.identifier === variable.identifiers[0])
1121
- continue;
1122
- const id = reference.identifier;
1123
- const markAndReturnTrue = () => {
1124
- externalApiUsageCache.set(variableName, true);
1125
- return true;
1126
- };
1127
- if (id.parent?.type === utils_1.AST_NODE_TYPES.Property &&
1128
- id.parent.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
1129
- id.parent.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
1130
- id.parent.parent.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
1131
- isImportedIdentifier(id.parent.parent.parent.callee.name)) {
1132
- return markAndReturnTrue();
1133
- }
1134
- if (id.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
1135
- if (id.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
1136
- isImportedIdentifier(id.parent.callee.name)) {
1137
- return markAndReturnTrue();
1138
- }
1139
- if (id.parent.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
1140
- id.parent.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
1141
- isImportedIdentifier(id.parent.callee.object.name)) {
1142
- return markAndReturnTrue();
1143
- }
1144
- }
1145
- if (id.parent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
1146
- id.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
1147
- id.parent.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXOpeningElement &&
1148
- id.parent.parent.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
1149
- isImportedIdentifier(id.parent.parent.parent.name.name)) {
1150
- return markAndReturnTrue();
1151
- }
1152
- if (id.parent?.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute &&
1153
- id.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXOpeningElement &&
1154
- id.parent.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
1155
- isImportedIdentifier(id.parent.parent.name.name)) {
1156
- return markAndReturnTrue();
1157
- }
1158
- }
1159
- externalApiUsageCache.set(variableName, false);
1160
- return false;
1161
- }
1162
- const HOOK_NAMES = new Set(['useMemo', 'useCallback', 'useState']);
1163
- function findVariableFromReactHook(objectExpr) {
1164
- let current = objectExpr;
1165
- while (current) {
1166
- if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
1167
- current = current.parent;
1168
- continue;
1169
- }
1170
- if (current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
1171
- current.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
1172
- current.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
1173
- HOOK_NAMES.has(current.parent.callee.name) &&
1174
- current.parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
1175
- current.parent.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
1176
- return current.parent.parent.id.name;
1177
- }
1178
- current = current.parent;
1179
- }
1180
- return undefined;
1181
- }
1182
- /**
1183
- * Check property definitions for boolean values
1184
- */
1185
- function checkProperty(node) {
1186
- if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
1187
- return;
1188
- const propertyName = node.key.name;
1189
- // Check if it's a boolean property
1190
- let isBooleanProperty = false;
1191
- // Check if it's initialized with a boolean value
1192
- if (node.value.type === utils_1.AST_NODE_TYPES.Literal &&
1193
- typeof node.value.value === 'boolean') {
1194
- isBooleanProperty = true;
1195
- }
1196
- // Check if it's a method that returns a boolean
1197
- if ((node.value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
1198
- node.value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) &&
1199
- node.value.returnType?.typeAnnotation.type ===
1200
- utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
1201
- isBooleanProperty = true;
1202
- }
1203
- // Skip checking if this property is part of an object literal passed to an external function
1204
- if (isBooleanProperty && !hasApprovedPrefix(propertyName)) {
1205
- // Special cases for common Node.js API boolean properties
1206
- if ((propertyName === 'recursive' || propertyName === 'keepAlive') &&
1207
- node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
1208
- node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
1209
- return; // Skip checking for these properties in object literals passed to functions
1210
- }
1211
- // Check if this property is in an object literal that's an argument to a function call
1212
- let isExternalApiCall = false;
1213
- // Navigate up to find if we're in an object expression that's an argument to a function call
1214
- if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
1215
- node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
1216
- const callExpression = node.parent.parent;
1217
- // Check if the function being called is an identifier (like mkdirSync, createServer, etc.)
1218
- if (callExpression.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
1219
- const calleeName = callExpression.callee.name;
1220
- if (isImportedIdentifier(calleeName)) {
1221
- isExternalApiCall = true;
1222
- }
1223
- }
1224
- // Also check for member expressions like fs.mkdirSync
1225
- if (callExpression.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
1226
- // For member expressions, check if the object is imported
1227
- const objectNode = callExpression.callee.object;
1228
- if (objectNode.type === utils_1.AST_NODE_TYPES.Identifier) {
1229
- const objectName = objectNode.name;
1230
- if (isImportedIdentifier(objectName)) {
1231
- isExternalApiCall = true;
1232
- }
1233
- }
1234
- }
1235
- }
1236
- // Check if this property is in an object literal that's being assigned to a variable
1237
- // This handles cases like const messageInputProps = { grow: true }
1238
- if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
1239
- node.parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
1240
- node.parent.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
1241
- const variableName = node.parent.parent.id.name;
1242
- if (isVariableUsedWithExternalApi(variableName)) {
1243
- isExternalApiCall = true;
1244
- }
1245
- }
1246
- // Special case for useMemo and other React hooks
1247
- // This handles cases like:
1248
- // 1. const messageInputProps = useMemo(() => ({ grow: true }), [])
1249
- // 2. const messageInputProps = useMemo(() => { return { grow: true }; }, [])
1250
- if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
1251
- const hookVariable = findVariableFromReactHook(node.parent);
1252
- if (hookVariable && isVariableUsedWithExternalApi(hookVariable)) {
1253
- isExternalApiCall = true;
1254
- }
1255
- }
1256
- // Check if this property is in an object literal that's directly passed to an imported function
1257
- // This handles cases like ExternalComponent({ grow: true })
1258
- if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
1259
- node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
1260
- node.parent.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
1261
- const calleeName = node.parent.parent.callee.name;
1262
- if (isImportedIdentifier(calleeName)) {
1263
- isExternalApiCall = true;
1264
- }
1265
- }
1266
- // Check if this property is in an object literal that's directly passed as a JSX attribute
1267
- // This handles cases like <ExternalComponent config={{ active: true }} />
1268
- if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
1269
- node.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
1270
- node.parent.parent.parent?.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
1271
- node.parent.parent.parent.parent?.type ===
1272
- utils_1.AST_NODE_TYPES.JSXOpeningElement) {
1273
- const jsxOpeningElement = node.parent.parent.parent.parent;
1274
- if (jsxOpeningElement.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
1275
- const componentName = jsxOpeningElement.name.name;
1276
- if (isImportedIdentifier(componentName)) {
1277
- isExternalApiCall = true;
1278
- }
1279
- }
1280
- }
1281
- // Only report if it's not an external API call
1282
- if (!isExternalApiCall) {
1283
- context.report({
1284
- node: node.key,
1285
- messageId: 'missingBooleanPrefix',
1286
- data: {
1287
- type: 'property',
1288
- name: propertyName,
1289
- capitalizedName: capitalizeFirst(propertyName),
1290
- prefixes: formatPrefixes(),
1291
- },
1292
- });
1293
- }
1294
- }
1295
- }
1296
1073
  /**
1297
1074
  * Check property signatures in interfaces/types for boolean types
1298
1075
  */
@@ -1402,7 +1179,6 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
1402
1179
  },
1403
1180
  MethodDefinition: checkMethodDefinition,
1404
1181
  TSAbstractMethodDefinition: checkMethodDefinition,
1405
- Property: checkProperty,
1406
1182
  ClassProperty: checkClassProperty,
1407
1183
  PropertyDefinition: checkClassPropertyDeclaration,
1408
1184
  TSPropertySignature: checkPropertySignature,
@@ -90,7 +90,7 @@ exports.default = (0, createRule_1.createRule)({
90
90
  return [];
91
91
  }
92
92
  function referencesParentScopeVariables(functionNode, parentParams) {
93
- const scopeManager = context.getSourceCode().scopeManager;
93
+ const scopeManager = context.sourceCode.scopeManager;
94
94
  if (!scopeManager)
95
95
  return false;
96
96
  const scope = scopeManager.acquire(functionNode);
@@ -140,7 +140,7 @@ exports.enforceCentralizedMockFirestore = (0, createRule_1.createRule)({
140
140
  },
141
141
  'Program:exit'() {
142
142
  if (mockFirestoreNodes.size > 0) {
143
- const sourceCode = context.getSourceCode();
143
+ const sourceCode = context.sourceCode;
144
144
  // Report only once for the entire file
145
145
  context.report({
146
146
  node: Array.from(mockFirestoreNodes)[0],
@@ -8,13 +8,13 @@ exports.enforceConsoleError = (0, createRule_1.createRule)({
8
8
  meta: {
9
9
  type: 'problem',
10
10
  docs: {
11
- description: 'Enforce proper logging for useAlertDialog based on severity. When severity is "error", console.error must be included. When severity is "warning", console.warn must be included. This ensures all user-facing errors and warnings are properly logged to monitoring systems.',
11
+ description: 'Enforce proper logging for useAlertDialog based on severity. When severity is "error", console.error must be included. When severity is "warning", console.warn must be included. This ensures all user-facing errors and warnings are properly logged to observability systems.',
12
12
  recommended: 'error',
13
13
  },
14
14
  messages: {
15
- missingConsoleError: 'useAlertDialog with severity "error" requires a console.error statement in the same function scope for proper monitoring.',
16
- missingConsoleWarn: 'useAlertDialog with severity "warning" requires a console.warn statement in the same function scope for proper monitoring.',
17
- missingConsoleBoth: 'useAlertDialog with dynamic severity requires both console.error and console.warn statements in the same function scope for proper monitoring.',
15
+ missingConsoleError: 'useAlertDialog call with severity "{{severity}}" shows an error dialog but this function never logs with {{consoleMethod}}. Without the log the alert is invisible to observability and post-incident breadcrumbs. Add a {{consoleMethod}} call in this function scope before or after the open() call so the dialog is paired with an observable error trail.',
16
+ missingConsoleWarn: 'useAlertDialog call with severity "{{severity}}" shows a warning dialog but this function never logs with {{consoleMethod}}. Without the log the warning is invisible to observability, making degraded states hard to diagnose. Add a {{consoleMethod}} call in this function scope before or after the open() call so the dialog is paired with an observable warning trail.',
17
+ missingConsoleBoth: 'useAlertDialog call uses dynamic severity, so runtime may render an error or a warning dialog. It is missing {{missingMethods}} in this function scope, leaving one or more runtime severity branches (error or warning) without observability breadcrumbs: {{missingPaths}}. Branch on the severity value and add the matching console method before or after the open() call (console.error for error branches, console.warn for warning branches), as in the conditional pattern shown in the rule docs, so each branch leaves a single appropriate log trail instead of double-logging both methods.',
18
18
  },
19
19
  schema: [],
20
20
  },
@@ -145,13 +145,33 @@ exports.enforceConsoleError = (0, createRule_1.createRule)({
145
145
  const needsConsoleError = hasError || hasDynamic;
146
146
  const needsConsoleWarn = hasWarning || hasDynamic;
147
147
  if (hasDynamic && (!hasConsoleError || !hasConsoleWarn)) {
148
- const dynamicCall = group.openCalls.find((call) => call.severity === 'dynamic');
149
- if (dynamicCall) {
148
+ const missingMethods = [];
149
+ const missingSeverities = [];
150
+ if (!hasConsoleError) {
151
+ missingMethods.push('console.error');
152
+ missingSeverities.push('error');
153
+ }
154
+ if (!hasConsoleWarn) {
155
+ missingMethods.push('console.warn');
156
+ missingSeverities.push('warning');
157
+ }
158
+ const missingMethodsLabel = missingMethods.length === 2
159
+ ? 'console.error and console.warn'
160
+ : missingMethods[0];
161
+ const missingPathsLabel = missingSeverities.length === 2
162
+ ? 'the error and warning severity paths'
163
+ : `the ${missingSeverities[0]} severity path`;
164
+ const dynamicCalls = group.openCalls.filter((call) => call.severity === 'dynamic');
165
+ dynamicCalls.forEach((dynamicCall) => {
150
166
  context.report({
151
167
  node: dynamicCall.node,
152
168
  messageId: 'missingConsoleBoth',
169
+ data: {
170
+ missingMethods: missingMethodsLabel,
171
+ missingPaths: missingPathsLabel,
172
+ },
153
173
  });
154
- }
174
+ });
155
175
  }
156
176
  else {
157
177
  if (needsConsoleError && !hasConsoleError) {
@@ -160,6 +180,10 @@ exports.enforceConsoleError = (0, createRule_1.createRule)({
160
180
  context.report({
161
181
  node: errorCall.node,
162
182
  messageId: 'missingConsoleError',
183
+ data: {
184
+ severity: 'error',
185
+ consoleMethod: 'console.error',
186
+ },
163
187
  });
164
188
  }
165
189
  }
@@ -169,6 +193,10 @@ exports.enforceConsoleError = (0, createRule_1.createRule)({
169
193
  context.report({
170
194
  node: warningCall.node,
171
195
  messageId: 'missingConsoleWarn',
196
+ data: {
197
+ severity: 'warning',
198
+ consoleMethod: 'console.warn',
199
+ },
172
200
  });
173
201
  }
174
202
  }
@@ -0,0 +1 @@
1
+ export declare const enforceDateTTime: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"enforceDateTTime", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.enforceDateTTime = void 0;
27
+ const utils_1 = require("@typescript-eslint/utils");
28
+ const ts = __importStar(require("typescript"));
29
+ const createRule_1 = require("../utils/createRule");
30
+ exports.enforceDateTTime = (0, createRule_1.createRule)({
31
+ name: 'enforce-date-ttime',
32
+ meta: {
33
+ type: 'suggestion',
34
+ docs: {
35
+ description: 'Enforce that any generic type parameter named TTime is explicitly set to Date in frontend code',
36
+ recommended: 'error',
37
+ },
38
+ fixable: 'code',
39
+ schema: [],
40
+ messages: {
41
+ enforceDateTTime: '{{typeName}} leaves TTime unspecified or not Date → Firestore Frontend Hooks convert Timestamp to Date on the client, so non-Date TTime misrepresents runtime values and forces defensive conversions → Set the TTime argument to Date explicitly (e.g., {{typeName}}<..., Date, ...>).',
42
+ },
43
+ },
44
+ defaultOptions: [],
45
+ create(context) {
46
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
47
+ const parserServices = sourceCode?.parserServices ?? context.parserServices;
48
+ const checker = parserServices?.program?.getTypeChecker();
49
+ if (!parserServices || !checker) {
50
+ return {};
51
+ }
52
+ const tTimeIndexCache = new WeakMap();
53
+ /**
54
+ * Finds the index of the TTime parameter in the given symbol's declarations.
55
+ */
56
+ function findTTimeParameterIndex(symbol) {
57
+ if (tTimeIndexCache.has(symbol)) {
58
+ return tTimeIndexCache.get(symbol);
59
+ }
60
+ const declarations = symbol.getDeclarations();
61
+ if (!declarations) {
62
+ tTimeIndexCache.set(symbol, undefined);
63
+ return undefined;
64
+ }
65
+ for (const declaration of declarations) {
66
+ if (ts.isTypeAliasDeclaration(declaration) ||
67
+ ts.isInterfaceDeclaration(declaration) ||
68
+ ts.isClassDeclaration(declaration)) {
69
+ if (declaration.typeParameters) {
70
+ const index = declaration.typeParameters.findIndex((tp) => tp.name.text === 'TTime');
71
+ if (index !== -1) {
72
+ tTimeIndexCache.set(symbol, index);
73
+ return index;
74
+ }
75
+ }
76
+ }
77
+ }
78
+ tTimeIndexCache.set(symbol, undefined);
79
+ return undefined;
80
+ }
81
+ /**
82
+ * Checks if a type node is exactly "Date".
83
+ */
84
+ function isExactDate(node) {
85
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
86
+ node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
87
+ node.typeName.name === 'Date' &&
88
+ (!node.typeParameters || node.typeParameters.params.length === 0)) {
89
+ return true;
90
+ }
91
+ return false;
92
+ }
93
+ return {
94
+ TSTypeReference(node) {
95
+ const typeNameNode = node.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName
96
+ ? node.typeName.right
97
+ : node.typeName;
98
+ if (typeNameNode.type !== utils_1.AST_NODE_TYPES.Identifier)
99
+ return;
100
+ // Skip the "Date" type itself
101
+ if (typeNameNode.name === 'Date')
102
+ return;
103
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
104
+ const symbol = checker.getSymbolAtLocation(tsNode.typeName);
105
+ if (!symbol)
106
+ return;
107
+ const resolvedSymbol = symbol.flags & ts.SymbolFlags.Alias
108
+ ? checker.getAliasedSymbol(symbol)
109
+ : symbol;
110
+ const tTimeIndex = findTTimeParameterIndex(resolvedSymbol);
111
+ if (tTimeIndex === undefined)
112
+ return;
113
+ const typeArgs = node.typeParameters?.params || [];
114
+ const tTimeArg = typeArgs[tTimeIndex];
115
+ const typeName = sourceCode.getText(node.typeName);
116
+ if (!tTimeArg) {
117
+ // TTime is omitted
118
+ context.report({
119
+ node,
120
+ messageId: 'enforceDateTTime',
121
+ data: { typeName },
122
+ fix(fixer) {
123
+ if (node.typeParameters) {
124
+ // Already has type parameters, but not enough to cover TTime
125
+ const lastParam = node.typeParameters.params[node.typeParameters.params.length - 1];
126
+ // Check if we can just append ", Date"
127
+ // We can only do this safely if all parameters between existing ones and TTime have defaults.
128
+ // For simplicity and safety, we only fix if tTimeIndex is the next one or if it's already provided.
129
+ if (tTimeIndex === node.typeParameters.params.length) {
130
+ return fixer.insertTextAfter(lastParam, ', Date');
131
+ }
132
+ }
133
+ else if (tTimeIndex === 0) {
134
+ // No type parameters and TTime is the first one
135
+ return fixer.insertTextAfter(node.typeName, '<Date>');
136
+ }
137
+ return null;
138
+ },
139
+ });
140
+ }
141
+ else if (!isExactDate(tTimeArg)) {
142
+ // TTime is provided but is not exactly Date
143
+ context.report({
144
+ node: tTimeArg,
145
+ messageId: 'enforceDateTTime',
146
+ data: { typeName },
147
+ fix(fixer) {
148
+ return fixer.replaceText(tTimeArg, 'Date');
149
+ },
150
+ });
151
+ }
152
+ },
153
+ };
154
+ },
155
+ });
156
+ //# sourceMappingURL=enforce-date-ttime.js.map
@@ -1,3 +1,5 @@
1
1
  export declare const RULE_NAME = "enforce-dynamic-file-naming";
2
+ export declare const REQUIRE_DYNAMIC_FIREBASE_IMPORTS_RULE = "@blumintinc/blumint/require-dynamic-firebase-imports";
3
+ export declare const DYNAMIC_RULES_LABEL: string;
2
4
  declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"requireDynamicExtension" | "requireDisableDirective", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
5
  export default _default;