build: enable more EmmyLua checks

- call-non-callable
- missing-fields
- redefined-label
- redefined-local
- undefined-field
- unreachable-code

Promote redefined-local and unreachable-code to warnings.

Fix DOS line-ending handling for multiline semantic tokens, and support
intersection types in the help parser.

AI-assisted
This commit is contained in:
Lewis Russell
2026-09-07 17:04:29 +01:00
committed by Lewis Russell
parent ec65796cea
commit 6a54396db4
42 changed files with 190 additions and 118 deletions

View File

@@ -29,31 +29,27 @@
"annotation-usage-error",
"assign-type-mismatch",
"await-in-sync",
"call-non-callable",
"code-style-check",
"deprecated",
"global-in-non-module",
"incomplete-signature-doc",
"invert-if",
"iter-variable-reassign",
"missing-fields",
"missing-global-doc",
"need-check-nil",
"non-literal-expressions-in-assert",
"param-type-mismatch",
"preferred-local-alias",
"redefined-label",
"redefined-local",
"return-type-mismatch",
"undefined-field",
"unknown-doc-tag",
"unnecessary-assert",
"unnecessary-if",
"unreachable-code",
"unused"
],
"severity": {
"duplicate-require": "warning"
"duplicate-require": "warning",
"redefined-local": "warning",
"unreachable-code": "warning"
}
},
"codeAction": {

View File

@@ -659,11 +659,11 @@ Lua module: vim.diagnostic *diagnostic-api*
• {severity}? (`vim.diagnostic.SeverityFilter`) Only show signs for
diagnostics matching the given severity
|diagnostic-severity|
• {text}? (`table<vim.diagnostic.Severity,string>`) A table mapping
|diagnostic-severity| to the sign text to display in the
sign column and statusline. The default is to use `"E"`,
`"W"`, `"I"`, and `"H"` for errors, warnings,
information, and hints, respectively. Example: >lua
• {text}? (`table<vim.diagnostic.Severity|vim.diagnostic.SeverityName,string>`)
A table mapping |diagnostic-severity| to the sign text to
display in the sign column and statusline. The default is
to use `"E"`, `"W"`, `"I"`, and `"H"` for errors,
warnings, information, and hints, respectively. Example: >lua
vim.diagnostic.config({
signs = { text = { [vim.diagnostic.severity.ERROR] = 'E', ... } }
})
@@ -803,6 +803,9 @@ config({opts}, {namespace}) *vim.diagnostic.config()*
• {namespace} (`integer?`) Update the options for the given namespace.
When omitted, update the global diagnostic options.
Overloads: ~
• `fun(opts?: nil, namespace?: integer): vim.diagnostic.Opts`
Return: ~
(`vim.diagnostic.Opts?`) Current diagnostic config if {opts} is
omitted.

View File

@@ -1438,7 +1438,8 @@ tagfunc({pattern}, {flags}) *vim.lsp.tagfunc()*
• {flags} (`string`) See |tag-function|
Return: ~
(`table[]`) tags A list of matching tags
(`table[]|vim.NIL`) tags A list of matching tags, or `vim.NIL` to use
the built-in tags.
==============================================================================

View File

@@ -1828,7 +1828,12 @@ vim.list.bisect({t}, {val}, {opts}) *vim.list.bisect()*
Use {bound} to determine whether to return the first or the last position,
defaults to "lower", i.e., the first position.
NOTE: Behavior is undefined on unsorted lists!
With {opts.key}, {val} may differ from the list elements, provided the key
function accepts both. For a string key, that field must exist on both. A
partial record is sufficient if it contains all fields used by the key.
NOTE: The values being compared must support `<`, and the list must be
sorted by those values (after applying {opts.key}, if provided).
Example: >lua
@@ -1858,7 +1863,7 @@ vim.list.bisect({t}, {val}, {opts}) *vim.list.bisect()*
Since: 0.12.0
Parameters: ~
• {t} (`any[]`) A comparable list.
• {t} (`any[]`) A sorted list.
• {val} (`any`) The value to search.
• {opts} (`table?`) A table with the following fields:
• {bound}? (`'lower'|'upper'`, default: `'lower'`) Specifies
@@ -1869,11 +1874,17 @@ vim.list.bisect({t}, {val}, {opts}) *vim.list.bisect()*
keeps the list sorted..
• {hi}? (`integer`, default: `#t + 1`) End index of the list,
exclusive.
• {key}? (`string|fun(val: any): any`) Optional, compare the
return value instead of the {val} itself if provided. If a
string, index each value by this field name.
• {key}? (`(string & keyof T)|fun(val: T): any`) Applied to
{val} and the list elements being compared. If a string,
index both by this field name. If a function, it must accept
both {val} and the list elements and return mutually
comparable keys.
• {lo}? (`integer`, default: `1`) Start index of the list.
Overloads: ~
• `fun<T, Q>(t: T[], val: Q, opts: vim.list.bisect.Opts<T|Q> & { key: fun(val: T|Q): any }): integer`
• `fun<T: table, Q: table>(t: T[], val: Q, opts: vim.list.bisect.Opts<T|Q> & { key: string & keyof T & keyof Q }): integer`
Return: ~
(`integer`) index serves as either the lower bound or the upper bound
position.
@@ -2252,11 +2263,11 @@ vim.tbl_map({fn}, {t}) *vim.tbl_map()*
change).
Parameters: ~
• {fn} (`fun(value: T): any`) Function
• {fn} (`fun(value: T): R`) Function
• {t} (`table<any, T>`) Table
Return: ~
(`table`) Table of transformed values
(`table<any, R>`) Table of transformed values
vim.tbl_values({t}) *vim.tbl_values()*
Return a list of all values used in a table. However, the order of the

