@blumintinc/eslint-plugin-blumint 1.12.5 → 1.13.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 (142) hide show
  1. package/README.md +140 -99
  2. package/lib/index.js +111 -20
  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/dynamic-https-errors.d.ts +3 -1
  6. package/lib/rules/dynamic-https-errors.js +48 -10
  7. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  8. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
  9. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  10. package/lib/rules/enforce-assert-throws.js +493 -51
  11. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  12. package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
  13. package/lib/rules/enforce-callable-types.js +4 -4
  14. package/lib/rules/enforce-callback-memo.js +143 -18
  15. package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
  16. package/lib/rules/enforce-console-error.d.ts +3 -0
  17. package/lib/rules/enforce-console-error.js +250 -0
  18. package/lib/rules/enforce-css-media-queries.js +24 -46
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  20. package/lib/rules/enforce-dynamic-imports.js +1 -1
  21. package/lib/rules/enforce-exported-function-types.js +3 -3
  22. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  23. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
  24. package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
  25. package/lib/rules/enforce-firestore-facade.js +347 -157
  26. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  27. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  28. package/lib/rules/enforce-global-constants.d.ts +4 -1
  29. package/lib/rules/enforce-global-constants.js +240 -12
  30. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  31. package/lib/rules/enforce-memoize-async.js +96 -27
  32. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  33. package/lib/rules/enforce-memoize-getters.js +123 -0
  34. package/lib/rules/enforce-microdiff.js +2 -1
  35. package/lib/rules/enforce-positive-naming.js +5 -7
  36. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  37. package/lib/rules/enforce-props-argument-name.js +165 -151
  38. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  39. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  40. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  41. package/lib/rules/enforce-querykey-ts.js +274 -0
  42. package/lib/rules/enforce-singular-type-names.js +9 -5
  43. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  44. package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
  45. package/lib/rules/enforce-verb-noun-naming.js +85 -10
  46. package/lib/rules/extract-global-constants.js +57 -45
  47. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
  49. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  50. package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
  51. package/lib/rules/generic-starts-with-t.js +8 -2
  52. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  53. package/lib/rules/memoize-root-level-hocs.js +242 -0
  54. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  55. package/lib/rules/no-array-length-in-deps.js +308 -0
  56. package/lib/rules/no-async-array-filter.js +5 -2
  57. package/lib/rules/no-complex-cloud-params.js +15 -2
  58. package/lib/rules/no-compositing-layer-props.js +4 -2
  59. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  60. package/lib/rules/no-entire-object-hook-deps.js +317 -63
  61. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  62. package/lib/rules/no-excessive-parent-chain.js +273 -0
  63. package/lib/rules/no-firestore-object-arrays.js +345 -30
  64. package/lib/rules/no-hungarian.js +13 -13
  65. package/lib/rules/no-margin-properties.d.ts +7 -1
  66. package/lib/rules/no-margin-properties.js +16 -3
  67. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  68. package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
  69. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  70. package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
  71. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  72. package/lib/rules/no-passthrough-getters.js +170 -0
  73. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  74. package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
  75. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  76. package/lib/rules/no-restricted-properties-fix.js +172 -0
  77. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  78. package/lib/rules/no-separate-loading-state.js +142 -0
  79. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  80. package/lib/rules/no-stale-state-across-await.js +185 -0
  81. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  82. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  83. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  84. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  85. package/lib/rules/no-undefined-null-passthrough.js +166 -19
  86. package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
  87. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  88. package/lib/rules/no-unused-props.d.ts +3 -0
  89. package/lib/rules/no-unused-props.js +213 -78
  90. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  91. package/lib/rules/optimize-object-boolean-conditions.js +234 -0
  92. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  93. package/lib/rules/parallelize-async-operations.js +375 -0
  94. package/lib/rules/prefer-batch-operations.js +199 -33
  95. package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
  96. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  97. package/lib/rules/prefer-document-flattening.js +239 -0
  98. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  99. package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
  100. package/lib/rules/prefer-fragment-component.js +3 -3
  101. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  102. package/lib/rules/prefer-global-router-state-key.js +230 -31
  103. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  104. package/lib/rules/prefer-next-dynamic.js +366 -0
  105. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  106. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
  107. package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
  108. package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
  109. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  110. package/lib/rules/prefer-params-over-parent-id.js +228 -0
  111. package/lib/rules/prefer-settings-object.js +23 -15
  112. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  113. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  114. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  115. package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
  116. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  117. package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
  118. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
  119. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  120. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  121. package/lib/rules/react-usememo-should-be-component.js +144 -78
  122. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  123. package/lib/rules/require-image-optimized.js +1 -1
  124. package/lib/rules/require-memo.js +11 -1
  125. package/lib/rules/require-usememo-object-literals.js +7 -1
  126. package/lib/rules/semantic-function-prefixes.js +38 -18
  127. package/lib/rules/sync-onwrite-name-func.js +5 -1
  128. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  129. package/lib/rules/test-file-location-enforcement.js +57 -0
  130. package/lib/rules/use-custom-link.js +6 -1
  131. package/lib/rules/use-custom-router.js +6 -1
  132. package/lib/rules/use-latest-callback.d.ts +3 -0
  133. package/lib/rules/use-latest-callback.js +270 -0
  134. package/lib/utils/ASTHelpers.js +10 -0
  135. package/lib/utils/createRule.js +1 -1
  136. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  137. package/lib/utils/ruleTester.d.ts +1 -0
  138. package/lib/utils/ruleTester.js +4 -1
  139. package/package.json +21 -14
  140. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
  141. package/lib/rules/no-restricted-imports-dynamic.d.ts +0 -25
  142. package/lib/rules/no-restricted-imports-dynamic.js +0 -213
