@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
|
@@ -8,6 +8,7 @@ const NON_TRANSACTIONAL_CLASSES = new Set([
|
|
|
8
8
|
'FirestoreDocFetcher',
|
|
9
9
|
'FirestoreFetcher',
|
|
10
10
|
]);
|
|
11
|
+
const FETCHER_CLASSES = new Set(['FirestoreDocFetcher', 'FirestoreFetcher']);
|
|
11
12
|
exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
|
|
12
13
|
name: 'no-mixed-firestore-transactions',
|
|
13
14
|
meta: {
|
|
@@ -18,19 +19,26 @@ exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
|
|
|
18
19
|
},
|
|
19
20
|
schema: [],
|
|
20
21
|
messages: {
|
|
21
|
-
noMixedTransactions: '
|
|
22
|
+
noMixedTransactions: 'Non-transactional Firestore helper "{{ className }}" is instantiated inside a transaction callback, so its reads and writes bypass the transaction context. That breaks Firestore\'s atomicity guarantees and can commit partial updates. Use the transaction-safe "{{ transactionalClass }}" and pass the provided transaction so every operation participates in the same commit.',
|
|
23
|
+
noMixedTransactionsFetcher: 'Non-transactional Firestore helper "{{ className }}" is instantiated inside a transaction callback, so its reads and writes bypass the transaction context. That breaks Firestore\'s atomicity guarantees and can commit partial updates. Pass the provided transaction to the constructor (e.g., new {{ className }}(ref, { transaction: tx })) or to fetch() (e.g., fetch({ transaction: tx })) so the operation participates in the transaction.',
|
|
22
24
|
},
|
|
23
25
|
},
|
|
24
26
|
defaultOptions: [],
|
|
25
27
|
create(context) {
|
|
26
28
|
const transactionScopes = new Set();
|
|
29
|
+
/**
|
|
30
|
+
* Tracks fetcher lifetimes per transaction scope and binding name.
|
|
31
|
+
* The outer map keys by the transaction scope node so nested transactions keep
|
|
32
|
+
* independent state. Each scope maps variable names to an ordered list of
|
|
33
|
+
* instances, preserving reassignment history. Arrays keep the latest instance
|
|
34
|
+
* easy to read for fetch calls while still retaining older instances for
|
|
35
|
+
* Program:exit validation, which must flag any overwritten fetcher that never
|
|
36
|
+
* received a transaction.
|
|
37
|
+
*/
|
|
38
|
+
const fetcherInstances = new Map();
|
|
27
39
|
function getTransactionalClassName(className) {
|
|
28
40
|
if (className === 'DocSetter')
|
|
29
41
|
return 'DocSetterTransaction';
|
|
30
|
-
if (className === 'FirestoreDocFetcher')
|
|
31
|
-
return 'FirestoreDocFetcherTransaction';
|
|
32
|
-
if (className === 'FirestoreFetcher')
|
|
33
|
-
return 'FirestoreFetcherTransaction';
|
|
34
42
|
return className;
|
|
35
43
|
}
|
|
36
44
|
function isFirestoreTransaction(node) {
|
|
@@ -65,53 +73,307 @@ exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
|
|
|
65
73
|
typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
66
74
|
typeName.right.name === 'Transaction');
|
|
67
75
|
}
|
|
68
|
-
function
|
|
76
|
+
function isFunctionLike(node) {
|
|
77
|
+
return (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
78
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
79
|
+
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression);
|
|
80
|
+
}
|
|
81
|
+
function identifyTransactionScope(node) {
|
|
82
|
+
if (transactionScopes.has(node)) {
|
|
83
|
+
return node;
|
|
84
|
+
}
|
|
85
|
+
if (isFunctionLike(node) && node.params.some(isTransactionParameter)) {
|
|
86
|
+
return node.body;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function getTransactionScopeChain(node) {
|
|
91
|
+
const scopes = [];
|
|
69
92
|
let current = node;
|
|
70
93
|
while (current) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
75
|
-
current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
76
|
-
current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
77
|
-
const params = current.params;
|
|
78
|
-
if (params.some(isTransactionParameter)) {
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
94
|
+
const scope = identifyTransactionScope(current);
|
|
95
|
+
if (scope && scopes[scopes.length - 1] !== scope) {
|
|
96
|
+
scopes.push(scope);
|
|
81
97
|
}
|
|
82
98
|
current = current.parent;
|
|
83
99
|
}
|
|
100
|
+
return scopes;
|
|
101
|
+
}
|
|
102
|
+
function getTransactionScope(node) {
|
|
103
|
+
return getTransactionScopeChain(node)[0] ?? null;
|
|
104
|
+
}
|
|
105
|
+
function getVariableName(node) {
|
|
106
|
+
const parent = node.parent;
|
|
107
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
108
|
+
parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
109
|
+
return parent.id.name;
|
|
110
|
+
}
|
|
111
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
|
|
112
|
+
parent.left.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
113
|
+
return parent.left.name;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function isFetcherClass(className) {
|
|
118
|
+
return FETCHER_CLASSES.has(className);
|
|
119
|
+
}
|
|
120
|
+
function isTransactionKey(key) {
|
|
121
|
+
if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
122
|
+
return key.name === 'transaction';
|
|
123
|
+
}
|
|
124
|
+
if (key.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
125
|
+
return key.value === 'transaction';
|
|
126
|
+
}
|
|
127
|
+
if (key.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
|
|
128
|
+
return (key.expressions.length === 0 &&
|
|
129
|
+
key.quasis.length === 1 &&
|
|
130
|
+
key.quasis[0].value.cooked === 'transaction');
|
|
131
|
+
}
|
|
84
132
|
return false;
|
|
85
133
|
}
|
|
134
|
+
function isTransactionOptionObject(arg) {
|
|
135
|
+
if (arg.type !== utils_1.AST_NODE_TYPES.ObjectExpression)
|
|
136
|
+
return false;
|
|
137
|
+
return arg.properties.some((property) => {
|
|
138
|
+
if (property.type !== utils_1.AST_NODE_TYPES.Property) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
const key = property.key;
|
|
142
|
+
return isTransactionKey(key);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function hasTransactionOption(args) {
|
|
146
|
+
return args.some(isTransactionOptionObject);
|
|
147
|
+
}
|
|
148
|
+
function getFetchCallContext(node) {
|
|
149
|
+
const transactionScopesForCall = getTransactionScopeChain(node);
|
|
150
|
+
const transactionScope = transactionScopesForCall[0];
|
|
151
|
+
if (!transactionScope)
|
|
152
|
+
return null;
|
|
153
|
+
return {
|
|
154
|
+
transactionScope,
|
|
155
|
+
transactionScopesForCall,
|
|
156
|
+
callHasTransaction: hasTransactionOption(node.arguments),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function ensureFetcherScope(scope) {
|
|
160
|
+
if (!fetcherInstances.has(scope)) {
|
|
161
|
+
fetcherInstances.set(scope, new Map());
|
|
162
|
+
}
|
|
163
|
+
return fetcherInstances.get(scope);
|
|
164
|
+
}
|
|
165
|
+
function addFetcherInstance(scope, variableName, instance) {
|
|
166
|
+
const scopeMap = ensureFetcherScope(scope);
|
|
167
|
+
if (!scopeMap.has(variableName)) {
|
|
168
|
+
scopeMap.set(variableName, []);
|
|
169
|
+
}
|
|
170
|
+
scopeMap.get(variableName).push(instance);
|
|
171
|
+
}
|
|
172
|
+
function findFetcherInstance(scopes, variableName) {
|
|
173
|
+
for (const scope of scopes) {
|
|
174
|
+
const scopeInstances = fetcherInstances.get(scope);
|
|
175
|
+
const instances = scopeInstances?.get(variableName);
|
|
176
|
+
if (instances?.length) {
|
|
177
|
+
return instances[instances.length - 1];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Determines whether a fetcher instance should be reported for mixed or unsafe
|
|
184
|
+
* usage.
|
|
185
|
+
*
|
|
186
|
+
* Reporting occurs in three scenarios:
|
|
187
|
+
* 1. The constructor lacks a transaction AND any call occurs without a transaction.
|
|
188
|
+
* 2. The constructor lacks a transaction AND no calls occur at all (suspicious
|
|
189
|
+
* unused instance in a transaction scope).
|
|
190
|
+
* 3. Constructor-level transactions make the instance always safe.
|
|
191
|
+
*
|
|
192
|
+
* If the constructor lacked a transaction but at least one call provided it, we
|
|
193
|
+
* skip reporting because the caller opted into explicit transactional use.
|
|
194
|
+
*/
|
|
195
|
+
function shouldReportFetcher(instance) {
|
|
196
|
+
if (instance.constructorHasTransaction)
|
|
197
|
+
return false;
|
|
198
|
+
if (instance.hasCallWithoutTransaction)
|
|
199
|
+
return true;
|
|
200
|
+
if (instance.hasTransactionCall)
|
|
201
|
+
return false;
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
function isFetchCall(callee) {
|
|
205
|
+
return (callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
206
|
+
callee.property.name === 'fetch');
|
|
207
|
+
}
|
|
208
|
+
function trackTransactionScope(node) {
|
|
209
|
+
if (!isFirestoreTransaction(node))
|
|
210
|
+
return;
|
|
211
|
+
const callback = node.arguments[0];
|
|
212
|
+
if (!callback)
|
|
213
|
+
return;
|
|
214
|
+
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
215
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
216
|
+
transactionScopes.add(callback.body);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function updateInstanceTransactionState(instance, callHasTransaction) {
|
|
220
|
+
if (callHasTransaction || instance.constructorHasTransaction) {
|
|
221
|
+
instance.hasTransactionCall =
|
|
222
|
+
instance.hasTransactionCall || callHasTransaction;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
instance.hasCallWithoutTransaction = true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function handleIdentifierFetchCall(identifier, transactionScopesForCall, callHasTransaction) {
|
|
229
|
+
const instance = findFetcherInstance(transactionScopesForCall, identifier.name);
|
|
230
|
+
if (!instance)
|
|
231
|
+
return;
|
|
232
|
+
updateInstanceTransactionState(instance, callHasTransaction);
|
|
233
|
+
}
|
|
234
|
+
function isInlineFetcherNew(object) {
|
|
235
|
+
return (object.type === utils_1.AST_NODE_TYPES.NewExpression &&
|
|
236
|
+
object.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
237
|
+
isFetcherClass(object.callee.name));
|
|
238
|
+
}
|
|
239
|
+
function handleInlineNewFetchCall(object, callHasTransaction) {
|
|
240
|
+
const constructorHasTransaction = hasTransactionOption(object.arguments);
|
|
241
|
+
if (constructorHasTransaction || callHasTransaction) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const className = object.callee.name;
|
|
245
|
+
context.report({
|
|
246
|
+
node: object,
|
|
247
|
+
messageId: 'noMixedTransactionsFetcher',
|
|
248
|
+
data: {
|
|
249
|
+
className,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function handleFetchCall(node, callee) {
|
|
254
|
+
const contextForCall = getFetchCallContext(node);
|
|
255
|
+
if (!contextForCall)
|
|
256
|
+
return;
|
|
257
|
+
const { transactionScopesForCall, callHasTransaction } = contextForCall;
|
|
258
|
+
const object = callee.object;
|
|
259
|
+
if (object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
260
|
+
handleIdentifierFetchCall(object, transactionScopesForCall, callHasTransaction);
|
|
261
|
+
}
|
|
262
|
+
else if (isInlineFetcherNew(object)) {
|
|
263
|
+
handleInlineNewFetchCall(object, callHasTransaction);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function reportNonFetcherInTransaction(node, className) {
|
|
267
|
+
context.report({
|
|
268
|
+
node,
|
|
269
|
+
messageId: 'noMixedTransactions',
|
|
270
|
+
data: {
|
|
271
|
+
className,
|
|
272
|
+
transactionalClass: getTransactionalClassName(className),
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function isNewExpressionUsedForFetchCall(node) {
|
|
277
|
+
return (node.parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
278
|
+
node.parent.object === node &&
|
|
279
|
+
isFetchCall(node.parent) &&
|
|
280
|
+
node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
281
|
+
node.parent.parent.callee === node.parent);
|
|
282
|
+
}
|
|
283
|
+
function handleFetcherWithoutVariable(node, className, constructorHasTransaction) {
|
|
284
|
+
if (constructorHasTransaction)
|
|
285
|
+
return;
|
|
286
|
+
if (isNewExpressionUsedForFetchCall(node))
|
|
287
|
+
return;
|
|
288
|
+
context.report({
|
|
289
|
+
node,
|
|
290
|
+
messageId: 'noMixedTransactionsFetcher',
|
|
291
|
+
data: {
|
|
292
|
+
className,
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function handleFetcherWithVariable(node, className, constructorHasTransaction, variableName, transactionScope) {
|
|
297
|
+
addFetcherInstance(transactionScope, variableName, {
|
|
298
|
+
node,
|
|
299
|
+
className,
|
|
300
|
+
constructorHasTransaction,
|
|
301
|
+
hasTransactionCall: constructorHasTransaction,
|
|
302
|
+
hasCallWithoutTransaction: false,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function handleFetcherClass(node, className, transactionScope) {
|
|
306
|
+
const constructorHasTransaction = hasTransactionOption(node.arguments);
|
|
307
|
+
const variableName = getVariableName(node);
|
|
308
|
+
if (!variableName) {
|
|
309
|
+
handleFetcherWithoutVariable(node, className, constructorHasTransaction);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
handleFetcherWithVariable(node, className, constructorHasTransaction, variableName, transactionScope);
|
|
313
|
+
}
|
|
314
|
+
function handleNewExpression(node) {
|
|
315
|
+
if (!isNonTransactionalClass(node))
|
|
316
|
+
return;
|
|
317
|
+
const transactionScope = getTransactionScope(node);
|
|
318
|
+
if (!transactionScope)
|
|
319
|
+
return;
|
|
320
|
+
const className = node.callee.name;
|
|
321
|
+
if (!isFetcherClass(className)) {
|
|
322
|
+
reportNonFetcherInTransaction(node, className);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
handleFetcherClass(node, className, transactionScope);
|
|
326
|
+
}
|
|
327
|
+
function reportFetcherInstance(instance) {
|
|
328
|
+
if (!shouldReportFetcher(instance))
|
|
329
|
+
return;
|
|
330
|
+
context.report({
|
|
331
|
+
node: instance.node,
|
|
332
|
+
messageId: 'noMixedTransactionsFetcher',
|
|
333
|
+
data: {
|
|
334
|
+
className: instance.className,
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function reportFetcherInstances(instances) {
|
|
339
|
+
for (const instance of instances) {
|
|
340
|
+
reportFetcherInstance(instance);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function reportFetcherScope(scope) {
|
|
344
|
+
for (const instances of scope.values()) {
|
|
345
|
+
reportFetcherInstances(instances);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function reportAllFetcherScopes() {
|
|
349
|
+
for (const scope of fetcherInstances.values()) {
|
|
350
|
+
reportFetcherScope(scope);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
86
353
|
return {
|
|
87
|
-
|
|
88
|
-
|
|
354
|
+
NewExpression: handleNewExpression,
|
|
355
|
+
CallExpression(node) {
|
|
356
|
+
const callee = node.callee;
|
|
357
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
89
358
|
return;
|
|
90
|
-
const callback = node.arguments[0];
|
|
91
|
-
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
92
|
-
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
93
|
-
transactionScopes.add(callback.body);
|
|
94
359
|
}
|
|
360
|
+
if (!isFetchCall(callee)) {
|
|
361
|
+
trackTransactionScope(node);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
handleFetchCall(node, callee);
|
|
95
365
|
},
|
|
96
366
|
'CallExpression[callee.property.name="runTransaction"]:exit'(node) {
|
|
97
367
|
const callback = node.arguments[0];
|
|
368
|
+
if (!callback)
|
|
369
|
+
return;
|
|
98
370
|
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
99
371
|
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
100
372
|
transactionScopes.delete(callback.body);
|
|
101
373
|
}
|
|
102
374
|
},
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return;
|
|
106
|
-
const className = node.callee.name;
|
|
107
|
-
context.report({
|
|
108
|
-
node,
|
|
109
|
-
messageId: 'noMixedTransactions',
|
|
110
|
-
data: {
|
|
111
|
-
className,
|
|
112
|
-
transactionalClass: getTransactionalClassName(className),
|
|
113
|
-
},
|
|
114
|
-
});
|
|
375
|
+
'Program:exit'() {
|
|
376
|
+
reportAllFetcherScopes();
|
|
115
377
|
},
|
|
116
378
|
};
|
|
117
379
|
},
|
|
@@ -14,12 +14,12 @@ exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
|
|
|
14
14
|
meta: {
|
|
15
15
|
type: 'problem',
|
|
16
16
|
docs: {
|
|
17
|
-
description: 'Prevent mocking of
|
|
17
|
+
description: 'Prevent direct mocking of firebaseAdmin; use shared test helpers instead',
|
|
18
18
|
recommended: 'error',
|
|
19
19
|
},
|
|
20
20
|
schema: [],
|
|
21
21
|
messages: {
|
|
22
|
-
noMockFirebaseAdmin: 'Do not mock firebaseAdmin
|
|
22
|
+
noMockFirebaseAdmin: 'Do not mock firebaseAdmin module "{{modulePath}}". The project already ships a stable mock in jest.setup.node.js; overriding it creates divergent Firestore/Auth state and brittle test fixtures. Keep the shared mock and use __test-utils__/mockFirestore to seed data instead of replacing the module.',
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
25
|
defaultOptions: [],
|
|
@@ -58,6 +58,9 @@ exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
|
|
|
58
58
|
context.report({
|
|
59
59
|
node,
|
|
60
60
|
messageId: 'noMockFirebaseAdmin',
|
|
61
|
+
data: {
|
|
62
|
+
modulePath: mockPath,
|
|
63
|
+
},
|
|
61
64
|
});
|
|
62
65
|
}
|
|
63
66
|
}
|
|
@@ -29,7 +29,7 @@ const utils_1 = require("@typescript-eslint/utils");
|
|
|
29
29
|
const ts = __importStar(require("typescript"));
|
|
30
30
|
exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
31
31
|
create(context) {
|
|
32
|
-
const sourceCode = context.
|
|
32
|
+
const sourceCode = context.sourceCode;
|
|
33
33
|
const parserServices = sourceCode.parserServices;
|
|
34
34
|
// If TypeScript parser services are not available, return an empty object
|
|
35
35
|
if (!parserServices ||
|
|
@@ -191,11 +191,13 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
191
191
|
// Check if the call is Object.values()
|
|
192
192
|
if (isObjectValuesCall(node)) {
|
|
193
193
|
const argument = node.arguments[0];
|
|
194
|
+
const argumentText = sourceCode.getText(argument);
|
|
194
195
|
// Quick check for string literals and template literals
|
|
195
196
|
if (isStringLiteral(argument)) {
|
|
196
197
|
context.report({
|
|
197
198
|
node,
|
|
198
199
|
messageId: 'unexpected',
|
|
200
|
+
data: { argument: argumentText },
|
|
199
201
|
});
|
|
200
202
|
return;
|
|
201
203
|
}
|
|
@@ -204,6 +206,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
204
206
|
context.report({
|
|
205
207
|
node,
|
|
206
208
|
messageId: 'unexpected',
|
|
209
|
+
data: { argument: argumentText },
|
|
207
210
|
});
|
|
208
211
|
return;
|
|
209
212
|
}
|
|
@@ -220,6 +223,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
220
223
|
context.report({
|
|
221
224
|
node,
|
|
222
225
|
messageId: 'unexpected',
|
|
226
|
+
data: { argument: argumentText },
|
|
223
227
|
});
|
|
224
228
|
return;
|
|
225
229
|
}
|
|
@@ -234,6 +238,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
234
238
|
context.report({
|
|
235
239
|
node,
|
|
236
240
|
messageId: 'unexpected',
|
|
241
|
+
data: { argument: argumentText },
|
|
237
242
|
});
|
|
238
243
|
return;
|
|
239
244
|
}
|
|
@@ -243,6 +248,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
243
248
|
context.report({
|
|
244
249
|
node,
|
|
245
250
|
messageId: 'unexpected',
|
|
251
|
+
data: { argument: argumentText },
|
|
246
252
|
});
|
|
247
253
|
return;
|
|
248
254
|
}
|
|
@@ -284,9 +290,11 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
284
290
|
.filter((p) => p.type === utils_1.AST_NODE_TYPES.Identifier)
|
|
285
291
|
.map((p) => p.name);
|
|
286
292
|
if (paramNames.includes(argument.name)) {
|
|
293
|
+
const argumentText = sourceCode.getText(argument);
|
|
287
294
|
context.report({
|
|
288
295
|
node: call,
|
|
289
296
|
messageId: 'unexpected',
|
|
297
|
+
data: { argument: argumentText },
|
|
290
298
|
});
|
|
291
299
|
}
|
|
292
300
|
}
|
|
@@ -304,7 +312,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
|
304
312
|
},
|
|
305
313
|
schema: [],
|
|
306
314
|
messages: {
|
|
307
|
-
unexpected: 'Object.values()
|
|
315
|
+
unexpected: 'Object.values() receives string-like argument {{argument}}. Strings are iterable, so Object.values returns one entry per character instead of object properties, which signals the argument is not an object. Pass an object (for example, a Record or Object.fromEntries(map)) to Object.values or use string helpers (split, spread, Array.from) to work with text.',
|
|
308
316
|
},
|
|
309
317
|
},
|
|
310
318
|
defaultOptions: [],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noOverridableMethodCallsInConstructor: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noOverridableMethodCallsInConstructor", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|