@blumintinc/eslint-plugin-blumint 1.13.0 → 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 +170 -139
- package/lib/index.js +190 -1
- package/lib/rules/array-methods-this-context.js +1 -1
- 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.js +3 -4
- package/lib/rules/enforce-assert-safe-object-key.js +11 -11
- package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
- package/lib/rules/enforce-callback-memo.js +1 -1
- package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
- package/lib/rules/enforce-console-error.js +35 -7
- 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-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 +1 -1
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
- package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
- package/lib/rules/enforce-firestore-facade.js +4 -2
- package/lib/rules/enforce-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.js +1 -1
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.js +2 -6
- package/lib/rules/enforce-memoize-getters.js +39 -15
- package/lib/rules/enforce-microdiff.js +1 -1
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +4 -0
- package/lib/rules/enforce-props-argument-name.js +2 -2
- 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 +1 -1
- 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.js +7 -3
- 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 +5 -4
- 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 +40 -13
- package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
- package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- 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/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.js +9 -3
- 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 +7 -3
- package/lib/rules/no-compositing-layer-props.js +2 -2
- 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.js +4 -6
- package/lib/rules/no-excessive-parent-chain.js +1 -1
- 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 +67 -12
- 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 +1 -1
- 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.js +6 -6
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
- 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.js +29 -17
- package/lib/rules/no-passthrough-getters.js +38 -2
- 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.js +16 -3
- 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.js +11 -10
- package/lib/rules/no-separate-loading-state.js +7 -16
- package/lib/rules/no-stale-state-across-await.js +1 -1
- package/lib/rules/no-type-assertion-returns.js +43 -49
- package/lib/rules/no-undefined-null-passthrough.js +61 -31
- 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 -1
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unused-props.d.ts +2 -2
- package/lib/rules/no-unused-props.js +295 -91
- 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.js +6 -4
- package/lib/rules/parallelize-async-operations.js +24 -5
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +32 -5
- package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
- 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-field-paths-in-transforms.js +4 -3
- package/lib/rules/prefer-fragment-component.js +1 -1
- 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.js +88 -13
- 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.js +60 -69
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
- package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
- package/lib/rules/prefer-params-over-parent-id.js +220 -171
- package/lib/rules/prefer-settings-object.js +2 -2
- package/lib/rules/prefer-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
- 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.js +2 -2
- 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 +1 -1
- package/lib/rules/require-memo.js +1 -1
- 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 +2 -3
- package/lib/rules/test-file-location-enforcement.js +1 -1
- package/lib/rules/use-latest-callback.js +6 -5
- 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 +24 -0
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +3 -1
|
@@ -14,13 +14,17 @@ 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;
|
|
25
29
|
let hasMicrodiffImport = false;
|
|
26
30
|
let microdiffImportName = 'diff';
|
|
@@ -360,6 +364,25 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
360
364
|
fixes.push(fixer.replaceText(node, replacement));
|
|
361
365
|
return fixes;
|
|
362
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
|
+
});
|
|
385
|
+
}
|
|
363
386
|
return {
|
|
364
387
|
// Track imports of microdiff and fast-deep-equal
|
|
365
388
|
ImportDeclaration(node) {
|
|
@@ -398,18 +421,7 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
398
421
|
if (reportedNodes.has(node)) {
|
|
399
422
|
return;
|
|
400
423
|
}
|
|
401
|
-
|
|
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
|
-
});
|
|
424
|
+
reportEqualityCheck(node, isMicrodiffEqualityCheck(node));
|
|
413
425
|
},
|
|
414
426
|
// Check if statements for microdiff equality patterns
|
|
415
427
|
IfStatement(node) {
|
|
@@ -419,18 +431,7 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
419
431
|
if (reportedNodes.has(node.test)) {
|
|
420
432
|
return;
|
|
421
433
|
}
|
|
422
|
-
|
|
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
|
-
});
|
|
434
|
+
reportEqualityCheck(node.test, isMicrodiffEqualityCheck(node.test));
|
|
434
435
|
},
|
|
435
436
|
// Check return statements for microdiff equality patterns
|
|
436
437
|
ReturnStatement(node) {
|
|
@@ -441,18 +442,7 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
|
|
|
441
442
|
if (!argument || reportedNodes.has(argument)) {
|
|
442
443
|
return;
|
|
443
444
|
}
|
|
444
|
-
|
|
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
|
-
});
|
|
445
|
+
reportEqualityCheck(argument, isMicrodiffEqualityCheck(argument));
|
|
456
446
|
},
|
|
457
447
|
};
|
|
458
448
|
},
|
|
@@ -16,7 +16,7 @@ exports.firestoreTransactionReadsBeforeWrites = (0, createRule_1.createRule)({
|
|
|
16
16
|
},
|
|
17
17
|
schema: [],
|
|
18
18
|
messages: {
|
|
19
|
-
readsAfterWrites: '
|
|
19
|
+
readsAfterWrites: 'Read operation "{{readMethod}}" runs after transaction writes ({{writeMethods}}). Firestore transactions must collect every read before any write to keep a consistent snapshot and avoid Firestore retries that reapply writes with stale data. Move this read before the first write in the same transaction callback.',
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
defaultOptions: [],
|
|
@@ -139,6 +139,48 @@ exports.firestoreTransactionReadsBeforeWrites = (0, createRule_1.createRule)({
|
|
|
139
139
|
const isWrite = WRITE_OPERATIONS.has(methodName);
|
|
140
140
|
return { isRead, isWrite, methodName };
|
|
141
141
|
}
|
|
142
|
+
function formatMethodName(callExpression) {
|
|
143
|
+
const callee = callExpression.callee;
|
|
144
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
145
|
+
return 'this transaction read';
|
|
146
|
+
}
|
|
147
|
+
const objectName = callee.object.type === utils_1.AST_NODE_TYPES.Identifier
|
|
148
|
+
? callee.object.name
|
|
149
|
+
: 'transaction';
|
|
150
|
+
if (callee.computed) {
|
|
151
|
+
if (callee.property.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
152
|
+
return `${objectName}[${String(callee.property.value)}]`;
|
|
153
|
+
}
|
|
154
|
+
if (callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
155
|
+
return `${objectName}[${callee.property.name}]`;
|
|
156
|
+
}
|
|
157
|
+
return `${objectName}[computed]`;
|
|
158
|
+
}
|
|
159
|
+
if (callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
160
|
+
return `${objectName}.${callee.property.name}`;
|
|
161
|
+
}
|
|
162
|
+
return `${objectName}[unknown]`;
|
|
163
|
+
}
|
|
164
|
+
function formatWriteMethods(writeNodes) {
|
|
165
|
+
const methodNames = writeNodes
|
|
166
|
+
.map((node) => node.type === utils_1.AST_NODE_TYPES.CallExpression
|
|
167
|
+
? formatMethodName(node)
|
|
168
|
+
: null)
|
|
169
|
+
.filter((name) => Boolean(name));
|
|
170
|
+
if (!methodNames.length) {
|
|
171
|
+
return 'earlier transaction writes';
|
|
172
|
+
}
|
|
173
|
+
const uniqueNames = [...new Set(methodNames)];
|
|
174
|
+
if (uniqueNames.length === 1) {
|
|
175
|
+
return uniqueNames[0];
|
|
176
|
+
}
|
|
177
|
+
if (uniqueNames.length === 2) {
|
|
178
|
+
return `${uniqueNames[0]} and ${uniqueNames[1]}`;
|
|
179
|
+
}
|
|
180
|
+
return `${uniqueNames
|
|
181
|
+
.slice(0, uniqueNames.length - 1)
|
|
182
|
+
.join(', ')} and ${uniqueNames[uniqueNames.length - 1]}`;
|
|
183
|
+
}
|
|
142
184
|
return {
|
|
143
185
|
// Track transaction scopes from runTransaction calls
|
|
144
186
|
'CallExpression[callee.property.name="runTransaction"]'(node) {
|
|
@@ -186,7 +228,7 @@ exports.firestoreTransactionReadsBeforeWrites = (0, createRule_1.createRule)({
|
|
|
186
228
|
// Check all call expressions for transaction operations
|
|
187
229
|
CallExpression(node) {
|
|
188
230
|
const transactionScope = findTransactionScope(node);
|
|
189
|
-
const { isRead, isWrite
|
|
231
|
+
const { isRead, isWrite } = isTransactionMethodCall(node, transactionScope);
|
|
190
232
|
if (!isRead && !isWrite) {
|
|
191
233
|
return;
|
|
192
234
|
}
|
|
@@ -202,10 +244,12 @@ exports.firestoreTransactionReadsBeforeWrites = (0, createRule_1.createRule)({
|
|
|
202
244
|
// This is a computed property access - we need to be more careful
|
|
203
245
|
// Only flag as a read if we already have writes in this scope
|
|
204
246
|
if (scopeInfo.hasWriteOperation) {
|
|
247
|
+
const readMethod = formatMethodName(node);
|
|
248
|
+
const writeMethods = formatWriteMethods(scopeInfo.writeNodes);
|
|
205
249
|
context.report({
|
|
206
250
|
node,
|
|
207
251
|
messageId: 'readsAfterWrites',
|
|
208
|
-
data: {
|
|
252
|
+
data: { readMethod, writeMethods },
|
|
209
253
|
});
|
|
210
254
|
}
|
|
211
255
|
// Don't mark as write since we don't know what method it is
|
|
@@ -218,10 +262,12 @@ exports.firestoreTransactionReadsBeforeWrites = (0, createRule_1.createRule)({
|
|
|
218
262
|
}
|
|
219
263
|
// If it's a read operation and the scope already has a write, report an error
|
|
220
264
|
if (isRead && scopeInfo.hasWriteOperation) {
|
|
265
|
+
const readMethod = formatMethodName(node);
|
|
266
|
+
const writeMethods = formatWriteMethods(scopeInfo.writeNodes);
|
|
221
267
|
context.report({
|
|
222
268
|
node,
|
|
223
269
|
messageId: 'readsAfterWrites',
|
|
224
|
-
data: {
|
|
270
|
+
data: { readMethod, writeMethods },
|
|
225
271
|
});
|
|
226
272
|
}
|
|
227
273
|
},
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flattenPushCalls = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const PUSH_METHOD_NAME = 'push';
|
|
7
|
+
function getRangeStart(node, sourceCode) {
|
|
8
|
+
return node.range?.[0] ?? sourceCode.getIndexFromLoc(node.loc.start);
|
|
9
|
+
}
|
|
10
|
+
function getRangeEnd(node, sourceCode) {
|
|
11
|
+
return node.range?.[1] ?? sourceCode.getIndexFromLoc(node.loc.end);
|
|
12
|
+
}
|
|
13
|
+
function unwrapExpression(expression) {
|
|
14
|
+
let current = expression;
|
|
15
|
+
/**
|
|
16
|
+
* Peel off harmless wrappers to compare the underlying array identity
|
|
17
|
+
* (e.g., arr!.push(), arr as Foo, (arr).push()).
|
|
18
|
+
*/
|
|
19
|
+
while (true) {
|
|
20
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
|
|
21
|
+
current = current.expression;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
25
|
+
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
26
|
+
current.type === utils_1.AST_NODE_TYPES.TSInstantiationExpression ||
|
|
27
|
+
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression) {
|
|
28
|
+
current = current.expression;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (current.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
32
|
+
current = current
|
|
33
|
+
.expression;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
return current;
|
|
39
|
+
}
|
|
40
|
+
function getPropertyKey(property, computed) {
|
|
41
|
+
if (!computed && property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
42
|
+
return property.name;
|
|
43
|
+
}
|
|
44
|
+
if (property.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
45
|
+
const value = typeof property.value === 'string' || typeof property.value === 'number'
|
|
46
|
+
? property.value
|
|
47
|
+
: null;
|
|
48
|
+
return value !== null ? String(value) : null;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function getCalleeWithTypeParams(call, sourceCode) {
|
|
53
|
+
const calleeStart = getRangeStart(call.callee, sourceCode);
|
|
54
|
+
const calleeEnd = call.typeParameters
|
|
55
|
+
? getRangeEnd(call.typeParameters, sourceCode)
|
|
56
|
+
: getRangeEnd(call.callee, sourceCode);
|
|
57
|
+
return sourceCode.text.slice(calleeStart, calleeEnd);
|
|
58
|
+
}
|
|
59
|
+
function getPreferredCalleeText(group) {
|
|
60
|
+
const withTypeParams = group.find((entry) => entry.call.typeParameters);
|
|
61
|
+
return withTypeParams ? withTypeParams.calleeText : group[0].calleeText;
|
|
62
|
+
}
|
|
63
|
+
function getExpressionIdentity(expression) {
|
|
64
|
+
const node = unwrapExpression(expression);
|
|
65
|
+
switch (node.type) {
|
|
66
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
67
|
+
return `id:${node.name}`;
|
|
68
|
+
case utils_1.AST_NODE_TYPES.ThisExpression:
|
|
69
|
+
return 'this';
|
|
70
|
+
case utils_1.AST_NODE_TYPES.Super:
|
|
71
|
+
return 'super';
|
|
72
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
73
|
+
if (typeof node.value === 'string' || typeof node.value === 'number') {
|
|
74
|
+
return `lit:${String(node.value)}`;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
case utils_1.AST_NODE_TYPES.MemberExpression: {
|
|
78
|
+
if (node.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier)
|
|
79
|
+
return null;
|
|
80
|
+
const objectKey = getExpressionIdentity(node.object);
|
|
81
|
+
const propertyKey = getPropertyKey(node.property, Boolean(node.computed));
|
|
82
|
+
if (!objectKey || !propertyKey)
|
|
83
|
+
return null;
|
|
84
|
+
return `${objectKey}.${propertyKey}`;
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isSafeMemberChain(expression) {
|
|
91
|
+
const node = unwrapExpression(expression);
|
|
92
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
93
|
+
node.type === utils_1.AST_NODE_TYPES.ThisExpression ||
|
|
94
|
+
node.type === utils_1.AST_NODE_TYPES.Super) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
98
|
+
if (node.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (node.computed || node.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return isSafeMemberChain(node.object);
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
function hasForbiddenSideEffects(node) {
|
|
109
|
+
if (!node)
|
|
110
|
+
return false;
|
|
111
|
+
const astNodeType = node.type;
|
|
112
|
+
if (astNodeType === 'ParenthesizedExpression') {
|
|
113
|
+
return hasForbiddenSideEffects(node.expression);
|
|
114
|
+
}
|
|
115
|
+
switch (node.type) {
|
|
116
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
117
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
118
|
+
case utils_1.AST_NODE_TYPES.ThisExpression:
|
|
119
|
+
case utils_1.AST_NODE_TYPES.Super:
|
|
120
|
+
return false;
|
|
121
|
+
case utils_1.AST_NODE_TYPES.CallExpression:
|
|
122
|
+
case utils_1.AST_NODE_TYPES.NewExpression:
|
|
123
|
+
case utils_1.AST_NODE_TYPES.UpdateExpression:
|
|
124
|
+
case utils_1.AST_NODE_TYPES.AwaitExpression:
|
|
125
|
+
case utils_1.AST_NODE_TYPES.YieldExpression:
|
|
126
|
+
case utils_1.AST_NODE_TYPES.TaggedTemplateExpression:
|
|
127
|
+
case utils_1.AST_NODE_TYPES.ImportExpression:
|
|
128
|
+
case utils_1.AST_NODE_TYPES.AssignmentExpression:
|
|
129
|
+
return true;
|
|
130
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
131
|
+
if (node.operator === 'delete')
|
|
132
|
+
return true;
|
|
133
|
+
return hasForbiddenSideEffects(node.argument);
|
|
134
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression:
|
|
135
|
+
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
136
|
+
return (hasForbiddenSideEffects(node.left) ||
|
|
137
|
+
hasForbiddenSideEffects(node.right));
|
|
138
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
139
|
+
return (hasForbiddenSideEffects(node.test) ||
|
|
140
|
+
hasForbiddenSideEffects(node.consequent) ||
|
|
141
|
+
hasForbiddenSideEffects(node.alternate));
|
|
142
|
+
case utils_1.AST_NODE_TYPES.MemberExpression:
|
|
143
|
+
return (hasForbiddenSideEffects(node.object) ||
|
|
144
|
+
(node.computed &&
|
|
145
|
+
hasForbiddenSideEffects(node.property)));
|
|
146
|
+
case utils_1.AST_NODE_TYPES.ChainExpression:
|
|
147
|
+
return hasForbiddenSideEffects(node.expression);
|
|
148
|
+
case utils_1.AST_NODE_TYPES.SequenceExpression:
|
|
149
|
+
return node.expressions.some((expr) => hasForbiddenSideEffects(expr));
|
|
150
|
+
case utils_1.AST_NODE_TYPES.TemplateLiteral:
|
|
151
|
+
return node.expressions.some((expr) => hasForbiddenSideEffects(expr));
|
|
152
|
+
case utils_1.AST_NODE_TYPES.ArrayExpression:
|
|
153
|
+
return node.elements.some((elem) => elem ? hasForbiddenSideEffects(elem) : false);
|
|
154
|
+
case utils_1.AST_NODE_TYPES.ObjectExpression:
|
|
155
|
+
return node.properties.some((prop) => {
|
|
156
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
157
|
+
return ((prop.computed &&
|
|
158
|
+
hasForbiddenSideEffects(prop.key)) ||
|
|
159
|
+
hasForbiddenSideEffects(prop.value));
|
|
160
|
+
}
|
|
161
|
+
if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
162
|
+
return hasForbiddenSideEffects(prop.argument);
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
});
|
|
166
|
+
case utils_1.AST_NODE_TYPES.SpreadElement:
|
|
167
|
+
return hasForbiddenSideEffects(node.argument);
|
|
168
|
+
case utils_1.AST_NODE_TYPES.TSAsExpression:
|
|
169
|
+
case utils_1.AST_NODE_TYPES.TSTypeAssertion:
|
|
170
|
+
case utils_1.AST_NODE_TYPES.TSNonNullExpression:
|
|
171
|
+
case utils_1.AST_NODE_TYPES.TSInstantiationExpression:
|
|
172
|
+
case utils_1.AST_NODE_TYPES.TSSatisfiesExpression:
|
|
173
|
+
return hasForbiddenSideEffects(node.expression);
|
|
174
|
+
default:
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function canSafelyFix(group) {
|
|
179
|
+
return group.every((entry) => {
|
|
180
|
+
const callee = entry.call.callee;
|
|
181
|
+
if (callee.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier ||
|
|
182
|
+
callee.computed ||
|
|
183
|
+
callee.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (!isSafeMemberChain(callee.object)) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (hasForbiddenSideEffects(callee.object)) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return entry.call.arguments.every((arg) => {
|
|
193
|
+
if (arg.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
194
|
+
return !hasForbiddenSideEffects(arg.argument);
|
|
195
|
+
}
|
|
196
|
+
return !hasForbiddenSideEffects(arg);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function isPushCallStatement(statement, sourceCode) {
|
|
201
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.ExpressionStatement)
|
|
202
|
+
return null;
|
|
203
|
+
const expr = statement.expression;
|
|
204
|
+
if (expr.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
205
|
+
return null;
|
|
206
|
+
if (expr.optional)
|
|
207
|
+
return null;
|
|
208
|
+
const callee = expr.callee;
|
|
209
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
|
|
210
|
+
callee.optional ||
|
|
211
|
+
callee.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const propertyName = callee.property.type === utils_1.AST_NODE_TYPES.Identifier
|
|
215
|
+
? callee.property.name
|
|
216
|
+
: callee.property.type === utils_1.AST_NODE_TYPES.Literal
|
|
217
|
+
? callee.property.value
|
|
218
|
+
: null;
|
|
219
|
+
if (propertyName !== PUSH_METHOD_NAME)
|
|
220
|
+
return null;
|
|
221
|
+
const targetKey = getExpressionIdentity(callee.object);
|
|
222
|
+
if (!targetKey)
|
|
223
|
+
return null;
|
|
224
|
+
return {
|
|
225
|
+
statement,
|
|
226
|
+
call: expr,
|
|
227
|
+
targetKey,
|
|
228
|
+
calleeText: getCalleeWithTypeParams(expr, sourceCode),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function getLineIndent(targetNode, sourceCode) {
|
|
232
|
+
const start = getRangeStart(targetNode, sourceCode);
|
|
233
|
+
const text = sourceCode.text;
|
|
234
|
+
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
|
235
|
+
const indentMatch = text.slice(lineStart, start).match(/^[\t ]*/u);
|
|
236
|
+
return indentMatch ? indentMatch[0] : '';
|
|
237
|
+
}
|
|
238
|
+
function normalizeIndentation(text) {
|
|
239
|
+
const lines = text.split('\n');
|
|
240
|
+
const indents = lines
|
|
241
|
+
.slice(1)
|
|
242
|
+
.filter((line) => line.trim().length > 0)
|
|
243
|
+
.map((line) => line.match(/^[\t ]*/u)?.[0].length ?? 0);
|
|
244
|
+
const minIndent = indents.length > 0 ? Math.min(...indents) : 0;
|
|
245
|
+
if (minIndent === 0)
|
|
246
|
+
return text;
|
|
247
|
+
return [
|
|
248
|
+
lines[0],
|
|
249
|
+
...lines
|
|
250
|
+
.slice(1)
|
|
251
|
+
.map((line) => line.slice(Math.min(minIndent, line.length))),
|
|
252
|
+
].join('\n');
|
|
253
|
+
}
|
|
254
|
+
function indentText(text, indent) {
|
|
255
|
+
const normalized = normalizeIndentation(text);
|
|
256
|
+
return normalized
|
|
257
|
+
.split('\n')
|
|
258
|
+
.map((line) => indent + line)
|
|
259
|
+
.join('\n');
|
|
260
|
+
}
|
|
261
|
+
function formatComments(comments, indent, sourceCode) {
|
|
262
|
+
return comments.map((comment) => indentText(sourceCode.getText(comment), indent));
|
|
263
|
+
}
|
|
264
|
+
function getLeadingCommentsBetween(sourceCode, previousStatement, current) {
|
|
265
|
+
const previousEnd = getRangeEnd(previousStatement, sourceCode);
|
|
266
|
+
return sourceCode
|
|
267
|
+
.getCommentsBefore(current)
|
|
268
|
+
.filter((comment) => comment.range[0] >= previousEnd);
|
|
269
|
+
}
|
|
270
|
+
exports.flattenPushCalls = (0, createRule_1.createRule)({
|
|
271
|
+
name: 'flatten-push-calls',
|
|
272
|
+
meta: {
|
|
273
|
+
type: 'suggestion',
|
|
274
|
+
docs: {
|
|
275
|
+
description: 'Consolidate consecutive push calls on the same array into a single push with multiple arguments.',
|
|
276
|
+
recommended: 'error',
|
|
277
|
+
},
|
|
278
|
+
fixable: 'code',
|
|
279
|
+
schema: [],
|
|
280
|
+
messages: {
|
|
281
|
+
flattenPushCalls: [
|
|
282
|
+
'What’s wrong: "{{target}}" is pushed to using multiple consecutive ".push(...)" calls.',
|
|
283
|
+
'Why it matters: repeated calls add property-access overhead and obscure that these values belong to one append operation.',
|
|
284
|
+
'How to fix: merge them into a single ".push(...)" call with multiple arguments (for example, "{{target}}.push(a, b, c)").',
|
|
285
|
+
].join(' '),
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
defaultOptions: [],
|
|
289
|
+
create(context) {
|
|
290
|
+
const sourceCode = context.getSourceCode();
|
|
291
|
+
function buildSegments(group) {
|
|
292
|
+
return group.map((entry, index) => {
|
|
293
|
+
const previousStatement = index > 0 ? group[index - 1].statement : null;
|
|
294
|
+
const comments = previousStatement === null
|
|
295
|
+
? []
|
|
296
|
+
: getLeadingCommentsBetween(sourceCode, previousStatement, entry.statement);
|
|
297
|
+
return { comments, args: entry.call.arguments };
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function shouldUseMultilineFormat(segments, group, totalArgs) {
|
|
301
|
+
const hasInterstitialComments = segments.some((segment) => segment.comments.length > 0);
|
|
302
|
+
const hasMultilineArgument = group.some((entry) => entry.call.arguments.some((arg) => sourceCode.getText(arg).includes('\n')));
|
|
303
|
+
return hasInterstitialComments || hasMultilineArgument || totalArgs > 2;
|
|
304
|
+
}
|
|
305
|
+
function detectSemicolon(first, last) {
|
|
306
|
+
return (sourceCode.getLastToken(last.statement)?.value === ';' ||
|
|
307
|
+
sourceCode.getLastToken(first.statement)?.value === ';');
|
|
308
|
+
}
|
|
309
|
+
function formatArguments(segments, shouldUseMultiline, argumentIndent) {
|
|
310
|
+
if (!shouldUseMultiline) {
|
|
311
|
+
return segments.flatMap((segment) => segment.args.map((arg) => sourceCode.getText(arg)));
|
|
312
|
+
}
|
|
313
|
+
return formatMultilineArguments(segments, argumentIndent);
|
|
314
|
+
}
|
|
315
|
+
function formatMultilineArguments(segments, argumentIndent) {
|
|
316
|
+
const argumentParts = [];
|
|
317
|
+
let pendingComments = [];
|
|
318
|
+
segments.forEach((segment) => {
|
|
319
|
+
const formattedComments = formatComments(segment.comments, argumentIndent, sourceCode);
|
|
320
|
+
pendingComments = pendingComments.concat(formattedComments);
|
|
321
|
+
if (segment.args.length === 0)
|
|
322
|
+
return;
|
|
323
|
+
segment.args.forEach((arg, index) => {
|
|
324
|
+
const argText = indentText(sourceCode.getText(arg), argumentIndent);
|
|
325
|
+
if (index === 0 && pendingComments.length > 0) {
|
|
326
|
+
argumentParts.push(`${pendingComments.join('\n')}\n${argText}`);
|
|
327
|
+
pendingComments = [];
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
argumentParts.push(argText);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
return attachTrailingComments(argumentParts, pendingComments);
|
|
335
|
+
}
|
|
336
|
+
function attachTrailingComments(argumentParts, pendingComments) {
|
|
337
|
+
if (pendingComments.length > 0 && argumentParts.length > 0) {
|
|
338
|
+
const lastIndex = argumentParts.length - 1;
|
|
339
|
+
argumentParts[lastIndex] = `${argumentParts[lastIndex]}\n${pendingComments.join('\n')}`;
|
|
340
|
+
}
|
|
341
|
+
return argumentParts;
|
|
342
|
+
}
|
|
343
|
+
function buildFinalReplacement(calleeText, argumentParts, shouldUseMultiline, baseIndent, hasSemicolon) {
|
|
344
|
+
const argsText = shouldUseMultiline
|
|
345
|
+
? `\n${argumentParts.join(',\n')}\n${baseIndent}`
|
|
346
|
+
: argumentParts.join(', ');
|
|
347
|
+
return `${calleeText}(${argsText})${hasSemicolon ? ';' : ''}`;
|
|
348
|
+
}
|
|
349
|
+
function buildReplacement(group) {
|
|
350
|
+
const first = group[0];
|
|
351
|
+
const last = group[group.length - 1];
|
|
352
|
+
const totalArgs = group.reduce((count, item) => count + item.call.arguments.length, 0);
|
|
353
|
+
const calleeText = getPreferredCalleeText(group);
|
|
354
|
+
const segments = buildSegments(group);
|
|
355
|
+
const shouldUseMultiline = shouldUseMultilineFormat(segments, group, totalArgs);
|
|
356
|
+
const baseIndent = getLineIndent(first.statement, sourceCode);
|
|
357
|
+
const argumentIndent = `${baseIndent} `;
|
|
358
|
+
const argumentParts = formatArguments(segments, shouldUseMultiline, argumentIndent);
|
|
359
|
+
const hasSemicolon = detectSemicolon(first, last);
|
|
360
|
+
const replacement = buildFinalReplacement(calleeText, argumentParts, shouldUseMultiline, baseIndent, hasSemicolon);
|
|
361
|
+
const currentText = sourceCode
|
|
362
|
+
.getText()
|
|
363
|
+
.slice(first.statement.range[0], last.statement.range[1]);
|
|
364
|
+
return currentText === replacement ? null : replacement;
|
|
365
|
+
}
|
|
366
|
+
function findConsecutivePushGroup(statements, startIndex, firstInfo) {
|
|
367
|
+
const group = [firstInfo];
|
|
368
|
+
let cursor = startIndex + 1;
|
|
369
|
+
while (cursor < statements.length) {
|
|
370
|
+
const next = isPushCallStatement(statements[cursor], sourceCode);
|
|
371
|
+
if (!next || next.targetKey !== firstInfo.targetKey)
|
|
372
|
+
break;
|
|
373
|
+
group.push(next);
|
|
374
|
+
cursor += 1;
|
|
375
|
+
}
|
|
376
|
+
return { group, nextIndex: cursor };
|
|
377
|
+
}
|
|
378
|
+
function shouldReportViolation(group) {
|
|
379
|
+
if (group.length <= 1)
|
|
380
|
+
return false;
|
|
381
|
+
const totalArgs = group.reduce((count, entry) => count + entry.call.arguments.length, 0);
|
|
382
|
+
const firstArgs = group[0].call.arguments.length;
|
|
383
|
+
return totalArgs > firstArgs && canSafelyFix(group);
|
|
384
|
+
}
|
|
385
|
+
function reportViolation(group) {
|
|
386
|
+
context.report({
|
|
387
|
+
node: group[0].call.callee,
|
|
388
|
+
messageId: 'flattenPushCalls',
|
|
389
|
+
data: {
|
|
390
|
+
target: sourceCode.getText(group[0].call.callee.object),
|
|
391
|
+
},
|
|
392
|
+
fix(fixer) {
|
|
393
|
+
const replacement = buildReplacement(group);
|
|
394
|
+
if (!replacement)
|
|
395
|
+
return null;
|
|
396
|
+
return fixer.replaceTextRange([
|
|
397
|
+
group[0].statement.range[0],
|
|
398
|
+
group[group.length - 1].statement.range[1],
|
|
399
|
+
], replacement);
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
function checkStatements(statements) {
|
|
404
|
+
for (let i = 0; i < statements.length; i++) {
|
|
405
|
+
const info = isPushCallStatement(statements[i], sourceCode);
|
|
406
|
+
if (!info)
|
|
407
|
+
continue;
|
|
408
|
+
const { group, nextIndex } = findConsecutivePushGroup(statements, i, info);
|
|
409
|
+
if (shouldReportViolation(group)) {
|
|
410
|
+
reportViolation(group);
|
|
411
|
+
}
|
|
412
|
+
i = nextIndex - 1;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
Program(node) {
|
|
417
|
+
checkStatements(node.body);
|
|
418
|
+
},
|
|
419
|
+
BlockStatement(node) {
|
|
420
|
+
checkStatements(node.body);
|
|
421
|
+
},
|
|
422
|
+
SwitchCase(node) {
|
|
423
|
+
checkStatements(node.consequent);
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
//# sourceMappingURL=flatten-push-calls.js.map
|