refactor(gen_lsp.lua): add typing for the LSP protocol JSON data model

Enhance readability and intellisense by incorporating type annotations.
Types are not very strict and may not encompass th entire LSP Protocol
metamodel; the scope is up to what's relevant for generating type
annotations for LSP (`_meta/protocol.lua`).

Based on the model schema:
https://raw.githubusercontent.com/microsoft/language-server-protocol/gh-pages/_specifications/lsp/3.18/metaModel/metaModel.schema.json

No behavioral changes (and hence no diff on _meta/protocol.lua) should
exist in this commit.
This commit is contained in:
Jongwook Choi 2023-12-10 23:50:54 -05:00 committed by Mathias Fußenegger
parent 3767468b96
commit 2f43af6423

View File

@ -24,7 +24,17 @@ local function tofile(fname, text)
end
end
---@param opt gen_lsp._opt
--- The LSP protocol JSON data (it's partial, non-exhaustive).
--- https://raw.githubusercontent.com/microsoft/language-server-protocol/gh-pages/_specifications/lsp/3.18/metaModel/metaModel.schema.json
--- @class vim._gen_lsp.Protocol
--- @field requests vim._gen_lsp.Request[]
--- @field notifications vim._gen_lsp.Notification[]
--- @field structures vim._gen_lsp.Structure[]
--- @field enumerations vim._gen_lsp.Enumeration[]
--- @field typeAliases vim._gen_lsp.TypeAlias[]
---@param opt vim._gen_lsp.opt
---@return vim._gen_lsp.Protocol
local function read_json(opt)
local uri = 'https://raw.githubusercontent.com/microsoft/language-server-protocol/gh-pages/_specifications/lsp/'
.. opt.version
@ -46,6 +56,7 @@ local function name(s)
return s:gsub('^%$', 'dollar'):gsub('/', '_')
end
---@param protocol vim._gen_lsp.Protocol
local function gen_methods(protocol)
local output = {
'-- Generated by gen_lsp.lua, keep at end of file.',
@ -56,6 +67,32 @@ local function gen_methods(protocol)
}
local indent = (' '):rep(2)
--- @class vim._gen_lsp.Request
--- @field deprecated? string
--- @field documentation? string
--- @field messageDirection string
--- @field method string
--- @field params? any
--- @field proposed? boolean
--- @field registrationMethod? string
--- @field registrationOptions? any
--- @field since? string
--- @class vim._gen_lsp.Notification
--- @field deprecated? string
--- @field documentation? string
--- @field errorData? any
--- @field messageDirection string
--- @field method string
--- @field params? any[]
--- @field partialResult? any
--- @field proposed? boolean
--- @field registrationMethod? string
--- @field registrationOptions? any
--- @field result any
--- @field since? string
---@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)
@ -106,14 +143,15 @@ return protocol
vim.cmd.write()
end
---@class gen_lsp._opt
---@class vim._gen_lsp.opt
---@field output_file string
---@field version string
---@field methods boolean
---@param opt gen_lsp._opt
---@param opt vim._gen_lsp.opt
function M.gen(opt)
local protocol = read_json(opt) --- @type table
--- @type vim._gen_lsp.Protocol
local protocol = read_json(opt)
if opt.methods then
gen_methods(protocol)
@ -144,6 +182,7 @@ function M.gen(opt)
local anonymous_num = 0
---@type string[]
local anonym_classes = {}
local simple_types = {
@ -154,32 +193,65 @@ function M.gen(opt)
'decimal',
}
--- @class vim._gen_lsp.Type
--- @field kind string a common field for all Types.
--- @field name? string for ReferenceType, BaseType
--- @field element? any for ArrayType
--- @field items? vim._gen_lsp.Type[] for OrType, AndType
--- @field key? vim._gen_lsp.Type for MapType
--- @field value? string|vim._gen_lsp.Type for StringLiteralType, MapType, StructureLiteralType
---@param type vim._gen_lsp.Type
---@return string
local function parse_type(type)
-- ReferenceType | BaseType
if type.kind == 'reference' or type.kind == 'base' then
if vim.tbl_contains(simple_types, type.name) then
return type.name
end
return 'lsp.' .. type.name
-- ArrayType
elseif type.kind == 'array' then
return parse_type(type.element) .. '[]'
-- OrType
elseif type.kind == 'or' then
local val = ''
for _, item in ipairs(type.items) do
val = val .. parse_type(item) .. '|'
val = val .. parse_type(item) .. '|' --[[ @as string ]]
end
val = val:sub(0, -2)
return val
-- StringLiteralType
elseif type.kind == 'stringLiteral' then
return '"' .. type.value .. '"'
-- MapType
elseif type.kind == 'map' then
return 'table<' .. parse_type(type.key) .. ', ' .. parse_type(type.value) .. '>'
local key = assert(type.key)
local value = type.value --[[ @as vim._gen_lsp.Type ]]
return 'table<' .. parse_type(key) .. ', ' .. parse_type(value) .. '>'
-- StructureLiteralType
elseif type.kind == 'literal' then
-- can I use ---@param disabled? {reason: string}
-- 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 }
for _, field in ipairs(type.value.properties) do
--- @class vim._gen_lsp.StructureLiteral translated to anonymous @class.
--- @field deprecated? string
--- @field description? string
--- @field properties vim._gen_lsp.Property[]
--- @field proposed? boolean
--- @field since? string
---@type vim._gen_lsp.StructureLiteral
local structural_literal = assert(type.value) --[[ @as vim._gen_lsp.StructureLiteral ]]
for _, field in ipairs(structural_literal.properties) do
if field.documentation then
field.documentation = field.documentation:gsub('\n', '\n---')
anonym[#anonym + 1] = '---' .. field.documentation
@ -195,6 +267,8 @@ function M.gen(opt)
anonym_classes[#anonym_classes + 1] = line
end
return 'anonym' .. anonymous_num
-- TupleType
elseif type.kind == 'tuple' then
local tuple = '{ '
for i, value in ipairs(type.items) do
@ -204,10 +278,20 @@ function M.gen(opt)
tuple = tuple:sub(0, -3)
return tuple .. ' }'
end
vim.print(type)
vim.print('WARNING: Unknown type ', type)
return ''
end
--- @class vim._gen_lsp.Structure translated to @class
--- @field deprecated? string
--- @field documentation? string
--- @field extends? { kind: string, name: string }[]
--- @field mixins? { kind: string, name: string }[]
--- @field name string
--- @field properties? vim._gen_lsp.Property[] members, translated to @field
--- @field proposed? boolean
--- @field since? string
for _, structure in ipairs(protocol.structures) do
if structure.documentation then
structure.documentation = structure.documentation:gsub('\n', '\n---')
@ -225,6 +309,15 @@ function M.gen(opt)
else
output[#output + 1] = '---@class lsp.' .. structure.name
end
--- @class vim._gen_lsp.Property translated to @field
--- @field deprecated? string
--- @field documentation? string
--- @field name string
--- @field optional? boolean
--- @field proposed? boolean
--- @field since? string
--- @field type { kind: string, name: string }
for _, field in ipairs(structure.properties or {}) do
if field.documentation then
field.documentation = field.documentation:gsub('\n', '\n---')
@ -239,6 +332,14 @@ function M.gen(opt)
output[#output + 1] = ''
end
--- @class vim._gen_lsp.Enumeration translated to @enum
--- @field deprecated string?
--- @field documentation string?
--- @field name string?
--- @field proposed boolean?
--- @field since string?
--- @field suportsCustomValues boolean?
--- @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---')
@ -256,6 +357,13 @@ function M.gen(opt)
output[#output + 1] = ''
end
--- @class vim._gen_lsp.TypeAlias translated to @alias
--- @field deprecated? string?
--- @field documentation? string
--- @field name string
--- @field proposed? boolean
--- @field since? string
--- @field type vim._gen_lsp.Type
for _, alias in ipairs(protocol.typeAliases) do
if alias.documentation then
alias.documentation = alias.documentation:gsub('\n', '\n---')
@ -274,6 +382,7 @@ function M.gen(opt)
output[#output + 1] = ''
end
-- anonymous classes
for _, line in ipairs(anonym_classes) do
output[#output + 1] = line
end
@ -281,6 +390,7 @@ function M.gen(opt)
tofile(opt.output_file, table.concat(output, '\n'))
end
---@type vim._gen_lsp.opt
local opt = {
output_file = 'runtime/lua/vim/lsp/_meta/protocol.lua',
version = DEFAULT_LSP_VERSION,