From 3960d50c98c28819814388d482018fbcecf8f7cb Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Fri, 14 Feb 2025 18:29:28 +0200 Subject: [PATCH] feat(doc): improve formatting for types in `@param`, `@return`, etc --- CHANGELOG.md | 4 ++ lua/mini/doc.lua | 51 ++++++++++----- tests/dir-doc/sections/alias.lua | 4 +- tests/dir-doc/sections/param.lua | 25 ++++++-- tests/dir-doc/sections/return.lua | 18 +++++- tests/dir-doc/sections/sections_reference.txt | 63 ++++++++++++++++--- 6 files changed, 130 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ffe4b5..b798f79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/lua/mini/doc.lua b/lua/mini/doc.lua index 0757c2ac..aa5a8197 100644 --- a/lua/mini/doc.lua +++ b/lua/mini/doc.lua @@ -256,12 +256,13 @@ 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'] = --, ['@overload'] = function(s) - H.enclose_type(s, '`%1`', 1) + s[1] = '`' .. s[1] .. '`' H.add_section_heading(s, 'Overload') end, --minidoc_replace_end @@ -269,7 +270,8 @@ MiniDoc.config = { ['@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'] = --, @@ -278,7 +280,7 @@ MiniDoc.config = { --minidoc_replace_start ['@return'] = --, ['@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 @@ -318,7 +320,7 @@ MiniDoc.config = { --minidoc_replace_end --minidoc_replace_start ['@type'] = --, ['@type'] = function(s) - H.enclose_type(s, '`%(%1%)`', 1) + H.enclose_type(s, 1) H.add_section_heading(s, 'Type') end, --minidoc_replace_end @@ -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 @@ -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 ------------------------------------------------- diff --git a/tests/dir-doc/sections/alias.lua b/tests/dir-doc/sections/alias.lua index d3f2649b..892d8492 100644 --- a/tests/dir-doc/sections/alias.lua +++ b/tests/dir-doc/sections/alias.lua @@ -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 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 diff --git a/tests/dir-doc/sections/param.lua b/tests/dir-doc/sections/param.lua index 86811598..87dfec52 100644 --- a/tests/dir-doc/sections/param.lua +++ b/tests/dir-doc/sections/param.lua @@ -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)` --- @@ -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 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 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`. diff --git a/tests/dir-doc/sections/return.lua b/tests/dir-doc/sections/return.lua index 2d5be83d..9d20614b 100644 --- a/tests/dir-doc/sections/return.lua +++ b/tests/dir-doc/sections/return.lua @@ -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 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. diff --git a/tests/dir-doc/sections/sections_reference.txt b/tests/dir-doc/sections/sections_reference.txt index 5dcb7f0b..6ddfc5a7 100644 --- a/tests/dir-doc/sections/sections_reference.txt +++ b/tests/dir-doc/sections/sections_reference.txt @@ -75,9 +75,9 @@ Test `@alias` section ------------------------------------------------------------------------------ Parameters ~ {x} `(fun(type: string, data: any))` -{y} Another data structure. +{y} `(table)` 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) @@ -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 Another data structure.\n Its description spans over multiple lines." } @@ -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)` @@ -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)` 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)` 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`. @@ -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)` 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.