Skip to content

Commit f9a45eb

Browse files
committed
fix: address tech debt and bugs from fork
Data bugs in util.lua: - Guard division by zero when LF:0 (produces NaN) - Add num_branches/num_partial_branches to file summary and totals - Deduplicate partial_lines when multiple BRDA records share a line - Remove unused meta field from return value Runtime bugs: - watch.lua: cancel pending debounce timer in stop() - watch.lua: fix uv fallback to vim.loop (not undefined global) - report.lua: escape filename in vim.cmd("edit ...") for special chars - report.lua: remove dead 7th argument from string.format with 6 specifiers - init.lua: fire on_load callback after cache.set, not before - neotest consumers: use named listener keys to avoid clobbering each other - neotest.lua: wrap load() in vim.schedule for async safety API modernization: - Replace nvim_buf_set_option/nvim_win_set_option with vim.bo/vim.wo - Replace vim.cmd("highlight") with nvim_set_hl in highlight.lua - Replace nvim_buf_set_keymap with vim.keymap.set in report/heatmap - Replace legacy au BufLeave with nvim_create_autocmd (once=true) Refactoring: - Rename report -> cache in overlay, quickfix, loclist, report modules - Extract duplicated file lookup into cache.find_file() - Convert O(n²) vim.tbl_contains to set lookups in signs.build() Fixes neotest/go.lua docstring (wrong output path) and neotest/python.lua misleading variable name. Tests updated with new assertions and fixtures.
1 parent 3dcc532 commit f9a45eb

17 files changed

Lines changed: 239 additions & 144 deletions

lua/coverage/cache.lua

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,24 @@ M.clear = function()
3232
loaded_file = nil
3333
end
3434

35+
--- Finds file coverage by filename, falling back to buffer number matching.
36+
--- @param fname string absolute path to look up
37+
--- @return FileCoverage|nil coverage data for the file
38+
--- @return string|nil matched filename key
39+
M.find_file = function(fname)
40+
if cached == nil then
41+
return nil, nil
42+
end
43+
local file = cached.files[fname]
44+
if file ~= nil then
45+
return file, fname
46+
end
47+
for sf, cov in pairs(cached.files) do
48+
if vim.fn.bufnr(sf, false) == vim.fn.bufnr(fname, false) then
49+
return cov, sf
50+
end
51+
end
52+
return nil, nil
53+
end
54+
3555
return M

lua/coverage/heatmap.lua

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,9 @@ local function render(bufnr, rects, W, H)
240240
end
241241

242242
-- Flush lines into buffer
243-
vim.api.nvim_buf_set_option(bufnr, "modifiable", true)
243+
vim.bo[bufnr].modifiable = true
244244
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
245-
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
245+
vim.bo[bufnr].modifiable = false
246246

247247
-- Apply highlights
248248
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
@@ -261,8 +261,8 @@ local function open_float()
261261
local height = vim.o.lines - vim.o.cmdheight - 1
262262

263263
local bufnr = vim.api.nvim_create_buf(false, true)
264-
vim.api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
265-
vim.api.nvim_buf_set_option(bufnr, "filetype", "coverage-heatmap")
264+
vim.bo[bufnr].bufhidden = "wipe"
265+
vim.bo[bufnr].filetype = "coverage-heatmap"
266266

267267
local win = vim.api.nvim_open_win(bufnr, true, {
268268
relative = "editor",
@@ -273,24 +273,24 @@ local function open_float()
273273
style = "minimal",
274274
})
275275

276-
vim.api.nvim_win_set_option(win, "cursorline", true)
277-
vim.api.nvim_win_set_option(win, "wrap", false)
276+
vim.wo[win].cursorline = true
277+
vim.wo[win].wrap = false
278278

279279
return bufnr, win, width, height
280280
end
281281

282282
--- Set keymaps on the heatmap buffer.
283283
local function set_keymaps(bufnr)
284-
local close_cmd = ":lua require('coverage.heatmap').close()<CR>"
285-
vim.api.nvim_buf_set_keymap(bufnr, "n", "q", close_cmd, { silent = true, noremap = true })
286-
vim.api.nvim_buf_set_keymap(bufnr, "n", "<Esc>", close_cmd, { silent = true, noremap = true })
287-
vim.api.nvim_buf_set_keymap(
288-
bufnr,
289-
"n",
290-
"<CR>",
291-
":lua require('coverage.heatmap').open_file()<CR>",
292-
{ silent = true, noremap = true }
293-
)
284+
local opts = { buffer = bufnr, silent = true, noremap = true }
285+
vim.keymap.set("n", "q", function()
286+
require("coverage.heatmap").close()
287+
end, opts)
288+
vim.keymap.set("n", "<Esc>", function()
289+
require("coverage.heatmap").close()
290+
end, opts)
291+
vim.keymap.set("n", "<CR>", function()
292+
require("coverage.heatmap").open_file()
293+
end, opts)
294294
end
295295

