If you not familiar with pre-commit, check the previous article: Git hooks and pre-commit.

I always find myself consulting documentation to remember how to configure tools, so this is a compiled reference of pre-commit hooks I use most.

Each section contains hooks for a language, or a set of languages. The first code block always shows the repository and hook mappings in the .pre-commit-config.yaml file. A baseline for configuration of the hook or tool is also given. Refer to the linked documentations for fine-tuning to your needs.

As sometimes rules do not apply or they are false positives, every code linter has a way to ignore rules. Usually it can be done in the command line, in the configuration file, or in code itself. These options are illustrated in each section, when applicable.

General

pre-commit has its own set of hooks that are quite generic and can be used in any project. Details about each hook can be found in the pre-commit-hooks repository.

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
      - id: check-added-large-files
        args: ["--maxkb=512"]
      - id: check-merge-conflict
      - id: check-toml
      - id: end-of-file-fixer
      - id: requirements-txt-fixer
      - id: trailing-whitespace

Python

ruff

Ruff really impressed me as an all-in-one code formatter and linter for Python. It wraps a lot of tools and extensions in a single place and is faster (it is written in Rust, actually).

Ruff also has an extension for VS Code.

Astral, the developers of Ruff, are coming up with several promising tools for the Python ecosystem. I recommend checking out rye, a project and package manager.

repos:
  # Format/lint: Python
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.2
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

Ruff can be configured through a pyproject.toml, ruff.toml, or .ruff.toml file. See Configurating Ruff for the default configuration and Settings for a comprehensive list of configuration options.

# pyproject.toml

[tool.ruff]
target-version = "py310"

[tool.ruff.lint]
select = [
  "E4", "E7", "E9", # pycodestyle
  "F",              # Pyflakes
  "I",              # isort
  "A",              # flake8-builtins
  "B",              # flake8-bugbear
  "SIM",            # flake8-simplify
]
unfixable = ["F401"]

The inline ignore syntax is:

#> Ignore rule F841
x = 0  # noqa: F841

#> Ignore rules E741 and F841
i = 1  # noqa: E741, F841

#> Ignore _all_ violations
j = 2  # noqa

The syntax applies to flake8 (see below).

black + flake8 + isort

I highly recommend using Ruff instead of these because it wraps so much in a single tool (and configuration file).

The canonical code formatter and linter tools for Python.

  • black: code formatter
  • flake8: code linter
  • isort: sort Python imports
repos:
  # Format: Python
  - repo: https://github.com/psf/black
    rev: 24.4.2
    hooks:
      - id: black

  # Lint: Python
  - repo: https://github.com/PyCQA/flake8
    rev: 7.0.0
    hooks:
      - id: flake8

  # Format: Python imports
  - repo: https://github.com/PyCQA/isort
    rev: 5.13.2
    hooks:
      - id: isort

Configuration is more troublesome, because you have to look for options in a bunch of different documentations and may be a lot of tweaking between tools. That is why I am enjoying Ruff so much. But here is a sample configuration:

# pyproject.toml

[tool.black]
target-version = ["py310"]
line-length = 88
include = '\.pyi?$'
extend-exclude = '''
/(
    \.bzr
  |\.git-rewrite
  |\.pants.d
  |\.pyenv
  |\.pytype
  |buck-out
  |node_modules
  |site-packages
)/
'''

[tool.isort]
profile = "black"
line_length = 88
multi_line_output = 3
include_trailing_comma = true
virtual_env = "venv"
# .flake8 (flake8 does not support pyproject.toml)

[flake8]
extend-exclude =
  .bzr,
  .git-rewrite,
  .pants.d,
  .pyenv,
  .pytype,
  buck-out,
  node_modules,
  site-packages
extend-ignore =
  "E501", # Line too long
  "W503", # Line break occurred before binary operator
  "E226", # Missing white space around arithmetic operator

R

Code lint and formatting are powered by lintr and styler, respectively.

  • lintr: code lint
  • style-files: code formatting
  • no-print-statement: Check if a R file contains a print() statement
