How to write better code by using a linter

Sometimes we build tools to help us build better software.

After all, we're humans. We make stupid grammar errors and we write code that has bugs in it.

In the following blog posts we're going to have a deep dive into machine-aided programming, and we're going to write a little helper program to aid us write better code.

Sounds fun, doesn't it? Let's get started!

What's a linter?

Have you ever, by accident, done this:

if (myVar = true) {
  doSomething()
}

Sounds like a rookie mistake, doesn't it? But I bet right now there's a developer somewhere that's trying to debug this kind of bug and getting frustrated.

This bug is a mistake in our code; there's (hopefully) no way you would mean to write that kind of statement in your code.

Wouldn't it be great if we wrote a small snippet of code that would actually prevent this happening in our codebase? "Shouldn't be hard", you might think. And boy, you're very right about that:

const matchIncorrectIfAssignments = /if\s*\([^=]*=[^=][^)]*\)/

This piece of code searches for any occurrences of if statements that only have one single equal sign inside them.

Now we could run the expression against all our code files like this:

const fs = require('fs')
const matchIncorrectIfAssignments = /.*if\s*\([^=]*=[^=][^)]*\).*/

const args = process.argv
const filesToCheck = args.slice(2)

filesToCheck
  .map(file => fs.readFileSync(file))
  .map(content =>
    matchIncorrectIfAssignments.exec(
      content
    )
  )
  .filter(matches => matches)
  .forEach(invalid =>
    console.error(
      'Invalid code detected: ' +
        invalid[0]
    )
  )

With this code we could run the error-checking script against any number of files from the command line:

node check-for-incorrect-ifs.js src/*.js

By assigning the third argument to point to several files, we can go through any number of files in our src/ folder.

More good practices

Why would we stop for just having a single rule for our small linter? There sure are more low-hanging fruits we could catch with this script.

Let's take this piece of code for example:

const myNumber = getMyNumber()
if (myNumber == false) {
  console.error(
    'Fetching my number failed'
  )
}

Ok, it might be bad practice itself to have a function return mixed values, but let's not concentrate on that now. You see here the coder is using the loose equality sign ==, and for some reason there's an expectation that the variable myNumber will sometimes have the value false. You might know that if the myNumber can have the value 0, this piece of code will still interpret it as an error case.

To avoid strange cases like this, we could restrict our codebase to only use the strict equality comparison === and let our little code-checker fail if there's any usage of the loose equality sign ==.

We could extend our syntax-checking code to support a variable number of test cases against our codebase:

const fs = require('fs')

const checkForIncorrectIfAssignments = /.*if\s*\([^=]*=[^=][^)]*\).*/g
const checkForLooseEquals = /.*[^=]==[^=].*/g

const flatten = (a, b) => a.concat(b)

const incorrectAssignments = [
  checkForIncorrectIfAssignments,
  checkForLooseEquals
]

const args = process.argv
const filesToCheck = args.slice(2)

filesToCheck
  .map(file =>
    fs.readFileSync(file).toString()
  )
  .map(content =>
    incorrectAssignments.map(test =>
      content.match(test)
    )
  )
  .reduce(flatten)
  .reduce(flatten)
  .forEach(invalid =>
    console.error(
      'Invalid code detected: ' +
        invalid
    )
  )

This code now has an array of tests that run against the code. And since all the regular expressions have the /g global modifier, they can find many occurrences of the issue in a single file. This is why we do flatten twice: Once for an array of files and once for an array of errors in the file.

Now that we have a couple of rules written for our small linter, it's rather trivial task to start extending the script with more rules. By spending a bit of extra time we could have a good, consistent ruleset of best practices to apply for our own projects.

Takeaways

Linter is a great tool to have for a dynamic language like JavaScript, because there are a lot of different ways of writing JavaScript code. Having an automated linter in your project restricts you to write consistent code and to follow best practices set by the industry.

In the next blog post we're going to have a look at the history of existing static code analyzers like JSLint, ESLint and Prettier. This helps us to gain a broader view of how these tools have evolved to their current state and to help you understand the differences and use cases for each tool.

Subscribe to the mailing list below to receive a notification when the new blog posts are out!

Read the next post here:

What's the difference between ESLint and Prettier?

Tweet

Be the first to know from new blog posts

Subscrbe to the mailing list to get priority access to new blog posts!