aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorLewis Russell <lewis6991@gmail.com>2023-07-26 09:50:54 +0100
committerGitHub <noreply@github.com>2023-07-26 09:50:54 +0100
commitfd089c8e50c211d7beae15dbc9492ae5a1a5f2e2 (patch)
treefca392972189b68ddeb65722463b464f4440024b /scripts
parentb8b77820371978a5f937ccc0db356574ae33371b (diff)
downloadrneovim-fd089c8e50c211d7beae15dbc9492ae5a1a5f2e2.tar.gz
rneovim-fd089c8e50c211d7beae15dbc9492ae5a1a5f2e2.tar.bz2
rneovim-fd089c8e50c211d7beae15dbc9492ae5a1a5f2e2.zip
feat(lua): typing for vim.fn.* (#24473)
Problem: No LSP information for `vim.fn.*` Solution: Add meta file for `vim.fn.*`.
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/gen_vimfn_types.lua244
1 files changed, 244 insertions, 0 deletions
diff --git a/scripts/gen_vimfn_types.lua b/scripts/gen_vimfn_types.lua
new file mode 100755
index 0000000000..f0dfd0665c
--- /dev/null
+++ b/scripts/gen_vimfn_types.lua
@@ -0,0 +1,244 @@
+#!/usr/bin/env -S nvim -l
+
+--- @class vim.EvalFn2 : vim.EvalFn
+--- @field signature string
+--- @field desc string[]
+--- @field params {[1]: string, [2]: string}[]
+
+--- @param filename string
+--- @return string
+local function safe_read(filename)
+ local file, err = io.open(filename, 'r')
+ if not file then
+ error(err)
+ end
+ local content = file:read('*a')
+ io.close(file)
+ return content
+end
+
+local nvim_eval = require'src/nvim/eval'
+
+local funcs = nvim_eval.funcs --[[@as table<string,vim.EvalFn2>]]
+
+local LUA_KEYWORDS = {
+ ['and'] = true,
+ ['end'] = true,
+ ['function'] = true,
+ ['or'] = true,
+ ['if'] = true,
+ ['while'] = true,
+ ['repeat'] = true
+}
+
+local SOURCES = {
+ {
+ path = 'runtime/doc/builtin.txt',
+ from = '^2. Details',
+ to = '==========',
+ },
+ {
+ path = 'runtime/doc/sign.txt',
+ from = '^3. Functions',
+ to = 'vim:'
+ },
+ {
+ path = 'runtime/doc/testing.txt',
+ from = '^3. Assert functions',
+ to = 'vim:'
+ }
+}
+
+local ARG_NAME_TYPES = {
+ col = 'integer',
+ nosuf = 'boolean',
+ dir = 'string',
+ mode = 'string',
+ width = 'integer',
+ height = 'integer',
+ timeout = 'integer',
+ libname = 'string',
+ funcname = 'string',
+ end_ = 'integer',
+ file = 'string',
+ flags = 'string',
+ fname = 'integer',
+ idx = 'integer',
+ lnum = 'integer',
+ mods = 'string',
+ name = 'string',
+ nr = 'integer',
+ options = 'table',
+ opts = 'table',
+ path = 'string',
+ regname = 'string',
+ silent = 'boolean',
+ string = 'string',
+ tabnr = 'integer',
+ varname = 'string',
+ winid = 'integer',
+ winnr = 'integer',
+}
+
+local function process_source(source)
+ local src_txt = safe_read(source.path)
+
+ --- @type string[]
+ local src_lines = vim.split(src_txt, '\n', { plain = true })
+
+ local s = 0
+ for i, l in ipairs(src_lines) do
+ if l:match(source.from) then
+ s = i+1
+ end
+ end
+
+ local lines = {} --- @type string[]
+ local last_f --- @type string?
+
+ for i = s, #src_lines do
+ local l = src_lines[i]
+ if not l or l:match(source.to) then
+ break
+ end
+ local f = l:match('^([a-z][a-zA-Z0-9_]*)%(')
+ if f then
+ if last_f then
+ funcs[last_f].desc = lines
+ end
+ last_f = f
+ local sig = l:match('[^)]+%)')
+ local params = {} --- @type string[]
+ if sig then
+ for param in string.gmatch(sig, '{([a-z][a-zA-Z0-9_]*)}') do
+ local t = ARG_NAME_TYPES[param] or 'any'
+ params[#params+1] = {param, t}
+ end
+ else
+ print('error parsing', l)
+ end
+
+ funcs[last_f].signature = sig
+ funcs[last_f].params = params
+
+ lines = {}
+ else
+ lines[#lines+1] = l:gsub('^(<?)\t\t', '%1'):gsub('\t', ' ')
+ end
+ end
+
+ if last_f then
+ funcs[last_f].desc = lines
+ end
+end
+
+local function render_fun_sig(f, params)
+ local param_str --- @type string
+ if params == true then
+ param_str = '...'
+ else
+ param_str = table.concat(vim.tbl_map(function(v)
+ return v[1]
+ end, params), ', ')
+ end
+
+ if LUA_KEYWORDS[f] then
+ return string.format('vim.fn[\'%s\'] = function(%s) end', f, param_str)
+ else
+ return string.format('function vim.fn.%s(%s) end', f, param_str)
+ end
+end
+
+--- Uniquify names
+--- Fix any names that are lua keywords
+--- @param fun vim.EvalFn2
+local function process_params(fun)
+ if not fun.params then
+ return
+ end
+
+ local seen = {} --- @type table<string,true>
+ local sfx = 1
+
+ for _, p in ipairs(fun.params) do
+ if LUA_KEYWORDS[p[1]] then
+ p[1] = p[1]..'_'
+ end
+ if seen[p[1]] then
+ p[1] = p[1]..sfx
+ sfx = sfx + 1
+ else
+ seen[p[1]] = true
+ end
+ end
+end
+
+--- @param funname string
+--- @param fun vim.EvalFn2
+--- @param write fun(line: string)
+local function render_fun(funname, fun, write)
+ if fun.deprecated then
+ write('')
+ write('--- @deprecated')
+ for _, l in ipairs(fun.deprecated) do
+ write('--- '.. l)
+ end
+ write(render_fun_sig(funname, true))
+ return
+ end
+
+ if fun.desc and fun.signature then
+ write('')
+ for _, l in ipairs(fun.desc) do
+ write('--- '.. l:gsub('@', '\\@'))
+ end
+
+ local req_args = type(fun.args) == 'table' and fun.args[1] or fun.args or 0
+
+ for i, param in ipairs(fun.params) do
+ if i <= req_args then
+ write('--- @param '..param[1]..' '..param[2])
+ else
+ write('--- @param '..param[1]..'? '..param[2])
+ end
+ end
+ if fun.returns ~= false then
+ write('--- @return '..(fun.returns or 'any'))
+ end
+ write(render_fun_sig(funname, fun.params))
+ return
+ end
+
+ print('no doc for', funname)
+end
+
+local function main(outfile)
+ local o = assert(io.open(outfile, 'w'))
+
+ local function write(l)
+ local l1 = l:gsub('%s+$', '')
+ o:write(l1)
+ o:write('\n')
+ end
+
+ for _, source in ipairs(SOURCES) do
+ process_source(source)
+ end
+
+ --- @type string[]
+ local fnames = vim.tbl_keys(funcs)
+ table.sort(fnames)
+
+ write('--- @meta')
+ write('-- THIS FILE IS GENERATED')
+ write('-- DO NOT EDIT')
+
+ for _, f in ipairs(fnames) do
+ local fun = funcs[f]
+ process_params(fun)
+ render_fun(f, fun, write)
+ end
+end
+
+main('runtime/lua/vim/_meta/vimfn.lua')
+