repos:
  # Format/lint: R
  - repo: https://github.com/lorenzwalthert/precommit
    rev: v0.4.2
    hooks:
      - id: lintr
        name: Lint R
      - id: style-files
        name: Format R
      - id: no-print-statement
        name: Check print statement

The repository includes other useful hooks directed at packages like roxygen2 and pkgdown.

You may configure lintr with the .lintr file. See Configuring linters and Available linters for more configuration options.

# .lintr

linters: linters_with_defaults(
    line_length_linter(120),
    commented_code_linter = NULL
  )

The inline ignore syntax is:

  • Ignore current line:

    #> Ignore line
    ignore( this) # styler: off
    f( ) # not ignored anymore
    
  • Ignore code block:

    blibala= 3
    
    # styler: off
    I_have(good+reasons, to = turn_off,
    styler
    )
    # styler: on
    
    1+1
    

SQL

SQLFluff is a code linter and formatter for several dialects of SQL. It helps detecting bad practices and inneficiencies in your query, besides auto-formatting your code. Great tool for refactoring prototype or legacy code.

repos:
  - repo: https://github.com/sqlfluff/sqlfluff
    rev: stable_version
    hooks:
      - id: sqlfluff-lint
        args: [--dialect, postgres]
      - id: sqlfluff-fix
        args: [--dialect, postgres]

SQLFluff support several configuration files, such as .sqlfluff and pyproject.toml. Check the Default Configuration and Rule index for full reference.

# pyproject.toml

[tool.sqlfluff.core]
templater = "jinja"
sql_file_exts = ".sql,.sql.j2,.sql.jinja2"

[tool.sqlfluff.indentation]
indented_joins = false
indented_using_on = true
template_blocks_indent = false

[tool.sqlfluff.templater]
unwrap_wrapped_queries = true

[tool.sqlfluff.templater.jinja]
apply_dbt_builtins = false

# For rule specific configuration, use dots between the names exactly
# as you would in .sqlfluff. In the background, SQLFluff will unpack the
# configuration paths accordingly.
[tool.sqlfluff.rules.capitalisation.keywords]
capitalisation_policy = "upper"

The inline ignore syntax is:

  • Ignore current line:

    -- Ignore one or more rules
    SELECT
      a.a*a.b AS bad_1,  -- noqa: LT01
      a.a *  a.b AS bad_2,  -- noqa: LT01, LT03
    FROM my_table
    
  • Ignore code block:

    -- Ignore rule AL02 from this line forward
    SELECT col_a a FROM foo -- noqa: disable=AL02
    
    -- Ignore all rules from this line forward
    SELECT col_a a FROM foo -- noqa: disable=all
    
    -- Enforce all rules from this line forward
    SELECT col_a a FROM foo -- noqa: enable=all
    

Webdev, YAML, JSON, and more

Prettier is a code formatter devoted to front-end langages like JavaScript, TypeScript, HTML and many more, but it also has plugins for other languages. As you may be using only a subset of its supported languages, limiting which files Prettier will run on can improve performance.

Prettier is a good choice for YAML, JSON and Markdown files. In the configuration below, I only select files with extension .ymal, .yml, .json, and .md.

repos:
  # Format: YAML, JSON and Markdown
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v4.0.0-alpha.8
    hooks:
      - id: prettier
        files: ^(.*\.ya?ml|.*\.json|.*\.md)$

There are several configuration files names, I will use .prettierrc.yaml here. See Configuration file for more options.

# .prettierrc.yaml

tabWidth: 2
endOfLine: lf

YAML

In addition to Prettier, yamllint provides a code linter for YAML files.

repos:
  # Lint: YAML
  - repo: https://github.com/adrienverge/yamllint
    rev: v1.35.1
    hooks:
      - id: yamllint
        args: ["-d {extends: relaxed, rules: {line-length: disable}}", "-s"]
        files: \.(yaml|yml)$

