Skip to content

Commit

Permalink
feat(doc): improve formatting for types in @param, @return, etc
Browse files Browse the repository at this point in the history
  • Loading branch information
echasnovski committed Feb 14, 2025
1 parent 50cddd7 commit 3960d50
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 35 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

- FEATURE: update textobject to respect `ignore_blank_line` option. Blank lines between commented lines are treated as part of a textobject.

## mini.doc

- FEATURE: improve detection and formatting for types in `@param`, `@return`, and similar.

## mini.hues

- FEATURE: add support for colored markdown headings.
Expand Down
51 changes: 35 additions & 16 deletions lua/mini/doc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -256,20 +256,22 @@ MiniDoc.config = {
['@field'] = function(s)
H.mark_optional(s)
H.enclose_var_name(s)
H.enclose_type(s, '`%(%1%)`', s[1]:find('%s'))
local col_past_var_name = s[1]:match('^%s*%S+%s+`%(optional%)`()') or s[1]:match('^%s*%S+()') or 1
H.enclose_type(s, col_past_var_name)
end,
--minidoc_replace_end
--minidoc_replace_start ['@overload'] = --<function>,
['@overload'] = function(s)
H.enclose_type(s, '`%1`', 1)
s[1] = '`' .. s[1] .. '`'
H.add_section_heading(s, 'Overload')
end,
--minidoc_replace_end
--minidoc_replace_start ['@param'] = --<function>,
['@param'] = function(s)
H.mark_optional(s)
H.enclose_var_name(s)
H.enclose_type(s, '`%(%1%)`', s[1]:find('%s'))
local col_past_var_name = s[1]:match('^%s*%S+%s+`%(optional%)`()') or s[1]:match('^%s*%S+()') or 1
H.enclose_type(s, col_past_var_name)
end,
--minidoc_replace_end
--minidoc_replace_start ['@private'] = --<function: registers block for removal>,
Expand All @@ -278,7 +280,7 @@ MiniDoc.config = {
--minidoc_replace_start ['@return'] = --<function>,
['@return'] = function(s)
H.mark_optional(s)
H.enclose_type(s, '`%(%1%)`', 1)
H.enclose_type(s, 1)
H.add_section_heading(s, 'Return')
end,
--minidoc_replace_end
Expand Down Expand Up @@ -318,7 +320,7 @@ MiniDoc.config = {
--minidoc_replace_end
--minidoc_replace_start ['@type'] = --<function>,
['@type'] = function(s)
H.enclose_type(s, '`%(%1%)`', 1)
H.enclose_type(s, 1)
H.add_section_heading(s, 'Type')
end,
--minidoc_replace_end
Expand Down Expand Up @@ -713,10 +715,14 @@ H.pattern_sets = {
-- Patterns to work with type descriptions
-- (see https://github.com/sumneko/lua-language-server/wiki/EmmyLua-Annotations#types-and-type)
types = {
'%b()', -- Allow union type
'%b[]',
'%b{}',
'table%b<>',
'fun%b(): %S+', 'fun%b()',
'nil', 'any', 'boolean', 'string', 'number', 'integer', 'function', 'table', 'thread', 'userdata', 'lightuserdata',
'%.%.%.'
'%.%.%.',
'[%a][%w_]*', -- Allow any class as a type
},
}
--stylua: ignore end
Expand Down Expand Up @@ -1031,22 +1037,35 @@ H.enclose_var_name = function(s) s[1] = s[1]:gsub('(%S+)', '{%1}', 1) end
---@param init number Start of searching for first "type-like" string. It is
--- needed to not detect type early. Like in `@param a_function function`.
---@private
H.enclose_type = function(s, enclosure, init)
H.enclose_type = function(s, init)
if #s == 0 or s.type ~= 'section' then return end
enclosure = enclosure or '`%(%1%)`'
init = init or 1

local type_pattern = H.find_pattern_with_first_match(s[1], H.pattern_sets['types'], init)
local type_pattern_set = H.pattern_sets['types']
local type_pattern = H.find_pattern_with_first_match(s[1], type_pattern_set, init)
if type_pattern == nil then return end

-- Add `%S*` to front and back of found pattern to support their combination
-- with `|`. Also allows using `[]` and `?` prefixes.
type_pattern = '%S*' .. type_pattern .. '%S*'
-- Find range representing type. It can be a match for type pattern (plain,
-- array `[]`, or optional `?`), possibly in a union (`|`).
local from, to = s[1]:find(type_pattern, init)
for _ = 1, s[1]:len() do
if s[1]:sub(to + 1, to + 2) == '[]' then to = to + 2 end
if s[1]:sub(to + 1, to + 1) == '?' then to = to + 1 end

local new_to = s[1]:sub(to + 1):match('^%s*|%s*()')
if new_to == nil then break end
to = to + new_to - 1
local next_type_pattern = H.find_pattern_with_first_match(s[1], type_pattern_set, to + 1)
if next_type_pattern == nil then break end
to = s[1]:match(next_type_pattern .. '()', to + 1) - 1
end

-- Avoid replacing match before `init` and avoid unnecessary () enclosing
local avoid_brackets = s[1]:sub(from, from) == '(' and s[1]:sub(to, to) == ')'
local left = avoid_brackets and '`' or '`('
local right = avoid_brackets and '`' or ')`'

-- Avoid replacing possible match before `init`
local l_start = s[1]:sub(1, init - 1)
local l_end = s[1]:sub(init):gsub(type_pattern, enclosure, 1)
s[1] = ('%s%s'):format(l_start, l_end)
s[1] = s[1]:sub(1, from - 1) .. left .. s[1]:sub(from, to) .. right .. s[1]:sub(to + 1)
end

-- Infer data from afterlines -------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions tests/dir-doc/sections/alias.lua
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
--- Test `@alias` section

---@alias var_one fun(type: string, data: any)
---@alias var_two Another data structure.
---@alias var_two table<string,number> Another data structure.
--- Its description spans over multiple lines.
---@alias %bad_name* This alias has bad name and should still work.

---@param x var_one
---@param y var_two
---@param z var_three
---@param z var_three Should be enclosed as custom classes are allowed.
---@alias var_three This alias shouldn't be applied to previous line as it is defined after it.

--- Aliases also expand inside text: var_one
Expand Down
25 changes: 20 additions & 5 deletions tests/dir-doc/sections/param.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
--- - Item 2.
---@param c table
---@param d
---@param x %%%bad_name!!
---@param x %%%bad_name!! Actual formatting is not defined

--- Test for expanding `?` to `(optional)`
---
Expand All @@ -20,11 +20,26 @@
---
---@param a number Should work.
---@param b number[] Should work.
---@param B number[]? Should work.
---@param c number|nil Should work.
---@param d table<string, number> Should work.
---@param e fun(a: string, b:number) Should work.
---@param f fun(a: string, b:number): table Should work.
---@param g NUMBER Shouldn't work.
---@param C number | nil Should work.
---@param d number | number[] Should work.
---@param e (number | nil) Should not be doubly enclosed in ().
---@param f [number, string] Should work.
---@param F [number, string[]] Should work.
---@param g {[string]:number} Should work.
---@param G { [string]: number } Should work.
---@param h {key1:string,key2:number} Should work.
---@param H { key1: string, key2: number } Should work.
---@param i table<string, number> Should work.
---@param j fun(a: string, b:number) Should work.
---@param k fun(a: string, b:number): table Should work.
---@param l NUMBER Should still work as custom classes are allowed.
---@param m NUMBER|nil Should still work as custom classes are allowed.
---@param M NUMBER | nil Should still work as custom classes are allowed.
---@param n (NUMBER | nil) Should not be doubly enclosed in ().
---@param o number[] | (string | nil) | [number, string] Should work.
---@param p number | string Should ignore |later| special[] characters?
---@param a_function function Should enclose second `function`.
---@param function_a function Should enclose second `function`.
---@param a_function_a function Should enclose second `function`.
Expand Down
18 changes: 16 additions & 2 deletions tests/dir-doc/sections/return.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,24 @@
---
---@return number Should work.
---@return number[] Should work.
---@return number[]? Should work.
---@return number|nil Should work.
---@return number | nil Should work.
---@return number | number[] Should work.
---@return (number | nil) Should not be doubly enclosed in ().
---@return [number, string] Should work.
---@return [number, string[]] Should work.
---@return {[string]:number} Should work.
---@return { [string]: number } Should work.
---@return {key1:string,key2:number} Should work.
---@return { key1: string, key2: number } Should work.
---@return table<string, number> Should work.
---@return fun(a: string, b:number) Should work.
---@return fun(a: string, b:number): table Should work.
---@return NUMBER Shouldn't work.
---@return function Should not enclose second time: function .
---@return NUMBER Should still work as custom classes are allowed.
---@return NUMBER|nil Should still work as custom classes are allowed.
---@return NUMBER | nil Should still work as custom classes are allowed.
---@return (NUMBER | nil) Should not be doubly enclosed in ().
---@return number[] | (string | nil) | [number, string] Should work
---@return number | string Should ignore |later| special[] characters?
---@return ... Should work.
63 changes: 53 additions & 10 deletions tests/dir-doc/sections/sections_reference.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ Test `@alias` section
------------------------------------------------------------------------------
Parameters ~
{x} `(fun(type: string, data: any))`
{y} Another data structure.
{y} `(table<string,number>)` Another data structure.
Its description spans over multiple lines.
{z} var_three
{z} `(var_three)` Should be enclosed as custom classes are allowed.

------------------------------------------------------------------------------
Aliases also expand inside text: fun(type: string, data: any)
Expand All @@ -89,7 +89,7 @@ Test of `MiniDoc.current.aliases`
["%bad_name*"] = "This alias has bad name and should still work.",
var_one = " fun(type: string, data: any)",
var_three = "This alias shouldn't be applied to previous line as it is defined after it.",
var_two = "Another data structure.\n Its description spans over multiple lines."
var_two = "table<string,number> Another data structure.\n Its description spans over multiple lines."
}


Expand Down Expand Up @@ -133,7 +133,7 @@ Parameters ~
- Item 2.
{c} `(table)`
{d}
{x} %%%bad_name!!
{x} %%%`(bad_name)`!! Actual formatting is not defined

------------------------------------------------------------------------------
Test for expanding `?` to `(optional)`
Expand All @@ -149,11 +149,26 @@ Test for enclosing type
Parameters ~
{a} `(number)` Should work.
{b} `(number[])` Should work.
{B} `(number[]?)` Should work.
{c} `(number|nil)` Should work.
{d} `(table<string, number>)` Should work.
{e} `(fun(a: string, b:number))` Should work.
{f} `(fun(a: string, b:number): table)` Should work.
{g} NUMBER Shouldn't work.
{C} `(number | nil)` Should work.
{d} `(number | number[])` Should work.
{e} `(number | nil)` Should not be doubly enclosed in ().
{f} `([number, string])` Should work.
{F} `([number, string[]])` Should work.
{g} `({[string]:number})` Should work.
{G} `({ [string]: number })` Should work.
{h} `({key1:string,key2:number})` Should work.
{H} `({ key1: string, key2: number })` Should work.
{i} `(table<string, number>)` Should work.
{j} `(fun(a: string, b:number))` Should work.
{k} `(fun(a: string, b:number): table)` Should work.
{l} `(NUMBER)` Should still work as custom classes are allowed.
{m} `(NUMBER|nil)` Should still work as custom classes are allowed.
{M} `(NUMBER | nil)` Should still work as custom classes are allowed.
{n} `(NUMBER | nil)` Should not be doubly enclosed in ().
{o} `(number[] | (string | nil) | [number, string])` Should work.
{p} `(number | string)` Should ignore |later| special[] characters?
{a_function} `(function)` Should enclose second `function`.
{function_a} `(function)` Should enclose second `function`.
{a_function_a} `(function)` Should enclose second `function`.
Expand Down Expand Up @@ -186,17 +201,45 @@ Return ~
Return ~
`(number[])` Should work.
Return ~
`(number[])` `(optional)` Should work.
Return ~
`(number|nil)` Should work.
Return ~
`(number | nil)` Should work.
Return ~
`(number | number[])` Should work.
Return ~
`(number | nil)` Should not be doubly enclosed in ().
Return ~
`([number, string])` Should work.
Return ~
`([number, string[]])` Should work.
Return ~
`({[string]:number})` Should work.
Return ~
`({ [string]: number })` Should work.
Return ~
`({key1:string,key2:number})` Should work.
Return ~
`({ key1: string, key2: number })` Should work.
Return ~
`(table<string, number>)` Should work.
Return ~
`(fun(a: string, b:number))` Should work.
Return ~
`(fun(a: string, b:number): table)` Should work.
Return ~
NUMBER Shouldn't work.
`(NUMBER)` Should still work as custom classes are allowed.
Return ~
`(NUMBER|nil)` Should still work as custom classes are allowed.
Return ~
`(NUMBER | nil)` Should still work as custom classes are allowed.
Return ~
`(NUMBER | nil)` Should not be doubly enclosed in ().
Return ~
`(number[] | (string | nil) | [number, string])` Should work
Return ~
`(function)` Should not enclose second time: function .
`(number | string)` Should ignore |later| special[] characters?
Return ~
`(...)` Should work.

Expand Down

0 comments on commit 3960d50

Please sign in to comment.