chrisgrieser/nvim-rulebook

github github
plugindiagnosticscomment
stars 89
issues 0
subscribers 2
forks 5
CREATED

2023-09-21

UPDATED

6 days ago


nvim-rulebook 📖

Add inline-comments to ignore rules or suppress formatters. Lookup rule documentation online. Built-in configuration for more than 50 tools.

Table of contents

Features

  • Look up official rule documentation, falling back to a customizable web search if the source does not have rule documentation.
  • Add inline-comments to ignore rules like // eslint disable-next-line some-rule. Supports previous line, same line, and enclosing lines.
  • Suppress formatting with via ignore comments of the respective formatter, such as // prettier-ignore.
  • Quality-of-life: auto-select a rule if it is the only one in the current line; if the line has no diagnostic, search forward to the next line that does.
  • Includes built-in support for dozens of linters and formatters. Thus, zero plugin configuration is required if you only use common tooling.
  • Customizing built-in sources or adding your own sources is easy. PRs to add more built-ins are welcome.

Supported sources

You easily add a custom source via the plugin configuration. Please consider making a PR to add support for a source if it is missing.

Rule data for built-in support of linters and formatters

Rule lookup

  • ansible-lint
  • basedpyright
  • biome
  • clang-tidy
  • eslint
  • ltex_plus
  • LTeX
  • Lua Diagnostics.
  • markdownlint
  • pylint
  • Pyrefly
  • Pyright
  • quick-lint-js
  • Ruff
  • selene
  • shellcheck
  • stylelint
  • stylelintplus
  • swiftlint
  • ts
  • tsserver
  • ty
  • typescript
  • yamllint

Add ignore comment

Suppress formatting

Installation

Requirements

  • nvim 0.10+
  • Diagnostics provided by a source that supports Neovim's built-in diagnostics system. (nvim's built-in LSP client, efm-langserver or nvim-lint are such sources.)
-- lazy.nvim
{ "chrisgrieser/nvim-rulebook" },

-- packer
use { "chrisgrieser/nvim-rulebook" }

Usage

You can use the commands via lua functions:

vim.keymap.set("n", "<leader>ri", function() require("rulebook").ignoreRule() end)
vim.keymap.set("n", "<leader>rl", function() require("rulebook").lookupRule() end)
vim.keymap.set("n", "<leader>ry", function() require("rulebook").yankDiagnosticCode() end)
vim.keymap.set({ "n", "x" }, "<leader>rf", function() require("rulebook").suppressFormatter() end)

Alternatively, you can use the :Rulebook ex-command:

:Rulebook ignoreRule
:Rulebook lookupRule
:Rulebook yankDiagnosticCode
:Rulebook suppressFormatter

Note that :Rulebook suppressFormatter only supports normal mode. To add formatter-ignore comments for a line range, you need to use the lua function require("rulebook").suppressFormatter() from visual mode.

Configuration

Base configuration

The .setup() call is optional. You only need to add a config when you want to add or customize sources.

When adding your own source, you must add the exact, case-sensitive source name (for example, clang-tidy, not clang).

require("rulebook").setup = ({
    -- if no diagnostic is found in current line, search this many lines forward
    forwSearchLines = 10,

    ignoreComments = {
        shellcheck = {
            comment = "# shellcheck disable=%s",
            location = "prevLine",
            multiRuleIgnore = true,
            multiRuleSeparator = ",",
        },
        -- ... (a full list of sources with builtin support can be found in the README)

        yourCustomSource = { -- exact, case-sensitive source-name
            ---@type string|fun(vim.Diagnostic): string if string, "%s" will be replaced with the rule id
            comment = "// disabling-comment %s",

            ---@type "prevLine"|"sameLine"|"encloseLine"
            location = "sameLine",

            -- whether multiple rules can be ignored with one comment, defaults to `false`
            multiRuleIgnore = true,

            -- separator for multiple rule-ids, defaults to ", " (with space)
            multiRuleSeparator = ",",
        }

        -- if location is `encloseLine`, the comment needs to be a list of two strings
        anotherCustomSource = {
            location = "encloseLine",
            comment = { 
                "// disable-rule %s", 
                "// enable-rule %s",
            },
        }
    },

    ruleDocs = {
        selene = "https://kampfkarren.github.io/selene/lints/%s.html"
        -- ... (a full list of sources with builtin support can be found in the README)

        -- Search URL when no documentation definition is available for a
        -- diagnostic source. `%s` will be replaced with the diagnostic source and 
        -- the code/message.
        fallback = "https://www.google.com/search?q=%s",

        -- the key must be named exactly like `diagnostic.source` (case-sensitive!)
        -- * string value: `%s` will be replaced with the rule id
        -- * function value: will be called with the diagnostic object
        -- * `false`: disable rule docs, just use the fallback
        ---@type string|false|fun(diag: vim.Diagnostic): string?
        yourCustomSource = "https://my-docs/%s.hthml",
        anotherCustomSource = function(diag)
            -- ...
            return url
        end,
    }

    suppressFormatter = {
        lua = {
            -- normal mode
            ignoreBlock = "-- stylua: ignore",
            location = "prevLine",

            -- visual mode
            ignoreRange = { "-- stylua: ignore start", "-- stylua: ignore end" },
        },
    }
})