@@ -0,0 +1,342 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noTryCatchAlreadyExistsInTransaction = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const ALREADY_EXISTS_STRINGS = new Set(['already-exists', 'ALREADY_EXISTS']);
7
+ const ALREADY_EXISTS_NUMBERS = new Set([6, '6']);
8
+ function unwrapChainExpression(expression) {
9
+ if (expression.type === utils_1.AST_NODE_TYPES.ChainExpression) {
10
+ return expression.expression;
11
+ }
12
+ return expression;
13
+ }
14
+ function isRunTransactionCall(node) {
15
+ const callee = unwrapChainExpression(node.callee);
16
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
17
+ return callee.name === 'runTransaction';
18
+ }
19
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
20
+ const property = callee.property;
21
+ return (!callee.computed &&
22
+ property.type === utils_1.AST_NODE_TYPES.Identifier &&
23
+ property.name === 'runTransaction');
24
+ }
25
+ return false;
26
+ }
27
+ function getCallbackArgument(args) {
28
+ for (const arg of args) {
29
+ if (arg.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
30
+ arg.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
31
+ return arg;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ function isErrorAliasExpression(expression, context) {
37
+ if (expression.type === utils_1.AST_NODE_TYPES.Identifier) {
38
+ return context.errorAliases.has(expression.name);
39
+ }
40
+ if (expression.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
41
+ expression.type === utils_1.AST_NODE_TYPES.TSTypeAssertion) {
42
+ return isErrorAliasExpression(expression.expression, context);
43
+ }
44
+ if (expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
45
+ return isErrorAliasExpression(expression.object, context);
46
+ }
47
+ if (expression.type === utils_1.AST_NODE_TYPES.ChainExpression) {
48
+ return isErrorAliasExpression(expression.expression, context);
49
+ }
50
+ return false;
51
+ }
52
+ function isCodeProperty(property) {
53
+ if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
54
+ return property.name === 'code';
55
+ }
56
+ if (property.type === utils_1.AST_NODE_TYPES.Literal) {
57
+ return property.value === 'code';
58
+ }
59
+ return false;
60
+ }
61
+ function isErrorCodeExpression(expression, context) {
62
+ if (!expression) {
63
+ return false;
64
+ }
65
+ if (expression.type === utils_1.AST_NODE_TYPES.Identifier) {
66
+ return context.codeAliases.has(expression.name);
67
+ }
68
+ const unwrapped = expression.type === utils_1.AST_NODE_TYPES.ChainExpression
69
+ ? expression.expression
70
+ : expression;
71
+ if (unwrapped.type === utils_1.AST_NODE_TYPES.MemberExpression) {
72
+ return (isCodeProperty(unwrapped.property) &&
73
+ isErrorAliasExpression(unwrapped.object, context));
74
+ }
75
+ return false;
76
+ }
77
+ function isAlreadyExistsLiteral(expression) {
78
+ if (!expression) {
79
+ return null;
80
+ }
81
+ if (expression.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
82
+ return null;
83
+ }
84
+ if (expression.type === utils_1.AST_NODE_TYPES.Literal) {
85
+ if (ALREADY_EXISTS_STRINGS.has(`${expression.value}`)) {
86
+ return `${expression.value}`;
87
+ }
88
+ if (ALREADY_EXISTS_NUMBERS.has(expression.value)) {
89
+ return `${expression.value}`;
90
+ }
91
+ }
92
+ if (expression.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
93
+ expression.expressions.length === 0 &&
94
+ expression.quasis.length === 1) {
95
+ const raw = expression.quasis[0].value.cooked;
96
+ if (raw && ALREADY_EXISTS_STRINGS.has(raw)) {
97
+ return raw;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ function isAlreadyExistsComparison(expression, context) {
103
+ if (expression.type !== utils_1.AST_NODE_TYPES.BinaryExpression) {
104
+ return null;
105
+ }
106
+ if (!['==', '==='].includes(expression.operator)) {
107
+ return null;
108
+ }
109
+ const leftLiteral = isAlreadyExistsLiteral(expression.left);
110
+ const rightLiteral = isAlreadyExistsLiteral(expression.right);
111
+ if (leftLiteral && isErrorCodeExpression(expression.right, context)) {
112
+ return leftLiteral;
113
+ }
114
+ if (rightLiteral && isErrorCodeExpression(expression.left, context)) {
115
+ return rightLiteral;
116
+ }
117
+ return null;
118
+ }
119
+ function addAliasesFromDeclarator(declarator, context) {
120
+ const init = declarator.init;
121
+ const id = declarator.id;
122
+ const initIsAliasSource = !!init &&
123
+ (isErrorAliasExpression(init, context) ||
124
+ (init.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
125
+ isErrorAliasExpression(init.right, context)));
126
+ if (id.type === utils_1.AST_NODE_TYPES.Identifier && initIsAliasSource) {
127
+ context.errorAliases.add(id.name);
128
+ }
129
+ if (id.type === utils_1.AST_NODE_TYPES.ObjectPattern && initIsAliasSource) {
130
+ for (const property of id.properties) {
131
+ if (property.type !== utils_1.AST_NODE_TYPES.Property) {
132
+ continue;
133
+ }
134
+ const value = property.value;
135
+ if (isCodeProperty(property.key) &&
136
+ value.type === utils_1.AST_NODE_TYPES.Identifier) {
137
+ context.codeAliases.add(value.name);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ function containsAlreadyExistsCheck(node, context) {
143
+ if (!node) {
144
+ return null;
145
+ }
146
+ switch (node.type) {
147
+ case utils_1.AST_NODE_TYPES.BlockStatement: {
148
+ for (const statement of node.body) {
149
+ const found = containsAlreadyExistsCheck(statement, context);
150
+ if (found)
151
+ return found;
152
+ }
153
+ return null;
154
+ }
155
+ case utils_1.AST_NODE_TYPES.ExpressionStatement:
156
+ return containsAlreadyExistsCheck(node.expression, context);
157
+ case utils_1.AST_NODE_TYPES.ReturnStatement:
158
+ return containsAlreadyExistsCheck(node.argument, context);
159
+ case utils_1.AST_NODE_TYPES.IfStatement: {
160
+ const testMatch = containsAlreadyExistsCheck(node.test, context);
161
+ if (testMatch)
162
+ return testMatch;
163
+ const consequentMatch = containsAlreadyExistsCheck(node.consequent, context);
164
+ if (consequentMatch)
165
+ return consequentMatch;
166
+ return containsAlreadyExistsCheck(node.alternate, context);
167
+ }
168
+ case utils_1.AST_NODE_TYPES.SwitchStatement: {
169
+ const discriminantIsCode = isErrorCodeExpression(node.discriminant, context);
170
+ for (const switchCase of node.cases) {
171
+ const caseLiteral = isAlreadyExistsLiteral(switchCase.test);
172
+ if (discriminantIsCode && caseLiteral) {
173
+ return caseLiteral;
174
+ }
175
+ const found = switchCase.consequent
176
+ .map((stmt) => containsAlreadyExistsCheck(stmt, context))
177
+ .find(Boolean);
178
+ if (found)
179
+ return found;
180
+ }
181
+ return null;
182
+ }
183
+ case utils_1.AST_NODE_TYPES.VariableDeclaration: {
184
+ for (const declarator of node.declarations) {
185
+ addAliasesFromDeclarator(declarator, context);
186
+ const found = containsAlreadyExistsCheck(declarator.init, context);
187
+ if (found)
188
+ return found;
189
+ }
190
+ return null;
191
+ }
192
+ case utils_1.AST_NODE_TYPES.VariableDeclarator:
193
+ addAliasesFromDeclarator(node, context);
194
+ return containsAlreadyExistsCheck(node.init, context);
195
+ case utils_1.AST_NODE_TYPES.AssignmentExpression:
196
+ if (node.left.type === utils_1.AST_NODE_TYPES.Identifier &&
197
+ isErrorAliasExpression(node.right, context)) {
198
+ context.errorAliases.add(node.left.name);
199
+ }
200
+ return containsAlreadyExistsCheck(node.right, context);
201
+ case utils_1.AST_NODE_TYPES.CallExpression:
202
+ case utils_1.AST_NODE_TYPES.NewExpression: {
203
+ const calleeMatch = containsAlreadyExistsCheck(node.callee, context);
204
+ if (calleeMatch)
205
+ return calleeMatch;
206
+ for (const arg of node.arguments) {
207
+ const found = containsAlreadyExistsCheck(arg, context);
208
+ if (found)
209
+ return found;
210
+ }
211
+ return null;
212
+ }
213
+ case utils_1.AST_NODE_TYPES.LogicalExpression: {
214
+ const leftMatch = containsAlreadyExistsCheck(node.left, context);
215
+ if (leftMatch)
216
+ return leftMatch;
217
+ return containsAlreadyExistsCheck(node.right, context);
218
+ }
219
+ case utils_1.AST_NODE_TYPES.BinaryExpression:
220
+ return (isAlreadyExistsComparison(node, context) ||
221
+ containsAlreadyExistsCheck(node.left, context) ||
222
+ containsAlreadyExistsCheck(node.right, context));
223
+ case utils_1.AST_NODE_TYPES.ConditionalExpression: {
224
+ const testMatch = containsAlreadyExistsCheck(node.test, context);
225
+ if (testMatch)
226
+ return testMatch;
227
+ const consequentMatch = containsAlreadyExistsCheck(node.consequent, context);
228
+ if (consequentMatch)
229
+ return consequentMatch;
230
+ return containsAlreadyExistsCheck(node.alternate, context);
231
+ }
232
+ case utils_1.AST_NODE_TYPES.MemberExpression:
233
+ return containsAlreadyExistsCheck(node.object, context);
234
+ case utils_1.AST_NODE_TYPES.ChainExpression:
235
+ return containsAlreadyExistsCheck(node.expression, context);
236
+ case utils_1.AST_NODE_TYPES.AwaitExpression:
237
+ case utils_1.AST_NODE_TYPES.UnaryExpression:
238
+ case utils_1.AST_NODE_TYPES.UpdateExpression:
239
+ return containsAlreadyExistsCheck(node.argument, context);
240
+ case utils_1.AST_NODE_TYPES.TemplateLiteral:
241
+ return null;
242
+ case utils_1.AST_NODE_TYPES.TryStatement: {
243
+ const blockMatch = containsAlreadyExistsCheck(node.block, context);
244
+ if (blockMatch)
245
+ return blockMatch;
246
+ const handlerMatch = containsAlreadyExistsCheck(node.handler?.body, context);
247
+ if (handlerMatch)
248
+ return handlerMatch;
249
+ return containsAlreadyExistsCheck(node.finalizer, context);
250
+ }
251
+ default:
252
+ return null;
253
+ }
254
+ }
255
+ function createCatchContext(handler) {
256
+ const errorAliases = new Set();
257
+ const codeAliases = new Set();
258
+ const param = handler.param;
259
+ if (param?.type === utils_1.AST_NODE_TYPES.Identifier) {
260
+ errorAliases.add(param.name);
261
+ }
262
+ else if (param?.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
263
+ for (const property of param.properties) {
264
+ if (property.type !== utils_1.AST_NODE_TYPES.Property) {
265
+ continue;
266
+ }
267
+ const value = property.value;
268
+ if (isCodeProperty(property.key) &&
269
+ value.type === utils_1.AST_NODE_TYPES.Identifier) {
270
+ codeAliases.add(value.name);
271
+ }
272
+ }
273
+ }
274
+ return { errorAliases, codeAliases };
275
+ }
276
+ exports.noTryCatchAlreadyExistsInTransaction = (0, createRule_1.createRule)({
277
+ name: 'no-try-catch-already-exists-in-transaction',
278
+ meta: {
279
+ type: 'problem',
280
+ docs: {
281
+ description: 'Disallow catching ALREADY_EXISTS errors inside Firestore transaction callbacks',
282
+ recommended: 'error',
283
+ },
284
+ schema: [],
285
+ messages: {
286
+ noAlreadyExistsCatchInTransaction: 'Do not catch ALREADY_EXISTS ({{codeLiteral}}) inside Firestore transaction callbacks. Firestore retries transaction bodies on contention, so this catch will re-run even though ALREADY_EXISTS is permanent. Move the try/catch outside the transaction or use runCreateForgivenessTransaction so the handler runs once.',
287
+ },
288
+ },
289
+ defaultOptions: [],
290
+ create(context) {
291
+ const transactionBodies = new Set();
292
+ function isInsideTransaction(node) {
293
+ let current = node;
294
+ while (current) {
295
+ if (transactionBodies.has(current)) {
296
+ return true;
297
+ }
298
+ current = current.parent;
299
+ }
300
+ return false;
301
+ }
302
+ return {
303
+ CallExpression(node) {
304
+ if (!isRunTransactionCall(node)) {
305
+ return;
306
+ }
307
+ const callback = getCallbackArgument(node.arguments);
308
+ if (callback && callback.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
309
+ transactionBodies.add(callback.body);
310
+ }
311
+ },
312
+ 'CallExpression:exit'(node) {
313
+ if (!isRunTransactionCall(node)) {
314
+ return;
315
+ }
316
+ const callback = getCallbackArgument(node.arguments);
317
+ if (callback && callback.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
318
+ transactionBodies.delete(callback.body);
319
+ }
320
+ },
321
+ TryStatement(node) {
322
+ if (!isInsideTransaction(node)) {
323
+ return;
324
+ }
325
+ const handler = node.handler;
326
+ if (!handler) {
327
+ return;
328
+ }
329
+ const catchContext = createCatchContext(handler);
330
+ const match = containsAlreadyExistsCheck(handler.body, catchContext);
331
+ if (match) {
332
+ context.report({
333
+ node: handler,
334
+ messageId: 'noAlreadyExistsCatchInTransaction',
335
+ data: { codeLiteral: match },
336
+ });
337
+ }
338
+ },
339
+ };
340
+ },
341
+ });
342
+ //# sourceMappingURL=no-try-catch-already-exists-in-transaction.js.map
@@ -6,8 +6,8 @@ exports.noUndefinedNullPassthrough = (0, createRule_1.createRule)({
6
6
  create(context) {
7
7
  return {
8
8
  FunctionDeclaration(node) {
9
- // Only apply to functions with exactly one parameter
10
- if (node.params.length !== 1) {
9
+ // Skip functions with no parameters
10
+ if (node.params.length === 0) {
11
11
  return;
12
12
  }
13
13
  // Skip React hooks (functions starting with 'use')
@@ -16,11 +16,18 @@ exports.noUndefinedNullPassthrough = (0, createRule_1.createRule)({
16
16
  node.id.name.startsWith('use')) {
17
17
  return;
18
18
  }
19
- checkFunctionBody(node.body, node.params[0], context);
19
+ // For functions with exactly one parameter, check if it's passing through null/undefined
20
+ if (node.params.length === 1) {
21
+ checkFunctionBody(node.body, node.params[0], context);
22
+ }
23
+ else {
24
+ // For functions with multiple parameters, check for early returns without arguments
25
+ checkFunctionBodyForEarlyReturns(node.body, node.params, context);
26
+ }
20
27
  },
21
28
  ArrowFunctionExpression(node) {
22
- // Only apply to arrow functions with exactly one parameter
23
- if (node.params.length !== 1) {
29
+ // Skip functions with no parameters
30
+ if (node.params.length === 0) {
24
31
  return;
25
32
  }
26
33
  // Skip if the function is part of a variable declaration that starts with 'use' (React hook)
@@ -33,16 +40,22 @@ exports.noUndefinedNullPassthrough = (0, createRule_1.createRule)({
33
40
  }
34
41
  // For arrow functions with block body
35
42
  if (node.body.type === 'BlockStatement') {
36
- checkFunctionBody(node.body, node.params[0], context);
43
+ if (node.params.length === 1) {
44
+ checkFunctionBody(node.body, node.params[0], context);
45
+ }
46
+ else {
47
+ // For functions with multiple parameters, check for early returns without arguments
48
+ checkFunctionBodyForEarlyReturns(node.body, node.params, context);
49
+ }
37
50
  }
38
- else {
39
- // For arrow functions with expression body (implicit return)
51
+ else if (node.params.length === 1) {
52
+ // For arrow functions with expression body (implicit return) and one parameter
40
53
  checkImplicitReturn(node, context);
41
54
  }
42
55
  },
43
56
  FunctionExpression(node) {
44
- // Only apply to functions with exactly one parameter
45
- if (node.params.length !== 1) {
57
+ // Skip functions with no parameters
58
+ if (node.params.length === 0) {
46
59
  return;
47
60
  }
48
61
  // Skip if the function is part of a variable declaration that starts with 'use' (React hook)
@@ -53,7 +66,14 @@ exports.noUndefinedNullPassthrough = (0, createRule_1.createRule)({
53
66
  parent.id.name.startsWith('use')) {
54
67
  return;
55
68
  }
56
- checkFunctionBody(node.body, node.params[0], context);
69
+ // For functions with exactly one parameter, check if it's passing through null/undefined
70
+ if (node.params.length === 1) {
71
+ checkFunctionBody(node.body, node.params[0], context);
72
+ }
73
+ else {
74
+ // For functions with multiple parameters, check for early returns without arguments
75
+ checkFunctionBodyForEarlyReturns(node.body, node.params, context);
76
+ }
57
77
  },
58
78
  };
59
79
  },
@@ -136,18 +156,81 @@ function checkFunctionBody(body, param, context) {
136
156
  function checkForTransformation(body, paramName) {
137
157
  if (!paramName)
138
158
  return false;
139
- // Look for return statements that call functions with the parameter
159
+ // Look for return statements or other statements that use the parameter in a transformation
140
160
  for (const statement of body.body) {
141
161
  if (statement.type === 'ReturnStatement' && statement.argument) {
142
- // Check for return transformData(data) pattern
143
- if (statement.argument.type === 'CallExpression' &&
144
- statement.argument.arguments.some(arg => arg.type === 'Identifier' && arg.name === paramName)) {
162
+ // Check for various transformation patterns
163
+ if (containsParameterTransformation(statement.argument, paramName)) {
164
+ return true;
165
+ }
166
+ }
167
+ // Check for other statements that might contain transformations (like for loops with yield)
168
+ if (statement.type === 'ForStatement' ||
169
+ statement.type === 'ForInStatement' ||
170
+ statement.type === 'ForOfStatement') {
171
+ if (containsTransformationInStatement(statement, paramName)) {
145
172
  return true;
146
173
  }
147
174
  }
148
175
  }
149
176
  return false;
150
177
  }
178
+ /**
179
+ * Check if a statement contains a transformation of the parameter
180
+ */
181
+ function containsTransformationInStatement(statement,
182
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
183
+ _paramName) {
184
+ // For generator functions, check if there are yield expressions with external function calls
185
+ if (statement.type === 'ForOfStatement' &&
186
+ statement.body.type === 'BlockStatement') {
187
+ for (const stmt of statement.body.body) {
188
+ if (stmt.type === 'ExpressionStatement' &&
189
+ stmt.expression.type === 'YieldExpression' &&
190
+ stmt.expression.argument &&
191
+ stmt.expression.argument.type === 'CallExpression' &&
192
+ stmt.expression.argument.callee.type === 'Identifier') {
193
+ return true;
194
+ }
195
+ }
196
+ }
197
+ return false;
198
+ }
199
+ /**
200
+ * Check if an expression contains a transformation of the parameter
201
+ * A transformation is considered to be calling external functions with the parameter,
202
+ * not just calling methods on the parameter itself.
203
+ */
204
+ function containsParameterTransformation(node, paramName) {
205
+ // Check for direct function calls with the parameter: transformData(data)
206
+ // This is considered a transformation because it's calling an external function
207
+ if (node.type === 'CallExpression' &&
208
+ node.callee.type === 'Identifier' && // External function call
209
+ node.arguments.some((arg) => arg.type === 'Identifier' && arg.name === paramName)) {
210
+ return true;
211
+ }
212
+ // Check for Object.* method calls: Object.keys(data), Object.values(data), Object.entries(data)
213
+ if (node.type === 'CallExpression' &&
214
+ node.callee.type === 'MemberExpression' &&
215
+ node.callee.object.type === 'Identifier' &&
216
+ node.callee.object.name === 'Object' &&
217
+ node.arguments.some((arg) => arg.type === 'Identifier' && arg.name === paramName)) {
218
+ return true;
219
+ }
220
+ // Check for chained operations that start with external functions: Object.entries(rounds).reduce(...).sort(...)
221
+ if (node.type === 'CallExpression' &&
222
+ node.callee.type === 'MemberExpression' &&
223
+ node.callee.object.type === 'CallExpression' &&
224
+ node.callee.object.callee.type === 'MemberExpression' &&
225
+ node.callee.object.callee.object.type === 'Identifier' &&
226
+ node.callee.object.callee.object.name === 'Object' &&
227
+ node.callee.object.arguments.some((arg) => arg.type === 'Identifier' && arg.name === paramName)) {
228
+ return true;
229
+ }
230
+ // Method calls on the parameter itself (like data.process(), items.filter())
231
+ // are NOT considered transformations for the purpose of this rule
232
+ return false;
233
+ }
151
234
  /**
152
235
  * Check arrow functions with expression bodies (implicit returns)
153
236
  */
@@ -205,8 +288,10 @@ function isNullUndefinedCheck(node, paramName) {
205
288
  }
206
289
  // Check for param === null, param === undefined, etc.
207
290
  if (node.type === 'BinaryExpression' &&
208
- (node.operator === '===' || node.operator === '==' ||
209
- node.operator === '!==' || node.operator === '!=')) {
291
+ (node.operator === '===' ||
292
+ node.operator === '==' ||
293
+ node.operator === '!==' ||
294
+ node.operator === '!=')) {
210
295
  const left = node.left;
211
296
  const right = node.right;
212
297
  // param === null/undefined
@@ -224,8 +309,7 @@ function isNullUndefinedCheck(node, paramName) {
224
309
  }
225
310
  }
226
311
  // Check for param === null || param === undefined
227
- if (node.type === 'LogicalExpression' &&
228
- node.operator === '||') {
312
+ if (node.type === 'LogicalExpression' && node.operator === '||') {
229
313
  return (isNullUndefinedCheck(node.left, paramName) ||
230
314
  isNullUndefinedCheck(node.right, paramName));
231
315
  }
@@ -251,4 +335,67 @@ function isParameterReference(node, paramName) {
251
335
  return false;
252
336
  return node.type === 'Identifier' && node.name === paramName;
253
337
  }
338
+ /**
339
+ * Check function body for early returns without arguments (implicit undefined)
340
+ * based on parameter null/undefined checks
341
+ */
342
+ function checkFunctionBodyForEarlyReturns(body, params, context) {
343
+ // Get parameter names
344
+ const paramNames = params
345
+ .map((param) => {
346
+ if (param.type === 'Identifier') {
347
+ return param.name;
348
+ }
349
+ else if (param.type === 'AssignmentPattern' &&
350
+ param.left.type === 'Identifier') {
351
+ return param.left.name;
352
+ }
353
+ return null;
354
+ })
355
+ .filter((name) => name !== null);
356
+ if (paramNames.length === 0)
357
+ return;
358
+ // Look for early returns without arguments based on parameter checks
359
+ for (const statement of body.body) {
360
+ if (statement.type === 'IfStatement') {
361
+ const test = statement.test;
362
+ // Check if the test is checking any parameter for null/undefined
363
+ const isParameterCheck = paramNames.some((paramName) => isNullUndefinedCheck(test, paramName));
364
+ if (isParameterCheck) {
365
+ // Check if the consequent is a block statement with a return
366
+ if (statement.consequent.type === 'BlockStatement') {
367
+ for (const consequentStmt of statement.consequent.body) {
368
+ if (consequentStmt.type === 'ReturnStatement' &&
369
+ (!consequentStmt.argument ||
370
+ isNullOrUndefinedLiteral(consequentStmt.argument))) {
371
+ // Check if there's a transformation in the function
372
+ const hasTransformation = paramNames.some((paramName) => checkForTransformation(body, paramName));
373
+ if (!hasTransformation) {
374
+ context.report({
375
+ node: statement,
376
+ messageId: 'unexpected',
377
+ });
378
+ }
379
+ return;
380
+ }
381
+ }
382
+ }
383
+ // Check if the consequent is a direct return statement without an argument
384
+ else if (statement.consequent.type === 'ReturnStatement' &&
385
+ (!statement.consequent.argument ||
386
+ isNullOrUndefinedLiteral(statement.consequent.argument))) {
387
+ // Check if there's a transformation in the function
388
+ const hasTransformation = paramNames.some((paramName) => checkForTransformation(body, paramName));
389
+ if (!hasTransformation) {
390
+ context.report({
391
+ node: statement,
392
+ messageId: 'unexpected',
393
+ });
394
+ }
395
+ return;
396
+ }
397
+ }
398
+ }
399
+ }
400
+ }
254
401
  //# sourceMappingURL=no-undefined-null-passthrough.js.map
@@ -15,8 +15,6 @@ const COMMON_PREPOSITION_SUFFIXES = new Set([
15
15
  'At',
16
16
  'Of',
17
17
  // Temporal prepositions
18
- 'After',
19
- 'Before',
20
18
  'During',
21
19
  'Until',
22
20
  'Till',
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noUnsafeFirestoreSpread = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const pathLabel = (path) => path || 'the merge payload';
6
7
  exports.noUnsafeFirestoreSpread = (0, createRule_1.createRule)({
7
8
  name: 'no-unsafe-firestore-spread',
8
9
  meta: {
@@ -14,8 +15,8 @@ exports.noUnsafeFirestoreSpread = (0, createRule_1.createRule)({
14
15
  fixable: 'code',
15
16
  schema: [],
16
17
  messages: {
17
- unsafeObjectSpread: 'Avoid using object spread in Firestore updates. Use dot notation with FieldPath instead.',
18
- unsafeArraySpread: 'Avoid using array spread in Firestore updates. Use FieldValue.arrayUnion() instead.',
18
+ unsafeObjectSpread: 'Firestore merge update spreads an object at "{{path}}" and rewrites that entire field. Spreading bypasses Firestore field-path merges and can drop sibling data. Update specific fields with dot notation or FieldPath instead.',
19
+ unsafeArraySpread: 'Firestore merge update spreads an array at "{{path}}" and writes a full array value. This bypasses arrayUnion/arrayRemove semantics and can lose concurrent changes. Use FieldValue.arrayUnion/arrayRemove (or the Web SDK equivalents) to add or remove items safely.',
19
20
  },
20
21
  },
21
22
  defaultOptions: [],
@@ -49,6 +50,7 @@ exports.noUnsafeFirestoreSpread = (0, createRule_1.createRule)({
49
50
  context.report({
50
51
  node: property,
51
52
  messageId: 'unsafeObjectSpread',
53
+ data: { path: pathLabel(parentPath) },
52
54
  fix: null,
53
55
  });
54
56
  }
@@ -61,14 +63,14 @@ exports.noUnsafeFirestoreSpread = (0, createRule_1.createRule)({
61
63
  checkObjectForSpreads(property.value, newPath);
62
64
  }
63
65
  else if (property.value.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
64
- checkArrayForSpreads(property.value);
66
+ checkArrayForSpreads(property.value, newPath);
65
67
  }
66
68
  else if (property.value.type === utils_1.AST_NODE_TYPES.CallExpression) {
67
69
  // Handle chained array methods like [...array].filter()
68
70
  let current = property.value;
69
71
  while (current) {
70
72
  if (current.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
71
- checkArrayForSpreads(current);
73
+ checkArrayForSpreads(current, newPath);
72
74
  break;
73
75
  }
74
76
  else if (current.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
@@ -88,13 +90,14 @@ exports.noUnsafeFirestoreSpread = (0, createRule_1.createRule)({
88
90
  }
89
91
  }
90
92
  }
91
- function checkArrayForSpreads(node) {
93
+ function checkArrayForSpreads(node, parentPath = '') {
92
94
  // Check for spreads in the array expression itself
93
95
  for (const element of node.elements) {
94
96
  if (element?.type === utils_1.AST_NODE_TYPES.SpreadElement) {
95
97
  context.report({
96
98
  node: element,
97
99
  messageId: 'unsafeArraySpread',
100
+ data: { path: pathLabel(parentPath) },
98
101
  fix: null,
99
102
  });
100
103
  }
@@ -3,4 +3,7 @@ export declare const noUnusedProps: import("@typescript-eslint/utils/dist/ts-esl
3
3
  TSTypeAliasDeclaration(node: TSESTree.TSTypeAliasDeclaration): void;
4
4
  VariableDeclaration(node: TSESTree.VariableDeclaration): void;
5
5
  'VariableDeclaration:exit'(node: never): void;
6
+ JSXElement(): void;
7
+ JSXFragment(): void;
8
+ 'Program:exit'(): void;
6
9
  }>;