Beware of auto-fixes done by linters

Nicolas Dubien
2 min readMar 31, 2018

--

The title might seem alarmist but auto-fixes provided by linters might really break down your code if you don’t mind so much about using them.

This article comes as a result of a past debug and review session following a regression introduced by a linter. See it as a simple experience sharing. The bug occurred while running tslint on fast-check source code. It took me a bit of time to fully understand what went wrong.

Before moving on to the issue itself, I just want to draw the line that differentiates linters from formatters.

Formatters vs.Linters

Formatters like Prettier are not Linters. They should be used for different purposes and in conjunction:

  • Formatters handle all the formatting related challenges going from line length to parameters alignment. They should not alter the way the code behave. They deal with formatting only.
    More examples on Prettier options
  • Linters, in another hand, deal with code style in terms of language features you can or can’t use. For instance a TypeScript linter will deal with problems like: Can I use var keyword? Can I use index-based loop or do I use for-of? Can I use require?
    More examples on TSLint rules

Another explanation of the differences is available at: https://prettier.io/docs/en/comparison.html

Problem in a nutshell

The idea to apply formatters and linters to my code came as a need. Indeed having a well formatted, structured and not too dangerous code style became an important point.

So I decided to run tslint in conjunction with tslint-microsoft-contrib and tslint-config-prettier. Unfortunately I did not imagine that it will introduce changes in the way my code behaves. But it did.

Indeed some rules might be a bit more dangerous than what they look. For instance the no-unnecessary-callback-wrapper rule can breaks your code if you don’t check the produced code in details. For instance the following code:

data.map(v=> f(v))

Will be changed into:

data.map(f)

But they are not fully equivalent. Indeed map has the following signature:

map<U>(mapFunction: (value: T, index: number) => U)

In my code, f function is called array. It accepts the following signatures:

array<T>(generator: Arbitrary<T>)
array<T>(generator: Arbitrary<T>, max: number)

The change suggested by the linter was changing the way the code worked by calling the second signature instead of the first one.

Possible solutions

Whenever you encounter this kind of issue with linters you can opt for one of the following solutions:

  • do not use auto-fix anymore
  • remove the rule from the set of rules you are using
  • ask the linter to ignore this line
  • revamp the way you write your code

Do not hesitate to leave a comment or clap if you liked or not ;)

Linting on going. I might update the article to provide a list of rules introducing possible unexpected changes.

If you also encountered this problem on eslint or tslint, I would be glad to know the rule that caused you the problem.

--

--