The plugin uses vim.ui.select, so the appearance of the rule selection can be customized by using a UI plugin like snacks.nvim.

Customize built-in sources

Built-in sources be customized by overwriting them in the configuration:

-- example: use `disable-line` instead of the default `disable-next-line` for eslint
require("rulebook").setup = {
    ignoreComments = {
        eslint = {
            comment = "// eslint-disable-line %s",
            location = "sameLine",
        },
    },
}

FAQ

How to configure diagnostic providers

This plugin requires that the diagnostic providers (the LSP or a linter integration tool like nvim-lint or efm-langserver) provide the source and code for the diagnostic.

  • Make sure that the source is named the same in the diagnostic source and in the nvim-rulebook config, including casing.
  • For nvim-lint, most linters should already be configured out of the box.
  • For efm-langserver, you have to set lintSource for the source name, and correctly configure the errorformat. Other than %l & %c (line number & column), this requires %n which efm langserver uses to fill in the diagnostic code. You can also use efmls-configs-nvim to configure those linters for you.

[!IMPORTANT] Note that vim's errorformat only matches numbers for %n, which means it is not possible to parse diagnostic codes that consist of letters. One such case is the linter selene. To use those linters with nvim-rulebook you need to use nvim-lint which allows more flexible parsing.

-- example: configuring efm langserver for `markdownlint` in `/lsp/efm.lua`
return {
    filetypes = { "markdown" },
    settings = { 
        languages = {
            markdown = {
                {
                    lintSource = "markdownlint",
                    lintCommand = "markdownlint $'{INPUT}'",
                    lintStdin = false,
                    lintIgnoreExitCode = true,
                    lintFormats = { 
                        "%f:%l:%c MD%n/%m", 
                        "%f:%l MD%n/%m"
                    },
                },
            },
        },
    },
}

How to directly ask an LLM about a rule

To ask an LLM about a rule, use a URL that opens a chat it for ruleDocs.fallback.

For ruleDocs.fallback, the %s placeholder will be replaced with the diagnostic source and the quoted code (or message, if no code is available), both URL-encoded.

-- example to use ChatGPT for rule lookup
require("rulebook").setup = ({
    ruleDocs = {
        fallback = "https://chatgpt.com/?q=Explain%20the%20following%20diagnostic%20error%3A%20%s"

        -- To use `fallback` instead of the builtin rule docs, overwrite the
        -- builtin one with `false`.
        typescript = false,
    }
})

How to display the availability of rule lookup

The function require("rulebook").hasDocs(diag), expects a diagnostic object and returns a boolean whether nvim-rulebook documentation for the respective diagnostic available. One use case for this is to add a visual indicator if there is a rule lookup available for a diagnostic (see vim.diagnostic.config).

vim.diagnostic.config {
    virtual_text = {
        suffix = function(diag) return require("rulebook").hasDocs(diag) and "  " or "" end,
    },
}

Credits

In my day job, I am a sociologist studying the social mechanisms underlying the digital economy. For my PhD project, I investigate the governance of the app economy and how software ecosystems manage the tension between innovation and compatibility. If you are interested in this subject, feel free to get in touch.