@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
@@ -0,0 +1,527 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noConsoleError = void 0;
4
+ const minimatch_1 = require("minimatch");
5
+ const utils_1 = require("@typescript-eslint/utils");
6
+ const createRule_1 = require("../utils/createRule");
7
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
8
+ const defaultIgnorePatterns = [
9
+ '**/__tests__/**',
10
+ '**/__mocks__/**',
11
+ '**/__playwright__/**',
12
+ '**/scripts/**',
13
+ '**/electron/**',
14
+ '**/node_modules/**',
15
+ '**/dist/**',
16
+ '**/build/**',
17
+ '**/.next/**',
18
+ '**/coverage/**',
19
+ ];
20
+ const normalizeFilename = (filename) => filename.replace(/\\/g, '/');
21
+ const isUseAlertDialogImportPath = (importPath) => {
22
+ const normalizedImportPath = normalizeFilename(importPath);
23
+ if (normalizedImportPath === '../useAlertDialog' ||
24
+ normalizedImportPath === './useAlertDialog' ||
25
+ normalizedImportPath === 'useAlertDialog' ||
26
+ normalizedImportPath === '@/hooks/useAlertDialog' ||
27
+ normalizedImportPath === 'src/hooks/useAlertDialog') {
28
+ return true;
29
+ }
30
+ return (normalizedImportPath.endsWith('/useAlertDialog') ||
31
+ normalizedImportPath.endsWith('/useAlertDialog/index'));
32
+ };
33
+ const createFileIgnorePredicate = (options) => {
34
+ const ignorePatterns = [
35
+ ...defaultIgnorePatterns,
36
+ ...(options?.ignorePatterns ?? []),
37
+ ];
38
+ return (filename) => {
39
+ const normalizedFilename = normalizeFilename(filename);
40
+ return (normalizedFilename &&
41
+ !normalizedFilename.startsWith('<') &&
42
+ ignorePatterns.some((pattern) => (0, minimatch_1.minimatch)(normalizedFilename, pattern, { dot: true })));
43
+ };
44
+ };
45
+ const unwrapChainExpression = (node) => {
46
+ if (!node)
47
+ return null;
48
+ if (node.type === utils_1.AST_NODE_TYPES.ChainExpression) {
49
+ return node.expression;
50
+ }
51
+ return node;
52
+ };
53
+ const findVariable = (scope, name) => {
54
+ let current = scope;
55
+ while (current) {
56
+ const variable = current.variables.find((v) => v.name === name);
57
+ if (variable) {
58
+ return variable;
59
+ }
60
+ current = current.upper;
61
+ }
62
+ return null;
63
+ };
64
+ const getResolvedVariable = (scope, identifier) => {
65
+ const ref = scope.references.find((r) => r.identifier === identifier);
66
+ if (ref?.resolved) {
67
+ return ref.resolved;
68
+ }
69
+ // Fallback for cases where scope analysis might not have linked it yet or for shadowed built-ins
70
+ return findVariable(scope, identifier.name);
71
+ };
72
+ const getScopeForNode = (context, node) => {
73
+ try {
74
+ const sourceCode = context.getSourceCode();
75
+ const sourceCodeWithScope = sourceCode;
76
+ if (typeof sourceCodeWithScope.getScope === 'function') {
77
+ return sourceCodeWithScope.getScope(node) ?? context.getScope();
78
+ }
79
+ return context.getScope();
80
+ }
81
+ catch {
82
+ return context.getScope();
83
+ }
84
+ };
85
+ const getDeclaredVariablesForNode = (context, node) => {
86
+ return ASTHelpers_1.ASTHelpers.getDeclaredVariables(context, node);
87
+ };
88
+ const isErrorKey = (key, computed) => {
89
+ if (!computed && key.type === utils_1.AST_NODE_TYPES.Identifier) {
90
+ return key.name === 'error';
91
+ }
92
+ if (computed) {
93
+ if (key.type === utils_1.AST_NODE_TYPES.Literal && key.value === 'error') {
94
+ return true;
95
+ }
96
+ if (key.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
97
+ key.expressions.length === 0 &&
98
+ key.quasis.length === 1 &&
99
+ key.quasis[0].value.raw === 'error') {
100
+ return true;
101
+ }
102
+ }
103
+ return false;
104
+ };
105
+ const getLocalNameFromPattern = (target) => {
106
+ if (target.type === utils_1.AST_NODE_TYPES.Identifier) {
107
+ return target.name;
108
+ }
109
+ if (target.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
110
+ target.left.type === utils_1.AST_NODE_TYPES.Identifier) {
111
+ return target.left.name;
112
+ }
113
+ return null;
114
+ };
115
+ const findDeclaredVariableByName = (name, variables) => variables.find((variable) => variable.name === name);
116
+ /**
117
+ * Tracks aliases of `console` and `console.error` to detect indirect calls.
118
+ */
119
+ class AliasTracker {
120
+ constructor(context) {
121
+ this.context = context;
122
+ this.consoleAliases = new Set();
123
+ this.errorAliases = new Set();
124
+ }
125
+ getScope(node) {
126
+ return getScopeForNode(this.context, node);
127
+ }
128
+ markConsole(variable) {
129
+ if (!variable)
130
+ return;
131
+ this.consoleAliases.add(variable);
132
+ this.errorAliases.delete(variable);
133
+ }
134
+ markError(variable) {
135
+ if (!variable)
136
+ return;
137
+ this.errorAliases.add(variable);
138
+ this.consoleAliases.delete(variable);
139
+ }
140
+ untrack(variable) {
141
+ if (!variable)
142
+ return;
143
+ this.consoleAliases.delete(variable);
144
+ this.errorAliases.delete(variable);
145
+ }
146
+ isConsoleObject(expression, scope) {
147
+ const unwrapped = unwrapChainExpression(expression);
148
+ if (!unwrapped || unwrapped.type !== utils_1.AST_NODE_TYPES.Identifier) {
149
+ return false;
150
+ }
151
+ const variable = getResolvedVariable(scope, unwrapped);
152
+ if (variable) {
153
+ if (unwrapped.name === 'console' &&
154
+ variable.scope.type === 'global' &&
155
+ variable.defs.length === 0) {
156
+ return true;
157
+ }
158
+ return this.consoleAliases.has(variable);
159
+ }
160
+ return unwrapped.name === 'console';
161
+ }
162
+ isConsoleErrorMemberExpression(member, scope) {
163
+ return (this.isConsoleObject(member.object, scope) &&
164
+ isErrorKey(member.property, member.computed ?? false));
165
+ }
166
+ isErrorAlias(identifier, scope) {
167
+ const variable = getResolvedVariable(scope, identifier);
168
+ return Boolean(variable && this.errorAliases.has(variable));
169
+ }
170
+ isConsoleErrorCall(node, scope) {
171
+ const callee = unwrapChainExpression(node.callee);
172
+ if (!callee)
173
+ return false;
174
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
175
+ return this.isConsoleErrorMemberExpression(callee, scope);
176
+ }
177
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
178
+ return this.isErrorAlias(callee, scope);
179
+ }
180
+ return false;
181
+ }
182
+ isDirectGlobalConsoleErrorCall(node, scope) {
183
+ const callee = unwrapChainExpression(node.callee);
184
+ if (!callee || callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
185
+ return false;
186
+ }
187
+ if (callee.computed ||
188
+ callee.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
189
+ callee.property.name !== 'error') {
190
+ return false;
191
+ }
192
+ const obj = unwrapChainExpression(callee.object);
193
+ if (!obj ||
194
+ obj.type !== utils_1.AST_NODE_TYPES.Identifier ||
195
+ obj.name !== 'console') {
196
+ return false;
197
+ }
198
+ const variable = getResolvedVariable(scope, obj);
199
+ return !variable || variable.scope.type === 'global';
200
+ }
201
+ handleVariableDeclarator(node) {
202
+ if (!node.init)
203
+ return;
204
+ const scope = this.getScope(node);
205
+ const init = unwrapChainExpression(node.init);
206
+ const declaredVariables = getDeclaredVariablesForNode(this.context, node);
207
+ const getDeclaredVar = (name) => findDeclaredVariableByName(name, declaredVariables) ?? null;
208
+ if (node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
209
+ init &&
210
+ this.isConsoleObject(init, scope)) {
211
+ this.markConsole(getDeclaredVar(node.id.name));
212
+ return;
213
+ }
214
+ if (node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
215
+ init &&
216
+ init.type === utils_1.AST_NODE_TYPES.MemberExpression &&
217
+ this.isConsoleErrorMemberExpression(init, scope)) {
218
+ this.markError(getDeclaredVar(node.id.name));
219
+ return;
220
+ }
221
+ if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
222
+ init &&
223
+ this.isConsoleObject(init, scope)) {
224
+ for (const prop of node.id.properties) {
225
+ if (prop.type === utils_1.AST_NODE_TYPES.Property &&
226
+ isErrorKey(prop.key, prop.computed ?? false)) {
227
+ const localName = getLocalNameFromPattern(prop.value);
228
+ if (localName)
229
+ this.markError(getDeclaredVar(localName));
230
+ }
231
+ }
232
+ }
233
+ }
234
+ handleAssignmentExpression(node) {
235
+ const scope = this.getScope(node);
236
+ const right = unwrapChainExpression(node.right);
237
+ if (!right)
238
+ return;
239
+ if (node.left.type === utils_1.AST_NODE_TYPES.Identifier) {
240
+ const variable = getResolvedVariable(scope, node.left);
241
+ if (this.isConsoleObject(right, scope)) {
242
+ this.markConsole(variable);
243
+ }
244
+ else if (right.type === utils_1.AST_NODE_TYPES.MemberExpression &&
245
+ this.isConsoleErrorMemberExpression(right, scope)) {
246
+ this.markError(variable);
247
+ }
248
+ else {
249
+ this.untrack(variable);
250
+ }
251
+ return;
252
+ }
253
+ if (node.left.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
254
+ const errorProps = node.left.properties.filter((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
255
+ isErrorKey(prop.key, prop.computed ?? false));
256
+ const localNames = errorProps
257
+ .map((prop) => getLocalNameFromPattern(prop.value))
258
+ .filter((name) => Boolean(name));
259
+ const isConsole = this.isConsoleObject(right, scope);
260
+ for (const name of localNames) {
261
+ const v = findVariable(scope, name);
262
+ if (isConsole)
263
+ this.markError(v);
264
+ else
265
+ this.untrack(v);
266
+ }
267
+ }
268
+ }
269
+ }
270
+ /**
271
+ * Tracks `useAlertDialog` hook usage and its `open` function to allow
272
+ * `console.error` calls when an error dialog is being shown.
273
+ */
274
+ class UseAlertDialogTracker {
275
+ constructor(context) {
276
+ this.context = context;
277
+ this.hookVariables = new Set();
278
+ this.instanceVariables = new Set();
279
+ this.openVariables = new Set();
280
+ this.hasUseAlertDialogCall = false;
281
+ }
282
+ getScope(node) {
283
+ return getScopeForNode(this.context, node);
284
+ }
285
+ static hasErrorSeverity(node) {
286
+ for (const prop of node.properties) {
287
+ if (prop.type !== utils_1.AST_NODE_TYPES.Property)
288
+ continue;
289
+ const isSeverityProperty = (!prop.computed &&
290
+ prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
291
+ prop.key.name === 'severity') ||
292
+ (prop.computed &&
293
+ prop.key.type === utils_1.AST_NODE_TYPES.Literal &&
294
+ prop.key.value === 'severity');
295
+ if (!isSeverityProperty)
296
+ continue;
297
+ if (prop.value.type === utils_1.AST_NODE_TYPES.Literal &&
298
+ typeof prop.value.value === 'string') {
299
+ return prop.value.value === 'error';
300
+ }
301
+ // Non-literal severities cannot be verified at lint time, so treat them
302
+ // conservatively as non-error to prevent false negatives.
303
+ return false;
304
+ }
305
+ return false;
306
+ }
307
+ trackImport(node) {
308
+ const importPath = String(node.source.value);
309
+ if (!isUseAlertDialogImportPath(importPath))
310
+ return;
311
+ for (const specifier of node.specifiers) {
312
+ for (const v of getDeclaredVariablesForNode(this.context, specifier)) {
313
+ this.hookVariables.add(v);
314
+ }
315
+ }
316
+ }
317
+ trackVariableDeclarator(node) {
318
+ if (!node.init)
319
+ return;
320
+ const scope = this.getScope(node);
321
+ // const dialog = useAlertDialog(...) OR const { open } = useAlertDialog(...)
322
+ if (node.init.type === utils_1.AST_NODE_TYPES.CallExpression &&
323
+ this.isHookCall(node.init)) {
324
+ this.hasUseAlertDialogCall = true;
325
+ if (node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
326
+ const v = findDeclaredVariableByName(node.id.name, getDeclaredVariablesForNode(this.context, node));
327
+ if (v)
328
+ this.instanceVariables.add(v);
329
+ }
330
+ else if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
331
+ this.trackDestructuredOpen(node.id, node);
332
+ }
333
+ return;
334
+ }
335
+ // const { open } = dialog
336
+ if (node.init.type === utils_1.AST_NODE_TYPES.Identifier &&
337
+ node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
338
+ const initVar = getResolvedVariable(scope, node.init);
339
+ if (initVar && this.instanceVariables.has(initVar)) {
340
+ this.trackDestructuredOpen(node.id, node);
341
+ }
342
+ }
343
+ }
344
+ trackDestructuredOpen(pattern, node) {
345
+ const declaredVariables = getDeclaredVariablesForNode(this.context, node);
346
+ for (const prop of pattern.properties) {
347
+ if (prop.type === utils_1.AST_NODE_TYPES.Property &&
348
+ !prop.computed &&
349
+ prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
350
+ prop.key.name === 'open') {
351
+ const localName = getLocalNameFromPattern(prop.value);
352
+ if (localName) {
353
+ const v = findDeclaredVariableByName(localName, declaredVariables);
354
+ if (v)
355
+ this.openVariables.add(v);
356
+ }
357
+ }
358
+ }
359
+ }
360
+ isHookCall(node) {
361
+ const callee = unwrapChainExpression(node.callee);
362
+ if (!callee || callee.type !== utils_1.AST_NODE_TYPES.Identifier)
363
+ return false;
364
+ const v = getResolvedVariable(this.getScope(callee), callee);
365
+ if (v && this.hookVariables.has(v))
366
+ return true;
367
+ return ((v === null || v.scope.type === 'global') &&
368
+ callee.name === 'useAlertDialog');
369
+ }
370
+ isOpenCall(node) {
371
+ const callee = unwrapChainExpression(node.callee);
372
+ if (!callee)
373
+ return false;
374
+ const scope = this.getScope(node);
375
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
376
+ const v = getResolvedVariable(scope, callee);
377
+ return v !== null && this.openVariables.has(v);
378
+ }
379
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
380
+ if (callee.computed ||
381
+ callee.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
382
+ callee.property.name !== 'open') {
383
+ return false;
384
+ }
385
+ const obj = unwrapChainExpression(callee.object);
386
+ if (obj && obj.type === utils_1.AST_NODE_TYPES.Identifier) {
387
+ const v = getResolvedVariable(scope, obj);
388
+ return v !== null && this.instanceVariables.has(v);
389
+ }
390
+ }
391
+ return false;
392
+ }
393
+ }
394
+ /**
395
+ * Manages function scopes and pending `console.error` calls that may be
396
+ * allowed if a `useAlertDialog` call with error severity follows.
397
+ */
398
+ class PendingCallTracker {
399
+ constructor() {
400
+ this.functionScopeStack = [];
401
+ this.functionsWithErrorDialogOpen = new WeakSet();
402
+ this.pendingConsoleErrorCalls = new Map();
403
+ }
404
+ pushScope(node) {
405
+ this.functionScopeStack.push(node);
406
+ }
407
+ popScope() {
408
+ return this.functionScopeStack.pop() ?? null;
409
+ }
410
+ getCurrentScope() {
411
+ return this.functionScopeStack[this.functionScopeStack.length - 1] ?? null;
412
+ }
413
+ markFunctionWithErrorDialog(node) {
414
+ this.functionsWithErrorDialogOpen.add(node);
415
+ }
416
+ queueCall(scope, node) {
417
+ const pending = this.pendingConsoleErrorCalls.get(scope) ?? [];
418
+ pending.push(node);
419
+ this.pendingConsoleErrorCalls.set(scope, pending);
420
+ }
421
+ flushForScope(scope, reportFn, allowWithUseAlertDialog, hasUseAlertDialogCall) {
422
+ const pending = this.pendingConsoleErrorCalls.get(scope);
423
+ if (!pending)
424
+ return;
425
+ const shouldAllow = allowWithUseAlertDialog &&
426
+ hasUseAlertDialogCall &&
427
+ scope !== null &&
428
+ this.functionsWithErrorDialogOpen.has(scope);
429
+ if (!shouldAllow) {
430
+ for (const node of pending) {
431
+ reportFn(node);
432
+ }
433
+ }
434
+ this.pendingConsoleErrorCalls.delete(scope);
435
+ }
436
+ }
437
+ exports.noConsoleError = (0, createRule_1.createRule)({
438
+ name: 'no-console-error',
439
+ meta: {
440
+ type: 'problem',
441
+ docs: {
442
+ description: 'Disallow console.error so errors flow through structured handling (HttpsError/useErrorAlert on frontend, structured loggers on backend).',
443
+ recommended: 'warn',
444
+ },
445
+ schema: [
446
+ {
447
+ type: 'object',
448
+ properties: {
449
+ ignorePatterns: {
450
+ type: 'array',
451
+ items: { type: 'string' },
452
+ },
453
+ allowWithUseAlertDialog: { type: 'boolean' },
454
+ },
455
+ additionalProperties: false,
456
+ },
457
+ ],
458
+ messages: {
459
+ noConsoleError: 'console.error bypasses the structured error pipeline → failures are harder to monitor, debug, and surface consistently to users → use structured error handling instead (frontend: throw HttpsError or route through useErrorAlert; backend: log with a structured logger such as firebase-functions/v2 logger and propagate/convert the error so monitoring captures it).',
460
+ },
461
+ },
462
+ defaultOptions: [{}],
463
+ create(context, [options]) {
464
+ const shouldIgnoreFile = createFileIgnorePredicate(options);
465
+ const filename = context.filename ??
466
+ context.getFilename();
467
+ if (shouldIgnoreFile(filename))
468
+ return {};
469
+ const allowWithUseAlertDialog = options.allowWithUseAlertDialog === true;
470
+ const aliasTracker = new AliasTracker(context);
471
+ const dialogTracker = new UseAlertDialogTracker(context);
472
+ const pendingTracker = new PendingCallTracker();
473
+ const report = (node) => {
474
+ context.report({ node, messageId: 'noConsoleError' });
475
+ };
476
+ const onFunctionEnter = (node) => {
477
+ pendingTracker.pushScope(node);
478
+ };
479
+ const onFunctionExit = (node) => {
480
+ pendingTracker.flushForScope(node, report, allowWithUseAlertDialog, dialogTracker.hasUseAlertDialogCall);
481
+ pendingTracker.popScope();
482
+ };
483
+ return {
484
+ ImportDeclaration(node) {
485
+ dialogTracker.trackImport(node);
486
+ },
487
+ FunctionDeclaration: onFunctionEnter,
488
+ FunctionExpression: onFunctionEnter,
489
+ ArrowFunctionExpression: onFunctionEnter,
490
+ 'FunctionDeclaration:exit': onFunctionExit,
491
+ 'FunctionExpression:exit': onFunctionExit,
492
+ 'ArrowFunctionExpression:exit': onFunctionExit,
493
+ 'Program:exit'() {
494
+ pendingTracker.flushForScope(null, report, allowWithUseAlertDialog, dialogTracker.hasUseAlertDialogCall);
495
+ },
496
+ VariableDeclarator(node) {
497
+ dialogTracker.trackVariableDeclarator(node);
498
+ aliasTracker.handleVariableDeclarator(node);
499
+ },
500
+ AssignmentExpression(node) {
501
+ aliasTracker.handleAssignmentExpression(node);
502
+ },
503
+ CallExpression(node) {
504
+ if (dialogTracker.isOpenCall(node) &&
505
+ node.arguments[0]?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
506
+ UseAlertDialogTracker.hasErrorSeverity(node.arguments[0])) {
507
+ const scope = pendingTracker.getCurrentScope();
508
+ if (scope)
509
+ pendingTracker.markFunctionWithErrorDialog(scope);
510
+ }
511
+ const scope = getScopeForNode(context, node);
512
+ if (!aliasTracker.isConsoleErrorCall(node, scope))
513
+ return;
514
+ // Queueing is limited to direct global `console.error(...)` calls because deferral relies on
515
+ // linear control flow. Aliased or destructured references can escape the current scope
516
+ // (stored, passed, invoked later), so they are reported immediately to avoid missing non-local calls.
517
+ if (allowWithUseAlertDialog &&
518
+ aliasTracker.isDirectGlobalConsoleErrorCall(node, scope)) {
519
+ pendingTracker.queueCall(pendingTracker.getCurrentScope(), node);
520
+ return;
521
+ }
522
+ report(node);
523
+ },
524
+ };
525
+ },
526
+ });
527
+ //# sourceMappingURL=no-console-error.js.map
@@ -0,0 +1,2 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noCurlyBracketsAroundCommentedProperties: TSESLint.RuleModule<"removeCommentWrappedBlock", [], TSESLint.RuleListener>;