Learn You a Linter for Great Good!
Originally published on the Build Galvanize blog.Have you ever found yourself working on a React component and seeing one of these pesky warnings?
'foo' is missing in props validation (react/prop-types)
You may know that these come from an ESLint plugin called eslint-plugin-react—it contains ESLint rules specific to linting React components. About a year ago I started studying and subsequently contributing to this plugin. In this post I’ll explain what led me there, summarize some of the lessons I’ve learned in this time, and talk about how understanding the inner workings of your tools can help you level up as an engineer.
Motivation
I spend a lot of my time writing React components in JavaScript. I’m at a point in my career where I want to dig even deeper into these technologies I’m using on a daily basis. Simultaneously, I’ve taken an interest in the tooling that typically comes into play in React projects—think compilers, bundlers, linters, etc. These tools have to deal with the language on a completely different level than what I’m used to, which is exactly what draws me to them.
It took a while before I realized that eslint-plugin-react stands at the intersection of all these areas. But once I did, it really seemed like it could be a perfect learning opportunity for me. (Spoiler alert: it was.)
In addition, we use this plugin at Galvanize to lint all our React code. So any improvements I’d be able to make would directly benefit our R&D brigades.
What I Did
I started with smaller tasks like helping close duplicate issues and answering questions. Eventually I was able to make code contributions, such as implementing bug fixes, improving test coverage, and adding new functionality. To get a sense of what I got to work on, here are some of the highlights:
- Implemented a new
react/jsx-fragments
rule allowing you to enforce your preferred React fragment syntax:<></>
or<React.Fragment></React.Fragment>
; - Existing rules were not aware of the shorthand
<></>
syntax out of the box, so added that support as well; - Implemented the
"detect"
settings option that automatically detects what your installed React version is, so that plugin rules can adjust their behaviour accordingly; - Carried out a fairly large refactoring of four rules that need to detect what propTypes are declared for a component and which props are actually used—deduplicating common logic in a few PRs that totalled 3000+ lines deleted 🎉.
Having gained that experience, I want to share some knowledge that I acquired along the way. Here are the skills that contributing to eslint-plugin-react helped me learn or improve.
1. Gaining a Better Understanding of React
I think it’s safe to say that no matter what level of experience you have with React, you’ll learn something new when
you get familiar with eslint-plugin-react rules. Especially if you follow discussions on some of the issues in the
repo — those are often quite insightful. Beginners will be
reminded to avoid deprecated features,
while those with more experience might appreciate some specifics on exactly how
using arrow functions can leave your components displayName
-less.
Now, let’s address the elephant in the room (at Galvanize, we might also say ”put the moose on the table”). There is plenty of criticism directed at eslint-plugin-react these days. As I see it, most of it is concerned with the perceived dogmatism in some of the rules. One notorious example is the react/jsx-no-bind rule that warns when you define functions inline in a component:
function Component(props) {
return (
<button
onClick={() => {
console.log('inline functions bad'); // JSX props should not use arrow functions (react/jsx-no-bind)
}}
type="button"
/>
);
}
You may have seen this pattern used in many articles and tutorials, so you may start wondering, ”hmm, those authors really seemed like they know what they’re doing. Can I really trust this plugin to know what’s right?” In search for answers, you get to Twitter and uh-oh—you see this, by none other than Dan Abramov, a React core team member:
At this point you’re really questioning, ”so which rules are good then? How and what am I supposed to learn here?!”
Well, here’s my take. Depending on your experience, you may not have been aware that some of these issues exist at all, and that’s totally fine. But as you familiarize yourself with the plugin repo, you gradually get exposed to these kinds of problems and, importantly, discussions around them. Personally, I like to use this plugin as a starting point in my own self-education and research. I remind myself that every choice we make when coding has some trade-offs. Instead of searching for The One And Only Source Of Truth (as much as we all love Dan, I’m sure he would agree that even he isn’t one 😄), I try to focus on learning what those trade-offs are in each such situation and just deepening my understanding of the framework. If you do that, then hopefully you’ll be able to use your own judgment when you find yourself in those situations, or when configuring the linter in your own projects.
Finally, to be fair to the plugin, the recommended set of rules that it exposes is in my opinion pretty uncontroversial and should be applicable to the vast majority of projects and situations. If you’re running across a rule that seems odd, you may be extending one of the more opinionated configs such as the one published by Airbnb.
2. Gaining a Better Understanding of JavaScript
The way that ESLint works is by first building an abstract syntax tree (AST) of the code being linted, and then looking for certain patterns while traversing that tree. Different kinds of tree nodes correspond to various syntax constructs in the JavaScript language. While those nodes don’t necessarily match up with the concepts that the language specification uses, getting to know them is definitely one way of learning more about JavaScript.
As a quick example, say you’re in the repo and you see a
reference
to both ClassDeclaration
and ClassExpression
. They’re both handled with the exact same logic, so you’re wondering:
could this mean there’s more than one way to define a class? Well, you google for ”class expression” and it turns out
that there is, and the two ways are
slightly different—if you didn’t know that before, congrats, you just learned something new!
But you don’t need to know the entire spec by heart to understand how the rules work, and even to be able to contribute. As I’ve alluded to above, tools like linters are often maintained by folks with a really intimate understanding of the language who will have your back. In this case, the bulk of that work is done by Jordan Harband, who is serving on the TC39 committee as an editor for the 2019 and 2020 versions of the ECMAScript specification.
3. Being a Better Programmer
Unless building some kind of tooling is your day-to-day responsibility, the code in eslint-plugin-react will likely be very different from what you’re used to writing or reading. Being in a new environment definitely lends itself to honing your coding skills.
One particular aspect I’d like to emphasize is that I really had to get my defensive programming style together. The
essence of linting is searching for patterns in the code. When you’re working on a rule, you’re usually focused on
handling a particular code pattern, and you have a piece of test code in your head. For example, say you’re targeting
this.state
initialization in React components; you might be testing your rule on the following code:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { /* some state */ }; // <-- state initialization
}
}
But your rule will be run on a myriad of different variations of that code when shipped to end users. So it’s critical
not to make any unverified assumptions (”well, I’m seeing an assignment to this.state
, so its parent has to be a
component constructor. Right?..”), otherwise the rule is very likely to stumble on an unexpected piece of code:
class MyComponent extends React.Component {
constructor(props) {
super(props);
if (props.foo) { /*
* Now the assignment to this.state has
* a different parent. If you didn't
* expect to see an if condition here,
* the rule may crash.
*/
this.state = { /* some state */ };
} else {
this.state = { /* some other state */ };
}
}
}
(And with 5.5+ million weekly downloads of the plugin at the time of this writing, chances are you’ll find out very soon after a release is published 😄. I learned this the hard way—if you had to upgrade from 7.11.0 to 7.11.1 due to crashes, that was my bad, sorry!)
4. Learning How to Write Your Own Rules
I like to say that a coding convention is only as strong as the lint rule that enforces it. Even though not all
conventions can be enforced that way, it really helps to automate what you can, especially in a team setting. But what
if a particular convention is too project or team-specific to be supported by existing rules? For example, there’s a
rule
in eslint-plugin-react allowing you to standardize prefixes for props and methods related to event handling (e.g.,
onChange={this.handleChange}
). But what if your team wants to go further: say, in
onDateChange={this.handleDateChange}
we want the event name to always go last (so, handleDateChange
, not
handleChangeDate
). The existing eslint-plugin-react rule doesn’t support that kind of granularity, and it probably
shouldn’t—it’s supposed to stay generic and applicable to a wide variety of projects. But with some knowledge of linting
you can roll your own! And with a bit more effort, you can make that rule automatically fixable—for me, this turned into
a perfect hackfest project.
5. Writing Your Own Tooling
Getting familiar with linting opens doors to understanding how other AST-based tools work, so you can start building your own when necessary. Examples include creating your own Babel plugins or macros. But perhaps a more commonly applicable one is codemods. Especially if you’re working on a medium to large-size codebase, being able to automate large-scale code transformations is really useful. And if you look at existing tools like jscodeshift or babel-codemod, most of them are similar in principle to how you do linting; everything is based on traversing the AST and looking for particular code patterns. One difference is in applying modifications—currently auto-fixes in ESLint are pretty much restricted to basic string replacement, whereas the tools I mentioned above provide nicer APIs that allow you to ”grow” a new piece of AST in place of the old one.
This will come in handy when my company starts migrating to the new generation of our internal React component library.
To summarize, I believe that getting at least somewhat familiar with how linters work can definitely make you a better engineer and a more valuable team member. Note that most of the points above are not limited just to JavaScript and React. For example, we use rubocop to lint our Ruby code, and the principles there are very much the same.
Happy linting!