Let the computer do the work.

March 8, 2021

I don't particularly appreciate spending a fair amount of time reading through documentation about style guides, team conventions, guidelines, and more. I wouldn't say I like having to think about every reference in those documentation pages to make sure that I don't write code that is violating any of these.

That is why I usually try to let the computer do the work. Here are some notes about the tools I like to use:

ESLint

ESLint is a linting tool that can cover many aspects of your source code. For example, I could enable the eqeqeq rule and automatically check that I'm using === instead of == in my conditions.

// ESLint will suggest using === instead of ==
if (iWrite == thisCondition) {
  console.log('ESLint will complain.')
}

Since the configuration file is extensible, different settings can be shared across the team, organization, or the world. This way, I can specify rules by providing a config and, whenever a rule is broken, give feedback without me needing to check it manually.

Prettier

Promoted as an opinionated code formatter, Prettier rectifies the need to discuss things like tabs and spaces and other formatting concerns. Instead, the code is formatted through rules defined in a config file.

It can format the code on save, check if the code is not formatted correctly, and automatically fix most of the errors.

I like to use ESLint for style guide purposes (like the eqeqeq rule mentioned above) while Prettier takes care of my code's formatting.

stylelint

Stylelint is a linter similar to ESLint, but instead of checking JavaScript code, stylelint will validate your stylesheets instead.

Much of the same benefits of ESLint apply. With stylelint, I can specify and enforce code styles and practices throughout the CSS code I write.

TypeScript

As a superset of JavaScript, TypeScript enables developers to add types to JS code. The TypeScript compiler can provide quick feedback should the code contain errors that can be determined statically.

const add = (a: number, b: number) => {
  return a + b
}

add(1, 'a')

TypeScript would catch the invalid type string of the second argument we pass to the add function. These checks can catch a lot of unexpected errors even without ever having to run the code.

Testing

Writing self-testing code is a topic in itself. However, I want to give notes on some of the tools I've liked to use so far:

Jest

Jest is a testing framework that offers an extensive API for writing your tests. It comes with a nifty --watch mode, as well as a plethora of different assertion possibilities and excellent mocking capabilities. Paired with additional features such as coverage reports, custom matcher support, and plugin support, I've primarily used Jest for testing my code.

Cypress

Cypress is a great way to write E2E tests that can verify use cases involving multiple system components without mocking parts.

Instead of manually clicking around on a webpage, Cypress will go through the commands with an automated runner. While doing that, assertions can automatically check if everything is as expected.

Testing Libary suite

I am a fan of the Testing Library suite, specifically the Vue Testing Library and React Testing Library. These test utilities allow me to write my tests in a more readable way while implicitly checking other aspects of my code, such as accessibility.

import { render, screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import Counter from './my-path/Counter.vue'

test(`updates the displayed text with correct counter value`, () => {
  render(Counter)
  
  screen.getByText('Counter is: 0')
  userEvent.click(screen.getByRole('button', {name: /increment/i}))
  
  screen.findByText('Counter is: 1')
})

In the future, I want to explore the Cypress Testing Library as well to use a similar API as the other Testing Libraries for E2E tests.

Husky for git hooks

husky is a great package that lets me add git hooks with ease. These hooks provide points at which I can run custom code for various purposes.

{
  "husky": {
    "hooks": {
      "pre-commit": "eslint . --ext .js,.ts"
    }
  }
}

Most of the time, I've used husky to add a pre-commit hook that runs a custom validate script (using many of the tools mentioned in this post). On failure, the developer cannot commit the code and has first to fix the issues. That enables a rapid feedback loop and ensures that the necessary checks have been made before making a commit.

lint-staged

lint-staged is a package that runs specified commands only against staged files. That avoids checking your whole project by narrowing it down to the files that will get committed next.

For me, husky and lint-staged go hand in hand together, meaning I usually configure husky to call my lint-staged script.

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.js": "npm run validate"
  }
}

Further notes

One of the projects I didn't use yet but would like to is commitlint. commitlint can transfer rules and concepts from conventional commits to a config file. It then lints git commit messages and tries to find violations against those rules.