question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Bug: multiple replaceTextRange calls returned in an array do not work unless the order of ranges is totally ordered

See original GitHub issue

Environment

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?

@typescript-eslint/parser

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:closed
  • Created a year ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
mdjermanoviccommented, Sep 29, 2022

They seem to imply that as long as a single call to context.report specifies non-overlapping fixes in the returned array, you’re good to go. And in a sense, this seems to be true, since it does work in “the real world” even though it fails in the test.

Yes, rules generally shouldn’t be concerned about overlapping fixes. as --fix runs linting and fixing again.

Is it not worth clarifying somewhere in the docs that the RuleTester only does a single pass by design, and as such, scenarios like the once I described will not be testable?

RulTester docs do say that there is only a single pass:

  • output (string, required if the rule fixes code): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the --fix command line flag). If this is null, asserts that none of the reported problems suggest autofixes.

but that e.g. with the `--fix` command line flag part is confusing because --fix is multi-pass. PR to clarify this is welcome!

0reactions
sstchurcommented, Sep 29, 2022

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

sstchur (sstchur) - PullAnswer
Bug: multiple replaceTextRange calls returned in an array do not work unless the order of ranges is totally ordered. 0 Likes 6 Replies....
Read more >
Triage - GitHub
Bug : multiple replaceTextRange calls returned in an array do not work unless the order of ranges is totally ordered. 1 task done....
Read more >
Concatenate an array with 2 different useEffect calls react
The goal is to make 2 calls to the api (each once) and concatenate the arrays together into 1 result -- which is...
Read more >
Function.prototype.apply() - JavaScript - MDN Web Docs
The apply() method calls the specified function with a given this value, and arguments provided as an array (or an array-like object).
Read more >
Difference Array | Range update query in O(1) - GeeksforGeeks
Difference array can be used to perform range update queries “l r x” where l is left index, r is right index and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found