mirror of
https://github.com/neovim/neovim
synced 2025-07-16 09:11:51 +00:00
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:
committed by
Lewis Russell
parent
813dd36b72
commit
a5fe8f59d9
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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]]
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
Reference in New Issue
Block a user