Run Semgrep inside Neovim. Scan files or the whole workspace, browse findings in Telescope or the quickfix list, apply Semgrep's data-driven autofixes, and surface rule documentation links on hover.
:SemgrepScan runs semgrep scan --json on the current
file and publishes results through vim.diagnostic.:SemgrepScanWorkspace scans the
whole project and pipes every finding into a searchable Telescope picker
(falls back to the quickfix list when Telescope is absent).match.extra.fix
and applies it with nvim_buf_set_text. Run on command (:SemgrepFix,
:SemgrepFixCursor, <C-f> in the picker) or automatically after each scan
(:SemgrepToggleAutofix).:SemgrepHover shows the finding message plus the
rule's match.extra.metadata.links. A virtual-text marker flags lines that
carry documentation links.| Requirement | Notes |
|---|---|
| Neovim 0.9+ | Needs vim.system and vim.json. |
semgrep on PATH |
pip install semgrep, brew install semgrep, or see the install docs. |
telescope.nvim |
Optional. Without it the plugin uses the quickfix list. |
Verify your setup any time with:
:checkhealth semgrep
The plugin loads its commands lazily — call require("semgrep").setup() once.
setup() accepts an optional config table (see Configuration).
{
"tumillanino/semgrep.nvim",
dependencies = { "nvim-telescope/telescope.nvim" }, -- optional
cmd = { "SemgrepScan", "SemgrepScanWorkspace", "SemgrepFindings", "SemgrepFix" },
opts = {}, -- same as require("semgrep").setup({})
}
use({
"tumillanino/semgrep.nvim",
requires = { "nvim-telescope/telescope.nvim" }, -- optional
config = function()
require("semgrep").setup()
end,
})
Plug 'nvim-telescope/telescope.nvim' " optional
Plug 'tumillanino/semgrep.nvim'
Then in your init.lua (or a lua << EOF block in init.vim):
require("semgrep").setup()
require("mini.deps").add({
source = "tumillanino/semgrep.nvim",
depends = { "nvim-telescope/telescope.nvim" }, -- optional
})
require("semgrep").setup()
Drop a file in ~/.config/nvim/lua/plugins/semgrep.lua:
return {
{
"tumillanino/semgrep.nvim",
dependencies = { "nvim-telescope/telescope.nvim" },
cmd = { "SemgrepScan", "SemgrepScanWorkspace", "SemgrepFindings", "SemgrepFix" },
opts = {
scan_on_save = true,
},
keys = {
{ "<leader>ss", "<cmd>SemgrepScan<cr>", desc = "Semgrep: scan file" },
{ "<leader>sw", "<cmd>SemgrepScanWorkspace<cr>", desc = "Semgrep: scan workspace" },
{ "<leader>sf", "<cmd>SemgrepFix<cr>", desc = "Semgrep: apply fixes" },
{ "<leader>sh", "<cmd>SemgrepHover<cr>", desc = "Semgrep: hover docs" },
},
},
}
LazyVim already ships Telescope, so no extra dependency is needed in practice.
NvChad uses lazy.nvim under the hood. Add a spec in
~/.config/nvim/lua/plugins/init.lua (or any file the NvChad plugin loader
imports):
return {
{
"tumillanino/semgrep.nvim",
dependencies = { "nvim-telescope/telescope.nvim" }, -- ships with NvChad
cmd = { "SemgrepScan", "SemgrepScanWorkspace", "SemgrepFindings", "SemgrepFix" },
config = function()
require("semgrep").setup()
end,
},
}
Add mappings the NvChad way in lua/mappings.lua:
local map = vim.keymap.set
map("n", "<leader>ss", "<cmd>SemgrepScan<cr>", { desc = "Semgrep scan file" })
map("n", "<leader>sw", "<cmd>SemgrepScanWorkspace<cr>", { desc = "Semgrep scan workspace" })
map("n", "<leader>sf", "<cmd>SemgrepFix<cr>", { desc = "Semgrep apply fixes" })
setup() defaults — pass only the keys you want to override:
require("semgrep").setup({
cmd = "semgrep", -- executable
config = "auto", -- --config value(s); string or string[] e.g. {"p/ci","auto"}
extra_args = {}, -- extra CLI args appended to every scan
scan_on_save = false, -- re-scan the current file on BufWritePost
autofix = false, -- apply available fixes automatically after a scan
virtual_text_links = true, -- show a doc-link marker as virtual text
use_telescope = true, -- prefer telescope; falls back to quickfix
})
| Command | Description |
|---|---|
:SemgrepScan |
Scan the current file → diagnostics |
:SemgrepScanWorkspace [dir] |
Scan workspace (default cwd) → picker |
:SemgrepFindings |
Reopen last findings in telescope/quickfix |
:SemgrepQuickfix |
Send last findings to the quickfix list |
:SemgrepFix |
Apply all autofixes in the current buffer |
:SemgrepFixCursor |
Apply the autofix under the cursor |
:SemgrepHover |
Show finding details + doc links at cursor |
:SemgrepToggleAutofix |
Toggle autofix-on-scan |
:SemgrepToggleLinks |
Toggle doc-link virtual text |
In the Telescope picker: <CR> jumps to the finding, <C-f> applies its autofix.
Contributions welcome — issues, feature requests, and PRs.
Fork and branch off main.
Keep the module layout: config, parser, store, scan, fix,
hover, pickers, health.
Sanity-check Lua before pushing:
# parse-check every file
for f in lua/semgrep/*.lua plugin/semgrep.lua; do
luajit -e "assert(loadfile('$f'))" && echo "OK $f"
done
For behaviour changes, run a quick headless check:
nvim --headless -u NONE -l your_test.lua
Open a PR describing the change and how you tested it.
Please keep style consistent with the surrounding code (2-space indent,
---@ annotations on public functions).
See LICENSE.