fix(gen_lsp.lua): improve type name, and fix wrong type inheritance

Style improvements:

1. Anonymous classes derived from `StructureLiteralType` should have a
   better name. The class name can be also nested. Examples:

```diff
----@field serverInfo? anonym1
+---@field serverInfo? lsp._anonym1.serverInfo
```
```diff
----@field insertTextModeSupport? anonym26
+---@field insertTextModeSupport? lsp._anonym26.completionItem.insertTextModeSupport
```

2. Add one separate empty line before each `@field` definition. Without
   these, empty lines the doc can look confusing because descriptions
   also may contain empty lines. See `lsp.CompletionItem` for example:

```lua
---The kind of this completion item. Based of the kind
---an icon is chosen by the editor.
---@field kind? lsp.CompletionItemKind
---Tags for this completion item.
---
---@since 3.15.0
---@field tags? lsp.CompletionItemTag[]
```

   It might feel like "Tags for this completion item" belongs to `kind`,
   not `tags` due to the lack of separator blank lines. The following
   (after this commit) should look much better:

```diff
 ---The kind of this completion item. Based of the kind
 ---an icon is chosen by the editor.
 ---@field kind? lsp.CompletionItemKind
+---
 ---Tags for this completion item.
 ---
 ---@since 3.15.0
 ---@field tags? lsp.CompletionItemTag[]
```

3. Escape some LSP-specific annotations that can't be recognized by
   lua-ls. It'd be better to make them visible in LSP hover doc windows.

   Example: `@sample ...`.

Fixes:

1. A type may extend from more than one base types (as well as mixin
   types). Previously only the first base class was being considered,
   resulting incomplete base classes for `@class` definitions.

   Example: `InlayHintOptions` (should have both of `resolveProvider`
   and `workDoneProgress`, the latter is from `WorkDoneProgressOptions`)

```diff
----@class lsp.InlayHintOptions
+---@class lsp.InlayHintOptions: lsp.WorkDoneProgressOptions
```

2. Remove `<200b>` (zero-width space) unicode characters.

