@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
|
@@ -33,6 +33,106 @@ function isPromiseAll(node) {
|
|
|
33
33
|
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
34
34
|
callee.property.name === 'all');
|
|
35
35
|
}
|
|
36
|
+
function isSetterCall(call) {
|
|
37
|
+
if (call.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
|
|
38
|
+
return false;
|
|
39
|
+
if (call.callee.property.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
40
|
+
return false;
|
|
41
|
+
return SETTER_METHODS.has(call.callee.property.name);
|
|
42
|
+
}
|
|
43
|
+
function getAllCallsFromExpression(expr) {
|
|
44
|
+
const calls = [];
|
|
45
|
+
function traverse(node) {
|
|
46
|
+
switch (node.type) {
|
|
47
|
+
case utils_1.AST_NODE_TYPES.CallExpression:
|
|
48
|
+
calls.push(node);
|
|
49
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
50
|
+
const obj = node.callee.object;
|
|
51
|
+
if (obj.type !== utils_1.AST_NODE_TYPES.Identifier &&
|
|
52
|
+
obj.type !== utils_1.AST_NODE_TYPES.ThisExpression &&
|
|
53
|
+
obj.type !== utils_1.AST_NODE_TYPES.Literal) {
|
|
54
|
+
traverse(obj);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const arg of node.arguments) {
|
|
58
|
+
if (!arg)
|
|
59
|
+
continue;
|
|
60
|
+
if (arg.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
61
|
+
traverse(arg.argument);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
traverse(arg);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case utils_1.AST_NODE_TYPES.AwaitExpression:
|
|
69
|
+
traverse(node.argument);
|
|
70
|
+
break;
|
|
71
|
+
case utils_1.AST_NODE_TYPES.ArrayExpression:
|
|
72
|
+
for (const el of node.elements) {
|
|
73
|
+
if (!el)
|
|
74
|
+
continue;
|
|
75
|
+
if (el.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
76
|
+
traverse(el.argument);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
traverse(el);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case utils_1.AST_NODE_TYPES.ObjectExpression:
|
|
84
|
+
for (const prop of node.properties) {
|
|
85
|
+
if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
86
|
+
traverse(prop.argument);
|
|
87
|
+
}
|
|
88
|
+
else if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
89
|
+
const val = prop.value;
|
|
90
|
+
if (val.type !== utils_1.AST_NODE_TYPES.Identifier &&
|
|
91
|
+
val.type !== utils_1.AST_NODE_TYPES.Literal &&
|
|
92
|
+
val.type !== utils_1.AST_NODE_TYPES.ThisExpression) {
|
|
93
|
+
traverse(val);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
case utils_1.AST_NODE_TYPES.SequenceExpression:
|
|
99
|
+
for (const expr of node.expressions) {
|
|
100
|
+
traverse(expr);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
104
|
+
traverse(node.argument);
|
|
105
|
+
break;
|
|
106
|
+
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
107
|
+
traverse(node.left);
|
|
108
|
+
traverse(node.right);
|
|
109
|
+
break;
|
|
110
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
111
|
+
traverse(node.test);
|
|
112
|
+
traverse(node.consequent);
|
|
113
|
+
traverse(node.alternate);
|
|
114
|
+
break;
|
|
115
|
+
case utils_1.AST_NODE_TYPES.MemberExpression:
|
|
116
|
+
if (node.object.type !== utils_1.AST_NODE_TYPES.Identifier &&
|
|
117
|
+
node.object.type !== utils_1.AST_NODE_TYPES.ThisExpression &&
|
|
118
|
+
node.object.type !== utils_1.AST_NODE_TYPES.Literal) {
|
|
119
|
+
traverse(node.object);
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
// For function expressions, we don't traverse inside them
|
|
123
|
+
case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
|
|
124
|
+
case utils_1.AST_NODE_TYPES.FunctionExpression:
|
|
125
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
126
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
127
|
+
case utils_1.AST_NODE_TYPES.ThisExpression:
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
traverse(expr);
|
|
134
|
+
return calls;
|
|
135
|
+
}
|
|
36
136
|
function findLoopNode(node) {
|
|
37
137
|
let current = node;
|
|
38
138
|
let loopNode = null;
|
|
@@ -48,7 +148,52 @@ function findLoopNode(node) {
|
|
|
48
148
|
case utils_1.AST_NODE_TYPES.CallExpression:
|
|
49
149
|
// Check for Promise.all
|
|
50
150
|
if (isPromiseAll(current)) {
|
|
51
|
-
|
|
151
|
+
// Check if Promise.all contains mixed operation types
|
|
152
|
+
if (current.arguments.length > 0) {
|
|
153
|
+
let hasSetterCalls = false;
|
|
154
|
+
let hasNonSetterCalls = false;
|
|
155
|
+
// Handle direct array expressions
|
|
156
|
+
if (current.arguments[0].type === utils_1.AST_NODE_TYPES.ArrayExpression) {
|
|
157
|
+
const arrayElements = current.arguments[0].elements;
|
|
158
|
+
// Analyze each element in the Promise.all array
|
|
159
|
+
for (const element of arrayElements) {
|
|
160
|
+
if (!element)
|
|
161
|
+
continue;
|
|
162
|
+
// Skip spread elements for now
|
|
163
|
+
if (element.type === utils_1.AST_NODE_TYPES.SpreadElement)
|
|
164
|
+
continue;
|
|
165
|
+
// Check all possible calls in this element
|
|
166
|
+
const allCalls = getAllCallsFromExpression(element);
|
|
167
|
+
let hasSetterInElement = false;
|
|
168
|
+
let hasNonSetterInElement = false;
|
|
169
|
+
for (const call of allCalls) {
|
|
170
|
+
if (isSetterCall(call)) {
|
|
171
|
+
hasSetterInElement = true;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
hasNonSetterInElement = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (hasSetterInElement) {
|
|
178
|
+
hasSetterCalls = true;
|
|
179
|
+
}
|
|
180
|
+
if (hasNonSetterInElement) {
|
|
181
|
+
hasNonSetterCalls = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// For non-array expressions (e.g., variables, method calls),
|
|
187
|
+
// we can't easily analyze the contents, so assume mixed operations
|
|
188
|
+
hasNonSetterCalls = true;
|
|
189
|
+
}
|
|
190
|
+
// If we have both setter calls and non-setter calls, don't flag
|
|
191
|
+
// This handles the case where different operation types are mixed
|
|
192
|
+
if (hasSetterCalls && hasNonSetterCalls) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { node: current, isArrayMethod: 'map', isPromiseAll: true };
|
|
52
197
|
}
|
|
53
198
|
// Check for array methods
|
|
54
199
|
const { isValid, methodName: currentMethodName } = isArrayMethod(current);
|
|
@@ -79,6 +224,27 @@ function findLoopNode(node) {
|
|
|
79
224
|
}
|
|
80
225
|
return undefined;
|
|
81
226
|
}
|
|
227
|
+
function describeLoopContext(loopInfo) {
|
|
228
|
+
if (loopInfo.isPromiseAll)
|
|
229
|
+
return 'Promise.all()';
|
|
230
|
+
if (loopInfo.isArrayMethod) {
|
|
231
|
+
return `${loopInfo.isArrayMethod}() callback`;
|
|
232
|
+
}
|
|
233
|
+
switch (loopInfo.node.type) {
|
|
234
|
+
case utils_1.AST_NODE_TYPES.ForStatement:
|
|
235
|
+
return 'for loop';
|
|
236
|
+
case utils_1.AST_NODE_TYPES.ForInStatement:
|
|
237
|
+
return 'for...in loop';
|
|
238
|
+
case utils_1.AST_NODE_TYPES.ForOfStatement:
|
|
239
|
+
return 'for...of loop';
|
|
240
|
+
case utils_1.AST_NODE_TYPES.WhileStatement:
|
|
241
|
+
return 'while loop';
|
|
242
|
+
case utils_1.AST_NODE_TYPES.DoWhileStatement:
|
|
243
|
+
return 'do...while loop';
|
|
244
|
+
default:
|
|
245
|
+
return 'this control flow';
|
|
246
|
+
}
|
|
247
|
+
}
|
|
82
248
|
function isFirestoreSetterInstance(node) {
|
|
83
249
|
// Check if it's a DocSetter instance
|
|
84
250
|
if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
@@ -144,6 +310,7 @@ function isSetterMethodCall(node) {
|
|
|
144
310
|
return { isValid: true, methodName, setterInstance };
|
|
145
311
|
}
|
|
146
312
|
const loopSetterCalls = new Map();
|
|
313
|
+
const reportedLoops = new Set();
|
|
147
314
|
exports.preferBatchOperations = (0, createRule_1.createRule)({
|
|
148
315
|
name: 'prefer-batch-operations',
|
|
149
316
|
meta: {
|
|
@@ -155,8 +322,7 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
|
|
|
155
322
|
fixable: 'code',
|
|
156
323
|
schema: [],
|
|
157
324
|
messages: {
|
|
158
|
-
|
|
159
|
-
preferOverwriteAll: 'Use overwriteAll() instead of multiple overwrite() calls for better performance',
|
|
325
|
+
preferBatch: 'DocSetter.{{setterMethod}} is invoked repeatedly inside {{contextDescription}}, which issues separate Firestore writes per document and can leave partial updates when later calls fail. Batch the documents with DocSetter.{{batchMethod}} so the writes stay grouped and latency stays predictable.',
|
|
160
326
|
},
|
|
161
327
|
},
|
|
162
328
|
defaultOptions: [],
|
|
@@ -165,6 +331,7 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
|
|
|
165
331
|
'Program:exit'() {
|
|
166
332
|
// Clear the maps for the next file
|
|
167
333
|
loopSetterCalls.clear();
|
|
334
|
+
reportedLoops.clear();
|
|
168
335
|
},
|
|
169
336
|
CallExpression(node) {
|
|
170
337
|
const { isValid, methodName, setterInstance } = isSetterMethodCall(node);
|
|
@@ -174,6 +341,13 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
|
|
|
174
341
|
const loopInfo = findLoopNode(node);
|
|
175
342
|
if (!loopInfo)
|
|
176
343
|
return;
|
|
344
|
+
const batchMethod = methodName === 'set' ? 'setAll()' : 'overwriteAll()';
|
|
345
|
+
const messageData = {
|
|
346
|
+
setterMethod: `${methodName}()`,
|
|
347
|
+
batchMethod,
|
|
348
|
+
contextDescription: describeLoopContext(loopInfo),
|
|
349
|
+
};
|
|
350
|
+
const messageId = 'preferBatch';
|
|
177
351
|
// Get or create the setter calls map for this loop
|
|
178
352
|
let setterCalls = loopSetterCalls.get(loopInfo.node);
|
|
179
353
|
if (!setterCalls) {
|
|
@@ -183,48 +357,67 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
|
|
|
183
357
|
// Track setter instance and method calls for this loop
|
|
184
358
|
const key = setterInstance;
|
|
185
359
|
const existing = setterCalls.get(key);
|
|
360
|
+
// Determine if this is a traditional loop
|
|
361
|
+
const isTraditionalLoop = !loopInfo.isArrayMethod &&
|
|
362
|
+
!loopInfo.isPromiseAll &&
|
|
363
|
+
(loopInfo.node.type === utils_1.AST_NODE_TYPES.ForStatement ||
|
|
364
|
+
loopInfo.node.type === utils_1.AST_NODE_TYPES.ForInStatement ||
|
|
365
|
+
loopInfo.node.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
|
|
366
|
+
loopInfo.node.type === utils_1.AST_NODE_TYPES.WhileStatement ||
|
|
367
|
+
loopInfo.node.type === utils_1.AST_NODE_TYPES.DoWhileStatement);
|
|
186
368
|
if (existing) {
|
|
187
369
|
// If we see a different method on the same setter instance, don't report
|
|
188
370
|
if (existing.methodName !== methodName)
|
|
189
371
|
return;
|
|
190
372
|
existing.count++;
|
|
373
|
+
// For Promise.all contexts, report only once per loop context, on the second occurrence
|
|
374
|
+
if (loopInfo.isPromiseAll &&
|
|
375
|
+
existing.count === 2 &&
|
|
376
|
+
!reportedLoops.has(loopInfo.node)) {
|
|
377
|
+
reportedLoops.add(loopInfo.node);
|
|
378
|
+
context.report({
|
|
379
|
+
node: existing.firstNode,
|
|
380
|
+
messageId,
|
|
381
|
+
data: messageData,
|
|
382
|
+
fix: () => null, // We can't provide a fix because we don't know the array structure
|
|
383
|
+
});
|
|
384
|
+
}
|
|
191
385
|
}
|
|
192
386
|
else {
|
|
193
|
-
setterCalls.set(key, { methodName, count: 1 });
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// Only check this for regular loops, not array methods or Promise.all
|
|
203
|
-
if (shouldReport && !loopInfo.isArrayMethod) {
|
|
204
|
-
const setterInstances = new Set(Array.from(setterCalls.keys()));
|
|
205
|
-
if (setterInstances.size > 1) {
|
|
206
|
-
// This is a valid use case when using multiple setters in a loop
|
|
207
|
-
// For example: userSetter.set(doc.user) and orderSetter.set(doc.order)
|
|
208
|
-
// Each setter operates on a different collection, so they can't be batched together
|
|
209
|
-
// We only want to report when using the same setter instance multiple times
|
|
210
|
-
// For example: userSetter.set(doc.user) multiple times should use userSetter.setAll()
|
|
211
|
-
if (loopInfo.node.type.startsWith('For') ||
|
|
212
|
-
loopInfo.node.type.startsWith('While') ||
|
|
213
|
-
loopInfo.node.type.startsWith('Do')) {
|
|
387
|
+
setterCalls.set(key, { methodName, count: 1, firstNode: node });
|
|
388
|
+
// Check if we now have multiple different setter instances in a traditional loop
|
|
389
|
+
if (isTraditionalLoop) {
|
|
390
|
+
const setterInstances = new Set(Array.from(setterCalls.keys()));
|
|
391
|
+
if (setterInstances.size > 1) {
|
|
392
|
+
// This is a valid use case when using multiple setters in a loop
|
|
393
|
+
// For example: userSetter.set(doc.user) and orderSetter.set(doc.order)
|
|
394
|
+
// Each setter operates on a different collection, so they can't be batched together
|
|
395
|
+
// Don't report anything for this loop
|
|
214
396
|
return;
|
|
215
397
|
}
|
|
398
|
+
// For traditional loops, report on the first occurrence since we know it's in a loop
|
|
399
|
+
if (!reportedLoops.has(loopInfo.node)) {
|
|
400
|
+
reportedLoops.add(loopInfo.node);
|
|
401
|
+
context.report({
|
|
402
|
+
node,
|
|
403
|
+
messageId,
|
|
404
|
+
data: messageData,
|
|
405
|
+
fix: () => null, // We can't provide a fix because we don't know the array structure
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// For array methods, report on the first occurrence
|
|
410
|
+
else if (loopInfo.isArrayMethod) {
|
|
411
|
+
if (!reportedLoops.has(loopInfo.node)) {
|
|
412
|
+
reportedLoops.add(loopInfo.node);
|
|
413
|
+
context.report({
|
|
414
|
+
node,
|
|
415
|
+
messageId,
|
|
416
|
+
data: messageData,
|
|
417
|
+
fix: () => null, // We can't provide a fix because we don't know the array structure
|
|
418
|
+
});
|
|
419
|
+
}
|
|
216
420
|
}
|
|
217
|
-
}
|
|
218
|
-
// Report on the first occurrence of a repeated call
|
|
219
|
-
// For Promise.all and array methods, report on the first occurrence
|
|
220
|
-
// For regular loops, report on the first occurrence too since we know it's in a loop
|
|
221
|
-
if (shouldReport) {
|
|
222
|
-
const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
|
|
223
|
-
context.report({
|
|
224
|
-
node,
|
|
225
|
-
messageId,
|
|
226
|
-
fix: () => null, // We can't provide a fix because we don't know the array structure
|
|
227
|
-
});
|
|
228
421
|
}
|
|
229
422
|
},
|
|
230
423
|
};
|
|
@@ -16,13 +16,23 @@ exports.preferBlockCommentsForDeclarations = (0, createRule_1.createRule)({
|
|
|
16
16
|
if (comment.type !== 'Line') {
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
|
-
// Ignore ESLint directive comments
|
|
19
|
+
// Ignore ESLint & TypeScript directive comments
|
|
20
20
|
const commentText = comment.value.trim();
|
|
21
|
-
if (
|
|
21
|
+
if (
|
|
22
|
+
// ESLint
|
|
23
|
+
commentText.startsWith('eslint-disable') ||
|
|
22
24
|
commentText.startsWith('eslint-enable') ||
|
|
23
25
|
commentText.startsWith('eslint-env') ||
|
|
26
|
+
commentText.startsWith('eslint ') ||
|
|
24
27
|
commentText.startsWith('global ') ||
|
|
25
|
-
commentText.startsWith('globals ')
|
|
28
|
+
commentText.startsWith('globals ') ||
|
|
29
|
+
commentText.startsWith('exported ') ||
|
|
30
|
+
// TypeScript line directives (keep as line comments)
|
|
31
|
+
/^@ts-(ignore|expect-error|check|nocheck)\b/.test(commentText) ||
|
|
32
|
+
// TypeScript triple-slash directives (value of a 'Line' comment that started with '///')
|
|
33
|
+
commentText.startsWith('/ <reference') ||
|
|
34
|
+
commentText.startsWith('/ <amd-') ||
|
|
35
|
+
commentText.startsWith('/ <jsxImportSource')) {
|
|
26
36
|
return false;
|
|
27
37
|
}
|
|
28
38
|
// Check if the comment is directly before the node
|
|
@@ -55,17 +65,22 @@ exports.preferBlockCommentsForDeclarations = (0, createRule_1.createRule)({
|
|
|
55
65
|
if (isInsideFunctionBody(node)) {
|
|
56
66
|
return;
|
|
57
67
|
}
|
|
58
|
-
const sourceCode = context.
|
|
68
|
+
const sourceCode = context.sourceCode;
|
|
59
69
|
const comments = sourceCode.getCommentsBefore(node);
|
|
60
70
|
// Find the closest comment to the node
|
|
61
71
|
const lastComment = comments[comments.length - 1];
|
|
62
72
|
if (lastComment && isLineCommentBeforeDeclaration(lastComment, node)) {
|
|
73
|
+
const commentText = lastComment.value.trim();
|
|
74
|
+
const commentLabel = commentText || 'declaration comment';
|
|
63
75
|
context.report({
|
|
64
76
|
loc: lastComment.loc,
|
|
65
77
|
messageId: 'preferBlockComment',
|
|
78
|
+
data: { commentText: commentLabel },
|
|
66
79
|
fix: (fixer) => {
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
if (commentLabel.includes('*/')) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return fixer.replaceText(lastComment, `/** ${commentLabel} */`);
|
|
69
84
|
},
|
|
70
85
|
});
|
|
71
86
|
}
|
|
@@ -119,7 +134,7 @@ exports.preferBlockCommentsForDeclarations = (0, createRule_1.createRule)({
|
|
|
119
134
|
fixable: 'code',
|
|
120
135
|
schema: [],
|
|
121
136
|
messages: {
|
|
122
|
-
preferBlockComment: '
|
|
137
|
+
preferBlockComment: 'Line comment "{{commentText}}" sits on a declaration, and IDE hovers and TypeScript docs ignore it as documentation. Rewrite it as a block comment (/** ... */) so the text stays attached to the declaration and tooling surfaces it.',
|
|
123
138
|
},
|
|
124
139
|
},
|
|
125
140
|
defaultOptions: [],
|
|
@@ -14,7 +14,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
14
14
|
fixable: 'code',
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
preferCloneDeep: 'Use cloneDeep from functions/src/util/cloneDeep.ts
|
|
17
|
+
preferCloneDeep: 'Nested spread copies only clone one level, so inner objects still point at the original and later mutations leak back. Use cloneDeep from functions/src/util/cloneDeep.ts and pass overrides as the second argument so the base object is deeply cloned before applying updates.',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultOptions: [],
|
|
@@ -74,7 +74,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
74
74
|
!hasSymbol);
|
|
75
75
|
}
|
|
76
76
|
function generateCloneDeepFix(node) {
|
|
77
|
-
const sourceCode = context.
|
|
77
|
+
const sourceCode = context.sourceCode;
|
|
78
78
|
// Find the base object (first spread element)
|
|
79
79
|
let baseObj = null;
|
|
80
80
|
// Extract the base object from the first spread element
|
|
@@ -248,7 +248,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
|
|
|
248
248
|
}
|
|
249
249
|
// Helper function to extract nested overrides without spread elements
|
|
250
250
|
function extractNestedOverrides(node) {
|
|
251
|
-
const sourceCode = context.
|
|
251
|
+
const sourceCode = context.sourceCode;
|
|
252
252
|
const overrides = [];
|
|
253
253
|
for (const prop of node.properties) {
|
|
254
254
|
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
1
2
|
type Options = [
|
|
2
3
|
{
|
|
3
4
|
object?: boolean;
|
|
4
5
|
enforceForRenamedProperties?: boolean;
|
|
5
6
|
}
|
|
6
7
|
];
|
|
7
|
-
export declare const preferDestructuringNoClass:
|
|
8
|
+
export declare const preferDestructuringNoClass: TSESLint.RuleModule<"preferDestructuring", Options, TSESLint.RuleListener>;
|
|
8
9
|
export {};
|