296296
--- Find the rect at the current cursor position.

lua/coverage/highlight.lua

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,26 @@ local config = require("coverage.config")
1212
--- @param group string name of the highlight group
1313
--- @param color Highlight
1414
local highlight = function(group, color)
15-
local style = color.style and "gui=" .. color.style or "gui=NONE"
16-
local fg = color.fg and "guifg=" .. color.fg or "guifg=NONE"
17-
local bg = color.bg and "guibg=" .. color.bg or "guibg=NONE"
18-
local sp = color.sp and "guisp=" .. color.sp or ""
19-
local hl = "highlight default " .. group .. " " .. style .. " " .. fg .. " " .. bg .. " " .. sp
20-
vim.cmd(hl)
2115
if color.link then
22-
vim.cmd("highlight default link " .. group .. " " .. color.link)
16+
vim.api.nvim_set_hl(0, group, { link = color.link, default = true })
17+
return
2318
end
19+
local opts = { default = true }
20+
if color.fg then
21+
opts.fg = color.fg
22+
end
23+
if color.bg then
24+
opts.bg = color.bg
25+
end
26+
if color.sp then
27+
opts.sp = color.sp
28+
end
29+
if color.style then
30+
for attr in color.style:gmatch("[^,]+") do
31+
opts[attr] = true
32+
end
33+
end
34+
vim.api.nvim_set_hl(0, group, opts)
2435
end
2536

2637
local create_highlight_groups = function()

lua/coverage/init.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,11 @@ M.load = function(file, opts)
155155
end
156156

157157
local reload = function()
158+
local data = util.lcov_to_table(p)
159+
cache.set(data, file)
158160
if config.opts.on_load ~= nil then
159161
vim.schedule(config.opts.on_load)
160162
end
161-
local data = util.lcov_to_table(p)
162-
cache.set(data, file)
163163
local sign_list = signs.build(data)
164164
if place or signs.is_enabled() then
165165
signs.place(sign_list)

lua/coverage/loclist.lua

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
11
local M = {}
22

3-
local report = require("coverage.cache")
3+
local cache = require("coverage.cache")
44

55
--- Populates the location list for the current window with lines of the given type.
66
--- @param sign_type? "uncovered"|"partial" Defaults to "uncovered".
77
M.populate = function(sign_type)
88
sign_type = sign_type or "uncovered"
99

10-
if not report.is_cached() then
10+
if not cache.is_cached() then
1111
vim.notify("Coverage report not loaded.", vim.log.levels.INFO)
1212
return
1313
end
1414

15-
local data = report.get()
1615
local fname = vim.fn.expand("%:p")
17-
local file = data.files[fname]
18-
19-
if file == nil then
20-
for sf, cov in pairs(data.files) do
21-
if vim.fn.bufnr(sf, false) == vim.fn.bufnr("%", false) then
22-
file = cov
23-
fname = sf
24-
break
25-
end
26-
end
27-
end
16+
local file
17+
file, fname = cache.find_file(fname)
2818

2919
if file == nil then
3020
vim.notify("No coverage data for current file.", vim.log.levels.INFO)

lua/coverage/neotest.lua

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
---
1212
--- @type fun(client: table): table
1313
local consumer = function(client)
14-
client.listeners.results = function(_, _, partial)
14+
client.listeners.results["coverage.neotest"] = function(_, _, partial)
1515
if not partial then
16-
require("coverage").load(nil, { place = require("coverage.signs").is_enabled(), silent = true })
16+
vim.schedule(function()
17+
require("coverage").load(nil, { place = require("coverage.signs").is_enabled(), silent = true })
18+
end)
1719
end
1820
end
1921
return {}

