aboutsummaryrefslogtreecommitdiff
path: root/scripts/lsp_types.lua
blob: 4a089bd76dc9281da911db50d9780a1f168769cb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
--[[
Generates lua-ls annotations for lsp
USAGE:
nvim -l scripts/lsp_types.lua gen  # this will overwrite runtime/lua/vim/lsp/types/protocol.lua
nvim -l scripts/lsp_types.lua gen --build/new_lsp_types.lua
nvim -l scripts/lsp_types.lua gen --out runtime/lua/vim/lsp/types/protocol.lua --ref 2023.0.0a2 # specify a git reference from microsoft/lsprotocol
--]]

local M = {}

local function tofile(fname, text)
  local f = io.open(fname, 'w')
  if not f then
    error(('failed to write: %s'):format(f))
  else
    f:write(text)
    f:close()
  end
end

function M.gen(opt)
  if vim.uv.fs_stat('./lsp.json') then
    vim.fn.delete('./lsp.json')
  end
  vim.fn.system({
    'curl',
    'https://raw.githubusercontent.com/microsoft/lsprotocol/' .. opt.ref .. '/generator/lsp.json',
    '-o',
    './lsp.json',
  })
  local protocol = vim.fn.json_decode(vim.fn.readfile('./lsp.json'))
  vim.fn.delete('./lsp.json')
  protocol = protocol or {}
  local output = {
    '--[[',
    'This file is autogenerated from scripts/lsp_types.lua',
    'Regenerate:',
    [=[nvim -l scripts/lsp_types.lua gen --runtime/lua/vim/lsp/types/protocol.lua]=],
    '--]]',
    '',
    '---@alias lsp.null nil',
    '---@alias uinteger integer',
    '---@alias lsp.decimal number',
    '---@alias lsp.DocumentUri string',
    '---@alias lsp.URI string',
    '---@alias lsp.LSPObject table<string, lsp.LSPAny>',
    '---@alias lsp.LSPArray lsp.LSPAny[]',
    '---@alias lsp.LSPAny lsp.LSPObject|lsp.LSPArray|string|number|boolean|nil',
    '',
  }

  local anonymous_num = 0

  local anonym_classes = {}

  local simple_types = {
    'string',
    'boolean',
    'integer',
    'uinteger',
    'decimal',
  }

  local function parse_type(type)
    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
    elseif type.kind == 'array' then
      return parse_type(type.element) .. '[]'
    elseif type.kind == 'or' then
      local val = ''
      for _, item in ipairs(type.items) do
        val = val .. parse_type(item) .. '|'
      end
      val = val:sub(0, -2)
      return val
    elseif type.kind == 'stringLiteral' then
      return '"' .. type.value .. '"'
    elseif type.kind == 'map' then
      return 'table<' .. parse_type(type.key) .. ', ' .. parse_type(type.value) .. '>'
    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
        if field.documentation then
          field.documentation = field.documentation:gsub('\n', '\n---')
          anonym[#anonym + 1] = '---' .. field.documentation
        end
        anonym[#anonym + 1] = '---@field '
          .. field.name
          .. (field.optional and '?' or '')
          .. ' '
          .. parse_type(field.type)
      end
      anonym[#anonym + 1] = ''
      for _, line in ipairs(anonym) do
        anonym_classes[#anonym_classes + 1] = line
      end
      return 'anonym' .. anonymous_num
    elseif type.kind == 'tuple' then
      local tuple = '{ '
      for i, value in ipairs(type.items) do
        tuple = tuple .. '[' .. i .. ']: ' .. parse_type(value) .. ', '
      end
      -- remove , at the end
      tuple = tuple:sub(0, -3)
      return tuple .. ' }'
    end
    vim.print(type)
    return ''
  end

  for _, structure in ipairs(protocol.structures) do
    if structure.documentation then
      structure.documentation = structure.documentation:gsub('\n', '\n---')
      output[#output + 1] = '---' .. 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)
      end
      output[#output + 1] = class_string
    else
      output[#output + 1] = '---@class lsp.' .. structure.name
    end
    for _, field in ipairs(structure.properties or {}) do
      if field.documentation then
        field.documentation = field.documentation:gsub('\n', '\n---')
        output[#output + 1] = '---' .. field.documentation
      end
      output[#output + 1] = '---@field '
        .. field.name
        .. (field.optional and '?' or '')
        .. ' '
        .. parse_type(field.type)
    end
    output[#output + 1] = ''
  end

  for _, enum in ipairs(protocol.enumerations) do
    if enum.documentation then
      enum.documentation = enum.documentation:gsub('\n', '\n---')
      output[#output + 1] = '---' .. enum.documentation
    end
    local enum_type = '---@alias lsp.' .. enum.name
    for _, value in ipairs(enum.values) do
      enum_type = enum_type
        .. '\n---| '
        .. (type(value.value) == 'string' and '"' .. value.value .. '"' or value.value)
        .. ' # '
        .. value.name
    end
    output[#output + 1] = enum_type
    output[#output + 1] = ''
  end

  for _, alias in ipairs(protocol.typeAliases) do
    if alias.documentation then
      alias.documentation = alias.documentation:gsub('\n', '\n---')
      output[#output + 1] = '---' .. 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) .. '|'
      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)
    end
    output[#output + 1] = ''
  end

  for _, line in ipairs(anonym_classes) do
    output[#output + 1] = line
  end

  tofile(opt.output_file, table.concat(output, '\n'))
end

local opt = {
  output_file = 'runtime/lua/vim/lsp/types/protocol.lua',
  ref = 'main',
}

for i = 1, #_G.arg do
  if _G.arg[i] == '--out' then
    opt.output_file = _G.arg[i+1]
  elseif _G.arg[i] == '--ref' then
    opt.ref = _G.arg[i+1]
  elseif vim.startswith(_G.arg[i], '--') then
    opt.output_file = _G.arg[i]:sub(3)
  end
end

for _, a in ipairs(arg) do
  if M[a] then
    M[a](opt)
  end
end

return M