The inline ignore syntax is:

  • Disable one or more rules in a line:

    # yamllint disable-line rule:line-length
    disable-rule: This line is waaaaaaaaaay too long but yamllint will not report anything about it
    
    # yamllint disable-line rule:hyphens rule:commas
    disable-rules: [hyphens, commas]
    
    # yamllint disable-line
    disable-all: Disable _all_ rules (not recommended)
    
  • Disable one or more rules in a code block:

    disable-rules:
        # yamllint disable rule:colons
        - Lorem       : ipsum
        dolor       : sit amet,
        consectetur : adipiscing elit
        # yamllint enable rule:colons
    
    disable-all:
        # yamllint disable
        - Lorem       :
                ipsum:
                dolor : [   sit,amet]
        -         consectetur : adipiscing elit
        # yamllint enable
    

Markdown

In addition to Prettier, markdownlint-cli provides a code linter for Markdown files.

repos:
  # Lint: Markdown
  - repo: https://github.com/igorshubovych/markdownlint-cli
    rev: v0.40.0
    hooks:
      - id: markdownlint
        args: ["--fix"]

Configuration can be set in a .markdownlint.yaml file. The configuration below addresses a common issue with tables: it lets them be as long as they need to. See Rules for a description of all rules.

# .markdownlint.yaml

# Default state for all rules
default: true

# MD013/line-length - Line length
MD013:
  line_length: 100
  tables: false

The inline ignore syntax is:

Disable one or more rules for the current line. <!-- markdownlint-disable-line MD001 MD005 -->

<!-- markdownlint-disable MD001 MD005 -->

Disable/enable one or more rules in a block.

- MD001: heading-increment

* MD005: list-indent

<!-- markdownlint-enable MD001 MD005 -->

<!-- markdownlint-disable -->

Disable/enable _all_ rules in a block.

<!-- markdownlint-enable -->

Shell and Bash

Shell and bash scripts are pretty common in many projects.

repos:
  # Lint: Shell scripts
  - repo: https://github.com/shellcheck-py/shellcheck-py
    rev: v0.10.0.1
    hooks:
      - id: shellcheck
        args: ["-x"]

    # Lint: Bash scripts
  - repo: https://github.com/openstack/bashate
    rev: 2.1.1
    hooks:
      - id: bashate

ShellCheck may be configured in the .shellcheckrc file.

# .shellcheckrc

# Don't suggest [ -n "$VAR" ] over [ ! -z "$VAR" ]
disable=SC2236

# Suggest ${VAR} in place of $VAR
enable=require-variable-braces

The inline ignore syntax is:

  • Disable one or more rules in a line:

    #!/bin/sh
    
    source $(dirname "$0/utils.sh")
    
    # shellcheck disable=SC2059,SC2034
    echo "Disable rules SC2059 and SC2034 for this line"
    
    # shellcheck disable=all
    echo "Disable all rules for this line"
    
  • Disable all rules in a file:

    #!/bin/sh
    # shellcheck disable=SC2059,SC2034
    
    source $(dirname "$0/utils.sh")
    
    echo "Disable rules SC2059 and SC2034 in the entire file."
    echo "Must be at the top of the file"
    

Docker

Hadolint helps a lot in improving the stability of Docker images as well as reducing their overall size.

repos:
  # Lint: Dockerfile
  - repo: https://github.com/hadolint/hadolint
    rev: v2.12.0
    hooks:
      - id: hadolint

Configuration may be done in a .hadolint.yaml file. You can find the list of Rules in the repository.

# .hadolint.yaml

ignored:
  - DL3008 # Pin versions in `apt-get install`

The inline ignore syntax is:

  • Ignore one or more rules in a file:
# hadolint global ignore=DL3003,DL3006,SC1035
FROM ubuntu:22.04

RUN echo "global"
  • Ignore one or more rules in a line:
# hadolint ignore=DL3006
FROM ubuntu

# hadolint ignore=DL3003,SC1035
RUN echo "line"