refactor: integer functions, optimize asserts #34112

refactor(lua): add integer coercion helpers

Add vim._tointeger() and vim._ensure_integer(), including optional base
support, and switch integer-only tonumber()/assert call sites in the Lua
runtime to use them.

This also cleans up related integer parsing in LSP, health, loader, URI,
tohtml, and Treesitter code.

supported by AI
This commit is contained in:
Lewis Russell
2026-03-12 15:04:05 +00:00
committed by GitHub
parent 2fe07cc965
commit ce1154048b
30 changed files with 144 additions and 95 deletions

View File

@@ -130,7 +130,7 @@ function properties.indent_size(bufnr, val, opts)
vim.bo[bufnr].shiftwidth = 0
vim.bo[bufnr].softtabstop = 0
else
local n = assert(tonumber(val), 'indent_size must be a number')
local n = assert(vim._tointeger(val), 'indent_size must be an integer')
vim.bo[bufnr].shiftwidth = n
vim.bo[bufnr].softtabstop = -1
if not opts.tab_width then
@@ -141,17 +141,17 @@ end
--- The display size of a single tab character. Sets the 'tabstop' option.
function properties.tab_width(bufnr, val)
vim.bo[bufnr].tabstop = assert(tonumber(val), 'tab_width must be a number')
vim.bo[bufnr].tabstop = assert(vim._tointeger(val), 'tab_width must be an integer')
end
--- A number indicating the maximum length of a single
--- line. Sets the 'textwidth' option.
function properties.max_line_length(bufnr, val)
local n = tonumber(val)
local n = vim._tointeger(val)
if n then
vim.bo[bufnr].textwidth = n
else
assert(val == 'off', 'max_line_length must be a number or "off"')
assert(val == 'off', 'max_line_length must be an integer or "off"')
vim.bo[bufnr].textwidth = 0
end
end

View File

@@ -438,7 +438,7 @@ local function get_page(path, silent)
if (vim.g.man_hardwrap or 1) ~= 1 then
manwidth = 999
elseif vim.env.MANWIDTH then
vim.env.MANWIDTH = tonumber(vim.env.MANWIDTH) or 0
vim.env.MANWIDTH = vim._tointeger(vim.env.MANWIDTH) or 0
manwidth = math.min(vim.env.MANWIDTH, api.nvim_win_get_width(0) - vim.o.wrapmargin)
else
manwidth = api.nvim_win_get_width(0) - vim.o.wrapmargin

View File

