@blumintinc/eslint-plugin-blumint 1.12.6 → 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.
- package/README.md +140 -99
- package/lib/index.js +111 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -10
- package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
- package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
- package/lib/rules/enforce-assert-throws.d.ts +3 -1
- package/lib/rules/enforce-assert-throws.js +493 -51
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
- package/lib/rules/enforce-callable-types.js +4 -4
- package/lib/rules/enforce-callback-memo.js +143 -18
- package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +250 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-exported-function-types.js +3 -3
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
- package/lib/rules/enforce-firestore-facade.js +347 -157
- package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
- package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +96 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +123 -0
- package/lib/rules/enforce-microdiff.js +2 -1
- package/lib/rules/enforce-positive-naming.js +5 -7
- package/lib/rules/enforce-props-argument-name.d.ts +2 -7
- package/lib/rules/enforce-props-argument-name.js +165 -151
- package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
- package/lib/rules/enforce-props-naming-consistency.js +145 -0
- package/lib/rules/enforce-querykey-ts.d.ts +9 -0
- package/lib/rules/enforce-querykey-ts.js +274 -0
- package/lib/rules/enforce-singular-type-names.js +9 -5
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
- package/lib/rules/enforce-verb-noun-naming.js +85 -10
- package/lib/rules/extract-global-constants.js +57 -45
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -0
- package/lib/rules/no-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +308 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-complex-cloud-params.js +15 -2
- package/lib/rules/no-compositing-layer-props.js +4 -2
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +317 -63
- package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
- package/lib/rules/no-excessive-parent-chain.js +273 -0
- package/lib/rules/no-firestore-object-arrays.js +345 -30
- package/lib/rules/no-hungarian.js +13 -13
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +16 -3
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +170 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +172 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +142 -0
- package/lib/rules/no-stale-state-across-await.d.ts +1 -0
- package/lib/rules/no-stale-state-across-await.js +185 -0
- package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
- package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
- package/lib/rules/no-type-assertion-returns.js +6 -0
- package/lib/rules/no-undefined-null-passthrough.js +166 -19
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +3 -0
- package/lib/rules/no-unused-props.js +213 -78
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +234 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +375 -0
- package/lib/rules/prefer-batch-operations.js +199 -33
- package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
- package/lib/rules/prefer-document-flattening.d.ts +4 -0
- package/lib/rules/prefer-document-flattening.js +239 -0
- package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
- package/lib/rules/prefer-fragment-component.js +3 -3
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +230 -31
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +366 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +228 -0
- package/lib/rules/prefer-settings-object.js +23 -15
- package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
- package/lib/rules/react-usememo-should-be-component.js +144 -78
- package/lib/rules/require-dynamic-firebase-imports.js +2 -1
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +11 -1
- package/lib/rules/require-usememo-object-literals.js +7 -1
- package/lib/rules/semantic-function-prefixes.js +38 -18
- package/lib/rules/sync-onwrite-name-func.js +5 -1
- package/lib/rules/test-file-location-enforcement.d.ts +1 -0
- package/lib/rules/test-file-location-enforcement.js +57 -0
- package/lib/rules/use-custom-link.js +6 -1
- package/lib/rules/use-custom-router.js +6 -1
- package/lib/rules/use-latest-callback.d.ts +3 -0
- package/lib/rules/use-latest-callback.js +270 -0
- package/lib/utils/ASTHelpers.js +10 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/package.json +21 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
|
@@ -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
|
|
@@ -28,6 +28,12 @@ function isTypePredicate(node) {
|
|
|
28
28
|
function isInsideReturnStatement(node) {
|
|
29
29
|
let current = node;
|
|
30
30
|
while (current?.parent) {
|
|
31
|
+
// If we encounter a variable declaration before a return statement,
|
|
32
|
+
// then the node is not directly inside a return statement
|
|
33
|
+
if (current.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
34
|
+
current.parent.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
31
37
|
if (current.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
32
38
|
return true;
|
|
33
39
|
}
|
|
@@ -6,8 +6,8 @@ exports.noUndefinedNullPassthrough = (0, createRule_1.createRule)({
|
|
|
6
6
|
create(context) {
|
|
7
7
|
return {
|
|
8
8
|
FunctionDeclaration(node) {
|
|
9
|
-
//
|
|
10
|
-
if (node.params.length
|
|
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
|
-
|
|
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
|
-
//
|
|
23
|
-
if (node.params.length
|
|
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
|
-
|
|
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
|
-
//
|
|
45
|
-
if (node.params.length
|
|
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
|
-
|
|
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
|
|
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
|
|
143
|
-
if (statement.argument
|
|
144
|
-
|
|
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 === '===' ||
|
|
209
|
-
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
|
|
@@ -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: '
|
|
18
|
-
unsafeArraySpread: '
|
|
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
|
}
|