@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
|
@@ -14,61 +14,280 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
14
14
|
fixable: 'code',
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
useFastDeepEqual: '
|
|
18
|
-
|
|
17
|
+
useFastDeepEqual: "What's wrong: This code uses `{{diffName}}(...).length` as a deep equality check.\n" +
|
|
18
|
+
'Why it matters: `{{diffName}}` allocates a full change list (paths, types, values) before you compare it to zero, which hides the boolean intent and wastes memory/time.\n' +
|
|
19
|
+
'How to fix: Call `{{fastEqualName}}(left, right)` for equality (or prefix with `!` for inequality) using the same two arguments instead of counting diff length.',
|
|
20
|
+
addFastDeepEqualImport: "What's wrong: This file checks equality via `{{diffName}}(...).length` but does not import `fast-deep-equal`.\n" +
|
|
21
|
+
'Why it matters: Without `fast-deep-equal`, equality checks keep building diff entries just to count them, adding overhead and obscuring intent.\n' +
|
|
22
|
+
'How to fix: Add a default import for `fast-deep-equal` as `{{fastEqualName}}` and use `{{fastEqualName}}(a, b)` for equality checks.',
|
|
19
23
|
},
|
|
20
24
|
},
|
|
21
25
|
defaultOptions: [],
|
|
22
26
|
create(context) {
|
|
23
|
-
const sourceCode = context.
|
|
27
|
+
const sourceCode = context.sourceCode;
|
|
24
28
|
let hasFastDeepEqualImport = false;
|
|
29
|
+
let hasMicrodiffImport = false;
|
|
25
30
|
let microdiffImportName = 'diff';
|
|
26
31
|
let fastDeepEqualImportName = 'isEqual';
|
|
27
32
|
const reportedNodes = new Set();
|
|
33
|
+
let plannedFastDeepEqualImport = false;
|
|
34
|
+
const isChainExpression = (node) => node.type === utils_1.AST_NODE_TYPES.ChainExpression;
|
|
35
|
+
function isMicrodiffCallee(callee) {
|
|
36
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
37
|
+
callee.name === microdiffImportName) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
41
|
+
!callee.computed &&
|
|
42
|
+
callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
43
|
+
callee.object.name === microdiffImportName &&
|
|
44
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
45
|
+
(callee.property.name === 'diff' || callee.property.name === 'default')) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve an identifier to its variable and return the initializer CallExpression if it's a microdiff call.
|
|
52
|
+
*/
|
|
53
|
+
function findVariableInScopeChain(identifier) {
|
|
54
|
+
// Use scopeManager to resolve variable from the identifier's reference
|
|
55
|
+
const scopeManager = sourceCode.scopeManager;
|
|
56
|
+
if (!scopeManager)
|
|
57
|
+
return undefined;
|
|
58
|
+
// Find the scope for this identifier
|
|
59
|
+
let scope = null;
|
|
60
|
+
let currentNode = identifier;
|
|
61
|
+
while (currentNode) {
|
|
62
|
+
scope = scopeManager.acquire(currentNode, true) || null;
|
|
63
|
+
if (scope)
|
|
64
|
+
break;
|
|
65
|
+
currentNode = currentNode.parent;
|
|
66
|
+
}
|
|
67
|
+
if (!scope) {
|
|
68
|
+
scope = scopeManager.globalScope;
|
|
69
|
+
}
|
|
70
|
+
if (!scope)
|
|
71
|
+
return undefined;
|
|
72
|
+
// Look up variable in the scope chain
|
|
73
|
+
let currentScope = scope;
|
|
74
|
+
while (currentScope) {
|
|
75
|
+
const variable = currentScope.variables.find((v) => v.name === identifier.name);
|
|
76
|
+
if (variable)
|
|
77
|
+
return variable;
|
|
78
|
+
currentScope = currentScope.upper;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
function resolveIdentifierToMicrodiffCall(identifier) {
|
|
83
|
+
// Look for a reference in the current scope chain
|
|
84
|
+
const variable = findVariableInScopeChain(identifier);
|
|
85
|
+
if (!variable)
|
|
86
|
+
return undefined;
|
|
87
|
+
const defNode = variable.defs[0]?.node;
|
|
88
|
+
if (!defNode)
|
|
89
|
+
return undefined;
|
|
90
|
+
// Variable declarator with initializer call
|
|
91
|
+
if (defNode.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
92
|
+
const { init } = defNode;
|
|
93
|
+
if (init &&
|
|
94
|
+
init.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
95
|
+
init.callee &&
|
|
96
|
+
init.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
97
|
+
init.callee.name === microdiffImportName) {
|
|
98
|
+
return init;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Determine if the given variable is used only for `.length` property accesses.
|
|
105
|
+
* If there is any non-length usage (e.g., indexing, iteration, method calls), return false.
|
|
106
|
+
*/
|
|
107
|
+
function isVariableOnlyUsedForLength(identifier) {
|
|
108
|
+
// Walk up scope chain to find a reference for this identifier
|
|
109
|
+
const variable = findVariableInScopeChain(identifier);
|
|
110
|
+
if (!variable)
|
|
111
|
+
return false;
|
|
112
|
+
// All references must be strictly of the form <id>.length, except the variable's own declaration
|
|
113
|
+
return variable.references.every((reference) => {
|
|
114
|
+
const idNode = reference.identifier;
|
|
115
|
+
const parent = idNode.parent;
|
|
116
|
+
if (!parent)
|
|
117
|
+
return false;
|
|
118
|
+
// Allow the declaration id (not considered a read)
|
|
119
|
+
if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
120
|
+
parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
121
|
+
parent.id === idNode) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
// Accept only the specific pattern: Identifier used as object in MemberExpression with property `length`
|
|
125
|
+
if (parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
126
|
+
parent.object === idNode &&
|
|
127
|
+
!parent.computed &&
|
|
128
|
+
parent.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
129
|
+
parent.property.name === 'length') {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
// Accept optional chaining: identifier?.length represented as ChainExpression
|
|
133
|
+
if (parent.type === utils_1.AST_NODE_TYPES.ChainExpression &&
|
|
134
|
+
parent.expression.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
135
|
+
parent.expression.object === idNode &&
|
|
136
|
+
!parent.expression.computed &&
|
|
137
|
+
parent.expression.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
138
|
+
parent.expression.property.name === 'length') {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* If a MemberExpression is `<something>.length`, returns the underlying microdiff call if applicable.
|
|
146
|
+
* Supports direct `diff(a,b).length` and `changes.length` where `changes` is initialized to a microdiff call.
|
|
147
|
+
*/
|
|
148
|
+
function getMicrodiffCallFromLengthAccess(member) {
|
|
149
|
+
if (!hasMicrodiffImport)
|
|
150
|
+
return { viaIdentifier: false };
|
|
151
|
+
if (member.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
152
|
+
member.property.name !== 'length') {
|
|
153
|
+
return { viaIdentifier: false };
|
|
154
|
+
}
|
|
155
|
+
const obj = member.object;
|
|
156
|
+
if (obj.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
157
|
+
isMicrodiffCallee(obj.callee)) {
|
|
158
|
+
return { diffCall: obj, viaIdentifier: false };
|
|
159
|
+
}
|
|
160
|
+
if (obj.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
161
|
+
const resolved = resolveIdentifierToMicrodiffCall(obj);
|
|
162
|
+
if (!resolved)
|
|
163
|
+
return { viaIdentifier: true };
|
|
164
|
+
// Ensure the identifier is only used for `.length` accesses (no content-based usage)
|
|
165
|
+
if (!isVariableOnlyUsedForLength(obj)) {
|
|
166
|
+
return { viaIdentifier: true };
|
|
167
|
+
}
|
|
168
|
+
return { diffCall: resolved, viaIdentifier: true };
|
|
169
|
+
}
|
|
170
|
+
return { viaIdentifier: false };
|
|
171
|
+
}
|
|
28
172
|
/**
|
|
29
173
|
* Check if a node is a microdiff equality check pattern
|
|
30
174
|
* Looks for patterns like:
|
|
31
175
|
* - diff(a, b).length === 0
|
|
32
|
-
* - diff(a, b).length
|
|
176
|
+
* - 0 === diff(a, b).length
|
|
177
|
+
* - changes.length === 0 (where `changes = diff(a,b)` and changes is only used for length checks)
|
|
178
|
+
* - changes.length !== 0
|
|
33
179
|
* - !diff(a, b).length
|
|
180
|
+
* - !changes.length
|
|
34
181
|
*/
|
|
35
182
|
function isMicrodiffEqualityCheck(node) {
|
|
36
|
-
// Check for binary expressions
|
|
37
|
-
if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
183
|
+
// Check for binary expressions comparing length to 0 (both directions)
|
|
184
|
+
if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
185
|
+
const operators = [
|
|
186
|
+
'===',
|
|
187
|
+
'==',
|
|
188
|
+
'!==',
|
|
189
|
+
'!=',
|
|
190
|
+
];
|
|
191
|
+
if (operators.includes(node.operator)) {
|
|
192
|
+
// side A: MemberExpression .length, side B: 0
|
|
193
|
+
if (node.right.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
194
|
+
node.right.value === 0 &&
|
|
195
|
+
node.left.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
196
|
+
const { diffCall } = getMicrodiffCallFromLengthAccess(node.left);
|
|
197
|
+
if (diffCall) {
|
|
198
|
+
return {
|
|
199
|
+
isEquality: node.operator === '===' || node.operator === '==',
|
|
200
|
+
diffCall,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// side A: 0, side B: MemberExpression .length
|
|
205
|
+
if (node.left.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
206
|
+
node.left.value === 0 &&
|
|
207
|
+
node.right.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
208
|
+
const { diffCall } = getMicrodiffCallFromLengthAccess(node.right);
|
|
209
|
+
if (diffCall) {
|
|
210
|
+
return {
|
|
211
|
+
isEquality: node.operator === '===' || node.operator === '==',
|
|
212
|
+
diffCall,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Check for unary expressions like !diff(a, b).length or !changes.length (including optional chaining)
|
|
56
219
|
if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
|
|
57
|
-
node.operator === '!'
|
|
58
|
-
node.argument
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
220
|
+
node.operator === '!') {
|
|
221
|
+
const argumentNode = node.argument;
|
|
222
|
+
const target = isChainExpression(argumentNode)
|
|
223
|
+
? argumentNode.expression
|
|
224
|
+
: argumentNode;
|
|
225
|
+
if (target.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
226
|
+
const { diffCall } = getMicrodiffCallFromLengthAccess(target);
|
|
227
|
+
if (diffCall) {
|
|
228
|
+
return {
|
|
229
|
+
isEquality: true,
|
|
230
|
+
diffCall,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
68
234
|
}
|
|
69
235
|
// Not a microdiff equality check
|
|
70
236
|
return { isEquality: false };
|
|
71
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Try to find the identifier used as `<id>.length` for the given equality node
|
|
240
|
+
*/
|
|
241
|
+
function getLengthIdentifierFromNode(node) {
|
|
242
|
+
if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
243
|
+
const left = node.left;
|
|
244
|
+
const right = node.right;
|
|
245
|
+
const isLengthMember = (n) => n.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
246
|
+
!n.computed &&
|
|
247
|
+
n.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
248
|
+
n.property.name === 'length';
|
|
249
|
+
if (isLengthMember(left) &&
|
|
250
|
+
left.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
251
|
+
return left.object;
|
|
252
|
+
}
|
|
253
|
+
if (isLengthMember(right) &&
|
|
254
|
+
right.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
255
|
+
return right.object;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression) {
|
|
259
|
+
const arg = node.argument;
|
|
260
|
+
if (arg.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
261
|
+
!arg.computed &&
|
|
262
|
+
arg.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
263
|
+
arg.property.name === 'length' &&
|
|
264
|
+
arg.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
265
|
+
return arg.object;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get the indentation at the start of the current line for a node
|
|
272
|
+
*/
|
|
273
|
+
function getLineBaseIndent(node) {
|
|
274
|
+
const text = sourceCode.text;
|
|
275
|
+
const start = node.range?.[0] ?? sourceCode.getIndexFromLoc(node.loc.start);
|
|
276
|
+
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
|
277
|
+
let i = lineStart;
|
|
278
|
+
let indent = '';
|
|
279
|
+
while (i < text.length) {
|
|
280
|
+
const ch = text[i];
|
|
281
|
+
if (ch === ' ' || ch === '\t') {
|
|
282
|
+
indent += ch;
|
|
283
|
+
i++;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return indent;
|
|
290
|
+
}
|
|
72
291
|
/**
|
|
73
292
|
* Create a fix for replacing microdiff equality check with fast-deep-equal
|
|
74
293
|
*/
|
|
@@ -79,21 +298,90 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
79
298
|
}
|
|
80
299
|
const arg1 = sourceCode.getText(args[0]);
|
|
81
300
|
const arg2 = sourceCode.getText(args[1]);
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
301
|
+
const fixes = [];
|
|
302
|
+
// Add import if needed (only once across all fixes in this file)
|
|
303
|
+
if (!hasFastDeepEqualImport && !plannedFastDeepEqualImport) {
|
|
85
304
|
const importDeclarations = sourceCode.ast.body.filter((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
|
|
86
305
|
const microdiffImport = importDeclarations.find((node) => node.source.value === 'microdiff');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
306
|
+
if (microdiffImport) {
|
|
307
|
+
fixes.push(fixer.insertTextAfter(microdiffImport, `\nimport ${fastDeepEqualImportName} from 'fast-deep-equal';`));
|
|
308
|
+
plannedFastDeepEqualImport = true;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
const lastImport = importDeclarations[importDeclarations.length - 1];
|
|
312
|
+
if (lastImport) {
|
|
313
|
+
fixes.push(fixer.insertTextAfter(lastImport, `\nimport ${fastDeepEqualImportName} from 'fast-deep-equal';`));
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
fixes.push(fixer.insertTextBeforeRange([0, 0], `import ${fastDeepEqualImportName} from 'fast-deep-equal';\n`));
|
|
317
|
+
}
|
|
318
|
+
plannedFastDeepEqualImport = true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// If the equality was via an identifier like `changes.length`, and that identifier
|
|
322
|
+
// is declared as `const changes = diff(...);` and used ONLY for `.length` checks,
|
|
323
|
+
// remove the redundant variable declaration.
|
|
324
|
+
const maybeIdentifier = getLengthIdentifierFromNode(node);
|
|
325
|
+
if (maybeIdentifier && isVariableOnlyUsedForLength(maybeIdentifier)) {
|
|
326
|
+
const variable = findVariableInScopeChain(maybeIdentifier);
|
|
327
|
+
const defNode = variable?.defs?.[0]?.node;
|
|
328
|
+
if (defNode &&
|
|
329
|
+
defNode.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
330
|
+
defNode.parent &&
|
|
331
|
+
defNode.parent.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
332
|
+
const declaration = defNode.parent;
|
|
333
|
+
// Only remove if it's the only declaration in the const/let statement
|
|
334
|
+
if (declaration.declarations.length === 1) {
|
|
335
|
+
const text = sourceCode.text;
|
|
336
|
+
const startOfLine = text.lastIndexOf('\n', declaration.range[0] - 1) + 1;
|
|
337
|
+
const nextNewline = text.indexOf('\n', declaration.range[1]);
|
|
338
|
+
const endOfLine = nextNewline === -1 ? declaration.range[1] : nextNewline + 1;
|
|
339
|
+
fixes.push(fixer.removeRange([startOfLine, endOfLine]));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Replace the equality expression with isEqual call
|
|
344
|
+
const isMultilineCall = diffCall.loc &&
|
|
345
|
+
args[0].loc &&
|
|
346
|
+
(diffCall.loc.start.line !== args[0].loc.start.line ||
|
|
347
|
+
(args[1] && args[0].loc.start.line !== args[1].loc.start.line));
|
|
348
|
+
let replacement;
|
|
349
|
+
if (isMultilineCall) {
|
|
350
|
+
const baseIndent = getLineBaseIndent(node);
|
|
351
|
+
const innerIndentPlus = baseIndent + ' ';
|
|
352
|
+
replacement =
|
|
353
|
+
`${fastDeepEqualImportName}(` +
|
|
354
|
+
`\n${innerIndentPlus}${arg1},` +
|
|
355
|
+
`\n${innerIndentPlus}${arg2},` +
|
|
356
|
+
`\n${baseIndent})`;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
replacement = `${fastDeepEqualImportName}(${arg1}, ${arg2})`;
|
|
360
|
+
}
|
|
361
|
+
if (!isEquality) {
|
|
362
|
+
replacement = `!${replacement}`;
|
|
363
|
+
}
|
|
364
|
+
fixes.push(fixer.replaceText(node, replacement));
|
|
365
|
+
return fixes;
|
|
366
|
+
}
|
|
367
|
+
function reportEqualityCheck(node, result) {
|
|
368
|
+
const { diffCall, isEquality } = result;
|
|
369
|
+
if (!diffCall)
|
|
370
|
+
return;
|
|
371
|
+
if (reportedNodes.has(node))
|
|
372
|
+
return;
|
|
373
|
+
reportedNodes.add(node);
|
|
374
|
+
context.report({
|
|
375
|
+
node,
|
|
376
|
+
messageId: 'useFastDeepEqual',
|
|
377
|
+
data: {
|
|
378
|
+
diffName: microdiffImportName,
|
|
379
|
+
fastEqualName: fastDeepEqualImportName,
|
|
380
|
+
},
|
|
381
|
+
fix(fixer) {
|
|
382
|
+
return createFix(fixer, node, diffCall, isEquality);
|
|
383
|
+
},
|
|
384
|
+
});
|
|
97
385
|
}
|
|
98
386
|
return {
|
|
99
387
|
// Track imports of microdiff and fast-deep-equal
|
|
@@ -101,12 +389,16 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
101
389
|
const importSource = node.source.value;
|
|
102
390
|
// Check for microdiff import
|
|
103
391
|
if (importSource === 'microdiff') {
|
|
392
|
+
hasMicrodiffImport = true;
|
|
104
393
|
// Get the local name of the imported diff function
|
|
105
394
|
node.specifiers.forEach((specifier) => {
|
|
106
395
|
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
107
396
|
specifier.imported.name === 'diff') {
|
|
108
397
|
microdiffImportName = specifier.local.name;
|
|
109
398
|
}
|
|
399
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
400
|
+
microdiffImportName = specifier.local.name;
|
|
401
|
+
}
|
|
110
402
|
});
|
|
111
403
|
}
|
|
112
404
|
// Check for fast-deep-equal import
|
|
@@ -123,58 +415,34 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
123
415
|
},
|
|
124
416
|
// Check expressions for microdiff equality patterns
|
|
125
417
|
['BinaryExpression, UnaryExpression'](node) {
|
|
418
|
+
if (!hasMicrodiffImport)
|
|
419
|
+
return;
|
|
126
420
|
// Skip if we've already reported this node
|
|
127
421
|
if (reportedNodes.has(node)) {
|
|
128
422
|
return;
|
|
129
423
|
}
|
|
130
|
-
|
|
131
|
-
if (result.isEquality !== undefined && result.diffCall) {
|
|
132
|
-
reportedNodes.add(node);
|
|
133
|
-
context.report({
|
|
134
|
-
node,
|
|
135
|
-
messageId: 'useFastDeepEqual',
|
|
136
|
-
fix(fixer) {
|
|
137
|
-
return createFix(fixer, node, result.diffCall, result.isEquality);
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
}
|
|
424
|
+
reportEqualityCheck(node, isMicrodiffEqualityCheck(node));
|
|
141
425
|
},
|
|
142
426
|
// Check if statements for microdiff equality patterns
|
|
143
427
|
IfStatement(node) {
|
|
428
|
+
if (!hasMicrodiffImport)
|
|
429
|
+
return;
|
|
144
430
|
// Skip if we've already reported this node
|
|
145
431
|
if (reportedNodes.has(node.test)) {
|
|
146
432
|
return;
|
|
147
433
|
}
|
|
148
|
-
|
|
149
|
-
if (result.isEquality !== undefined && result.diffCall) {
|
|
150
|
-
reportedNodes.add(node.test);
|
|
151
|
-
context.report({
|
|
152
|
-
node: node.test,
|
|
153
|
-
messageId: 'useFastDeepEqual',
|
|
154
|
-
fix(fixer) {
|
|
155
|
-
return createFix(fixer, node.test, result.diffCall, result.isEquality);
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
}
|
|
434
|
+
reportEqualityCheck(node.test, isMicrodiffEqualityCheck(node.test));
|
|
159
435
|
},
|
|
160
436
|
// Check return statements for microdiff equality patterns
|
|
161
437
|
ReturnStatement(node) {
|
|
438
|
+
if (!hasMicrodiffImport)
|
|
439
|
+
return;
|
|
162
440
|
// Skip if we've already reported this node or if there's no argument
|
|
163
|
-
|
|
441
|
+
const argument = node.argument;
|
|
442
|
+
if (!argument || reportedNodes.has(argument)) {
|
|
164
443
|
return;
|
|
165
444
|
}
|
|
166
|
-
|
|
167
|
-
if (result.isEquality !== undefined && result.diffCall) {
|
|
168
|
-
reportedNodes.add(node.argument);
|
|
169
|
-
context.report({
|
|
170
|
-
node: node.argument,
|
|
171
|
-
messageId: 'useFastDeepEqual',
|
|
172
|
-
fix(fixer) {
|
|
173
|
-
// We already checked that node.argument is not null above
|
|
174
|
-
return createFix(fixer, node.argument, result.diffCall, result.isEquality);
|
|
175
|
-
},
|
|
176
|
-
});
|
|
177
|
-
}
|
|
445
|
+
reportEqualityCheck(argument, isMicrodiffEqualityCheck(argument));
|
|
178
446
|
},
|
|
179
447
|
};
|
|
180
448
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const firestoreTransactionReadsBeforeWrites: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"readsAfterWrites", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|