@@ -57,7 +57,7 @@ function M.apply_marks()
vim.b.tutor_extmarks = {}
for expct, _ in pairs(vim.b.tutor_metadata.expect) do
---@diagnostic disable-next-line: assign-type-mismatch
local lnum = tonumber(expct) ---@type integer
local lnum = vim._ensure_integer(expct)
vim.api.nvim_buf_set_extmark(0, tutor_hl_ns, lnum - 1, 0, {
line_hl_group = 'tutorExpect',
invalidate = true,

View File

@@ -228,9 +228,8 @@ local function cterm_to_hex(colorstr)
if colorstr:sub(1, 1) == '#' then
return colorstr
end
assert(colorstr ~= '')
local color = tonumber(colorstr) --[[@as integer]]
assert(color and 0 <= color and color <= 255)
local color = vim._ensure_integer(colorstr)
assert(0 <= color and color <= 255)
if cterm_color_cache[color] then
return cterm_color_cache[color]
end
@@ -239,7 +238,7 @@ local function cterm_to_hex(colorstr)
cterm_color_cache[color] = hex
else
notify("Couldn't get terminal colors, using fallback")
local t_Co = tonumber(vim.api.nvim_eval('&t_Co'))
local t_Co = vim._ensure_integer(vim.api.nvim_eval('&t_Co'))
if t_Co <= 8 then
cterm_color_cache = cterm_8_to_hex
elseif t_Co == 88 then
@@ -744,7 +743,7 @@ local function styletable_statuscolumn(state)
signcolumn = 'auto'
end
if signcolumn ~= 'no' then
local max = tonumber(signcolumn:match('^%w-:(%d)')) --[[@as integer?]] or 1
local max = vim._tointeger(signcolumn:match('^%w-:(%d)')) or 1
if signcolumn:match('^auto') then
--- @type table<integer,integer>
local signcount = {}
@@ -771,7 +770,7 @@ local function styletable_statuscolumn(state)
local foldcolumn = state.opt.foldcolumn
if foldcolumn ~= '0' then
if foldcolumn:match('^auto') then
local max = tonumber(foldcolumn:match('^%w-:(%d)')) --[[@as integer?]] or 1
local max = vim._tointeger(foldcolumn:match('^%w-:(%d)')) or 1
local maxfold = 0
vim._with({ buf = state.bufnr }, function()
for row = state.start, state.end_ do
@@ -783,7 +782,7 @@ local function styletable_statuscolumn(state)
end)
minwidth = minwidth + math.min(maxfold, max)
else
minwidth = minwidth + tonumber(foldcolumn) --[[@as integer]]
minwidth = minwidth + vim._ensure_integer(foldcolumn)
end
end

View File

@@ -794,12 +794,12 @@ do
return nil
end
local val = tonumber(c, 16)
local val = vim._tointeger(c, 16)
if not val then
return nil
end
local max = assert(tonumber(string.rep('f', #c), 16))
local max = vim._ensure_integer(string.rep('f', #c), 16)
return val / max
end
@@ -1001,9 +1001,9 @@ do
end
if
tonumber(params[#params - 2]) == r
and tonumber(params[#params - 1]) == g
and tonumber(params[#params]) == b
vim._tointeger(params[#params - 2]) == r
and vim._tointeger(params[#params - 1]) == g
and vim._tointeger(params[#params]) == b
then
setoption('termguicolors', true)
end

View File

@@ -1,3 +1,5 @@
local tointeger = vim._tointeger
-- Nvim-Lua stdlib: the `vim` module (:help lua-stdlib)
--
@@ -74,7 +76,7 @@ function vim._os_proc_info(pid)
local ppid_string = assert(vim.system({ 'ps', '-p', pid, '-o', 'ppid=' }):wait().stdout)
-- Remove trailing whitespace.
name = vim.trim(name):gsub('^.*/', '')
local ppid = tonumber(ppid_string) or -1
local ppid = tointeger(ppid_string) or -1
return {
name = name,
pid = pid,
@@ -95,12 +97,9 @@ function vim._os_proc_children(ppid)
elseif r.code ~= 0 then
error('command failed: ' .. vim.fn.string(cmd))
end
local children = {}
local children = {} --- @type integer[]
for s in r.stdout:gmatch('%S+') do
local i = tonumber(s)
if i ~= nil then
table.insert(children, i)
end
children[#children + 1] = tointeger(s)
end
return children
end
@@ -476,17 +475,17 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
local c2 --- @type number
if regtype:byte() == 22 then -- block selection: take width from regtype
c1 = pos1[2]
c2 = c1 + tonumber(regtype:sub(2))
c2 = c1 + vim._ensure_integer(regtype:sub(2))
-- and adjust for non-ASCII characters
local bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1]
local utflen = vim.str_utfindex(bufline, 'utf-32', #bufline)
if c1 <= utflen then
c1 = assert(tonumber(vim.str_byteindex(bufline, 'utf-32', c1)))
c1 = vim.str_byteindex(bufline, 'utf-32', c1)
else
c1 = #bufline + 1
end
if c2 <= utflen then
c2 = assert(tonumber(vim.str_byteindex(bufline, 'utf-32', c2)))
c2 = vim.str_byteindex(bufline, 'utf-32', c2)
else
c2 = #bufline + 1
end
@@ -1264,7 +1263,7 @@ function vim.deprecate(name, alternative, version, plugin, backtrace)
-- Example: if removal `version` is 0.12 (soft-deprecated since 0.10-dev), show warnings
-- starting at 0.11, including 0.11-dev.
local major, minor = version:match('(%d+)%.(%d+)')
major, minor = tonumber(major), tonumber(minor)
major, minor = tointeger(major), tointeger(minor)
local nvim_major = 0 --- Current Nvim major version.
-- We can't "subtract" from a major version, so:

View File

@@ -692,7 +692,7 @@ local function create_option_accessor(scope)
local option_mt
local function make_option(name, value)
local info = assert(get_options_info(name), 'Not a valid option name: ' .. name)
local info = get_options_info(name) or error('Not a valid option name: ' .. name)
if type(value) == 'table' and getmetatable(value) == option_mt then
assert(name == value._name, "must be the same value, otherwise that's weird.")

View File

@@ -1620,6 +1620,35 @@ function vim._ensure_list(x)
return { x }
end
--- Coerces {x} to an integer, like `tonumber()`, but rejects fractional values.
---
--- Returns `nil` if {x} cannot be converted with `tonumber()`, or if the
--- resulting number is not integral.
---
--- @param x any Value to convert.
--- @param base? integer Numeric base passed to `tonumber()`.
--- @return integer? integer Converted integer value, or `nil`.
function vim._tointeger(x, base)
--- @diagnostic disable-next-line:param-type-mismatch optional `base` is equivalent to `tonumber(x)`
local nx = tonumber(x, base)
if nx and nx == math.floor(nx) then
--- @cast nx integer
return nx
end
end
--- Coerces {x} to an integer and errors if conversion fails.
---
--- This is the throwing counterpart to |vim._tointeger()| and should be used
--- when non-integer input is a programming error.
---
--- @param x any Value to convert.
--- @param base? integer Numeric base passed to `tonumber()`.
--- @return integer integer Converted integer value.
function vim._ensure_integer(x, base)
return vim._tointeger(x, base) or error(('Cannot convert %s to integer'):format(x))
end
-- Use max 32-bit signed int value to avoid overflow on 32-bit systems. #31633
vim._maxint = 2 ^ 32 - 1

View File

@@ -14,7 +14,7 @@ function M.check()
local version, backtraces, alternative = v[1], v[2], v[3]
local major, minor = version:match('(%d+)%.(%d+)')
major, minor = tonumber(major), tonumber(minor)
major, minor = vim._ensure_integer(major), vim._ensure_integer(minor)
local removal_version = string.format('nvim-%d.%d', major, minor)
local will_be_removed = vim.fn.has(removal_version) == 1 and 'was removed' or 'will be removed'

View File

@@ -2857,7 +2857,7 @@ function M.match(str, pat, groups, severity_map, defaults)
if field == 'severity' then
diagnostic[field] = severity_map[match]
elseif field == 'lnum' or field == 'end_lnum' or field == 'col' or field == 'end_col' then
diagnostic[field] = assert(tonumber(match)) - 1
diagnostic[field] = vim._ensure_integer(match) - 1
elseif field then
diagnostic[field] = match
end

View File

@@ -790,8 +790,7 @@ function M.abspath(path)
-- Windows allows paths like C:foo/bar, these paths are relative to the current working directory
-- of the drive specified in the path
local cwd = (iswin and prefix:match('^%w:$')) and uv.fs_realpath(prefix) or uv.cwd()
assert(cwd ~= nil)
local cwd = assert((iswin and prefix:match('^%w:$')) and uv.fs_realpath(prefix) or uv.cwd())
-- Convert cwd path separator to `/`
cwd = cwd:gsub(os_sep, '/')

View File

@@ -24,11 +24,15 @@ local function resolve_hash(hash)
if type(hash) == 'number' then
hash = idx_hash(hash)
elseif type(hash) == 'string' then
local c = hash == 'concat' or hash:match('^concat%-(%d+)')
if c then
hash = concat_hash(tonumber(c))
if hash == 'concat' then
hash = concat_hash()
else
error('invalid value for hash: ' .. hash)
local c = hash:match('^concat%-(%d+)')
if c then
hash = concat_hash(vim._ensure_integer(c))
else
error('invalid value for hash: ' .. hash)
end
end
end
--- @cast hash -integer

View File

@@ -147,7 +147,7 @@ local function filepath_to_healthcheck(path)
-- */health/init.lua
name = vim.fs.dirname(vim.fs.dirname(subpath))
end
name = assert(name:gsub('/', '.')) --- @type string
name = assert(name:gsub('/', '.')) --[[@as string]]
func = 'require("' .. name .. '.health").check()'
filetype = 'l'

View File

@@ -295,10 +295,15 @@ local function check_tmux()
if tmux_esc_time ~= 'error' then
if tmux_esc_time == '' then
health.error('`escape-time` is not set', suggestions)
elseif tonumber(tmux_esc_time) > 300 then
health.error('`escape-time` (' .. tmux_esc_time .. ') is higher than 300ms', suggestions)
else
health.ok('escape-time: ' .. tmux_esc_time)
local tmux_esc_time_ms = vim._tointeger(tmux_esc_time)
if not tmux_esc_time_ms then
health.error('`escape-time` (' .. tmux_esc_time .. ') is not an integer', suggestions)
elseif tmux_esc_time_ms > 300 then
health.error('`escape-time` (' .. tmux_esc_time .. ') is higher than 300ms', suggestions)
else
health.ok('escape-time: ' .. tmux_esc_time)
end
end
end

View File

@@ -174,13 +174,21 @@ local function read_cachefile(cname)
--- @type integer[]|{[0]:integer}
local header = vim.split(data:sub(1, zero - 1), ',')
if tonumber(header[1]) ~= VERSION then
local version = vim._tointeger(header[1])
if version ~= VERSION then
return
end
local size = vim._tointeger(header[2])
local sec = vim._tointeger(header[3])
local nsec = vim._tointeger(header[4])
if not (size and sec and nsec) then
return
end
local hash = {
size = tonumber(header[2]),
mtime = { sec = tonumber(header[3]), nsec = tonumber(header[4]) },
size = size,
mtime = { sec = sec, nsec = nsec },
}
local chunk = data:sub(zero + 1)

View File

@@ -1136,7 +1136,7 @@ api.nvim_create_autocmd('VimLeavePre', {
local max_timeout = 0
for _, client in pairs(active_clients) do
max_timeout = math.max(max_timeout, tonumber(client.exit_timeout) or 0)
max_timeout = math.max(max_timeout, vim._tointeger(client.exit_timeout) or 0)
client:stop(client.exit_timeout)
end

View File

@@ -32,8 +32,8 @@ local M = {}
--- @field lines string[] snapshot of buffer lines from last didChange
--- @field lines_tmp string[]
--- @field pending_changes table[] List of debounced changes in incremental sync mode
--- @field timer uv.uv_timer_t? uv_timer
--- @field last_flush nil|number uv.hrtime of the last flush/didChange-notification
--- @field timer? uv.uv_timer_t uv_timer
--- @field last_flush? number uv.hrtime of the last flush/didChange-notification
--- @field needs_flush boolean true if buffer updates haven't been sent to clients/servers yet
--- @field refs integer how many clients are using this group
---
@@ -68,7 +68,7 @@ local function get_group(client)
local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
local sync_kind = change_capability or protocol.TextDocumentSyncKind.None
if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then
sync_kind = protocol.TextDocumentSyncKind.Full --[[@as integer]]
sync_kind = protocol.TextDocumentSyncKind.Full
end
return {
sync_kind = sync_kind,

View File

@@ -174,9 +174,7 @@ local G = P({
--- @param input string
--- @return vim.snippet.Node<vim.snippet.SnippetData>
function M.parse(input)
local snippet = G:match(input)
assert(snippet, 'snippet parsing failed')
return snippet --- @type vim.snippet.Node<vim.snippet.SnippetData>
return assert(G:match(input), 'snippet parsing failed')
end
return M

View File

@@ -713,10 +713,8 @@ end
--- @see |vim.lsp.buf_request_all()|
function Client:request(method, params, handler, bufnr)
if not handler then
handler = assert(
self:_resolve_handler(method),
string.format('not found: %q request handler for client %q.', method, self.name)
)
handler = self:_resolve_handler(method)
or error(('not found: %q request handler for client %q.'):format(method, self.name))
end
-- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
changetracking.flush(self, bufnr)

View File

@@ -324,7 +324,13 @@ local function generate_kind(item)
-- extract hex from RGB format
local r, g, b = doc:match('rgb%((%d+)%s*,?%s*(%d+)%s*,?%s*(%d+)%)')
local hex = r and string.format('%02x%02x%02x', tonumber(r), tonumber(g), tonumber(b))
local hex = r
and string.format(
'%02x%02x%02x',
vim._ensure_integer(r),
vim._ensure_integer(g),
vim._ensure_integer(b)
)
or doc:match('#?([%da-fA-F]+)')
if not hex then
@@ -696,7 +702,7 @@ function CompletionResolver:is_valid()
return vim.api.nvim_buf_is_valid(self.bufnr)
and vim.api.nvim_get_current_buf() == self.bufnr
and vim.startswith(vim.api.nvim_get_mode().mode, 'i')
and tonumber(vim.fn.pumvisible()) == 1
and vim.fn.pumvisible() ~= 0
and (vim.tbl_get(cmp_info, 'completed', 'word') or '') == self.word,
cmp_info
end
@@ -916,7 +922,7 @@ local function trigger(bufnr, clients, ctx)
reset_timer()
Context:cancel_pending()
if tonumber(vim.fn.pumvisible()) == 1 and not Context.isIncomplete then
if vim.fn.pumvisible() ~= 0 and not Context.isIncomplete then
return
end
@@ -1018,7 +1024,7 @@ end
--- @param handle vim.lsp.completion.BufHandle
local function on_insert_char_pre(handle)
if tonumber(vim.fn.pumvisible()) == 1 then
if vim.fn.pumvisible() ~= 0 then
if Context.isIncomplete then
reset_timer()
@@ -1143,8 +1149,7 @@ local function enable_completions(client_id, bufnr, opts)
end
if not buf_handle.clients[client_id] then
local client = lsp.get_client_by_id(client_id)
assert(client, 'invalid client ID')
local client = assert(lsp.get_client_by_id(client_id), 'invalid client ID')
-- Add the new client to the buffer's clients.
buf_handle.clients[client_id] = client
@@ -1245,7 +1250,6 @@ end
--- - findstart=1: list of matches (actually just calls |complete()|)
function M._omnifunc(findstart, base)
lsp.log.debug('omnifunc.findstart', { findstart = findstart, base = base })
assert(base) -- silence luals
local bufnr = api.nvim_get_current_buf()
local clients = lsp.get_clients({ bufnr = bufnr, method = 'textDocument/completion' })
local remaining = #clients

View File

@@ -47,10 +47,9 @@ local function get_contrast_color(color)
if not (r_s and g_s and b_s) then
error('Invalid color format: ' .. color)
end
local r, g, b = tonumber(r_s, 16), tonumber(g_s, 16), tonumber(b_s, 16)
if not (r and g and b) then
error('Invalid color format: ' .. color)
end
local r = vim._ensure_integer(r_s, 16)
local g = vim._ensure_integer(g_s, 16)
local b = vim._ensure_integer(b_s, 16)
-- Source: https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
-- Using power 2.2 is a close approximation to full piecewise transform

View File

@@ -137,8 +137,7 @@ local function check_watcher()
return
end
local watchfunc = vim.lsp._watchfiles._watchfunc
assert(watchfunc)
local watchfunc = assert(vim.lsp._watchfiles._watchfunc)
local watchfunc_name --- @type string
if watchfunc == vim._watch.watch then
watchfunc_name = 'libuv-watch'

View File

@@ -56,7 +56,10 @@ local function get_content_length(header)
elseif state == 'value' then
if c == 13 and header:byte(i + 1) == 10 then -- must end with \r\n
local value = buf:get()
return assert(digit and tonumber(value), 'value of Content-Length is not number: ' .. value)
if digit then
return vim._ensure_integer(value)
end
error('value of Content-Length is not number: ' .. value)
else
buf:put(string.char(c))
end
@@ -429,7 +432,7 @@ function Client:handle_body(body)
)
then
-- We sent a number, so we expect a number.
local result_id = assert(tonumber(decoded.id), 'response id must be a number') --[[@as integer]]
local result_id = vim._ensure_integer(decoded.id)
-- Notify the user that a response was received for the request
local notify_reply_callback = self.notify_reply_callbacks[result_id]

View File

@@ -636,8 +636,7 @@ function M.rename(old_fname, new_fname, opts)
local newdir = vim.fs.dirname(new_fname)
vim.fn.mkdir(newdir, 'p')
local ok, err = os.rename(old_fname_full, new_fname)
assert(ok, err)
assert(os.rename(old_fname_full, new_fname))
local old_undofile = vim.fn.undofile(old_fname_full)
if uv.fs_stat(old_undofile) ~= nil then
@@ -1745,10 +1744,10 @@ function M.open_floating_preview(contents, syntax, opts)
api.nvim_create_autocmd('WinClosed', {
group = api.nvim_create_augroup('nvim.closing_floating_preview', { clear = true }),
callback = function(args)
local winid = tonumber(args.match)
local ok, preview_bufnr = pcall(api.nvim_win_get_var, winid, 'lsp_floating_bufnr')
local winid = vim._tointeger(args.match)
local preview_bufnr = vim.w[winid].lsp_floating_bufnr
if
ok
preview_bufnr
and api.nvim_buf_is_valid(preview_bufnr)
and winid == vim.b[preview_bufnr].lsp_floating_preview
then

View File

@@ -1157,7 +1157,7 @@ local function show_confirm_buf(lines, on_finish)
--- @type integer
local cancel_au_id
local function on_cancel(data)
if tonumber(data.match) ~= win_id then
if vim._tointeger(data.match) ~= win_id then
return
end
pcall(api.nvim_del_autocmd, cancel_au_id)

View File

@@ -20,7 +20,7 @@ function methods.shutdown(_, callback)
end
local get_confirm_bufnr = function(uri)
return tonumber(uri:match('^nvim%-pack://confirm#(%d+)$'))
return vim._tointeger(uri:match('^nvim%-pack://confirm#(%d+)$'))
end
local group_header_pattern = '^# (%S+)'

View File

@@ -86,7 +86,8 @@ local function get_error_entry(err, node)
local start_line, start_col = node:range()
local line_offset, col_offset, msg = err:gmatch('.-:%d+: Query error at (%d+):(%d+)%. ([^:]+)')() ---@type string, string, string
start_line, start_col =
start_line + tonumber(line_offset) - 1, start_col + tonumber(col_offset) - 1
start_line + vim._ensure_integer(line_offset) - 1,
start_col + vim._ensure_integer(col_offset) - 1
local end_line, end_col = start_line, start_col
if msg:match('^Invalid syntax') or msg:match('^Impossible') then
-- Use the length of the underlined node

View File

@@ -429,7 +429,7 @@ local function on_range_impl(
-- The "priority" attribute can be set at the pattern level or on a particular capture
local priority = (
tonumber(metadata.priority or metadata[capture] and metadata[capture].priority)
vim._tointeger(metadata.priority or metadata[capture] and metadata[capture].priority)
or vim.hl.priorities.treesitter
) + spell_pri_offset

View File

@@ -25,7 +25,7 @@ local PATTERNS = {
---@param hex string
---@return string
local function hex_to_char(hex)
return schar(tonumber(hex, 16))
return schar(vim._ensure_integer(hex, 16))
end
---@param char string
@@ -101,7 +101,7 @@ end
---@param uri string
---@return string filename or unchanged URI for non-file URIs
function M.uri_to_fname(uri)
local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
local scheme = uri:match(URI_SCHEME_PATTERN) or error('URI must contain a scheme: ' .. uri)
if scheme ~= 'file' then
return uri
end

View File

@@ -86,8 +86,12 @@ local function cmp_prerel(prerel1, prerel2)
if word1 == nil and word2 == nil then -- Done iterating.
return 0
end
word1, n1, word2, n2 =
word1 or '', n1 and tonumber(n1) or 0, word2 or '', n2 and tonumber(n2) or 0
word1 = word1 or ''
n1 = vim._tointeger(n1) or 0
word2 = word2 or ''
n2 = vim._tointeger(n2) or 0
if word1 ~= word2 then
return word1 < word2 and -1 or 1
end
@@ -197,9 +201,9 @@ function M._version(version, strict) -- Adapted from https://github.com/folke/la
or (major and minor and patch and major ~= '' and minor ~= '' and patch ~= '')
then
return setmetatable({
major = tonumber(major),
minor = minor == '' and 0 or tonumber(minor),
patch = patch == '' and 0 or tonumber(patch),
major = vim._ensure_integer(major),
minor = minor == '' and 0 or vim._ensure_integer(minor),
patch = patch == '' and 0 or vim._ensure_integer(patch),
prerelease = prerel ~= '' and prerel or nil,
build = build ~= '' and build or nil,
}, Version)
@@ -286,7 +290,6 @@ function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim
return setmetatable({ from = M.parse('0.0.0') }, range_mt)
end
---@type number?
local hyphen = spec:find(' - ', 1, true)
if hyphen then
local a = spec:sub(1, hyphen - 1)
@@ -379,15 +382,15 @@ function M.intersect(r1, r2)
end
end
---@param v string|vim.Version
---@param v string|vim.Version|number[]
---@return string
local function create_err_msg(v)
local function err_msg(v)
if type(v) == 'string' then
return string.format('invalid version: "%s"', tostring(v))
return ('invalid version: "%s"'):format(v)
elseif type(v) == 'table' and v.major then
return string.format('invalid version: %s', vim.inspect(v))
return ('invalid version: %s'):format(vim.inspect(v))
end
return string.format('invalid version: %s (%s)', tostring(v), type(v))
return ('invalid version: %s (%s)'):format(tostring(v), type(v))
end
--- Parses and compares two version objects (the result of |vim.version.parse()|, or
@@ -413,8 +416,8 @@ end
---@param v2 vim.Version|number[]|string Version to compare with `v1`.
---@return integer -1 if `v1 < v2`, 0 if `v1 == v2`, 1 if `v1 > v2`.
function M.cmp(v1, v2)
local v1_parsed = assert(M._version(v1), create_err_msg(v1))
local v2_parsed = assert(M._version(v2), create_err_msg(v1))
local v1_parsed = M._version(v1) or error(err_msg(v1))
local v2_parsed = M._version(v2) or error(err_msg(v2))
if v1_parsed == v2_parsed then
return 0
end
@@ -492,7 +495,9 @@ end
---@param opts vim.version.parse.Opts? Options for parsing.
---@return vim.Version? # `Version` object or `nil` if input is invalid.
function M.parse(version, opts)
assert(type(version) == 'string', create_err_msg(version))
if type(version) ~= 'string' then
error(err_msg(version))
end
opts = opts or { strict = false }
return M._version(version, opts.strict)
end