lua/coverage/neotest/go.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
--- Expects tests to be run with `-coverprofile=coverage.out` (e.g. via ginkgo
55
--- or neotest-go/neotest-golang with coverage flags enabled).
66
---
7-
--- After tests finish the consumer globs for all `**/coverage.out` files under
8-
--- cwd, converts each to lcov in pure Lua, concatenates the results
9-
--- into `cwd/coverage/lcov.info`, and loads the merged file.
7+
--- After tests finish the consumer finds `coverage.out` in the neotest output
8+
--- directory, converts it to lcov in pure Lua, writes the result to
9+
--- `cwd/lcov.info`, and loads the merged file.
1010
---
1111
--- Usage:
1212
--- require("neotest").setup({
@@ -36,7 +36,7 @@ local function find_coverage_profile(results)
3636
end
3737

3838
local consumer = function(client)
39-
client.listeners.results = function(_, results, partial)
39+
client.listeners.results["coverage.neotest.go"] = function(_, results, partial)
4040
if partial then
4141
return
4242
end

lua/coverage/neotest/python.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@
1414
---
1515
--- @type fun(client: table): table
1616
local consumer = function(client)
17-
client.listeners.results = function(_, _, partial)
17+
client.listeners.results["coverage.neotest.python"] = function(_, _, partial)
1818
if partial then
1919
return
2020
end
2121

2222
vim.schedule(function()
2323
local cwd = vim.fn.getcwd()
24-
local dir = cwd .. "/.coverage"
24+
local coverage_db = cwd .. "/.coverage"
2525
local path = cwd .. "/coverage/lcov.info"
2626

27-
if vim.fn.filereadable(dir) ~= 1 then
27+
if vim.fn.filereadable(coverage_db) ~= 1 then
2828
return
2929
end
3030

lua/coverage/overlay.lua

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
local M = {}
22

3-
local report = require("coverage.cache")
3+
local cache = require("coverage.cache")
44

55
local float_win = nil
66
local float_bufnr = nil
@@ -36,8 +36,8 @@ local show_for_line = function(lnum, branches)
3636

3737
float_bufnr = vim.api.nvim_create_buf(false, true)
3838
vim.api.nvim_buf_set_lines(float_bufnr, 0, -1, false, lines)
39-
vim.api.nvim_buf_set_option(float_bufnr, "modifiable", false)
40-
vim.api.nvim_buf_set_option(float_bufnr, "filetype", "coverage-overlay")
39+
vim.bo[float_bufnr].modifiable = false
40+
vim.bo[float_bufnr].filetype = "coverage-overlay"
4141

4242
local width = 30
4343
for _, l in ipairs(lines) do
@@ -59,7 +59,7 @@ local show_for_line = function(lnum, branches)
5959
border = "rounded",
6060
})
6161

62-
vim.api.nvim_win_set_option(float_win, "winhl", "Normal:CoverageReportNormal,FloatBorder:CoverageReportBorder")
62+
vim.wo[float_win].winhl = "Normal:CoverageReportNormal,FloatBorder:CoverageReportBorder"
6363

6464
-- highlight header
6565
vim.api.nvim_buf_add_highlight(float_bufnr, -1, "CoverageReportHeader", 0, 0, -1)
@@ -73,23 +73,13 @@ end
7373

7474
--- Checks current cursor line and updates the overlay accordingly.
7575
local on_cursor_moved = function()
76-
if not report.is_cached() then
76+
if not cache.is_cached() then
7777
close_float()
7878
return
7979
end
8080

8181
local fname = vim.fn.expand("%:p")
82-
local data = report.get()
83-
local file = data.files[fname]
84-
if file == nil then
85-
-- fallback: match by buffer number the same way signs.build does
86-
for sf, cov in pairs(data.files) do
87-
if vim.fn.bufnr(sf, false) == vim.fn.bufnr("%", false) then
88-
file = cov
89-
break
90-
end
91-
end
92-
end
82+
local file = cache.find_file(fname)
9383
if file == nil then
9484
close_float()
9585
return

lua/coverage/quickfix.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
local M = {}
22

3-
local report = require("coverage.cache")
3+
local cache = require("coverage.cache")
44

55
--- Populates the quickfix list with one entry per file showing coverage summary.
66
--- @param filter? "uncovered" When given, only includes files with uncovered lines.
77
M.populate = function(filter)
8-
if not report.is_cached() then
8+
if not cache.is_cached() then
99
vim.notify("Coverage report not loaded.", vim.log.levels.INFO)
1010
return
1111
end
1212

13-
local data = report.get()
13+
local data = cache.get()
1414
local rows = {}
1515

1616
for filename, file in pairs(data.files) do

0 commit comments

Comments
 (0)