From 355605200624b7e41b24133e8cb49abe46093d4f Mon Sep 17 00:00:00 2001 From: Tom Maisey Date: Thu, 21 Aug 2025 17:40:06 +0100 Subject: [PATCH 1/6] Render enum/alias comments as markdown --- script/core/hover/description.lua | 25 ++++---- test/crossfile/hover.lua | 96 +++++++++++++------------------ 2 files changed, 52 insertions(+), 69 deletions(-) diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index ea7f16340..acacb62d3 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -250,20 +250,21 @@ local function buildEnumChunk(docType, name, uri) local comment = tryDocClassComment(tp) if comment then for line in util.eachLine(comment) do - lines[#lines+1] = ('-- %s'):format(line) + lines[#lines+1] = line end end end if #enums == 0 then return nil end - lines[#lines+1] = ('%s:'):format(name) + lines[#lines+1] = ('**%s**:'):format(name) for _, enum in ipairs(enums) do - local enumDes = (' %s %s'):format( - (enum.default and '->') - or (enum.additional and '+>') - or ' |', - vm.getInfer(enum):view(uri) + local suffix = (enum.default and ' (default)') + or (enum.additional and ' (additional)') + or '' + local enumDes = (' - `%s`%s'):format( + vm.getInfer(enum):view(uri), + suffix ) if enum.comment then local first = true @@ -271,9 +272,9 @@ local function buildEnumChunk(docType, name, uri) for comm in enum.comment:gmatch '[^\r\n]+' do if first then first = false - enumDes = ('%s -- %s'):format(enumDes, comm) + enumDes = ('%s — %s'):format(enumDes, comm) else - enumDes = ('%s\n%s -- %s'):format(enumDes, (' '):rep(len), comm) + enumDes = ('%s\n%s %s'):format(enumDes, (' '):rep(len + 3), comm) end end end @@ -391,7 +392,7 @@ local function getFunctionCommentMarkdown(source, raw) end local enums = getBindEnums(source, docGroup) - md:add('lua', enums) + md:add('md', enums) return md end @@ -410,11 +411,11 @@ local function tryDocComment(source, raw) md:add('md', comment) if source.type == 'doc.alias' then local enums = buildEnumChunk(source, source.alias[1], guide.getUri(source)) - md:add('lua', enums) + md:add('md', enums) end if source.type == 'doc.enum' then local enums = buildEnumChunk(source, source.enum[1], guide.getUri(source)) - md:add('lua', enums) + md:add('md', enums) end local result = md:string() if result == '' then diff --git a/test/crossfile/hover.lua b/test/crossfile/hover.lua index 25701f55d..e85c2e46b 100644 --- a/test/crossfile/hover.lua +++ b/test/crossfile/hover.lua @@ -243,7 +243,7 @@ TEST { function mt:add(a, b) end - + return function () return setmetatable({}, mt) end @@ -400,11 +400,9 @@ function f(x: string|"选项1"|"选项2") --- -```lua -x: - | "选项1" -- 注释1 - -> "选项2" -- 注释2 -```]] +**x**: + - `"选项1"` — 注释1 + - `"选项2"` (default) — 注释2]] } TEST { @@ -429,11 +427,9 @@ function f(x: "选项1"|"选项2") --- -```lua -x: - | "选项1" -- 注释1 - -> "选项2" -- 注释2 -```]] +**x**: + - `"选项1"` — 注释1 + - `"选项2"` (default) — 注释2]] } TEST { @@ -459,11 +455,9 @@ function f() --- -```lua -x: - | "选项1" -- 注释1 - -> "选项2" -- 注释2 -```]] +**x**: + - `"选项1"` — 注释1 + - `"选项2"` (default) — 注释2]] } TEST { @@ -489,11 +483,9 @@ function f() --- -```lua -return #1: - | "选项1" -- 注释1 - -> "选项2" -- 注释2 -```]] +**return #1**: + - `"选项1"` — 注释1 + - `"选项2"` (default) — 注释2]] } TEST { @@ -717,11 +709,9 @@ function f(a: boolean) @*param* `a` — xxx -```lua -a: - | true -- ttt - | false -- fff -```]]} +**a**: + - `true` — ttt + - `false` — fff]]} TEST {{ path = 'a.lua', content = '', }, { path = 'b.lua', @@ -1020,13 +1010,11 @@ function f(p: 'a'|'b') --- -```lua -p: - | 'a' -- comment 1 - -- comment 2 - | 'b' -- comment 3 - -- comment 4 -```]]} +**p**: + - `'a'` — comment 1 + comment 2 + - `'b'` — comment 3 + comment 4]]} --TEST {{ path = 'a.lua', content = '', }, { -- path = 'b.lua', @@ -1243,23 +1231,21 @@ function f(p: 'a1'|'a2', ...'a3'|'a4') --- -```lua -p: - | 'a1' - | 'a2' +**p**: + - `'a1'` + - `'a2'` -...(param): - | 'a3' - | 'a4' +**...(param)**: + - `'a3'` + - `'a4'` -ret1: - | 'r1' - | 'r2' +**ret1**: + - `'r1'` + - `'r2'` -...(return): - | 'r3' - | 'r4' -```]] +**...(return)**: + - `'r3'` + - `'r4'`]] } TEST { @@ -1510,11 +1496,9 @@ local x: 1|2 --- -```lua -A: - | 1 -- comment1 - | 2 -- comment2 -```]] +**A**: + - `1` — comment1 + - `2` — comment2]] } TEST { @@ -1632,7 +1616,7 @@ TEST { content = [[ ---@alias someType ---| "#" # description - + ---@type someType local ]] @@ -1644,10 +1628,8 @@ local someValue: "#" --- -```lua -someType: - | "#" -- description -```]] +**someType**: + - `"#"` — description]] } TEST { { path = 'a.lua', content = [[ From f69f5fe00b2f7d21ddb74978b111e85f788e3763 Mon Sep 17 00:00:00 2001 From: Tom Maisey Date: Thu, 21 Aug 2025 23:21:58 +0100 Subject: [PATCH 2/6] Update changelog for alias markdown rendering --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 64cffd748..4eb69e77f 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ ## 3.18.0 `2026-04-03` +* `NEW` Render `@alias` value types with markdown instead of code blocks * `CHG` Always track symbol-links * `CHG` Modified the `ResolveRequire` function to pass the source URI as a third argument. * `CHG` Improved the output of test failures during development From 8cd8babe998ba128dcf16277a6b32efcde9a207a Mon Sep 17 00:00:00 2001 From: Tom Maisey Date: Fri, 22 Aug 2025 10:31:02 +0100 Subject: [PATCH 3/6] Fix alias markdown rendering (use header) --- script/core/hover/description.lua | 5 ++++- test/crossfile/hover.lua | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index acacb62d3..3b2f532c2 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -257,7 +257,10 @@ local function buildEnumChunk(docType, name, uri) if #enums == 0 then return nil end - lines[#lines+1] = ('**%s**:'):format(name) + if #lines > 0 and lines[#lines] ~= "" then + lines[#lines+1] = "" + end + lines[#lines+1] = ('#### %s:'):format(name) for _, enum in ipairs(enums) do local suffix = (enum.default and ' (default)') or (enum.additional and ' (additional)') diff --git a/test/crossfile/hover.lua b/test/crossfile/hover.lua index e85c2e46b..82982f756 100644 --- a/test/crossfile/hover.lua +++ b/test/crossfile/hover.lua @@ -400,7 +400,7 @@ function f(x: string|"选项1"|"选项2") --- -**x**: +#### x: - `"选项1"` — 注释1 - `"选项2"` (default) — 注释2]] } @@ -427,7 +427,7 @@ function f(x: "选项1"|"选项2") --- -**x**: +#### x: - `"选项1"` — 注释1 - `"选项2"` (default) — 注释2]] } @@ -455,7 +455,7 @@ function f() --- -**x**: +#### x: - `"选项1"` — 注释1 - `"选项2"` (default) — 注释2]] } @@ -483,7 +483,7 @@ function f() --- -**return #1**: +#### return #1: - `"选项1"` — 注释1 - `"选项2"` (default) — 注释2]] } @@ -709,7 +709,7 @@ function f(a: boolean) @*param* `a` — xxx -**a**: +#### a: - `true` — ttt - `false` — fff]]} @@ -1010,7 +1010,7 @@ function f(p: 'a'|'b') --- -**p**: +#### p: - `'a'` — comment 1 comment 2 - `'b'` — comment 3 @@ -1231,19 +1231,19 @@ function f(p: 'a1'|'a2', ...'a3'|'a4') --- -**p**: +#### p: - `'a1'` - `'a2'` -**...(param)**: +#### ...(param): - `'a3'` - `'a4'` -**ret1**: +#### ret1: - `'r1'` - `'r2'` -**...(return)**: +#### ...(return): - `'r3'` - `'r4'`]] } @@ -1496,7 +1496,7 @@ local x: 1|2 --- -**A**: +#### A: - `1` — comment1 - `2` — comment2]] } @@ -1628,7 +1628,7 @@ local someValue: "#" --- -**someType**: +#### someType: - `"#"` — description]] } From 3f998afd9b9ce576e3838db8ab467457a38f8d3d Mon Sep 17 00:00:00 2001 From: Tom Maisey Date: Wed, 25 Mar 2026 12:30:38 +0000 Subject: [PATCH 4/6] Alias markdown hover does not repeat description --- script/core/hover/description.lua | 35 ++++++++++++++++++------------- test/crossfile/hover.lua | 24 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index 3b2f532c2..4df95a91b 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -212,11 +212,11 @@ local function lookUpDocComments(source) return table.concat(lines, '\n') end -local function tryDocClassComment(source) +local function tryDocClassComment(source, types) + types = types or {['doc.class'] = true} + for _, def in ipairs(vm.getDefs(source)) do - if def.type == 'doc.class' - or def.type == 'doc.alias' - or def.type == 'doc.enum' then + if types[def.type] then local comment = getBindComment(def) if comment then return comment @@ -238,7 +238,9 @@ local function buildEnumChunk(docType, name, uri) end local enums = {} local types = {} - local lines = {} + local lines = {('#### %s:'):format(name)} + local commentTypes = {['doc.enum'] = true, ['doc.alias'] = true} + for _, tp in ipairs(vm.getDefs(docType)) do types[#types+1] = vm.getInfer(tp):view(guide.getUri(docType)) if tp.type == 'doc.type.string' @@ -247,20 +249,22 @@ local function buildEnumChunk(docType, name, uri) or tp.type == 'doc.type.code' then enums[#enums+1] = tp end - local comment = tryDocClassComment(tp) + + local comment = tryDocClassComment(tp, commentTypes) if comment then for line in util.eachLine(comment) do lines[#lines+1] = line end end end + if #enums == 0 then return nil end - if #lines > 0 and lines[#lines] ~= "" then + if #lines > 1 then lines[#lines+1] = "" end - lines[#lines+1] = ('#### %s:'):format(name) + for _, enum in ipairs(enums) do local suffix = (enum.default and ' (default)') or (enum.additional and ' (additional)') @@ -410,21 +414,24 @@ local function tryDocComment(source, raw) md:add('md', getFunctionCommentMarkdown(source, raw)) source = source.parent end + local comment = lookUpDocComments(source) md:add('md', comment) + if source.type == 'doc.alias' then local enums = buildEnumChunk(source, source.alias[1], guide.getUri(source)) + or tryDocClassComment(source, {['doc.alias'] = true}) + md:add('md', enums) - end - if source.type == 'doc.enum' then + elseif source.type == 'doc.enum' then local enums = buildEnumChunk(source, source.enum[1], guide.getUri(source)) + or tryDocClassComment(source, {['doc.enum'] = true}) + md:add('md', enums) end + local result = md:string() - if result == '' then - return nil - end - return result + return result ~= '' and result or nil end ---@async diff --git a/test/crossfile/hover.lua b/test/crossfile/hover.lua index 82982f756..00d331075 100644 --- a/test/crossfile/hover.lua +++ b/test/crossfile/hover.lua @@ -1481,6 +1481,7 @@ TEST { { path = 'a.lua', content = [[ + ---Description comment ---@alias A ---| 1 # comment1 ---| 2 # comment2 @@ -1497,10 +1498,33 @@ local x: 1|2 --- #### A: +Description comment + - `1` — comment1 - `2` — comment2]] } +TEST { + { + path = 'a.lua', + content = [[ + ---Description comment + ---@alias A number + + ---@type A + local + ]] + }, + hover = [[ +```lua +local x: number +``` + +--- + +Description comment]] +} + TEST { { path = 'a.lua', From b05dd789ea28754be1eac8e2b0651d20a1bdff15 Mon Sep 17 00:00:00 2001 From: Tom Maisey Date: Wed, 25 Mar 2026 12:31:11 +0000 Subject: [PATCH 5/6] Clean up some code in hover/init.lua loop --- script/core/hover/init.lua | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/script/core/hover/init.lua b/script/core/hover/init.lua index cf8552acf..c49def03c 100644 --- a/script/core/hover/init.lua +++ b/script/core/hover/init.lua @@ -56,20 +56,13 @@ local function getHover(source, level) local oop if vm.getInfer(source):view(guide.getUri(source)) == 'function' then local defs = vm.getDefs(source) + -- make sure `function` is before `doc.type.function` - local orders = {} - for i, def in ipairs(defs) do - if def.type == 'function' then - orders[def] = i - 20000 - elseif def.type == 'doc.type.function' then - orders[def] = i - 10000 - else - orders[def] = i - end - end + local orders = {'function', 'doc.type.function'} table.sort(defs, function (a, b) - return orders[a] < orders[b] + return (orders[a.type] or (#orders + 1)) < (orders[b.type] or (#orders + 1)) end) + local hasFunc for _, def in ipairs(defs) do if guide.isOOP(def) then @@ -90,21 +83,15 @@ local function getHover(source, level) end else addHover(source, true, oop) + for _, def in ipairs(vm.getDefs(source)) do - if def.type == 'global' - or def.type == 'setlocal' then - goto CONTINUE - end - if guide.isOOP(def) then - oop = true - end - local isFunction - if def.type == 'function' - or def.type == 'doc.type.function' then - isFunction = true + if def.type ~= 'global' + and def.type ~= 'setlocal' then + oop = oop or guide.isOOP(def) + local isFunction = def.type == 'function' or def.type == 'doc.type.function' + + addHover(def, isFunction, oop) end - addHover(def, isFunction, oop) - ::CONTINUE:: end end From 6951ae42c3e2e4c41154ebdc8083ef5f7dd182c1 Mon Sep 17 00:00:00 2001 From: Tom Maisey Date: Mon, 30 Mar 2026 02:55:51 +0100 Subject: [PATCH 6/6] Cache an array length to avoid recomputing --- script/core/hover/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/core/hover/init.lua b/script/core/hover/init.lua index c49def03c..162140723 100644 --- a/script/core/hover/init.lua +++ b/script/core/hover/init.lua @@ -59,8 +59,9 @@ local function getHover(source, level) -- make sure `function` is before `doc.type.function` local orders = {'function', 'doc.type.function'} + local orderAnyOtherType = #orders + 1 table.sort(defs, function (a, b) - return (orders[a.type] or (#orders + 1)) < (orders[b.type] or (#orders + 1)) + return (orders[a.type] or orderAnyOtherType) < (orders[b.type] or orderAnyOtherType) end) local hasFunc