View File

@@ -96,7 +96,8 @@ local function runnables()
if name == 'code' then
local code = vim.treesitter.get_node_text(node, 0)
local lang_node = match[metadata[id].lang][1] --[[@as TSNode]]
local lang_id = metadata[id].lang --[[@as integer]]
local lang_node = match[lang_id][1]
local lang = vim.treesitter.get_node_text(lang_node, 0)
for i = start + 1, end_ do
code_blocks[i] = { lang = lang, code = code }

View File

@@ -270,12 +270,13 @@ function M._match_manpage_path(paths, name, sect)
end
-- find any that match the specified name
--- @type string[]
--- @param v string
local namematches = vim.tbl_filter(function(v)
local tail = vim.fs.basename(v)
return tail:find(name, 1, true) ~= nil
end, paths) or {}
local sectmatches = {}
end, paths)
local sectmatches = {} --- @type string[]
if #namematches > 0 and sect ~= '' then
--- @param v string

View File

@@ -272,10 +272,10 @@ end
--- Applies function `fn` to all values of table `t`, in `pairs()` iteration order (which is not
--- guaranteed to be stable, even when the data doesn't change).
---
---@generic T
---@param fn fun(value: T): any Function
---@generic T, R
---@param fn fun(value: T): R Function
---@param t table<any, T> Table
---@return table : Table of transformed values
---@return table<any, R> : Table of transformed values
function vim.tbl_map(fn, t)
vim.validate('fn', fn, 'callable')
vim.validate('t', t, 'table')
@@ -457,7 +457,7 @@ function vim.list.unique(t, key)
return t
end
---@class vim.list.bisect.Opts
---@class vim.list.bisect.Opts<T>
---@inlinedoc
---
--- Start index of the list.
@@ -468,9 +468,10 @@ end
--- (default: `#t + 1`)
---@field hi? integer
---
--- Optional, compare the return value instead of the {val} itself if provided.
--- If a string, index each value by this field name.
---@field key? string|fun(val: any): any
--- Applied to {val} and the list elements being compared.
--- If a string, index both by this field name. If a function, it must accept both
--- {val} and the list elements and return mutually comparable keys.
---@field key? (string & keyof T)|fun(val: T): any
---
--- Specifies the search variant.
--- - "lower": returns the first position
@@ -480,12 +481,12 @@ end
--- (default: `'lower'`)
---@field bound? 'lower' | 'upper'
---@generic T
---@generic T, Q
---@param t T[]
---@param val T
---@param val Q
---@param lo integer
---@param hi integer
---@param key_fn fun(val: any): any
---@param key_fn fun(val: T|Q): any
---@return integer i in range such that `t[j]` < {val} for all j < i,
--- and `t[j]` >= {val} for all j >= i,
--- or return {hi} if no such index is found.
@@ -503,12 +504,12 @@ local function lower_bound(t, val, lo, hi, key_fn)
return lo
end
---@generic T
---@generic T, Q
---@param t T[]
---@param val T
---@param val Q
---@param lo integer
---@param hi integer
---@param key_fn fun(val: any): any
---@param key_fn fun(val: T|Q): any
---@return integer i in range such that `t[j]` <= {val} for all j < i,
--- and `t[j]` > {val} for all j >= i,
--- or return {hi} if no such index is found.
@@ -532,7 +533,12 @@ end
--- Use {bound} to determine whether to return the first or the last position,
--- defaults to "lower", i.e., the first position.
---
--- NOTE: Behavior is undefined on unsorted lists!
--- With {opts.key}, {val} may differ from the list elements, provided the key
--- function accepts both. For a string key, that field must exist on both.
--- A partial record is sufficient if it contains all fields used by the key.
---
--- NOTE: The values being compared must support `<`, and the list must be sorted
--- by those values (after applying {opts.key}, if provided).
---
--- Example:
--- ```lua
@@ -560,10 +566,12 @@ end
--- ```
---@since 14
---@generic T
---@param t T[] A comparable list.
---@param t T[] A sorted list.
---@param val T The value to search.
---@param opts? vim.list.bisect.Opts
---@param opts? vim.list.bisect.Opts<T>
---@return integer index serves as either the lower bound or the upper bound position.
---@overload fun<T, Q>(t: T[], val: Q, opts: vim.list.bisect.Opts<T|Q> & { key: fun(val: T|Q): any }): integer
---@overload fun<T: table, Q: table>(t: T[], val: Q, opts: vim.list.bisect.Opts<T|Q> & { key: string & keyof T & keyof Q }): integer
function vim.list.bisect(t, val, opts)
vim.validate('t', t, 'table')
vim.validate('opts', opts, 'table', true)
@@ -1131,6 +1139,7 @@ do
end
elseif vim.is_callable(validator) then
-- Check user-provided validation function
---@cast validator fun(v: any): boolean, string?
local valid, opt_msg = validator(val)
if not valid then
local err_msg = ('%s: expected %s, got %s'):format(

View File

@@ -237,6 +237,7 @@ local function setup_output(output, text)
end
end
--- @diagnostic disable-next-line:missing-fields luvit/luv#827
local pipe_fd = assert(uv.pipe({ nonblock = true }, {}))
local pipe = assert(uv.new_pipe(false))
pipe:open(pipe_fd.read)
@@ -268,6 +269,7 @@ local function setup_input(input)
towrite = input
end
--- @diagnostic disable-next-line:missing-fields luvit/luv#827
local pipe_fd = assert(uv.pipe({}, { nonblock = true }))
local pipe = assert(uv.new_pipe(false))
pipe:open(pipe_fd.write)

View File

@@ -21,6 +21,8 @@ if not has_clear then
end
end
---@cast new fun(narr: integer, nrec: integer): table
---@cast clear fun(tab: table)
M.new = new
M.clear = clear

View File

@@ -737,13 +737,14 @@ end
---@param focus? boolean Enter the pager: it was explicitly requested.
function M.set_pos(tgt, focus)
for t, win in pairs(ui.wins) do
local cfg = (t == tgt or (tgt == nil and t ~= 'cmd'))
local current_cfg = (t == tgt or (tgt == nil and t ~= 'cmd'))
and api.nvim_win_is_valid(win)
and api.nvim_win_get_config(win)
if cfg and (tgt or not cfg.hide) then
if current_cfg and (tgt or not current_cfg.hide) then
local texth = api.nvim_win_text_height(win, { max_height = o.lines })
local top = { mopt.msgsep, 'MsgSeparator' }
cfg = { hide = false, relative = 'laststatus', col = 10000 } ---@type table
---@type vim.api.keyset.win_config
local cfg = { hide = false, relative = 'laststatus', col = 10000 }
cfg.row, cfg.height, cfg.border = win_row_height_border(t, texth.all)
cfg.border = cfg.border and t ~= 'msg' and { '', top, '', '', '', '', '', '' } or nil
cfg.mouse = tgt == 'cmd' or t == 'msg' or nil

View File

@@ -245,9 +245,9 @@ function M.watchdirs(path, opts, callback)
for name, type in
vim.fs.dir(path, {
depth = max_depth,
skip = function(name)
skip = function(dir)
return not opts.exclude_pattern
or opts.exclude_pattern:match(vim.fs.joinpath(path, name)) == nil
or opts.exclude_pattern:match(vim.fs.joinpath(path, dir)) == nil
end,
})
do

View File

@@ -1,7 +1,7 @@
local M = {}
local health = vim.health
local deprecated = {} ---@type [string, table, string][]
local deprecated = {} ---@type table<string, [string, string[], string?]>
function M.check()
if next(deprecated) == nil then

View File

@@ -309,7 +309,7 @@ local M = vim._defer_require('vim.diagnostic', {
--- signs = { text = { [vim.diagnostic.severity.ERROR] = 'E', ... } }
--- })
--- ```
--- @field text? table<vim.diagnostic.Severity,string>
--- @field text? table<vim.diagnostic.Severity|vim.diagnostic.SeverityName,string>
---
--- A table mapping |diagnostic-severity| to the highlight group used for the
--- line number where the sign is placed.
@@ -441,6 +441,7 @@ local all_namespaces = {}
---@param namespace integer? Update the options for the given namespace.
--- When omitted, update the global diagnostic options.
---@return vim.diagnostic.Opts? : Current diagnostic config if {opts} is omitted.
---@overload fun(opts?: nil, namespace?: integer): vim.diagnostic.Opts
function M.config(opts, namespace)
return M._config.config(opts, namespace)
end

View File

@@ -95,6 +95,7 @@ end
--- @param namespace integer? Update the options for the given namespace.
--- When omitted, update the global diagnostic options.
--- @return vim.diagnostic.Opts? : Current diagnostic config if {opts} is omitted.
--- @overload fun(opts?: nil, namespace?: integer): vim.diagnostic.Opts
function M.config(opts, namespace)
vim.validate('opts', opts, 'table', true)
vim.validate('namespace', namespace, 'number', true)

View File

@@ -219,8 +219,11 @@ function M.signs.show(namespace, bufnr, diagnostics, opts)
for _, diagnostic0 in ipairs(diagnostics) do
if diagnostic0.lnum <= line_count then
local severity_name = (
severity --[[@as table<vim.diagnostic.Severity, vim.diagnostic.SeverityName>]]
)[diagnostic0.severity]
api.nvim_buf_set_extmark(bufnr, ns.user_data.sign_ns, diagnostic0.lnum, 0, {
sign_text = text[diagnostic0.severity] or text[severity[diagnostic0.severity]] or 'U',
sign_text = text[diagnostic0.severity] or text[severity_name] or 'U',
sign_hl_group = sign_highlight_map[diagnostic0.severity],
number_hl_group = numhl[diagnostic0.severity],
line_hl_group = linehl[diagnostic0.severity],

View File

@@ -135,11 +135,11 @@ local function filepath_to_healthcheck(path)
else
local rtp_lua = vim
.iter(vim.api.nvim_get_runtime_file('lua/', true))
:map(function(rtp_lua)
return vim.fs.abspath(vim.fs.normalize(rtp_lua))
:map(function(dir)
return vim.fs.abspath(vim.fs.normalize(dir))
end)
:find(function(rtp_lua)
return vim.fs.relpath(rtp_lua, path)
:find(function(dir)
return vim.fs.relpath(dir, path)
end)
-- "/path/to/rtp/lua/foo/bar/health.lua" => "foo/bar/health.lua"
-- "/another/rtp/lua/baz/health/init.lua" => "baz/health/init.lua"
@@ -314,6 +314,8 @@ function M.error(msg, ...)
check_summary['error'] = check_summary['error'] + 1
end
---@param path string
---@return string
local path2name = function(path)
if vim.fs.ext(path) == 'lua' then
-- Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp"
@@ -340,13 +342,12 @@ end
local PATTERNS = { '/autoload/health/*.vim', '/lua/**/**/health.lua', '/lua/**/**/health/init.lua' }
--- :checkhealth completion function used by cmdexpand.c get_healthcheck_names()
M._complete = function()
local unique = vim ---@type table<string,boolean>
---@param pattern string
---@type table<string,boolean>
local unique = vim
.iter(vim.tbl_map(function(pattern)
return vim.tbl_map(path2name, vim.api.nvim_get_runtime_file(pattern, true))
end, PATTERNS))
:flatten()
---@param t table<string,boolean>
:fold({}, function(t, name)
t[name] = true -- Remove duplicates
return t

View File

@@ -1313,8 +1313,11 @@ function IterArray.new(t)
}, IterArray)
end
return setmetatable(M, {
setmetatable(M, {
__call = function(_, ...)
return Iter.new(...)
end,
})
-- Return M separately to work around EmmyLuaLs/emmylua-analyzer-rust#1240.
return M

View File

@@ -284,8 +284,9 @@ end
---
--- @param name string
--- @param cfg vim.lsp.Config
--- @type vim.lsp.config
--- @diagnostic disable-next-line:assign-type-mismatch
function lsp.config(name, cfg)
lsp.config = function(name, cfg)
local _, _ = name, cfg -- ignore unused
-- dummy proto for docs
end
@@ -321,6 +322,7 @@ end
--- @class vim.lsp.config
--- @field [string] vim.lsp.Config?
--- @field package _configs table<string,vim.lsp.Config>
--- @overload fun(name: string, cfg: vim.lsp.Config)
lsp.config = setmetatable({ _configs = {} }, {
--- @param self vim.lsp.config
--- @param name string
@@ -332,7 +334,7 @@ lsp.config = setmetatable({ _configs = {} }, {
if not rconfig.resolved_config then
if name == '*' then
rconfig.resolved_config = lsp.config._configs['*'] or {}
rconfig.resolved_config = self._configs['*'] or {}
return rconfig.resolved_config
end
@@ -355,7 +357,7 @@ lsp.config = setmetatable({ _configs = {} }, {
rconfig.resolved_config = vim.tbl_deep_extend(
'force',
lsp.config._configs['*'] or {},
self._configs['*'] or {},
rtp_config or {},
self._configs[name] or {}
)
@@ -1449,8 +1451,10 @@ end
---@param pattern string Pattern used to find a workspace symbol
---@param flags string See |tag-function|
---
---@return table[] tags A list of matching tags
---@return table[]|vim.NIL tags A list of matching tags, or `vim.NIL` to use the built-in tags.
function lsp.tagfunc(pattern, flags)
-- EmmyLua incorrectly treats function exports referenced by @module as non-callable.
--- @diagnostic disable-next-line:call-non-callable EmmyLuaLs/emmylua-analyzer-rust#1238
return vim.lsp._tagfunc(pattern, flags)
end

View File

@@ -48,9 +48,11 @@ local buf_capabilities = {}
local M = {}
M.__index = M
---@generic T: vim.lsp.Capability
---@param self T
---@param bufnr integer
---@return self
function M:new(bufnr)
---@return T
function M.new(self, bufnr)
-- `self` in the `new()` function refers to the concrete type (i.e., the metatable).
-- `Class` may be a subtype of `Capability`, as it supports inheritance.
---@type vim.lsp.Capability
@@ -63,7 +65,7 @@ function M:new(bufnr)
all_capabilities[Class.name] = Class
end
---@type vim.lsp.Capability
---@type T
self = setmetatable({}, Class)
self.bufnr = bufnr
self.augroup = api.nvim_create_augroup(string.format('nvim.lsp.%s:%s', self.name, bufnr), {

View File

@@ -387,9 +387,9 @@ function M.foldtext(lnum)
line = state.row_text[row] or line
local ok, parser = pcall(function()
local parser = vim.treesitter.get_string_parser(line, lang)
parser:parse(true)
return parser
local string_parser = vim.treesitter.get_string_parser(line, lang)
string_parser:parse(true)
return string_parser
end)
if not ok then
return line

View File

@@ -89,12 +89,11 @@ M.NodeType = Type
--- @class vim.snippet.FormatData: { capture: number, modifier?: string, if_text?: string, else_text?: string }
--- @class vim.snippet.SnippetData: { children: vim.snippet.Node<any>[] }
--- @type vim.snippet.Node<any>
local Node = {}
--- @param self vim.snippet.Node<any>
--- @return string
--- @diagnostic disable-next-line: inject-field
function Node:__tostring()
function Node.__tostring(self)
local node_text = {}
local type, data = self.type, self.data
if type == Type.Snippet then

View File

@@ -84,6 +84,9 @@ local function query_workspace_symbols(pattern)
return results
end
---@param pattern string
---@param flags string
---@return table[]|vim.NIL
local function tagfunc(pattern, flags)
-- avoid definition/symbol queries for insert completion
if string.match(flags, 'i') then

View File

@@ -796,7 +796,7 @@ function M.rename(new_name, opts)
if client:supports_method('textDocument/prepareRename') then
local params = util.make_position_params(win, client.offset_encoding)
---@param result? lsp.Range|{ range: lsp.Range, placeholder: string }
---@param result? lsp.PrepareRenameResult
client:request('textDocument/prepareRename', params, function(err, result)
if err or result == nil then
if next(clients, idx) then
@@ -816,10 +816,8 @@ function M.rename(new_name, opts)
local range ---@type vim.Range?
if result.start then
---@cast result lsp.Range
range = vim.range.lsp(bufnr, result, client.offset_encoding)
elseif result.range then
---@cast result { range: lsp.Range, placeholder: string }
range = vim.range.lsp(bufnr, result.range, client.offset_encoding)
end
if range then
@@ -841,8 +839,6 @@ function M.rename(new_name, opts)
prompt_opts.default = result.placeholder
elseif result.start then
prompt_opts.default = get_text_at_range(result, client.offset_encoding)
elseif result.range then
prompt_opts.default = get_text_at_range(result.range, client.offset_encoding)
else
prompt_opts.default = cword
end
@@ -1273,6 +1269,8 @@ local function on_code_action_results(results, opts)
return
end
-- Work around incorrect union narrowing: EmmyLuaLs/emmylua-analyzer-rust#1239.
---@cast action lsp.CodeAction
if action.disabled then
vim.notify(action.disabled.reason, vim.log.levels.ERROR)
return

View File

@@ -417,7 +417,7 @@ function Client.create(config)
local name = get_name(id, config)
--- @type vim.lsp.Client
local self = {
local self = setmetatable({
id = id,
config = config,
handlers = config.handlers or {},
@@ -455,7 +455,7 @@ function Client.create(config)
--- @deprecated use client.progress instead
messages = { name = name, messages = {}, progress = {}, status = {} },
}
}, Client)
self.capabilities =
vim.tbl_deep_extend('force', lsp.protocol.make_client_capabilities(), self.capabilities or {})
@@ -516,8 +516,6 @@ function Client.create(config)
})
end
setmetatable(self, Client)
method_wrapper(self, Client, 'request')
method_wrapper(self, Client, 'request_sync')
method_wrapper(self, Client, 'notify')
@@ -768,19 +766,19 @@ function Client:request(method, params, handler, bufnr)
local request_registered = false
-- NOTE: rpc.request might call an in-process (Lua) server, thus may be synchronous.
local success, request_id = self.rpc.request(method, params, function(err, result, request_id)
local success, request_id = self.rpc.request(method, params, function(err, result, id)
handler(err, result, {
method = method,
client_id = self.id,
request_id = request_id,
request_id = id,
bufnr = bufnr,
params = params,
version = version,
})
end, function(request_id)
end, function(id)
-- Called when the server sends a response to the request (including cancelled acknowledgment).
if request_registered then
self:_process_request(request_id, 'complete')
self:_process_request(id, 'complete')
end
already_responded = true
end)

View File

@@ -10,12 +10,12 @@ local Capability = require('vim.lsp._capability')
---@class (private) vim.lsp.codelens.RowLenses
---@field lenses lsp.CodeLens[]
---@field version? integer `TextDocument` version most recently applied to this row.
---
---@class (private) vim.lsp.codelens.ClientState
---@field row_lenses table<integer, vim.lsp.codelens.RowLenses>
---@field namespace integer
---@field version? integer `TextDocument` version current state corresponds to.
---
---@class (private) vim.lsp.codelens.Provider : vim.lsp.Capability
---@field active table<integer, vim.lsp.codelens.Provider>
---

View File

@@ -163,7 +163,7 @@ local function tokens_to_ranges(data, bufnr, client, request, ranges)
if last_insert_idx < #ranges then
local needs_insert = true
local idx = vim.list.bisect(ranges, { line = range.line }, {
local idx = vim.list.bisect(ranges, range, {
lo = last_insert_idx,
key = function(highlight)
return highlight.line

View File

@@ -107,6 +107,7 @@ function M.request(method, url, opts, on_response)
return (b == nil and true) or (type(b) == 'string' and not b:match('^@'))
end, true, 'body should be string and not start with @')
vim.validate('on_response', on_response, 'function', true)
---@cast on_response vim.net.request.ResponseFunc?
local retry = opts.retry or 3

View File

@@ -953,6 +953,7 @@ local function pack_add(plug, load)
active_plugins[plug.path] = { plug = plug, id = n_active_plugins }
if vim.is_callable(load) then
---@cast load -boolean
load({ spec = vim.deepcopy(plug.spec), path = plug.path })
return
end
@@ -1389,6 +1390,7 @@ end
--- @class vim.pack.keyset.update
--- @inlinedoc
--- @field package _ex? boolean
--- @field force? boolean Whether to skip confirmation and make updates immediately. Default `false`.
---
--- @field offline? boolean Whether to skip downloading new updates. Default: `false`.
@@ -1520,6 +1522,7 @@ end
--- @class vim.pack.keyset.del
--- @inlinedoc
--- @field package _ex? boolean
--- @field force? boolean Whether to allow deleting an active plugin. Default `false`.
--- Remove plugins from disk

View File

@@ -345,8 +345,8 @@ function M.offset(buf, offset)
local lnum = vim.list.bisect(
setmetatable({}, {
__index = function(_, lnum)
return api.nvim_buf_get_offset(buf, lnum - 1)
__index = function(_, idx)
return api.nvim_buf_get_offset(buf, idx - 1)
end,
}),
offset,
@@ -364,6 +364,6 @@ setmetatable(M, {
return M.new(...)
end,
})
---@cast M +fun(buf: integer, row: integer, col: integer): vim.Pos
---@cast M vim.Pos & fun(buf: integer, row: integer, col: integer): vim.Pos
return M

View File

@@ -3,6 +3,9 @@ local iswin = vim.fn.has('win32') == 1
local M = {}
---@param cmd string[]
---@return boolean
---@return string?
local function cmd_ok(cmd)
local result = vim.system(cmd, { text = true }):wait()
return result.code == 0, result.stdout

View File

@@ -465,7 +465,6 @@ setmetatable(M, {
return M.new(...)
end,
})
---@cast M +fun(start: vim.Pos, end_: vim.Pos): vim.Range
---@cast M +fun(buf: integer, start_row: integer, start_col: integer, end_row: integer, end_col: integer): vim.Range
---@cast M vim.Range & (fun(start: vim.Pos, end_: vim.Pos): vim.Range) & (fun(buf: integer, start_row: integer, start_col: integer, end_row: integer, end_col: integer): vim.Range)
return M

View File

@@ -214,6 +214,7 @@ local exp = m.P{ "Exp",
((m.V"Definition" / firstdef) * (m.V"Definition" % adddef)^0) / mm.P
}
--- @type vim.lpeg.Pattern
local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)

View File

@@ -31,7 +31,7 @@ local history = {
--- @field ltree vim.treesitter.LanguageTree
--- @field region Range4
local M = {}
local M = { TEST_SWITCH_PRIORITY = false }
--- @param node vim.treesitter.select.node
--- @return string
@@ -96,10 +96,8 @@ end
--- @param ltree vim.treesitter.LanguageTree
--- @return vim.treesitter.select.node.top
local function create_top_node(tree, region, ltree)
--- @type vim.treesitter.select.node.top
local self = {
node = tree:root(),
top = {} --[[@as any]],
ltree = ltree,
region = region,
}

View File

@@ -298,7 +298,7 @@ end
--- @param metadata vim.treesitter.query.TSMetadata
--- @return string?
local function get_url(match, bufnr, capture, metadata)
---@type string|number|nil
---@type string|integer|nil
local url = metadata[capture] and metadata[capture].url
if not url or type(url) == 'string' then
@@ -369,7 +369,6 @@ local function on_range_impl(
self:for_each_highlight_state(function(state)
subtree_counter = subtree_counter + 1
local root_node = state.tstree:root()
---@type { [1]: integer, [2]: integer, [3]: integer, [4]: integer }
local root_range = { root_node:range() }
if

View File

@@ -140,7 +140,7 @@ function LanguageTree.new(source, lang, opts)
local injections = opts.injections or {}
--- @type vim.treesitter.LanguageTree
local self = {
local self = setmetatable({
_source = source,
_lang = lang,
_children = {},
@@ -159,9 +159,7 @@ function LanguageTree.new(source, lang, opts)
_cb_queues = {},
_callbacks = {},
_callbacks_rec = {},
}
setmetatable(self, LanguageTree)
}, LanguageTree)
if vim.g.__ts_debug and type(vim.g.__ts_debug) == 'number' then
self:_set_logger()

View File

@@ -53,6 +53,8 @@
--- 1.2 - 2.3.0 is 1.2.0 - 2.3.0
--- ```
---@class (internal) vim.VersionModule
---@operator call: vim.Version
local M = {}
---@nodoc
@@ -101,6 +103,7 @@ local function cmp_prerel(prerel1, prerel2)
end
end
---@param key string|integer
function Version:__index(key)
return type(key) == 'number' and ({ self.major, self.minor, self.patch })[key] or Version[key]
end

View File

@@ -261,7 +261,7 @@ local function diff_dirs_builtin(left_dir, right_dir, opt)
-- Detect possible renames
if opt.rename.detect then
for left_rel, left_path in pairs(left_only) do
---@type {similarity: number, path: string?, rel: string}
---@type {similarity: number, path: string?, rel: string?}
local best_match = { similarity = opt.rename.similarity, path = nil }
for right_rel, right_path in pairs(right_only) do

View File

@@ -1337,16 +1337,7 @@ local styletable_funcs = {
local function state_generate_style(state)
vim._with({ win = state.winid }, function()
for _, fn in ipairs(styletable_funcs) do
--- @type string?
local cond
if type(fn) == 'table' then
cond = fn[2] --[[@as string]]
--- @type function
fn = fn[1]
end
if not cond or cond(state) then
fn(state)
end
fn(state)
end
end)
end

View File

@@ -49,12 +49,27 @@ local function treefy(ent, _tree, _last)
return tree
end
--- @class (private) vim.undotree.graph_line
--- @field kind 'node'|'remove'|'branch'|'remove+branch'|'nochange_remove'
--- @class (private) vim.undotree.graph_line.base
--- @field index integer
--- @field node_count integer
--- @field node integer|integer[]
--- @field index2 integer? -- for branch-index in `remove+branch`
--- @class (private) vim.undotree.graph_line.node: vim.undotree.graph_line.base
--- @field kind 'node'|'remove'|'nochange_remove'
--- @field node integer
--- @class (private) vim.undotree.graph_line.branch: vim.undotree.graph_line.base
--- @field kind 'branch'
--- @field node integer[]
--- @class (private) vim.undotree.graph_line.remove_branch: vim.undotree.graph_line.base
--- @field kind 'remove+branch'
--- @field node integer
--- @field index2 integer
--- @alias vim.undotree.graph_line
--- | vim.undotree.graph_line.node
--- | vim.undotree.graph_line.branch
--- | vim.undotree.graph_line.remove_branch
--- @param tree vim.undotree.tree
--- @return vim.undotree.graph_line[]
@@ -181,13 +196,15 @@ local function buf_apply_graph_lines(tree, graph_lines, buf, meta, find_seq)
--- @type string?
local line
if v.kind == 'node' then
-- Work around tagged union narrowing: EmmyLuaLs/emmylua-analyzer-rust#1241.
local seq = v.node --[[@as integer]]
line = ('| '):rep(v.index - 1)
.. '*'
.. (' |'):rep(v.node_count - v.index)
.. ' '
.. v.node
.. seq
.. ' ('
.. undo_fmt_time(tree[v.node].time)
.. undo_fmt_time(tree[seq].time)
.. ')'
elseif v.kind == 'remove' then
line = ('| '):rep(v.index - 1) .. (' /'):rep(v.node_count - v.index)

View File

@@ -257,6 +257,11 @@ local config = {
fun.table = nil
end
-- Render the callable version module as ordinary module functions.
if fun.class == 'vim.VersionModule' then
fun.classvar = nil
end
if fun.classvar or vim.startswith(fun.name, 'vim.') or fun.module == 'vim.iter' then
return
end
@@ -345,6 +350,9 @@ local config = {
},
fn_xform = function(fun)
fun.name = fun.name:gsub('result%.', '')
if fun.module == 'vim.lsp' and fun.name == 'config' then
fun.table = nil
end
if fun.module == 'vim.lsp.protocol' then
fun.classvar = nil
end
@@ -674,7 +682,7 @@ local function get_class(ty, classes)
return
end
local cty = ty:gsub('%s*|%s*nil', '?'):gsub('?$', ''):gsub('%[%]$', '')
local cty = ty:gsub('%s*|%s*nil', '?'):gsub('?$', ''):gsub('%[%]$', ''):gsub('%b<>$', '')
return classes[cty]
end

View File

@@ -145,14 +145,15 @@ local typedef = P({
'typedef',
typedef = C(v.type),
type = v.ty * rep_array_opt_postfix * rep(Pf('|') * v.ty * rep_array_opt_postfix),
type = v.ty * rep_array_opt_postfix * rep(Sf('|&') * v.ty * rep_array_opt_postfix),
ty = v.composite + paren(v.typedef),
composite = (v.types * array_postfix)
+ (v.types * opt_postfix)
+ (P(ty_ident) * P('...')) -- Generic vararg
+ v.types,
types = v.fun + v.generics + v.kv_table + v.tuple + v.dict + v.table_literal + ty_prims,
types = v.keyof + v.fun + v.generics + v.kv_table + v.tuple + v.dict + v.table_literal + ty_prims,
keyof = P('keyof') * ws * v.ty,
tuple = Pf('[') * comma1(v.type) * Plf(']'),
dict = Pf('{') * comma1(Pf('[') * v.type * Pf(']') * colon * v.type) * Plf('}'),
kv_table = Pf('table') * Pf('<') * v.type * Pf(',') * v.type * Plf('>'),

View File

@@ -34,6 +34,14 @@ describe('luacats grammar', function()
desc = 'this is a description',
})
test(
'@overload fun<T, Q>(opts: vim.list.bisect.Opts<T|Q> & { key: string & keyof T & keyof Q }): integer',
{
kind = 'overload',
type = 'fun<T, Q>(opts: vim.list.bisect.Opts<T|Q> & { key: string & keyof T & keyof Q }): integer',
}
)
test('@param hello vim.type?|string? this is a description', {
kind = 'param',
name = 'hello',
@@ -244,6 +252,8 @@ describe('luacats grammar', function()
{ 'number[][][]' },
{ 'number[][]?' },
{ 'string|integer[][]?' },
{ 'vim.type & { key: string|function }' },
{ '(vim.type & { key: string })|nil' },
-- tuples
{ '[string]' },