@blumintinc/eslint-plugin-blumint 1.12.6 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -99
- package/lib/index.js +300 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +1 -1
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -11
- 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} +52 -96
- 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 +820 -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 +5 -2
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +278 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
- package/lib/rules/enforce-dynamic-file-naming.js +53 -28
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-early-destructuring.d.ts +2 -0
- package/lib/rules/enforce-early-destructuring.js +980 -0
- package/lib/rules/enforce-empty-object-check.d.ts +11 -0
- package/lib/rules/enforce-empty-object-check.js +502 -0
- package/lib/rules/enforce-exported-function-types.js +4 -4
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +262 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +350 -3
- package/lib/rules/enforce-firestore-facade.js +349 -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-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +92 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +147 -0
- package/lib/rules/enforce-microdiff.js +3 -2
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +9 -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-safe-stringify.js +1 -1
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-singular-type-names.js +10 -6
- package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
- package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
- package/lib/rules/enforce-storage-context.d.ts +9 -0
- package/lib/rules/enforce-storage-context.js +646 -0
- package/lib/rules/enforce-timestamp-now.js +23 -7
- package/lib/rules/enforce-transform-memoization.d.ts +4 -0
- package/lib/rules/enforce-transform-memoization.js +416 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +100 -0
- package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
- package/lib/rules/enforce-unique-cursor-headers.js +365 -0
- package/lib/rules/enforce-verb-noun-naming.js +90 -14
- package/lib/rules/ensure-pointer-events-none.js +28 -2
- package/lib/rules/export-if-in-doubt.js +27 -4
- package/lib/rules/extract-global-constants.js +92 -53
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +351 -83
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +277 -0
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- package/lib/rules/global-const-style.js +50 -19
- package/lib/rules/jsdoc-above-field.d.ts +11 -0
- package/lib/rules/jsdoc-above-field.js +208 -0
- package/lib/rules/key-only-outermost-element.js +21 -3
- package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
- package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
- package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
- package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +410 -0
- 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-always-true-false-conditions.d.ts +2 -1
- package/lib/rules/no-always-true-false-conditions.js +68 -8
- package/lib/rules/no-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +314 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-async-foreach.js +210 -12
- package/lib/rules/no-circular-references.js +18 -17
- package/lib/rules/no-class-instance-destructuring.js +55 -12
- package/lib/rules/no-complex-cloud-params.js +22 -5
- package/lib/rules/no-compositing-layer-props.js +6 -4
- package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
- package/lib/rules/no-console-error.d.ts +9 -0
- package/lib/rules/no-console-error.js +525 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
- package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
- package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +320 -68
- 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-explicit-return-type.d.ts +2 -1
- package/lib/rules/no-explicit-return-type.js +215 -26
- package/lib/rules/no-filter-without-return.js +5 -1
- package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
- package/lib/rules/no-firestore-jest-mock.js +126 -8
- package/lib/rules/no-firestore-object-arrays.js +398 -28
- package/lib/rules/no-handler-suffix.d.ts +12 -0
- package/lib/rules/no-handler-suffix.js +305 -0
- package/lib/rules/no-hungarian.js +14 -14
- package/lib/rules/no-inline-component-prop.d.ts +10 -0
- package/lib/rules/no-inline-component-prop.js +456 -0
- package/lib/rules/no-jsx-in-hooks.js +6 -1
- package/lib/rules/no-jsx-whitespace-literal.js +8 -2
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +22 -9
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +331 -0
- package/lib/rules/no-misused-switch-case.js +22 -1
- package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
- package/lib/rules/no-mixed-firestore-transactions.js +296 -34
- package/lib/rules/no-mock-firebase-admin.js +5 -2
- package/lib/rules/no-object-values-on-strings.js +10 -2
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +272 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +206 -0
- package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
- package/lib/rules/no-redundant-annotation-assertion.js +402 -0
- package/lib/rules/no-redundant-param-types.js +16 -6
- package/lib/rules/no-redundant-this-params.d.ts +3 -0
- package/lib/rules/no-redundant-this-params.js +459 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +328 -0
- package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
- package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +173 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +133 -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 +49 -49
- package/lib/rules/no-undefined-null-passthrough.js +217 -40
- package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
- package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
- package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
- package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
- package/lib/rules/no-unnecessary-destructuring.js +14 -4
- package/lib/rules/no-unnecessary-verb-suffix.js +2 -3
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +5 -2
- package/lib/rules/no-unused-props.js +451 -112
- package/lib/rules/no-unused-usestate.js +6 -2
- package/lib/rules/no-useless-fragment.js +28 -2
- package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
- package/lib/rules/no-useless-usememo-primitives.js +393 -0
- package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
- package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
- package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
- package/lib/rules/omit-index-html.d.ts +2 -1
- package/lib/rules/omit-index-html.js +62 -7
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +236 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +394 -0
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +228 -35
- package/lib/rules/prefer-block-comments-for-declarations.js +22 -7
- package/lib/rules/prefer-clone-deep.js +3 -3
- package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
- package/lib/rules/prefer-destructuring-no-class.js +244 -59
- package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
- package/lib/rules/prefer-docsetter-setall.js +243 -0
- 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 +251 -0
- package/lib/rules/prefer-fragment-component.js +4 -4
- package/lib/rules/prefer-fragment-shorthand.js +2 -1
- package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
- package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +305 -31
- package/lib/rules/prefer-memoized-props.d.ts +3 -0
- package/lib/rules/prefer-memoized-props.js +445 -0
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +357 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +435 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +7 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +188 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +277 -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-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +169 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +431 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +25 -2
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
- package/lib/rules/prevent-children-clobber.d.ts +2 -0
- package/lib/rules/prevent-children-clobber.js +536 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +495 -0
- 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-hooks-default-params.js +17 -16
- package/lib/rules/require-https-error-cause.d.ts +4 -0
- package/lib/rules/require-https-error-cause.js +136 -0
- package/lib/rules/require-https-error.js +43 -21
- package/lib/rules/require-image-optimized.js +2 -2
- package/lib/rules/require-memo.js +12 -2
- package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
- package/lib/rules/require-memoize-jsx-returners.js +485 -0
- package/lib/rules/require-usememo-object-literals.js +9 -4
- 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 +271 -0
- package/lib/rules/vertically-group-related-functions.d.ts +16 -0
- package/lib/rules/vertically-group-related-functions.js +480 -0
- package/lib/utils/ASTHelpers.d.ts +13 -1
- package/lib/utils/ASTHelpers.js +34 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +23 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.firestoreTransactionReadsBeforeWrites = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
// Define the operations that are considered reads and writes
|
|
7
|
+
const READ_OPERATIONS = new Set(['get']);
|
|
8
|
+
const WRITE_OPERATIONS = new Set(['set', 'update', 'delete']);
|
|
9
|
+
exports.firestoreTransactionReadsBeforeWrites = (0, createRule_1.createRule)({
|
|
10
|
+
name: 'firestore-transaction-reads-before-writes',
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'problem',
|
|
13
|
+
docs: {
|
|
14
|
+
description: 'Enforce that all Firestore transaction read operations are performed before any write operations',
|
|
15
|
+
recommended: 'error',
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
readsAfterWrites: 'Read operation "{{readMethod}}" runs after transaction writes ({{writeMethods}}). Firestore transactions must collect every read before any write to keep a consistent snapshot and avoid Firestore retries that reapply writes with stale data. Move this read before the first write in the same transaction callback.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
// Track transaction objects and their operations
|
|
25
|
+
const transactionScopes = new Map();
|
|
26
|
+
// Helper to check if a node is a transaction parameter
|
|
27
|
+
function isTransactionParameter(param) {
|
|
28
|
+
if (param.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
29
|
+
return false;
|
|
30
|
+
// Check for type annotation
|
|
31
|
+
const typeAnnotation = param.typeAnnotation;
|
|
32
|
+
if (!typeAnnotation ||
|
|
33
|
+
typeAnnotation.type !== utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
|
|
34
|
+
// If no type annotation, check the parameter name for common patterns
|
|
35
|
+
// Include 't' as a valid transaction parameter name
|
|
36
|
+
return /^(transaction|tx|t)$/i.test(param.name);
|
|
37
|
+
}
|
|
38
|
+
const type = typeAnnotation.typeAnnotation;
|
|
39
|
+
// Check for Transaction type
|
|
40
|
+
if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
41
|
+
const typeName = type.typeName;
|
|
42
|
+
// Check for FirebaseFirestore.Transaction
|
|
43
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
|
|
44
|
+
return (typeName.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
45
|
+
typeName.left.name === 'FirebaseFirestore' &&
|
|
46
|
+
typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
47
|
+
typeName.right.name === 'Transaction');
|
|
48
|
+
}
|
|
49
|
+
// Check for Transaction or FirestoreTransaction
|
|
50
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
51
|
+
return /Transaction/i.test(typeName.name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// Helper to find the transaction scope for a node
|
|
57
|
+
function findTransactionScope(node) {
|
|
58
|
+
let current = node;
|
|
59
|
+
while (current) {
|
|
60
|
+
// Check if this node is a transaction scope
|
|
61
|
+
if (transactionScopes.has(current)) {
|
|
62
|
+
return current;
|
|
63
|
+
}
|
|
64
|
+
// Check if this is a function with a transaction parameter
|
|
65
|
+
if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
66
|
+
current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
67
|
+
current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
68
|
+
const params = current.params;
|
|
69
|
+
if (params.some(isTransactionParameter)) {
|
|
70
|
+
// If this function has a transaction parameter, track it
|
|
71
|
+
if (!transactionScopes.has(current.body)) {
|
|
72
|
+
const transactionParam = params.find(isTransactionParameter);
|
|
73
|
+
const transactionVariables = new Set();
|
|
74
|
+
if (transactionParam &&
|
|
75
|
+
transactionParam.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
76
|
+
transactionVariables.add(transactionParam.name);
|
|
77
|
+
}
|
|
78
|
+
transactionScopes.set(current.body, {
|
|
79
|
+
hasWriteOperation: false,
|
|
80
|
+
writeNodes: [],
|
|
81
|
+
transactionVariables,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return current.body;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
current = current.parent;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// Helper to check if a node is a transaction method call
|
|
92
|
+
function isTransactionMethodCall(node, transactionScope) {
|
|
93
|
+
const callee = node.callee;
|
|
94
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
95
|
+
return { isRead: false, isWrite: false, methodName: null };
|
|
96
|
+
}
|
|
97
|
+
const object = callee.object;
|
|
98
|
+
const property = callee.property;
|
|
99
|
+
// Check if the object is a transaction
|
|
100
|
+
if (object.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
101
|
+
return { isRead: false, isWrite: false, methodName: null };
|
|
102
|
+
}
|
|
103
|
+
// If we have a transaction scope, check if the object name is a known transaction variable
|
|
104
|
+
if (transactionScope) {
|
|
105
|
+
const scopeInfo = transactionScopes.get(transactionScope);
|
|
106
|
+
if (scopeInfo && !scopeInfo.transactionVariables.has(object.name)) {
|
|
107
|
+
return { isRead: false, isWrite: false, methodName: null };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Fallback to general pattern matching
|
|
112
|
+
if (!/^(transaction|tx|t)$/i.test(object.name)) {
|
|
113
|
+
return { isRead: false, isWrite: false, methodName: null };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let methodName = null;
|
|
117
|
+
// Check if the property is a read or write operation
|
|
118
|
+
if (property.type === utils_1.AST_NODE_TYPES.Identifier && !callee.computed) {
|
|
119
|
+
// Normal property access: transaction.get()
|
|
120
|
+
methodName = property.name;
|
|
121
|
+
}
|
|
122
|
+
else if (callee.computed &&
|
|
123
|
+
property.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
124
|
+
typeof property.value === 'string') {
|
|
125
|
+
// Computed property access with string literal: transaction['get']
|
|
126
|
+
methodName = property.value;
|
|
127
|
+
}
|
|
128
|
+
else if (callee.computed &&
|
|
129
|
+
property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
130
|
+
// Computed property access with variable: transaction[methodName]
|
|
131
|
+
// This is tricky to analyze statically. For now, we'll be conservative
|
|
132
|
+
// and assume it could be any method. We'll handle this in the caller.
|
|
133
|
+
return { isRead: true, isWrite: true, methodName: null }; // Could be either - let caller decide
|
|
134
|
+
}
|
|
135
|
+
if (!methodName) {
|
|
136
|
+
return { isRead: false, isWrite: false, methodName: null };
|
|
137
|
+
}
|
|
138
|
+
const isRead = READ_OPERATIONS.has(methodName);
|
|
139
|
+
const isWrite = WRITE_OPERATIONS.has(methodName);
|
|
140
|
+
return { isRead, isWrite, methodName };
|
|
141
|
+
}
|
|
142
|
+
function formatMethodName(callExpression) {
|
|
143
|
+
const callee = callExpression.callee;
|
|
144
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
145
|
+
return 'this transaction read';
|
|
146
|
+
}
|
|
147
|
+
const objectName = callee.object.type === utils_1.AST_NODE_TYPES.Identifier
|
|
148
|
+
? callee.object.name
|
|
149
|
+
: 'transaction';
|
|
150
|
+
if (callee.computed) {
|
|
151
|
+
if (callee.property.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
152
|
+
return `${objectName}[${String(callee.property.value)}]`;
|
|
153
|
+
}
|
|
154
|
+
if (callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
155
|
+
return `${objectName}[${callee.property.name}]`;
|
|
156
|
+
}
|
|
157
|
+
return `${objectName}[computed]`;
|
|
158
|
+
}
|
|
159
|
+
if (callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
160
|
+
return `${objectName}.${callee.property.name}`;
|
|
161
|
+
}
|
|
162
|
+
return `${objectName}[unknown]`;
|
|
163
|
+
}
|
|
164
|
+
function formatWriteMethods(writeNodes) {
|
|
165
|
+
const methodNames = writeNodes
|
|
166
|
+
.map((node) => node.type === utils_1.AST_NODE_TYPES.CallExpression
|
|
167
|
+
? formatMethodName(node)
|
|
168
|
+
: null)
|
|
169
|
+
.filter((name) => Boolean(name));
|
|
170
|
+
if (!methodNames.length) {
|
|
171
|
+
return 'earlier transaction writes';
|
|
172
|
+
}
|
|
173
|
+
const uniqueNames = [...new Set(methodNames)];
|
|
174
|
+
if (uniqueNames.length === 1) {
|
|
175
|
+
return uniqueNames[0];
|
|
176
|
+
}
|
|
177
|
+
if (uniqueNames.length === 2) {
|
|
178
|
+
return `${uniqueNames[0]} and ${uniqueNames[1]}`;
|
|
179
|
+
}
|
|
180
|
+
return `${uniqueNames
|
|
181
|
+
.slice(0, uniqueNames.length - 1)
|
|
182
|
+
.join(', ')} and ${uniqueNames[uniqueNames.length - 1]}`;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
// Track transaction scopes from runTransaction calls
|
|
186
|
+
'CallExpression[callee.property.name="runTransaction"]'(node) {
|
|
187
|
+
const callback = node.arguments[0];
|
|
188
|
+
if (callback &&
|
|
189
|
+
(callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
190
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
|
|
191
|
+
const transactionParam = callback.params.find(isTransactionParameter);
|
|
192
|
+
const transactionVariables = new Set();
|
|
193
|
+
if (transactionParam &&
|
|
194
|
+
transactionParam.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
195
|
+
transactionVariables.add(transactionParam.name);
|
|
196
|
+
}
|
|
197
|
+
transactionScopes.set(callback.body, {
|
|
198
|
+
hasWriteOperation: false,
|
|
199
|
+
writeNodes: [],
|
|
200
|
+
transactionVariables,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
// Clean up transaction scopes when exiting runTransaction calls
|
|
205
|
+
'CallExpression[callee.property.name="runTransaction"]:exit'(node) {
|
|
206
|
+
const callback = node.arguments[0];
|
|
207
|
+
if (callback &&
|
|
208
|
+
(callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
209
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
|
|
210
|
+
transactionScopes.delete(callback.body);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
// Track variable assignments that reference the transaction
|
|
214
|
+
VariableDeclarator(node) {
|
|
215
|
+
if (node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
216
|
+
node.init &&
|
|
217
|
+
node.init.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
218
|
+
const transactionScope = findTransactionScope(node);
|
|
219
|
+
if (transactionScope) {
|
|
220
|
+
const scopeInfo = transactionScopes.get(transactionScope);
|
|
221
|
+
if (scopeInfo &&
|
|
222
|
+
scopeInfo.transactionVariables.has(node.init.name)) {
|
|
223
|
+
scopeInfo.transactionVariables.add(node.id.name);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
// Check all call expressions for transaction operations
|
|
229
|
+
CallExpression(node) {
|
|
230
|
+
const transactionScope = findTransactionScope(node);
|
|
231
|
+
const { isRead, isWrite } = isTransactionMethodCall(node, transactionScope);
|
|
232
|
+
if (!isRead && !isWrite) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (!transactionScope) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const scopeInfo = transactionScopes.get(transactionScope);
|
|
239
|
+
if (!scopeInfo) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Handle computed property access specially
|
|
243
|
+
if (isRead && isWrite) {
|
|
244
|
+
// This is a computed property access - we need to be more careful
|
|
245
|
+
// Only flag as a read if we already have writes in this scope
|
|
246
|
+
if (scopeInfo.hasWriteOperation) {
|
|
247
|
+
const readMethod = formatMethodName(node);
|
|
248
|
+
const writeMethods = formatWriteMethods(scopeInfo.writeNodes);
|
|
249
|
+
context.report({
|
|
250
|
+
node,
|
|
251
|
+
messageId: 'readsAfterWrites',
|
|
252
|
+
data: { readMethod, writeMethods },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
// Don't mark as write since we don't know what method it is
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// If it's a write operation, mark the scope as having a write
|
|
259
|
+
if (isWrite) {
|
|
260
|
+
scopeInfo.hasWriteOperation = true;
|
|
261
|
+
scopeInfo.writeNodes.push(node);
|
|
262
|
+
}
|
|
263
|
+
// If it's a read operation and the scope already has a write, report an error
|
|
264
|
+
if (isRead && scopeInfo.hasWriteOperation) {
|
|
265
|
+
const readMethod = formatMethodName(node);
|
|
266
|
+
const writeMethods = formatWriteMethods(scopeInfo.writeNodes);
|
|
267
|
+
context.report({
|
|
268
|
+
node,
|
|
269
|
+
messageId: 'readsAfterWrites',
|
|
270
|
+
data: { readMethod, writeMethods },
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
//# sourceMappingURL=firestore-transaction-reads-before-writes.js.map
|