Bug: multiple replaceTextRange calls returned in an array do not work unless the order of ranges is totally ordered
See original GitHub issueEnvironment
Node version: 16.14.2 npm version: 8.5.0 Local ESLint version: 8.13.0 Global ESLint version: N/A Operating System: Ubuntu
What parser are you using?
What did you do?
Full repro available at: https://github.com/sstchur/eslint-repro
Given the following code snippet in a rule test:
// A
// B
class Person {
constructor(A: string, B: string) {}
}
And the following (contrived) rule code:
return {
Identifier(node) {
if (node.name === 'A') {
context.report({
node,
messageId: 'someError',
fix(fixer) {
return [
fixer.replaceTextRange([9,13], '// C'),
fixer.replaceTextRange([72,73], 'C')
]
}
});
}
else if (node.name === 'B') {
context.report({
node,
messageId: 'someError',
fix(fixer) {
return [
fixer.replaceTextRange([22,26], '// D'),
fixer.replaceTextRange([82,83], 'D')
];
}
});
}
}
};
The fixers for both A and B will run, but only the fixer for A will “stick” (for lack of a better word), despite these ranges all being ranges that do not overlap.
What did you expect to happen?
I expected the fixers for both A and B to run and the resulting output code to be:
// C
// D
class Person {
constructor(C: string, D: string) {}
}
What actually happened?
Instead, the resulting output is:
// C
// B
class Person {
constructor(C: string, B: string) {}
}
Actual ESLint output from running the rule test:
Expected value to strictly be equal to:
"
// C
// D
class Person {
constructor(C: string, D: string) {}
}"
Received:
"
// C
// B
class Person {
constructor(C: string, B: string) {}
}"
Message:
Output is incorrect.
Difference:
- Expected
+ Received
// C
- // D
+ // B
class Person {
- constructor(C: string, D: string) {}
+ constructor(C: string, B: string) {}
}
Participation
- I am willing to submit a pull request for this issue.
Additional comments
I think it’s actually really useful to note that if I change the order of the fixing objects so that they are effectively called in an order in which the ranges are sorted, then everything works, like so:
fix(fixer) {
return [
fixer.replaceTextRange([9,13], '// C'),
fixer.replaceTextRange([22,26], '// D'),
];
}
...
fix(fixer) {
return [
fixer.replaceTextRange([72,73], 'C')
fixer.replaceTextRange([83,84], 'D')
];
}
Note that above, the ranges are totally ordered: [9,13], [22,26], [72,73], [83,84].
The docs clearly call out the fixing objects cannot overlap. However, it says (and I quote): “If you make a fix() function which returns multiple fixing objects, those fixing objects must not be overlapped.”
My interpretation of that is that the fixing objects that are part of an array from within a fix
function must not overlap (which the ones in my example do not).
However, it seems like subsequent calls to a fixer cannot “go backwards” (so to speak). I’m not sure if the comment in the docs about “fixing object must not be overlapped” was meant to cover this case? I didn’t interpret it as such, but maybe I misunderstood.
In any case, a more fundamental question: should it be possible to calling replaceTextRange([x,y], 'whatever')
and then later call replaceTextRange([x-something, y-something], 'blah')
(so long as the ranges [x,y]
and [x-something,y-something]
are NON overlapping? Or must replaceTextRange([x-something, y-something], 'blah')
be called before replaceTextRange([x,y], 'whatever')
(as appears to be the case today)?
Issue Analytics
- State:
- Created a year ago
- Comments:6 (2 by maintainers)
Yes, rules generally shouldn’t be concerned about overlapping fixes. as
--fix
runs linting and fixing again.RulTester docs do say that there is only a single pass:
but that
e.g. with the `--fix` command line flag
part is confusing because--fix
is multi-pass. PR to clarify this is welcome!Sounds good. Thanks for your help and explanation. I’ll see if I can find some time to put together a simple PR to clarify this a little.