docs: improve/add documentation of Lua types

- Added `@inlinedoc` so single use Lua types can be inlined into the
  functions docs. E.g.

  ```lua
  --- @class myopts
  --- @inlinedoc
  ---
  --- Documentation for some field
  --- @field somefield integer

  --- @param opts myOpts
  function foo(opts)
  end
  ```

  Will be rendered as

  ```
  foo(opts)

    Parameters:
      - {opts} (table) Object with the fields:
               - somefield (integer) Documentation
                 for some field
  ```

- Marked many classes with with `@nodoc` or `(private)`.
  We can eventually introduce these when we want to.
This commit is contained in:
Lewis Russell
2024-02-27 15:20:32 +00:00
committed by Lewis Russell
parent 813dd36b72
commit a5fe8f59d9
47 changed files with 2014 additions and 1450 deletions

View File

@ -752,7 +752,7 @@ end
---
--- @param fname string help file to parse
--- @param parser_path string? path to non-default vimdoc.so
--- @return LanguageTree, integer (lang_tree, bufnr)
--- @return vim.treesitter.LanguageTree, integer (lang_tree, bufnr)
local function parse_buf(fname, parser_path)
local buf ---@type integer
if type(fname) == 'string' then

View File

@ -269,6 +269,7 @@ local config = {
filename = 'lsp.txt',
section_order = {
'lsp.lua',
'client.lua',
'buf.lua',
'diagnostic.lua',
'codelens.lua',
@ -362,15 +363,25 @@ local function replace_generics(ty, generics)
return generics[ty] or ty
end
--- @param name string
local function fmt_field_name(name)
local name0, opt = name:match('^([^?]*)(%??)$')
return fmt('{%s}%s', name0, opt)
end
--- @param ty string
--- @param generics? table<string,string>
local function render_type(ty, generics)
--- @param default? string
local function render_type(ty, generics, default)
if generics then
ty = replace_generics(ty, generics)
end
ty = ty:gsub('%s*|%s*nil', '?')
ty = ty:gsub('nil%s*|%s*(.*)', '%1?')
ty = ty:gsub('%s*|%s*', '|')
if default then
return fmt('(`%s`, default: %s)', ty, default)
end
return fmt('(`%s`)', ty)
end
@ -379,10 +390,101 @@ local function should_render_param(p)
return not p.access and not contains(p.name, { '_', 'self' })
end
--- @param desc? string
--- @return string?, string?
local function get_default(desc)
if not desc then
return
end
local default = desc:match('\n%s*%([dD]efault: ([^)]+)%)')
if default then
desc = desc:gsub('\n%s*%([dD]efault: [^)]+%)', '')
end
return desc, default
end
--- @param ty string
--- @param classes? table<string,nvim.luacats.parser.class>
--- @return nvim.luacats.parser.class?
local function get_class(ty, classes)
if not classes then
return
end
local cty = ty:gsub('%s*|%s*nil', '?'):gsub('?$', ''):gsub('%[%]$', '')
return classes[cty]
end
--- @param obj nvim.luacats.parser.param|nvim.luacats.parser.return|nvim.luacats.parser.field
--- @param classes? table<string,nvim.luacats.parser.class>
local function inline_type(obj, classes)
local ty = obj.type
if not ty then
return
end
local cls = get_class(ty, classes)
if not cls or cls.nodoc then
return
end
if not cls.inlinedoc then
-- Not inlining so just add a: "See |tag|."
local tag = fmt('|%s|', cls.name)
if obj.desc and obj.desc:find(tag) then
-- Tag already there
return
end
-- TODO(lewis6991): Aim to remove this. Need this to prevent dead
-- references to types defined in runtime/lua/vim/lsp/_meta/protocol.lua
if not vim.startswith(cls.name, 'vim.') then
return
end
obj.desc = obj.desc or ''
local period = (obj.desc == '' or vim.endswith(obj.desc, '.')) and '' or '.'
obj.desc = obj.desc .. fmt('%s See %s.', period, tag)
return
end
local ty_isopt = (ty:match('%?$') or ty:match('%s*|%s*nil')) ~= nil
local ty_islist = (ty:match('%[%]$')) ~= nil
ty = ty_isopt and 'table?' or ty_islist and 'table[]' or 'table'
local desc = obj.desc or ''
if cls.desc then
desc = desc .. cls.desc
elseif desc == '' then
if ty_islist then
desc = desc .. 'A list of objects with the following fields:'
else
desc = desc .. 'A table with the following fields:'
end
end
local desc_append = {}
for _, f in ipairs(cls.fields) do
local fdesc, default = get_default(f.desc)
local fty = render_type(f.type, nil, default)
local fnm = fmt_field_name(f.name)
table.insert(desc_append, table.concat({ '-', fnm, fty, fdesc }, ' '))
end
desc = desc .. '\n' .. table.concat(desc_append, '\n')
obj.type = ty
obj.desc = desc
end
--- @param xs (nvim.luacats.parser.param|nvim.luacats.parser.field)[]
--- @param generics? table<string,string>
--- @param classes? table<string,nvim.luacats.parser.class>
--- @param exclude_types? true
local function render_fields_or_params(xs, generics, exclude_types)
local function render_fields_or_params(xs, generics, classes, exclude_types)
local ret = {} --- @type string[]
xs = vim.tbl_filter(should_render_param, xs)
@ -398,15 +500,27 @@ local function render_fields_or_params(xs, generics, exclude_types)
end
for _, p in ipairs(xs) do
local nm, ty = p.name, p.type
local desc = p.desc
local pnm = fmt(' • %-' .. indent .. 's', '{' .. nm .. '}')
local pdesc, default = get_default(p.desc)
p.desc = pdesc
inline_type(p, classes)
local nm, ty, desc = p.name, p.type, p.desc
local fnm = p.kind == 'operator' and fmt('op(%s)', nm) or fmt_field_name(nm)
local pnm = fmt(' • %-' .. indent .. 's', fnm)
if ty then
local pty = render_type(ty, generics)
local pty = render_type(ty, generics, default)
if desc then
desc = fmt('%s %s', pty, desc)
table.insert(ret, pnm)
table.insert(ret, md_to_vimdoc(desc, 1, 9 + indent, TEXT_WIDTH, true))
if #pty > TEXT_WIDTH - indent then
vim.list_extend(ret, { ' ', pty, '\n' })
table.insert(ret, md_to_vimdoc(desc, 9 + indent, 9 + indent, TEXT_WIDTH, true))
else
desc = fmt('%s %s', pty, desc)
table.insert(ret, md_to_vimdoc(desc, 1, 9 + indent, TEXT_WIDTH, true))
end
else
table.insert(ret, fmt('%s %s\n', pnm, pty))
end
@ -421,24 +535,46 @@ local function render_fields_or_params(xs, generics, exclude_types)
return table.concat(ret)
end
-- --- @param class lua2vimdoc.class
-- local function render_class(class)
-- writeln(fmt('*%s*', class.name))
-- writeln()
-- if #class.fields > 0 then
-- writeln(' Fields: ~')
-- render_fields_or_params(class.fields)
-- end
-- writeln()
-- end
--- @param class nvim.luacats.parser.class
local function render_class(class)
if class.access or class.nodoc or class.inlinedoc then
return
end
-- --- @param cls table<string,lua2vimdoc.class>
-- local function render_classes(cls)
-- --- @diagnostic disable-next-line:no-unknown
-- for _, class in vim.spairs(cls) do
-- render_class(class)
-- end
-- end
local ret = {} --- @type string[]
table.insert(ret, fmt('*%s*\n', class.name))
if class.parent then
local txt = fmt('Extends: |%s|', class.parent)
table.insert(ret, md_to_vimdoc(txt, INDENTATION, INDENTATION, TEXT_WIDTH))
end
if class.desc then
table.insert(ret, md_to_vimdoc(class.desc, INDENTATION, INDENTATION, TEXT_WIDTH))
end
local fields_txt = render_fields_or_params(class.fields)
if not fields_txt:match('^%s*$') then
table.insert(ret, '\n Fields: ~\n')
table.insert(ret, fields_txt)
end
table.insert(ret, '\n')
return table.concat(ret)
end
--- @param cls table<string,nvim.luacats.parser.class>
local function render_classes(cls)
local ret = {} --- @type string[]
--- @diagnostic disable-next-line:no-unknown
for _, class in vim.spairs(cls) do
ret[#ret + 1] = render_class(class)
end
return table.concat(ret)
end
--- @param fun nvim.luacats.parser.fun
--- @param cfg nvim.gen_vimdoc.Config
@ -448,7 +584,7 @@ local function render_fun_header(fun, cfg)
local args = {} --- @type string[]
for _, p in ipairs(fun.params or {}) do
if p.name ~= 'self' then
args[#args + 1] = fmt('{%s}', p.name:gsub('%?$', ''))
args[#args + 1] = fmt_field_name(p.name)
end
end
@ -480,8 +616,9 @@ end
--- @param returns nvim.luacats.parser.return[]
--- @param generics? table<string,string>
--- @param classes? table<string,nvim.luacats.parser.class>
--- @param exclude_types boolean
local function render_returns(returns, generics, exclude_types)
local function render_returns(returns, generics, classes, exclude_types)
local ret = {} --- @type string[]
returns = vim.deepcopy(returns)
@ -498,26 +635,26 @@ local function render_returns(returns, generics, exclude_types)
end
for _, p in ipairs(returns) do
inline_type(p, classes)
local rnm, ty, desc = p.name, p.type, p.desc
local blk = ''
local blk = {} --- @type string[]
if ty then
blk = render_type(ty, generics)
blk[#blk + 1] = render_type(ty, generics)
end
if rnm then
blk = blk .. ' ' .. rnm
end
if desc then
blk = blk .. ' ' .. desc
end
table.insert(ret, md_to_vimdoc(blk, 8, 8, TEXT_WIDTH, true))
blk[#blk + 1] = rnm
blk[#blk + 1] = desc
table.insert(ret, md_to_vimdoc(table.concat(blk, ' '), 8, 8, TEXT_WIDTH, true))
end
return table.concat(ret)
end
--- @param fun nvim.luacats.parser.fun
--- @param classes table<string,nvim.luacats.parser.class>
--- @param cfg nvim.gen_vimdoc.Config
local function render_fun(fun, cfg)
local function render_fun(fun, classes, cfg)
if fun.access or fun.deprecated or fun.nodoc then
return
end
@ -570,7 +707,7 @@ local function render_fun(fun, cfg)
end
if fun.params and #fun.params > 0 then
local param_txt = render_fields_or_params(fun.params, fun.generics, cfg.exclude_types)
local param_txt = render_fields_or_params(fun.params, fun.generics, classes, cfg.exclude_types)
if not param_txt:match('^%s*$') then
table.insert(ret, '\n Parameters: ~\n')
ret[#ret + 1] = param_txt
@ -578,7 +715,7 @@ local function render_fun(fun, cfg)
end
if fun.returns then
local txt = render_returns(fun.returns, fun.generics, cfg.exclude_types)
local txt = render_returns(fun.returns, fun.generics, classes, cfg.exclude_types)
if not txt:match('^%s*$') then
table.insert(ret, '\n')
ret[#ret + 1] = txt
@ -597,15 +734,16 @@ local function render_fun(fun, cfg)
end
--- @param funs nvim.luacats.parser.fun[]
--- @param classes table<string,nvim.luacats.parser.class>
--- @param cfg nvim.gen_vimdoc.Config
local function render_funs(funs, cfg)
local function render_funs(funs, classes, cfg)
local ret = {} --- @type string[]
for _, f in ipairs(funs) do
if cfg.fn_xform then
cfg.fn_xform(f)
end
ret[#ret + 1] = render_fun(f, cfg)
ret[#ret + 1] = render_fun(f, classes, cfg)
end
-- Sort via prototype
@ -745,15 +883,35 @@ local function gen_target(cfg)
expand_files(cfg.files)
--- @type table<string,{[1]:table<string,nvim.luacats.parser.class>, [2]: nvim.luacats.parser.fun[], [3]: string[]}>
local file_results = {}
--- @type table<string,nvim.luacats.parser.class>
local all_classes = {}
--- First pass so we can collect all classes
for _, f in pairs(cfg.files) do
local ext = assert(f:match('%.([^.]+)$')) --[[@as 'h'|'c'|'lua']]
local parser = assert(parsers[ext])
local _, funs, briefs = parser(f)
local classes, funs, briefs = parser(f)
file_results[f] = { classes, funs, briefs }
all_classes = vim.tbl_extend('error', all_classes, classes)
end
for f, r in pairs(file_results) do
local classes, funs, briefs = r[1], r[2], r[3]
local briefs_txt = {} --- @type string[]
for _, b in ipairs(briefs) do
briefs_txt[#briefs_txt + 1] = md_to_vimdoc(b, 0, 0, TEXT_WIDTH)
end
local funs_txt = render_funs(funs, cfg)
local funs_txt = render_funs(funs, all_classes, cfg)
if next(classes) then
local classes_txt = render_classes(classes)
if vim.trim(classes_txt) ~= '' then
funs_txt = classes_txt .. '\n' .. funs_txt
end
end
-- FIXME: Using f_base will confuse `_meta/protocol.lua` with `protocol.lua`
local f_base = assert(vim.fs.basename(f))
sections[f_base] = make_section(f_base, cfg, briefs_txt, funs_txt)

View File

@ -21,8 +21,7 @@ local function opt(x)
return x ^ -1
end
local nl = P('\r\n') + P('\n')
local ws = rep1(S(' \t') + nl)
local ws = rep1(S(' \t'))
local fill = opt(ws)
local any = P(1) -- (consume one character)
@ -30,11 +29,11 @@ local letter = R('az', 'AZ') + S('_$')
local num = R('09')
local ident = letter * rep(letter + num + S '-.')
local string_single = P "'" * rep(any - P "'") * P "'"
local string_double = P '"' * rep(any - P '"') * P '"'
local string_double = P('"') * rep(any - P('"')) * P('"')
local literal = (string_single + string_double + (opt(P '-') * num) + P 'false' + P 'true')
local literal = (string_single + string_double + (opt(P('-')) * num) + P('false') + P('true'))
local lname = (ident + P '...') * opt(P '?')
local lname = (ident + P('...')) * opt(P('?'))
--- @param x string
local function Pf(x)
@ -47,13 +46,23 @@ local function Sf(x)
end
--- @param x vim.lpeg.Pattern
local function comma(x)
return x * rep(Pf ',' * x)
local function paren(x)
return Pf('(') * x * fill * P(')')
end
--- @param x vim.lpeg.Pattern
local function parenOpt(x)
return (Pf('(') * x * fill * P(')')) + x
return paren(x) + x
end
--- @param x vim.lpeg.Pattern
local function comma1(x)
return parenOpt(x * rep(Pf(',') * x))
end
--- @param x vim.lpeg.Pattern
local function comma(x)
return opt(comma1(x))
end
--- @type table<string,vim.lpeg.Pattern>
@ -63,7 +72,15 @@ local v = setmetatable({}, {
end,
})
local colon = Pf(':')
local opt_exact = opt(Cg(Pf('(exact)'), 'access'))
local access = P('private') + P('protected') + P('package')
local caccess = Cg(access, 'access')
local desc_delim = Sf '#:' + ws
local desc = Cg(rep(any), 'desc')
local opt_desc = opt(desc_delim * desc)
local cname = Cg(ident, 'name')
local opt_parent = opt(colon * Cg(ident, 'parent'))
--- @class nvim.luacats.Param
--- @field kind 'param'
@ -85,6 +102,7 @@ local desc_delim = Sf '#:' + ws
--- @field kind 'class'
--- @field name string
--- @field parent? string
--- @field access? 'private'|'protected'|'package'
--- @class nvim.luacats.Field
--- @field kind 'field'
@ -107,112 +125,60 @@ local desc_delim = Sf '#:' + ws
--- @class nvim.luacats.grammar
--- @field match fun(self, input: string): nvim.luacats.grammar.result?
local function annot(nm, pat)
if type(nm) == 'string' then
nm = P(nm)
end
if pat then
return Ct(Cg(P(nm), 'kind') * fill * pat)
end
return Ct(Cg(P(nm), 'kind'))
end
local grammar = P {
rep1(P('@') * (v.ats + v.ext_ats)),
ats = v.at_param
+ v.at_return
+ v.at_type
+ v.at_cast
+ v.at_generic
+ v.at_class
+ v.at_field
+ v.at_access
+ v.at_deprecated
+ v.at_alias
+ v.at_enum
+ v.at_see
+ v.at_diagnostic
+ v.at_overload
+ v.at_meta,
ext_ats = v.ext_at_note + v.ext_at_since + v.ext_at_nodoc + v.ext_at_brief,
at_param = Ct(
Cg(P('param'), 'kind')
* ws
* Cg(lname, 'name')
* ws
* parenOpt(Cg(v.ltype, 'type'))
* opt(desc_delim * Cg(rep(any), 'desc'))
),
at_return = Ct(
Cg(P('return'), 'kind')
* ws
* parenOpt(comma(Ct(Cg(v.ltype, 'type') * opt(ws * Cg(ident, 'name')))))
* opt(desc_delim * Cg(rep(any), 'desc'))
),
at_type = Ct(
Cg(P('type'), 'kind')
* ws
* parenOpt(comma(Ct(Cg(v.ltype, 'type'))))
* opt(desc_delim * Cg(rep(any), 'desc'))
),
at_cast = Ct(
Cg(P('cast'), 'kind') * ws * Cg(lname, 'name') * ws * opt(Sf('+-')) * Cg(v.ltype, 'type')
),
at_generic = Ct(
Cg(P('generic'), 'kind') * ws * Cg(ident, 'name') * opt(Pf ':' * Cg(v.ltype, 'type'))
),
at_class = Ct(
Cg(P('class'), 'kind')
* ws
* opt(P('(exact)') * ws)
* Cg(lname, 'name')
* opt(Pf(':') * Cg(lname, 'parent'))
),
at_field = Ct(
Cg(P('field'), 'kind')
* ws
* opt(Cg(Pf('private') + Pf('package') + Pf('protected'), 'access'))
* Cg(lname, 'name')
* ws
* Cg(v.ltype, 'type')
* opt(desc_delim * Cg(rep(any), 'desc'))
),
at_access = Ct(Cg(P('private') + P('protected') + P('package'), 'kind')),
at_deprecated = Ct(Cg(P('deprecated'), 'kind')),
-- Types may be provided on subsequent lines
at_alias = Ct(Cg(P('alias'), 'kind') * ws * Cg(lname, 'name') * opt(ws * Cg(v.ltype, 'type'))),
at_enum = Ct(Cg(P('enum'), 'kind') * ws * Cg(lname, 'name')),
at_see = Ct(Cg(P('see'), 'kind') * ws * opt(Pf('#')) * Cg(rep(any), 'desc')),
at_diagnostic = Ct(Cg(P('diagnostic'), 'kind') * ws * opt(Pf('#')) * Cg(rep(any), 'desc')),
at_overload = Ct(Cg(P('overload'), 'kind') * ws * Cg(v.ltype, 'type')),
at_meta = Ct(Cg(P('meta'), 'kind')),
ats = annot('param', Cg(lname, 'name') * ws * v.ctype * opt_desc)
+ annot('return', comma1(Ct(v.ctype * opt(ws * cname))) * opt_desc)
+ annot('type', comma1(Ct(v.ctype)) * opt_desc)
+ annot('cast', cname * ws * opt(Sf('+-')) * v.ctype)
+ annot('generic', cname * opt(colon * v.ctype))
+ annot('class', opt_exact * opt(paren(caccess)) * fill * cname * opt_parent)
+ annot('field', opt(caccess * ws) * v.field_name * ws * v.ctype * opt_desc)
+ annot('operator', cname * opt(paren(Cg(v.ltype, 'argtype'))) * colon * v.ctype)
+ annot(access)
+ annot('deprecated')
+ annot('alias', cname * opt(ws * v.ctype))
+ annot('enum', cname)
+ annot('overload', v.ctype)
+ annot('see', opt(desc_delim) * desc)
+ annot('diagnostic', opt(desc_delim) * desc)
+ annot('meta'),
--- Custom extensions
ext_at_note = Ct(Cg(P('note'), 'kind') * ws * Cg(rep(any), 'desc')),
ext_ats = (
annot('note', desc)
+ annot('since', desc)
+ annot('nodoc')
+ annot('inlinedoc')
+ annot('brief', desc)
),
-- TODO only consume 1 line
ext_at_since = Ct(Cg(P('since'), 'kind') * ws * Cg(rep(any), 'desc')),
field_name = Cg(lname + (v.ty_index * opt(P('?'))), 'name'),
ext_at_nodoc = Ct(Cg(P('nodoc'), 'kind')),
ext_at_brief = Ct(Cg(P('brief'), 'kind') * opt(ws * Cg(rep(any), 'desc'))),
ctype = parenOpt(Cg(v.ltype, 'type')),
ltype = parenOpt(v.ty_union),
ltype = v.ty_union + Pf '(' * v.ty_union * fill * P ')',
ty_union = v.ty_opt * rep(Pf '|' * v.ty_opt),
ty_union = v.ty_opt * rep(Pf('|') * v.ty_opt),
ty = v.ty_fun + ident + v.ty_table + literal,
ty_param = Pf '<' * comma(v.ltype) * fill * P '>',
ty_opt = v.ty * opt(v.ty_param) * opt(P '[]') * opt(P '?'),
table_key = (Pf '[' * literal * Pf ']') + lname,
table_elem = v.table_key * Pf ':' * v.ltype,
ty_table = Pf '{' * comma(v.table_elem) * Pf '}',
fun_param = lname * opt(Pf ':' * v.ltype),
ty_fun = Pf 'fun(' * rep(comma(v.fun_param)) * fill * P ')' * opt(Pf ':' * comma(v.ltype)),
ty_param = Pf('<') * comma1(v.ltype) * fill * P('>'),
ty_opt = v.ty * opt(v.ty_param) * opt(P('[]')) * opt(P('?')),
ty_index = (Pf('[') * v.ltype * Pf(']')),
table_key = v.ty_index + lname,
table_elem = v.table_key * colon * v.ltype,
ty_table = Pf('{') * comma1(v.table_elem) * Pf('}'),
fun_param = lname * opt(colon * v.ltype),
ty_fun = Pf('fun') * paren(comma(lname * opt(colon * v.ltype))) * opt(colon * comma1(v.ltype)),
}
return grammar --[[@as nvim.luacats.grammar]]

View File

@ -19,7 +19,7 @@ local luacats_grammar = require('scripts.luacats_grammar')
--- @class nvim.luacats.parser.alias
--- @field kind 'alias'
--- @field type string
--- @field type string[]
--- @field desc string
--- @class nvim.luacats.parser.fun
@ -49,8 +49,12 @@ local luacats_grammar = require('scripts.luacats_grammar')
--- @class nvim.luacats.parser.class
--- @field kind 'class'
--- @field parent? string
--- @field name string
--- @field desc string
--- @field nodoc? true
--- @field inlinedoc? true
--- @field access? 'private'|'package'|'protected'
--- @field fields nvim.luacats.parser.field[]
--- @field notes? string[]
@ -64,6 +68,7 @@ local luacats_grammar = require('scripts.luacats_grammar')
--- | nvim.luacats.parser.class
--- | nvim.luacats.parser.fun
--- | nvim.luacats.parser.brief
--- | nvim.luacats.parser.alias
-- Remove this when we document classes properly
--- Some doc lines have the form:
@ -142,22 +147,27 @@ local function process_doc_line(line, state)
}
elseif kind == 'class' then
--- @cast parsed nvim.luacats.Class
state.cur_obj = {
kind = 'class',
name = parsed.name,
parent = parsed.parent,
desc = '',
fields = {},
}
cur_obj.kind = 'class'
cur_obj.name = parsed.name
cur_obj.parent = parsed.parent
cur_obj.access = parsed.access
cur_obj.desc = state.doc_lines and table.concat(state.doc_lines, '\n') or nil
state.doc_lines = nil
cur_obj.fields = {}
elseif kind == 'field' then
--- @cast parsed nvim.luacats.Field
if not parsed.access then
parsed.desc = parsed.desc or state.doc_lines and table.concat(state.doc_lines, '\n') or nil
if parsed.desc then
parsed.desc = vim.trim(parsed.desc)
end
table.insert(cur_obj.fields, parsed)
parsed.desc = parsed.desc or state.doc_lines and table.concat(state.doc_lines, '\n') or nil
if parsed.desc then
parsed.desc = vim.trim(parsed.desc)
end
table.insert(cur_obj.fields, parsed)
state.doc_lines = nil
elseif kind == 'operator' then
parsed.desc = parsed.desc or state.doc_lines and table.concat(state.doc_lines, '\n') or nil
if parsed.desc then
parsed.desc = vim.trim(parsed.desc)
end
table.insert(cur_obj.fields, parsed)
state.doc_lines = nil
elseif kind == 'param' then
state.last_doc_item_indent = nil
@ -191,6 +201,8 @@ local function process_doc_line(line, state)
cur_obj.access = 'protected'
elseif kind == 'deprecated' then
cur_obj.deprecated = true
elseif kind == 'inlinedoc' then
cur_obj.inlinedoc = true
elseif kind == 'nodoc' then
cur_obj.nodoc = true
elseif kind == 'since' then
@ -383,11 +395,11 @@ end
--- Determine the table name used to export functions of a module
--- Usually this is `M`.
--- @param filename string
--- @param str string
--- @return string?
local function determine_modvar(filename)
local function determine_modvar(str)
local modvar --- @type string?
for line in io.lines(filename) do
for line in vim.gsplit(str, '\n') do
do
--- @type string?
local m = line:match('^return%s+([a-zA-Z_]+)')
@ -462,17 +474,12 @@ end
local M = {}
--- @param filename string
--- @return table<string,nvim.luacats.parser.class> classes
--- @return nvim.luacats.parser.fun[] funs
--- @return string[] briefs
--- @return nvim.luacats.parser.obj[]
function M.parse(filename)
function M.parse_str(str, filename)
local funs = {} --- @type nvim.luacats.parser.fun[]
local classes = {} --- @type table<string,nvim.luacats.parser.class>
local briefs = {} --- @type string[]
local mod_return = determine_modvar(filename)
local mod_return = determine_modvar(str)
--- @type string
local module = filename:match('.*/lua/([a-z_][a-z0-9_/]+)%.lua') or filename
@ -485,7 +492,7 @@ function M.parse(filename)
-- Keep track of any partial objects we don't commit
local uncommitted = {} --- @type nvim.luacats.parser.obj[]
for line in io.lines(filename) do
for line in vim.gsplit(str, '\n') do
local has_indent = line:match('^%s+') ~= nil
line = vim.trim(line)
if vim.startswith(line, '---') then
@ -518,4 +525,13 @@ function M.parse(filename)
return classes, funs, briefs, uncommitted
end
--- @param filename string
function M.parse(filename)
local f = assert(io.open(filename, 'r'))
local txt = f:read('*all')
f:close()
return M.parse_str(txt, filename)
end
return M

View File

@ -175,7 +175,11 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
error(fmt('cannot render:\n%s', vim.inspect(node)))
end
for i, child in ipairs(node) do
vim.list_extend(parts, render_md(child, start_indent, indent, text_width, level + 1, is_list))
local start_indent0 = i == 1 and start_indent or indent
vim.list_extend(
parts,
render_md(child, start_indent0, indent, text_width, level + 1, is_list)
)
if node.type ~= 'list' and i ~= #node then
if (node[i + 1] or {}).type ~= 'list' then
parts[#parts + 1] = '\n'