Auto-format and lint everything

Build faster with automated formatting, linting, and vulnerability scanning

May 30, 2021 5 min read

When building software I want to automate everything possible.

Automated code formatting, linting, and vulnerability scanning helps teams I work with write better code, faster.

Critically, these automations make it easier to write, read, and maintain code.

Overview

  1. Add essential linters and auto-formatters for your code
  2. Use keybindings in your IDE to auto-format code with a few key strokes
  3. Add lint-staged to automatically format and lint code before every commit
  4. Have CI, Circle CI or Github Actions, run full linters and vulnerability scanners in parallel with your test suite
  5. Require all Pull Requests have your lint jobs pass before merging

Add essential linters and auto-formatters

Good linters and auto-formatters have sane defaults, allow some customization, and integrate with your automated test suite / CI setup.

Rubocop config

In your .rubocop.yml setup, enable NewCops by default:

#`rubocop` to run normally
#`rubocop --auto-gen-config` to update .rubocop_todo.yml
inherit_from: .rubocop_todo.yml

require:
  - rubocop-minitest
  - rubocop-performance
  - rubocop-rails
  - rubocop-rake
  - rubocop-thread_safety

AllCops:
  TargetRubyVersion: 2.7
  TargetRailsVersion: 6.1
  Exclude:
    - 'db/**/*'
    - 'node_modules/**/*'
    - 'vendor/bundle/**/*'
  NewCops: enable

Rails:
  Enabled: true

Prettier, ESLint, and Stylelint configs

In your package.json

{
  // ...
  "eslintConfig": {
    // "env": {...}
    "extends": ["eslint:recommended", "prettier"],
    "parser": "@babel/eslint-parser",
    "rules": {
      "class-methods-use-this": 2,
      "no-console": 2
    },
    "reportUnusedDisableDirectives": true
  },
  "prettier": {
    "semi": false,
    "singleQuote": true,
    "trailingComma": "none"
  },
  "stylelint": {
    "extends": ["stylelint-config-standard", "stylelint-config-prettier"],
    "plugins": ["stylelint-scss"]
  },
  "pre-commit": ["lint-staged"]
}

Use keybindings in your IDE to auto-format code with a few key strokes

For VS Code .vscode/tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "rubocop auto-correct file",
      "type": "shell",
      "command": "bundle exec rubocop -A ${relativeFile}",
      "problemMatcher": [],
      "presentation": {
        "reveal": "always",
        "panel": "dedicated"
      }
    },
    {
      "label": "erblint auto-correct file",
      "type": "shell",
      "command": "bundle exec erblint -a ${relativeFile}",
      "problemMatcher": [],
      "presentation": {
        "reveal": "always",
        "panel": "dedicated"
      }
    },
    {
      "label": "prettier auto-correct file",
      "type": "shell",
      "command": "yarn prettier --write ${relativeFile}",
      "problemMatcher": [],
      "presentation": {
        "reveal": "always",
        "panel": "dedicated"
      }
    }
  ]
}

And your VS Code keybindings.json to set up autocorrect from pressing a combination of keys when looking at a file.

[
  {
    "key": "ctrl+cmd+shift+r",
    "command": "workbench.action.tasks.runTask",
    "args": "rubocop auto-correct file"
  },
  {
    "key": "ctrl+cmd+shift+e",
    "command": "workbench.action.tasks.runTask",
    "args": "erblint auto-correct file"
  },
  {
    "key": "ctrl+cmd+shift+p",
    "command": "workbench.action.tasks.runTask",
    "args": "prettier auto-correct file"
  }
]

And in your package.json add these "scripts":

{
  // ...
  "scripts": {
    "fix": "yarn css:fix; yarn js:fix; yarn md:fix; yarn json:fix",
    "lint": "yarn css:lint; yarn js:lint; yarn md:lint; yarn json:lint",
    "css:fix": "yarn prettier --write 'app/**/*.scss'; yarn stylelint --fix 'app/**/*.scss'",
    "css:lint": "stylelint 'app/**/*.scss'",
    "erb:fix": "bundle exec erblint -a --lint-all",
    "erb:lint": "bundle exec erblint --lint-all",
    "i18n:fix": "i18n-tasks normalize",
    "i18n:lint": "i18n-tasks health",
    "md:fix": "yarn prettier --write './**/*.md'",
    "md:lint": "yarn prettier --check './**/*.md'",
    "json:fix": "yarn prettier --write '*.json'; yarn prettier --write 'config/**/*.json'",
    "json:lint": "yarn prettier --check '*.json'; yarn prettier --check 'config/**/*.json'",
    "js:fix": "yarn prettier --write './**/*.js'; yarn eslint --fix app",
    "js:lint": "yarn prettier --check './**/*.js'; yarn eslint app",
    "ruby:fix": "bundle exec rubocop -a",
    "ruby:lint": "bundle exec rubocop",
    "eslint": "eslint",
    "prettier": "prettier",
    "lint-staged": "$(yarn bin)/lint-staged"
  }
}

Add lint-staged to automatically format and lint code before every commit

Install with yarn add -D lint-staged and add a lint-staged.config.js file:

// lint-staged.config.js
module.exports = {
  '(app|config|lib|test)/**/*.rb': (files) =>
    `bundle exec rubocop ${files.join(' ')}`,
  '**/*.html.erb': (files) => `bundle exec erblint ${files.join(' ')}`,
  'config/locales/**/*.yml': () => 'yarn i18n:fix',
  './**/*.md': ['prettier --write'],
  './**/*.js': ['prettier --write', 'eslint --fix'],
  './**/*.scss': ['stylelint --fix', 'prettier --write']
}

Have CI, Circle CI or Github Actions, run full linters and vulnerability scanners in parallel with your test suite

# .circleci/config.yml

#
# Jobs have two parts: the execution environment and a set of steps.
jobs:
  setup:
  # Install libraries like gems, packages and cache for next steps in parallel

  test:
  # Run test suite...

  lint:
    # resource_class: ...
    # executor: ...
    steps:
      - prepare-workspace
      - run:
          name: Lint JS
          command: yarn js:lint
      - run:
          name: Lint CSS
          command: yarn css:lint
      - run:
          name: Lint Ruby
          command: bundle exec rubocop
      - run:
          name: Lint Rails erb templates
          command: bundle exec erblint --lint-all
      - run:
          name: Scan for Rails code vulnerabilities
          command: |
            gem install brakeman
            brakeman            
      - run:
          name: Scan for bundled Ruby gem vulnerabilities
          command: |
            gem install bundler-audit
            bundle audit check --update            
      - run:
          name: Scan for Ruby and RubyGems system vulnerabilities
          command: |
            bundle add ruby_audit --group "test"
            bundle exec ruby-audit check            

#
# Workflows are sequences of jobs. Both lint and test will run in parallel once
# setup completes.
workflows:
  build:
    jobs:
      - setup:
      - lint:
          requires:
            - setup
      - test:
          requires:
            - setup

Require all Pull Requests have your lint jobs pass before merging

Update your GitHub repository Settings for Branches to “Require status checks to pass before merging”. Select the check that only passes in the lint job in your CI passes.

This ensures your team writes consistent code, even as the team grows.

Read more posts like this in the Software Engineering Toolbox collection.
Visit homepage
comments powered by Disqus