3. Add the missing newline at EOF.
This commit is contained in:
Jongwook Choi 2023-12-11 02:25:17 -05:00 committed by Mathias Fußenegger
parent 2f43af6423
commit 6c35fb421e
2 changed files with 1045 additions and 235 deletions

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,7 @@ local function read_json(opt)
end
-- Gets the Lua symbol for a given fully-qualified LSP method name.
local function name(s)
local function to_luaname(s)
-- "$/" prefix is special: https://microsoft.github.io/language-server-protocol/specification/#dollarRequests
return s:gsub('^%$', 'dollar'):gsub('/', '_')
end
@ -95,7 +95,7 @@ local function gen_methods(protocol)
---@type (vim._gen_lsp.Request|vim._gen_lsp.Notification)[]
local all = vim.list_extend(protocol.requests, protocol.notifications)
table.sort(all, function(a, b)
return name(a.method) < name(b.method)
return to_luaname(a.method) < to_luaname(b.method)
end)
for _, item in ipairs(all) do
if item.method then
@ -105,7 +105,7 @@ local function gen_methods(protocol)
output[#output + 1] = indent .. '--- ' .. docstring
end
end
output[#output + 1] = ("%s%s = '%s',"):format(indent, name(item.method), item.method)
output[#output + 1] = ("%s%s = '%s',"):format(indent, to_luaname(item.method), item.method)
end
end
output[#output + 1] = '}'
@ -193,6 +193,16 @@ function M.gen(opt)
'decimal',
}
---@param documentation string
local _process_documentation = function(documentation)
documentation = documentation:gsub('\n', '\n---')
-- Remove <200b> (zero-width space) unicode characters: e.g., `**/<200b>*`
documentation = documentation:gsub('\226\128\139', '')
-- Escape annotations that are not recognized by lua-ls
documentation = documentation:gsub('%^---@sample', '---\\@sample')
return '---' .. documentation
end
--- @class vim._gen_lsp.Type
--- @field kind string a common field for all Types.
--- @field name? string for ReferenceType, BaseType
@ -202,8 +212,10 @@ function M.gen(opt)
--- @field value? string|vim._gen_lsp.Type for StringLiteralType, MapType, StructureLiteralType
---@param type vim._gen_lsp.Type
---@param prefix? string Optional prefix associated with the this type, made of (nested) field name.
--- Used to generate class name for structure literal types.
---@return string
local function parse_type(type)
local function parse_type(type, prefix)
-- ReferenceType | BaseType
if type.kind == 'reference' or type.kind == 'base' then
if vim.tbl_contains(simple_types, type.name) then
@ -213,13 +225,13 @@ function M.gen(opt)
-- ArrayType
elseif type.kind == 'array' then
return parse_type(type.element) .. '[]'
return parse_type(type.element, prefix) .. '[]'
-- OrType
elseif type.kind == 'or' then
local val = ''
for _, item in ipairs(type.items) do
val = val .. parse_type(item) .. '|' --[[ @as string ]]
val = val .. parse_type(item, prefix) .. '|' --[[ @as string ]]
end
val = val:sub(0, -2)
return val
@ -232,7 +244,7 @@ function M.gen(opt)
elseif type.kind == 'map' then
local key = assert(type.key)
local value = type.value --[[ @as vim._gen_lsp.Type ]]
return 'table<' .. parse_type(key) .. ', ' .. parse_type(value) .. '>'
return 'table<' .. parse_type(key, prefix) .. ', ' .. parse_type(value, prefix) .. '>'
-- StructureLiteralType
elseif type.kind == 'literal' then
@ -240,7 +252,14 @@ function M.gen(opt)
-- use | to continue the inline class to be able to add docs
-- https://github.com/LuaLS/lua-language-server/issues/2128
anonymous_num = anonymous_num + 1
local anonym = { '---@class anonym' .. anonymous_num }
local anonymous_classname = 'lsp._anonym' .. anonymous_num
if prefix then
anonymous_classname = anonymous_classname .. '.' .. prefix
end
local anonym = vim.tbl_flatten { -- remove nil
anonymous_num > 1 and '' or nil,
'---@class ' .. anonymous_classname,
}
--- @class vim._gen_lsp.StructureLiteral translated to anonymous @class.
--- @field deprecated? string
@ -252,27 +271,29 @@ function M.gen(opt)
---@type vim._gen_lsp.StructureLiteral
local structural_literal = assert(type.value) --[[ @as vim._gen_lsp.StructureLiteral ]]
for _, field in ipairs(structural_literal.properties) do
anonym[#anonym + 1] = '---'
if field.documentation then
field.documentation = field.documentation:gsub('\n', '\n---')
anonym[#anonym + 1] = '---' .. field.documentation
anonym[#anonym + 1] = _process_documentation(field.documentation)
end
anonym[#anonym + 1] = '---@field '
.. field.name
.. (field.optional and '?' or '')
.. ' '
.. parse_type(field.type)
.. parse_type(field.type, prefix .. '.' .. field.name)
end
anonym[#anonym + 1] = ''
-- anonym[#anonym + 1] = ''
for _, line in ipairs(anonym) do
if line then
anonym_classes[#anonym_classes + 1] = line
end
return 'anonym' .. anonymous_num
end
return anonymous_classname
-- TupleType
elseif type.kind == 'tuple' then
local tuple = '{ '
for i, value in ipairs(type.items) do
tuple = tuple .. '[' .. i .. ']: ' .. parse_type(value) .. ', '
tuple = tuple .. '[' .. i .. ']: ' .. parse_type(value, prefix) .. ', '
end
-- remove , at the end
tuple = tuple:sub(0, -3)
@ -293,22 +314,22 @@ function M.gen(opt)
--- @field proposed? boolean
--- @field since? string
for _, structure in ipairs(protocol.structures) do
-- output[#output + 1] = ''
if structure.documentation then
structure.documentation = structure.documentation:gsub('\n', '\n---')
output[#output + 1] = '---' .. structure.documentation
output[#output + 1] = _process_documentation(structure.documentation)
end
if structure.extends then
local class_string = '---@class lsp.'
.. structure.name
.. ': '
.. parse_type(structure.extends[1])
for _, mixin in ipairs(structure.mixins or {}) do
class_string = class_string .. ', ' .. parse_type(mixin)
local class_string = ('---@class lsp.%s'):format(structure.name)
if structure.extends or structure.mixins then
local inherits_from = table.concat(
vim.list_extend(
vim.tbl_map(parse_type, structure.extends or {}),
vim.tbl_map(parse_type, structure.mixins or {})
),
', '
)
class_string = class_string .. ': ' .. inherits_from
end
output[#output + 1] = class_string
else
output[#output + 1] = '---@class lsp.' .. structure.name
end
--- @class vim._gen_lsp.Property translated to @field
--- @field deprecated? string
@ -319,15 +340,15 @@ function M.gen(opt)
--- @field since? string
--- @field type { kind: string, name: string }
for _, field in ipairs(structure.properties or {}) do
output[#output + 1] = '---' -- Insert a single newline between @fields (and after @class)
if field.documentation then
field.documentation = field.documentation:gsub('\n', '\n---')
output[#output + 1] = '---' .. field.documentation
output[#output + 1] = _process_documentation(field.documentation)
end
output[#output + 1] = '---@field '
.. field.name
.. (field.optional and '?' or '')
.. ' '
.. parse_type(field.type)
.. parse_type(field.type, field.name)
end
output[#output + 1] = ''
end
@ -342,8 +363,7 @@ function M.gen(opt)
--- @field values { name: string, value: string, documentation?: string, since?: string }[]
for _, enum in ipairs(protocol.enumerations) do
if enum.documentation then
enum.documentation = enum.documentation:gsub('\n', '\n---')
output[#output + 1] = '---' .. enum.documentation
output[#output + 1] = _process_documentation(enum.documentation)
end
local enum_type = '---@alias lsp.' .. enum.name
for _, value in ipairs(enum.values) do
@ -366,18 +386,20 @@ function M.gen(opt)
--- @field type vim._gen_lsp.Type
for _, alias in ipairs(protocol.typeAliases) do
if alias.documentation then
alias.documentation = alias.documentation:gsub('\n', '\n---')
output[#output + 1] = '---' .. alias.documentation
output[#output + 1] = _process_documentation(alias.documentation)
end
if alias.type.kind == 'or' then
local alias_type = '---@alias lsp.' .. alias.name .. ' '
for _, item in ipairs(alias.type.items) do
alias_type = alias_type .. parse_type(item) .. '|'
alias_type = alias_type .. parse_type(item, alias.name) .. '|'
end
alias_type = alias_type:sub(0, -2)
output[#output + 1] = alias_type
else
output[#output + 1] = '---@alias lsp.' .. alias.name .. ' ' .. parse_type(alias.type)
output[#output + 1] = '---@alias lsp.'
.. alias.name
.. ' '
.. parse_type(alias.type, alias.name)
end
output[#output + 1] = ''
end
@ -387,7 +409,7 @@ function M.gen(opt)
output[#output + 1] = line
end
tofile(opt.output_file, table.concat(output, '\n'))
tofile(opt.output_file, table.concat(output, '\n') .. '\n')
end
---@type vim._gen_lsp.opt