build(docs): sort/lint class fields and keysets

Problem:
Fields (key names) in classes, keysets, and quasi-keysets are ordered
randomly, which adds friction when reading docs.

Solution:
- Sort class fields and keysets when generating docs.
- Add a lint check for quasi-keysets (keysets defined as unstructured
  markdown lists within a docstring).
This commit is contained in:
Justin M. Keyes
2026-04-26 12:36:07 +02:00
parent 1e3e06f582
commit 033efbbd32
4 changed files with 61 additions and 0 deletions

View File

@@ -328,6 +328,7 @@ local function get_api_keysets_meta()
lint.lint_names('src/nvim/api/keysets_defs.h', nil, keysets)
for _, k in ipairs(keysets) do
util.sort_by_key(k.keys)
local params = {}
for _, key in ipairs(k.keys) do
local pty = k.types[key] or 'any'

View File

@@ -610,6 +610,8 @@ local function inline_type(obj, classes)
end
end
util.sort_by_key(cls.fields, 'name')
local desc_append = {}
for _, f in ipairs(cls.fields) do
if not f.access then
@@ -715,6 +717,8 @@ local function render_class(class, classes, hidden_fields, cfg)
end, fields)
end
util.sort_by_key(fields, 'name')
local fields_txt = render_fields_or_params(fields, nil, classes, cfg)
if not fields_txt:match('^%s*$') then
table.insert(ret, '\n Fields: ~\n')
@@ -1145,6 +1149,7 @@ local function gen_target(cfg)
if not f:find('ui_events%.in%.h$') then -- TODO(justinmk): also lint UI events.
lint.lint_names(f, funs, nil, classes)
lint.lint_quasi_keysets(f, funs)
end
local mod_cls_nm = find_module_class(classes, 'M')

View File

@@ -226,4 +226,46 @@ function M.lint_names(source, api_funs, keysets, classes)
end
end
--- Checks that markdown list items (`- name :` or `- {name}`) in parameter/field
--- descriptions are in sorted order.
---
--- This is for "quasi-keysets" defined as markdown lists in docstrings. Ideally they would be
--- defined as a luals `@class` or a API keyset, but sometimes that isn't possible.
---
--- @param source string Source filename.
--- @param funs {name:string, params:{name:string, desc?:string}[]}[]
function M.lint_quasi_keysets(source, funs)
local errors = {} --- @type string[]
for _, fun in ipairs(funs) do
for _, p in ipairs(fun.params or {}) do
if p.desc then
local prev = nil --- @type string?
for name in p.desc:gmatch('\n%- ([%w_]+)') do
-- Sort underscore-prefixed keys (e.g. "_cmdline_offset") at the end.
local prev_key = prev and prev:gsub('^_', '~') or nil
local name_key = name:gsub('^_', '~')
if prev_key and name_key < prev_key then
errors[#errors + 1] = fmt(
'%s: %s(): param "%s": key "%s" should come before "%s" (sort alphabetically)',
source,
fun.name,
p.name,
name,
prev
)
end
prev = name
end
end
end
end
if #errors > 0 then
error(
'lint_quasi_keysets(): unsorted keys in doc comments:\n ' .. table.concat(errors, '\n ')
)
end
end
return M

View File

@@ -404,4 +404,17 @@ function M.md_to_vimdoc(text, start_indent, indent, text_width, is_list)
return s
end
--- Sorts a list in-place, with underscore-prefixed names last (e.g. "_cmdline_offset").
--- If `key` is given, sorts by `item[key]`; otherwise sorts by value.
---
--- @param t any[]
--- @param key? string
function M.sort_by_key(t, key)
table.sort(t, function(a, b)
local a_val = key and a[key] or a
local b_val = key and b[key] or b
return a_val:gsub('^_', '~') < b_val:gsub('^_', '~')
end)
end
return M