@blumintinc/eslint-plugin-blumint 1.12.6 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -99
- package/lib/index.js +111 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -10
- package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
- package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
- package/lib/rules/enforce-assert-throws.d.ts +3 -1
- package/lib/rules/enforce-assert-throws.js +493 -51
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
- package/lib/rules/enforce-callable-types.js +4 -4
- package/lib/rules/enforce-callback-memo.js +143 -18
- package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +250 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-exported-function-types.js +3 -3
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
- package/lib/rules/enforce-firestore-facade.js +347 -157
- package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
- package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +96 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +123 -0
- package/lib/rules/enforce-microdiff.js +2 -1
- package/lib/rules/enforce-positive-naming.js +5 -7
- package/lib/rules/enforce-props-argument-name.d.ts +2 -7
- package/lib/rules/enforce-props-argument-name.js +165 -151
- package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
- package/lib/rules/enforce-props-naming-consistency.js +145 -0
- package/lib/rules/enforce-querykey-ts.d.ts +9 -0
- package/lib/rules/enforce-querykey-ts.js +274 -0
- package/lib/rules/enforce-singular-type-names.js +9 -5
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
- package/lib/rules/enforce-verb-noun-naming.js +85 -10
- package/lib/rules/extract-global-constants.js +57 -45
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -0
- package/lib/rules/no-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +308 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-complex-cloud-params.js +15 -2
- package/lib/rules/no-compositing-layer-props.js +4 -2
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +317 -63
- package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
- package/lib/rules/no-excessive-parent-chain.js +273 -0
- package/lib/rules/no-firestore-object-arrays.js +345 -30
- package/lib/rules/no-hungarian.js +13 -13
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +16 -3
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +170 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +172 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +142 -0
- package/lib/rules/no-stale-state-across-await.d.ts +1 -0
- package/lib/rules/no-stale-state-across-await.js +185 -0
- package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
- package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
- package/lib/rules/no-type-assertion-returns.js +6 -0
- package/lib/rules/no-undefined-null-passthrough.js +166 -19
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +3 -0
- package/lib/rules/no-unused-props.js +213 -78
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +234 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +375 -0
- package/lib/rules/prefer-batch-operations.js +199 -33
- package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
- package/lib/rules/prefer-document-flattening.d.ts +4 -0
- package/lib/rules/prefer-document-flattening.js +239 -0
- package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
- package/lib/rules/prefer-fragment-component.js +3 -3
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +230 -31
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +366 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +228 -0
- package/lib/rules/prefer-settings-object.js +23 -15
- package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
- package/lib/rules/react-usememo-should-be-component.js +144 -78
- package/lib/rules/require-dynamic-firebase-imports.js +2 -1
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +11 -1
- package/lib/rules/require-usememo-object-literals.js +7 -1
- package/lib/rules/semantic-function-prefixes.js +38 -18
- package/lib/rules/sync-onwrite-name-func.js +5 -1
- package/lib/rules/test-file-location-enforcement.d.ts +1 -0
- package/lib/rules/test-file-location-enforcement.js +57 -0
- package/lib/rules/use-custom-link.js +6 -1
- package/lib/rules/use-custom-router.js +6 -1
- package/lib/rules/use-latest-callback.d.ts +3 -0
- package/lib/rules/use-latest-callback.js +270 -0
- package/lib/utils/ASTHelpers.js +10 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/package.json +21 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
1
2
|
type MessageIds = 'useFastDeepEqual' | 'addFastDeepEqualImport';
|
|
2
|
-
export declare const fastDeepEqualOverMicrodiff:
|
|
3
|
+
export declare const fastDeepEqualOverMicrodiff: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
|
|
3
4
|
export {};
|
|
@@ -22,53 +22,268 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
22
22
|
create(context) {
|
|
23
23
|
const sourceCode = context.getSourceCode();
|
|
24
24
|
let hasFastDeepEqualImport = false;
|
|
25
|
+
let hasMicrodiffImport = false;
|
|
25
26
|
let microdiffImportName = 'diff';
|
|
26
27
|
let fastDeepEqualImportName = 'isEqual';
|
|
27
28
|
const reportedNodes = new Set();
|
|
29
|
+
let plannedFastDeepEqualImport = false;
|
|
30
|
+
const isChainExpression = (node) => node.type === utils_1.AST_NODE_TYPES.ChainExpression;
|
|
31
|
+
function isMicrodiffCallee(callee) {
|
|
32
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
33
|
+
callee.name === microdiffImportName) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
37
|
+
!callee.computed &&
|
|
38
|
+
callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
39
|
+
callee.object.name === microdiffImportName &&
|
|
40
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
41
|
+
(callee.property.name === 'diff' || callee.property.name === 'default')) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve an identifier to its variable and return the initializer CallExpression if it's a microdiff call.
|
|
48
|
+
*/
|
|
49
|
+
function findVariableInScopeChain(identifier) {
|
|
50
|
+
// Use scopeManager to resolve variable from the identifier's reference
|
|
51
|
+
const scopeManager = sourceCode.scopeManager;
|
|
52
|
+
if (!scopeManager)
|
|
53
|
+
return undefined;
|
|
54
|
+
// Find the scope for this identifier
|
|
55
|
+
let scope = null;
|
|
56
|
+
let currentNode = identifier;
|
|
57
|
+
while (currentNode) {
|
|
58
|
+
scope = scopeManager.acquire(currentNode, true) || null;
|
|
59
|
+
if (scope)
|
|
60
|
+
break;
|
|
61
|
+
currentNode = currentNode.parent;
|
|
62
|
+
}
|
|
63
|
+
if (!scope) {
|
|
64
|
+
scope = scopeManager.globalScope;
|
|
65
|
+
}
|
|
66
|
+
if (!scope)
|
|
67
|
+
return undefined;
|
|
68
|
+
// Look up variable in the scope chain
|
|
69
|
+
let currentScope = scope;
|
|
70
|
+
while (currentScope) {
|
|
71
|
+
const variable = currentScope.variables.find((v) => v.name === identifier.name);
|
|
72
|
+
if (variable)
|
|
73
|
+
return variable;
|
|
74
|
+
currentScope = currentScope.upper;
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
function resolveIdentifierToMicrodiffCall(identifier) {
|
|
79
|
+
// Look for a reference in the current scope chain
|
|
80
|
+
const variable = findVariableInScopeChain(identifier);
|
|
81
|
+
if (!variable)
|
|
82
|
+
return undefined;
|
|
83
|
+
const defNode = variable.defs[0]?.node;
|
|
84
|
+
if (!defNode)
|
|
85
|
+
return undefined;
|
|
86
|
+
// Variable declarator with initializer call
|
|
87
|
+
if (defNode.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
88
|
+
const { init } = defNode;
|
|
89
|
+
if (init &&
|
|
90
|
+
init.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
91
|
+
init.callee &&
|
|
92
|
+
init.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
93
|
+
init.callee.name === microdiffImportName) {
|
|
94
|
+
return init;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Determine if the given variable is used only for `.length` property accesses.
|
|
101
|
+
* If there is any non-length usage (e.g., indexing, iteration, method calls), return false.
|
|
102
|
+
*/
|
|
103
|
+
function isVariableOnlyUsedForLength(identifier) {
|
|
104
|
+
// Walk up scope chain to find a reference for this identifier
|
|
105
|
+
const variable = findVariableInScopeChain(identifier);
|
|
106
|
+
if (!variable)
|
|
107
|
+
return false;
|
|
108
|
+
// All references must be strictly of the form <id>.length, except the variable's own declaration
|
|
109
|
+
return variable.references.every((reference) => {
|
|
110
|
+
const idNode = reference.identifier;
|
|
111
|
+
const parent = idNode.parent;
|
|
112
|
+
if (!parent)
|
|
113
|
+
return false;
|
|
114
|
+
// Allow the declaration id (not considered a read)
|
|
115
|
+
if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
116
|
+
parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
117
|
+
parent.id === idNode) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
// Accept only the specific pattern: Identifier used as object in MemberExpression with property `length`
|
|
121
|
+
if (parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
122
|
+
parent.object === idNode &&
|
|
123
|
+
!parent.computed &&
|
|
124
|
+
parent.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
125
|
+
parent.property.name === 'length') {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
// Accept optional chaining: identifier?.length represented as ChainExpression
|
|
129
|
+
if (parent.type === utils_1.AST_NODE_TYPES.ChainExpression &&
|
|
130
|
+
parent.expression.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
131
|
+
parent.expression.object === idNode &&
|
|
132
|
+
!parent.expression.computed &&
|
|
133
|
+
parent.expression.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
134
|
+
parent.expression.property.name === 'length') {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* If a MemberExpression is `<something>.length`, returns the underlying microdiff call if applicable.
|
|
142
|
+
* Supports direct `diff(a,b).length` and `changes.length` where `changes` is initialized to a microdiff call.
|
|
143
|
+
*/
|
|
144
|
+
function getMicrodiffCallFromLengthAccess(member) {
|
|
145
|
+
if (!hasMicrodiffImport)
|
|
146
|
+
return { viaIdentifier: false };
|
|
147
|
+
if (member.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
148
|
+
member.property.name !== 'length') {
|
|
149
|
+
return { viaIdentifier: false };
|
|
150
|
+
}
|
|
151
|
+
const obj = member.object;
|
|
152
|
+
if (obj.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
153
|
+
isMicrodiffCallee(obj.callee)) {
|
|
154
|
+
return { diffCall: obj, viaIdentifier: false };
|
|
155
|
+
}
|
|
156
|
+
if (obj.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
157
|
+
const resolved = resolveIdentifierToMicrodiffCall(obj);
|
|
158
|
+
if (!resolved)
|
|
159
|
+
return { viaIdentifier: true };
|
|
160
|
+
// Ensure the identifier is only used for `.length` accesses (no content-based usage)
|
|
161
|
+
if (!isVariableOnlyUsedForLength(obj)) {
|
|
162
|
+
return { viaIdentifier: true };
|
|
163
|
+
}
|
|
164
|
+
return { diffCall: resolved, viaIdentifier: true };
|
|
165
|
+
}
|
|
166
|
+
return { viaIdentifier: false };
|
|
167
|
+
}
|
|
28
168
|
/**
|
|
29
169
|
* Check if a node is a microdiff equality check pattern
|
|
30
170
|
* Looks for patterns like:
|
|
31
171
|
* - diff(a, b).length === 0
|
|
32
|
-
* - diff(a, b).length
|
|
172
|
+
* - 0 === diff(a, b).length
|
|
173
|
+
* - changes.length === 0 (where `changes = diff(a,b)` and changes is only used for length checks)
|
|
174
|
+
* - changes.length !== 0
|
|
33
175
|
* - !diff(a, b).length
|
|
176
|
+
* - !changes.length
|
|
34
177
|
*/
|
|
35
178
|
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
|
-
|
|
179
|
+
// Check for binary expressions comparing length to 0 (both directions)
|
|
180
|
+
if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
181
|
+
const operators = [
|
|
182
|
+
'===',
|
|
183
|
+
'==',
|
|
184
|
+
'!==',
|
|
185
|
+
'!=',
|
|
186
|
+
];
|
|
187
|
+
if (operators.includes(node.operator)) {
|
|
188
|
+
// side A: MemberExpression .length, side B: 0
|
|
189
|
+
if (node.right.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
190
|
+
node.right.value === 0 &&
|
|
191
|
+
node.left.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
192
|
+
const { diffCall } = getMicrodiffCallFromLengthAccess(node.left);
|
|
193
|
+
if (diffCall) {
|
|
194
|
+
return {
|
|
195
|
+
isEquality: node.operator === '===' || node.operator === '==',
|
|
196
|
+
diffCall,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// side A: 0, side B: MemberExpression .length
|
|
201
|
+
if (node.left.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
202
|
+
node.left.value === 0 &&
|
|
203
|
+
node.right.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
204
|
+
const { diffCall } = getMicrodiffCallFromLengthAccess(node.right);
|
|
205
|
+
if (diffCall) {
|
|
206
|
+
return {
|
|
207
|
+
isEquality: node.operator === '===' || node.operator === '==',
|
|
208
|
+
diffCall,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Check for unary expressions like !diff(a, b).length or !changes.length (including optional chaining)
|
|
56
215
|
if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
|
|
57
|
-
node.operator === '!'
|
|
58
|
-
node.argument
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
216
|
+
node.operator === '!') {
|
|
217
|
+
const argumentNode = node.argument;
|
|
218
|
+
const target = isChainExpression(argumentNode)
|
|
219
|
+
? argumentNode.expression
|
|
220
|
+
: argumentNode;
|
|
221
|
+
if (target.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
222
|
+
const { diffCall } = getMicrodiffCallFromLengthAccess(target);
|
|
223
|
+
if (diffCall) {
|
|
224
|
+
return {
|
|
225
|
+
isEquality: true,
|
|
226
|
+
diffCall,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
68
230
|
}
|
|
69
231
|
// Not a microdiff equality check
|
|
70
232
|
return { isEquality: false };
|
|
71
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Try to find the identifier used as `<id>.length` for the given equality node
|
|
236
|
+
*/
|
|
237
|
+
function getLengthIdentifierFromNode(node) {
|
|
238
|
+
if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
239
|
+
const left = node.left;
|
|
240
|
+
const right = node.right;
|
|
241
|
+
const isLengthMember = (n) => n.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
242
|
+
!n.computed &&
|
|
243
|
+
n.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
244
|
+
n.property.name === 'length';
|
|
245
|
+
if (isLengthMember(left) &&
|
|
246
|
+
left.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
247
|
+
return left.object;
|
|
248
|
+
}
|
|
249
|
+
if (isLengthMember(right) &&
|
|
250
|
+
right.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
251
|
+
return right.object;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression) {
|
|
255
|
+
const arg = node.argument;
|
|
256
|
+
if (arg.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
257
|
+
!arg.computed &&
|
|
258
|
+
arg.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
259
|
+
arg.property.name === 'length' &&
|
|
260
|
+
arg.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
261
|
+
return arg.object;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get the indentation at the start of the current line for a node
|
|
268
|
+
*/
|
|
269
|
+
function getLineBaseIndent(node) {
|
|
270
|
+
const text = sourceCode.text;
|
|
271
|
+
const start = node.range?.[0] ?? sourceCode.getIndexFromLoc(node.loc.start);
|
|
272
|
+
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
|
273
|
+
let i = lineStart;
|
|
274
|
+
let indent = '';
|
|
275
|
+
while (i < text.length) {
|
|
276
|
+
const ch = text[i];
|
|
277
|
+
if (ch === ' ' || ch === '\t') {
|
|
278
|
+
indent += ch;
|
|
279
|
+
i++;
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return indent;
|
|
286
|
+
}
|
|
72
287
|
/**
|
|
73
288
|
* Create a fix for replacing microdiff equality check with fast-deep-equal
|
|
74
289
|
*/
|
|
@@ -79,21 +294,71 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
79
294
|
}
|
|
80
295
|
const arg1 = sourceCode.getText(args[0]);
|
|
81
296
|
const arg2 = sourceCode.getText(args[1]);
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
297
|
+
const fixes = [];
|
|
298
|
+
// Add import if needed (only once across all fixes in this file)
|
|
299
|
+
if (!hasFastDeepEqualImport && !plannedFastDeepEqualImport) {
|
|
85
300
|
const importDeclarations = sourceCode.ast.body.filter((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
|
|
86
301
|
const microdiffImport = importDeclarations.find((node) => node.source.value === 'microdiff');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
302
|
+
if (microdiffImport) {
|
|
303
|
+
fixes.push(fixer.insertTextAfter(microdiffImport, `\nimport ${fastDeepEqualImportName} from 'fast-deep-equal';`));
|
|
304
|
+
plannedFastDeepEqualImport = true;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
const lastImport = importDeclarations[importDeclarations.length - 1];
|
|
308
|
+
if (lastImport) {
|
|
309
|
+
fixes.push(fixer.insertTextAfter(lastImport, `\nimport ${fastDeepEqualImportName} from 'fast-deep-equal';`));
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
fixes.push(fixer.insertTextBeforeRange([0, 0], `import ${fastDeepEqualImportName} from 'fast-deep-equal';\n`));
|
|
313
|
+
}
|
|
314
|
+
plannedFastDeepEqualImport = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// If the equality was via an identifier like `changes.length`, and that identifier
|
|
318
|
+
// is declared as `const changes = diff(...);` and used ONLY for `.length` checks,
|
|
319
|
+
// remove the redundant variable declaration.
|
|
320
|
+
const maybeIdentifier = getLengthIdentifierFromNode(node);
|
|
321
|
+
if (maybeIdentifier && isVariableOnlyUsedForLength(maybeIdentifier)) {
|
|
322
|
+
const variable = findVariableInScopeChain(maybeIdentifier);
|
|
323
|
+
const defNode = variable?.defs?.[0]?.node;
|
|
324
|
+
if (defNode &&
|
|
325
|
+
defNode.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
326
|
+
defNode.parent &&
|
|
327
|
+
defNode.parent.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
328
|
+
const declaration = defNode.parent;
|
|
329
|
+
// Only remove if it's the only declaration in the const/let statement
|
|
330
|
+
if (declaration.declarations.length === 1) {
|
|
331
|
+
const text = sourceCode.text;
|
|
332
|
+
const startOfLine = text.lastIndexOf('\n', declaration.range[0] - 1) + 1;
|
|
333
|
+
const nextNewline = text.indexOf('\n', declaration.range[1]);
|
|
334
|
+
const endOfLine = nextNewline === -1 ? declaration.range[1] : nextNewline + 1;
|
|
335
|
+
fixes.push(fixer.removeRange([startOfLine, endOfLine]));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Replace the equality expression with isEqual call
|
|
340
|
+
const isMultilineCall = diffCall.loc &&
|
|
341
|
+
args[0].loc &&
|
|
342
|
+
(diffCall.loc.start.line !== args[0].loc.start.line ||
|
|
343
|
+
(args[1] && args[0].loc.start.line !== args[1].loc.start.line));
|
|
344
|
+
let replacement;
|
|
345
|
+
if (isMultilineCall) {
|
|
346
|
+
const baseIndent = getLineBaseIndent(node);
|
|
347
|
+
const innerIndentPlus = baseIndent + ' ';
|
|
348
|
+
replacement =
|
|
349
|
+
`${fastDeepEqualImportName}(` +
|
|
350
|
+
`\n${innerIndentPlus}${arg1},` +
|
|
351
|
+
`\n${innerIndentPlus}${arg2},` +
|
|
352
|
+
`\n${baseIndent})`;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
replacement = `${fastDeepEqualImportName}(${arg1}, ${arg2})`;
|
|
356
|
+
}
|
|
357
|
+
if (!isEquality) {
|
|
358
|
+
replacement = `!${replacement}`;
|
|
359
|
+
}
|
|
360
|
+
fixes.push(fixer.replaceText(node, replacement));
|
|
361
|
+
return fixes;
|
|
97
362
|
}
|
|
98
363
|
return {
|
|
99
364
|
// Track imports of microdiff and fast-deep-equal
|
|
@@ -101,12 +366,16 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
101
366
|
const importSource = node.source.value;
|
|
102
367
|
// Check for microdiff import
|
|
103
368
|
if (importSource === 'microdiff') {
|
|
369
|
+
hasMicrodiffImport = true;
|
|
104
370
|
// Get the local name of the imported diff function
|
|
105
371
|
node.specifiers.forEach((specifier) => {
|
|
106
372
|
if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
107
373
|
specifier.imported.name === 'diff') {
|
|
108
374
|
microdiffImportName = specifier.local.name;
|
|
109
375
|
}
|
|
376
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
377
|
+
microdiffImportName = specifier.local.name;
|
|
378
|
+
}
|
|
110
379
|
});
|
|
111
380
|
}
|
|
112
381
|
// Check for fast-deep-equal import
|
|
@@ -123,58 +392,67 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
123
392
|
},
|
|
124
393
|
// Check expressions for microdiff equality patterns
|
|
125
394
|
['BinaryExpression, UnaryExpression'](node) {
|
|
395
|
+
if (!hasMicrodiffImport)
|
|
396
|
+
return;
|
|
126
397
|
// Skip if we've already reported this node
|
|
127
398
|
if (reportedNodes.has(node)) {
|
|
128
399
|
return;
|
|
129
400
|
}
|
|
130
401
|
const result = isMicrodiffEqualityCheck(node);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
402
|
+
const { diffCall, isEquality } = result;
|
|
403
|
+
if (!diffCall)
|
|
404
|
+
return;
|
|
405
|
+
reportedNodes.add(node);
|
|
406
|
+
context.report({
|
|
407
|
+
node,
|
|
408
|
+
messageId: 'useFastDeepEqual',
|
|
409
|
+
fix(fixer) {
|
|
410
|
+
return createFix(fixer, node, diffCall, isEquality);
|
|
411
|
+
},
|
|
412
|
+
});
|
|
141
413
|
},
|
|
142
414
|
// Check if statements for microdiff equality patterns
|
|
143
415
|
IfStatement(node) {
|
|
416
|
+
if (!hasMicrodiffImport)
|
|
417
|
+
return;
|
|
144
418
|
// Skip if we've already reported this node
|
|
145
419
|
if (reportedNodes.has(node.test)) {
|
|
146
420
|
return;
|
|
147
421
|
}
|
|
148
422
|
const result = isMicrodiffEqualityCheck(node.test);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
423
|
+
const { diffCall, isEquality } = result;
|
|
424
|
+
if (!diffCall)
|
|
425
|
+
return;
|
|
426
|
+
reportedNodes.add(node.test);
|
|
427
|
+
context.report({
|
|
428
|
+
node: node.test,
|
|
429
|
+
messageId: 'useFastDeepEqual',
|
|
430
|
+
fix(fixer) {
|
|
431
|
+
return createFix(fixer, node.test, diffCall, isEquality);
|
|
432
|
+
},
|
|
433
|
+
});
|
|
159
434
|
},
|
|
160
435
|
// Check return statements for microdiff equality patterns
|
|
161
436
|
ReturnStatement(node) {
|
|
437
|
+
if (!hasMicrodiffImport)
|
|
438
|
+
return;
|
|
162
439
|
// Skip if we've already reported this node or if there's no argument
|
|
163
|
-
|
|
440
|
+
const argument = node.argument;
|
|
441
|
+
if (!argument || reportedNodes.has(argument)) {
|
|
164
442
|
return;
|
|
165
443
|
}
|
|
166
|
-
const result = isMicrodiffEqualityCheck(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
}
|
|
444
|
+
const result = isMicrodiffEqualityCheck(argument);
|
|
445
|
+
const { diffCall, isEquality } = result;
|
|
446
|
+
if (!diffCall)
|
|
447
|
+
return;
|
|
448
|
+
reportedNodes.add(argument);
|
|
449
|
+
context.report({
|
|
450
|
+
node: argument,
|
|
451
|
+
messageId: 'useFastDeepEqual',
|
|
452
|
+
fix(fixer) {
|
|
453
|
+
return createFix(fixer, argument, diffCall, isEquality);
|
|
454
|
+
},
|
|
455
|
+
});
|
|
178
456
|
},
|
|
179
457
|
};
|
|
180
458
|
},
|
|
@@ -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>;
|