aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2023-03-16 14:50:20 +0100
committerJustin M. Keyes <justinkz@gmail.com>2023-03-20 13:36:55 +0100
commit990c481551af2b346f315d75aa0815e9b65051f3 (patch)
treec7d99c099853be3fd886b153a2ebde1e5307fe15
parentd8f9dcd5deb72f1fbc706d7b3bb5b823c7998e38 (diff)
downloadrneovim-990c481551af2b346f315d75aa0815e9b65051f3.tar.gz
rneovim-990c481551af2b346f315d75aa0815e9b65051f3.tar.bz2
rneovim-990c481551af2b346f315d75aa0815e9b65051f3.zip
refactor(vim.version): use lazy.nvim semver module
Use semver code from https://github.com/folke/lazy.nvim License: Apache License 2.0 Co-authored-by: Folke Lemaitre <folke.lemaitre@gmail.com>
-rw-r--r--runtime/lua/vim/version.lua191
-rw-r--r--test/functional/lua/version_spec.lua120
2 files changed, 300 insertions, 11 deletions
diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua
index 35629c461f..b409483755 100644
--- a/runtime/lua/vim/version.lua
+++ b/runtime/lua/vim/version.lua
@@ -1,5 +1,196 @@
local M = {}
+local LazyM = {}
+M.LazyM = LazyM
+
+---@class Semver
+---@field [1] number
+---@field [2] number
+---@field [3] number
+---@field major number
+---@field minor number
+---@field patch number
+---@field prerelease? string
+---@field build? string
+local Semver = {}
+Semver.__index = Semver
+
+function Semver:__index(key)
+ return type(key) == "number" and ({ self.major, self.minor, self.patch })[key] or Semver[key]
+end
+
+function Semver:__newindex(key, value)
+ if key == 1 then
+ self.major = value
+ elseif key == 2 then
+ self.minor = value
+ elseif key == 3 then
+ self.patch = value
+ else
+ rawset(self, key, value)
+ end
+end
+
+---@param other Semver
+function Semver:__eq(other)
+ for i = 1, 3 do
+ if self[i] ~= other[i] then
+ return false
+ end
+ end
+ return self.prerelease == other.prerelease
+end
+
+function Semver:__tostring()
+ local ret = table.concat({ self.major, self.minor, self.patch }, ".")
+ if self.prerelease then
+ ret = ret .. "-" .. self.prerelease
+ end
+ if self.build then
+ ret = ret .. "+" .. self.build
+ end
+ return ret
+end
+
+---@param other Semver
+function Semver:__lt(other)
+ for i = 1, 3 do
+ if self[i] > other[i] then
+ return false
+ elseif self[i] < other[i] then
+ return true
+ end
+ end
+ if self.prerelease and not other.prerelease then
+ return true
+ end
+ if other.prerelease and not self.prerelease then
+ return false
+ end
+ return (self.prerelease or "") < (other.prerelease or "")
+end
+
+---@param other Semver
+function Semver:__le(other)
+ return self < other or self == other
+end
+
+---@param version string|number[]
+---@return Semver?
+function LazyM.parse(version)
+ if type(version) == "table" then
+ return setmetatable({
+ major = version[1] or 0,
+ minor = version[2] or 0,
+ patch = version[3] or 0,
+ }, Semver)
+ end
+ local major, minor, patch, prerelease, build = version:match("^v?(%d+)%.?(%d*)%.?(%d*)%-?([^+]*)+?(.*)$")
+ if major then
+ return setmetatable({
+ major = tonumber(major),
+ minor = minor == "" and 0 or tonumber(minor),
+ patch = patch == "" and 0 or tonumber(patch),
+ prerelease = prerelease ~= "" and prerelease or nil,
+ build = build ~= "" and build or nil,
+ }, Semver)
+ end
+end
+
+---@generic T: Semver
+---@param versions T[]
+---@return T?
+function LazyM.last(versions)
+ local last = versions[1]
+ for i = 2, #versions do
+ if versions[i] > last then
+ last = versions[i]
+ end
+ end
+ return last
+end
+
+---@class SemverRange
+---@field from Semver
+---@field to? Semver
+local Range = {}
+
+---@param version string|Semver
+function Range:matches(version)
+ if type(version) == "string" then
+ ---@diagnostic disable-next-line: cast-local-type
+ version = LazyM.parse(version)
+ end
+ if version then
+ if version.prerelease ~= self.from.prerelease then
+ return false
+ end
+ return version >= self.from and (self.to == nil or version < self.to)
+ end
+end
+
+---@param spec string
+function LazyM.range(spec)
+ if spec == "*" or spec == "" then
+ return setmetatable({ from = LazyM.parse("0.0.0") }, { __index = Range })
+ end
+
+ ---@type number?
+ local hyphen = spec:find(" - ", 1, true)
+ if hyphen then
+ local a = spec:sub(1, hyphen - 1)
+ local b = spec:sub(hyphen + 3)
+ local parts = vim.split(b, ".", { plain = true })
+ local ra = LazyM.range(a)
+ local rb = LazyM.range(b)
+ return setmetatable({
+ from = ra and ra.from,
+ to = rb and (#parts == 3 and rb.from or rb.to),
+ }, { __index = Range })
+ end
+ ---@type string, string
+ local mods, version = spec:lower():match("^([%^=>~]*)(.*)$")
+ version = version:gsub("%.[%*x]", "")
+ local parts = vim.split(version:gsub("%-.*", ""), ".", { plain = true })
+ if #parts < 3 and mods == "" then
+ mods = "~"
+ end
+
+ local semver = LazyM.parse(version)
+ if semver then
+ local from = semver
+ local to = vim.deepcopy(semver)
+ if mods == "" or mods == "=" then
+ to.patch = to.patch + 1
+ elseif mods == ">" then
+ from.patch = from.patch + 1
+ to = nil
+ elseif mods == ">=" then
+ to = nil
+ elseif mods == "~" then
+ if #parts >= 2 then
+ to[2] = to[2] + 1
+ to[3] = 0
+ else
+ to[1] = to[1] + 1
+ to[2] = 0
+ to[3] = 0
+ end
+ elseif mods == "^" then
+ for i = 1, 3 do
+ if to[i] ~= 0 then
+ to[i] = to[i] + 1
+ for j = i + 1, 3 do
+ to[j] = 0
+ end
+ break
+ end
+ end
+ end
+ return setmetatable({ from = from, to = to }, { __index = Range })
+ end
+end
+
---@private
---@param version string
---@return string
diff --git a/test/functional/lua/version_spec.lua b/test/functional/lua/version_spec.lua
index b68727ca77..2901646e66 100644
--- a/test/functional/lua/version_spec.lua
+++ b/test/functional/lua/version_spec.lua
@@ -6,17 +6,115 @@ local matches = helpers.matches
local pcall_err = helpers.pcall_err
local version = require('vim.version')
+local Semver = version.LazyM
local function quote_empty(s)
return tostring(s) == '' and '""' or tostring(s)
end
+local function v(ver)
+ return Semver.parse(ver)
+end
+
describe('version', function()
+
it('package', function()
clear()
eq({ major = 42, minor = 3, patch = 99 }, exec_lua("return vim.version.parse('v42.3.99')"))
end)
+ describe('semver version', function()
+ local tests = {
+ ['v1.2.3'] = { major = 1, minor = 2, patch = 3 },
+ ['v1.2'] = { major = 1, minor = 2, patch = 0 },
+ ['v1.2.3-prerelease'] = { major = 1, minor = 2, patch = 3, prerelease = 'prerelease' },
+ ['v1.2-prerelease'] = { major = 1, minor = 2, patch = 0, prerelease = 'prerelease' },
+ ['v1.2.3-prerelease+build'] = { major = 1, minor = 2, patch = 3, prerelease = 'prerelease', build = "build" },
+ ['1.2.3+build'] = { major = 1, minor = 2, patch = 3, build = 'build' },
+ }
+ for input, output in pairs(tests) do
+ it('parses ' .. input, function()
+ assert.same(output, v(input))
+ end)
+ end
+ end)
+
+ describe('semver range', function()
+ local tests = {
+ ['1.2.3'] = { from = { 1, 2, 3 }, to = { 1, 2, 4 } },
+ ['1.2'] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } },
+ ['=1.2.3'] = { from = { 1, 2, 3 }, to = { 1, 2, 4 } },
+ ['>1.2.3'] = { from = { 1, 2, 4 } },
+ ['>=1.2.3'] = { from = { 1, 2, 3 } },
+ ['~1.2.3'] = { from = { 1, 2, 3 }, to = { 1, 3, 0 } },
+ ['^1.2.3'] = { from = { 1, 2, 3 }, to = { 2, 0, 0 } },
+ ['^0.2.3'] = { from = { 0, 2, 3 }, to = { 0, 3, 0 } },
+ ['^0.0.1'] = { from = { 0, 0, 1 }, to = { 0, 0, 2 } },
+ ['^1.2'] = { from = { 1, 2, 0 }, to = { 2, 0, 0 } },
+ ['~1.2'] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } },
+ ['~1'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['^1'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['1.*'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['1'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['1.x'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['1.2.x'] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } },
+ ['1.2.*'] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } },
+ ['*'] = { from = { 0, 0, 0 } },
+ ['1.2 - 2.3.0'] = { from = { 1, 2, 0 }, to = { 2, 3, 0 } },
+ ['1.2.3 - 2.3.4'] = { from = { 1, 2, 3 }, to = { 2, 3, 4 } },
+ ['1.2.3 - 2'] = { from = { 1, 2, 3 }, to = { 3, 0, 0 } },
+ }
+ for input, output in pairs(tests) do
+ output.from = v(output.from)
+ output.to = output.to and v(output.to)
+
+ local range = Semver.range(input)
+ it('parses ' .. input, function()
+ assert.same(output, range)
+ end)
+
+ it('[from] in range ' .. input, function()
+ assert(range:matches(output.from))
+ end)
+
+ it('[from-1] not in range ' .. input, function()
+ local lower = vim.deepcopy(range.from)
+ lower.major = lower.major - 1
+ assert(not range:matches(lower))
+ end)
+
+ it('[to] not in range ' .. input .. ' to:' .. tostring(range.to), function()
+ if range.to then
+ assert(not (range.to < range.to))
+ assert(not range:matches(range.to))
+ end
+ end)
+ end
+
+ it("handles prerelease", function()
+ assert(not Semver.range('1.2.3'):matches('1.2.3-alpha'))
+ assert(Semver.range('1.2.3-alpha'):matches('1.2.3-alpha'))
+ assert(not Semver.range('1.2.3-alpha'):matches('1.2.3-beta'))
+ end)
+ end)
+
+ describe('semver order', function()
+ it('is correct', function()
+ assert(v('v1.2.3') == v('1.2.3'))
+ assert(not (v('v1.2.3') < v('1.2.3')))
+ assert(v('v1.2.3') > v('1.2.3-prerelease'))
+ assert(v('v1.2.3-alpha') < v('1.2.3-beta'))
+ assert(v('v1.2.3-prerelease') < v('1.2.3'))
+ assert(v('v1.2.3') >= v('1.2.3'))
+ assert(v('v1.2.3') >= v('1.0.3'))
+ assert(v('v1.2.3') >= v('1.2.2'))
+ assert(v('v1.2.3') > v('1.2.2'))
+ assert(v('v1.2.3') > v('1.0.3'))
+ assert.same(Semver.last({ v('1.2.3'), v('2.0.0') }), v('2.0.0'))
+ assert.same(Semver.last({ v('2.0.0'), v('1.2.3') }), v('2.0.0'))
+ end)
+ end)
+
describe('cmp()', function()
local testcases = {
{
@@ -206,16 +304,6 @@ describe('version', function()
want = { major = 1, minor = 2, patch = 3 },
},
{
- desc = 'valid version with leading "v" and whitespace',
- version = ' v1.2.3',
- want = { major = 1, minor = 2, patch = 3 },
- },
- {
- desc = 'valid version with leading "v" and trailing whitespace',
- version = 'v1.2.3 ',
- want = { major = 1, minor = 2, patch = 3 },
- },
- {
desc = 'version with prerelease',
version = '1.2.3-alpha',
want = { major = 1, minor = 2, patch = 3, prerelease = 'alpha' },
@@ -246,7 +334,7 @@ describe('version', function()
it(
string.format('for %q: version = %q', tc.desc, tc.version),
function()
- eq(tc.want, version.parse(tc.version, { strict = true }))
+ eq(tc.want, Semver.parse(tc.version))
end
)
end
@@ -274,6 +362,16 @@ describe('version', function()
version = '1-1.0',
want = { major = 1, minor = 0, patch = 0, prerelease = '1.0' },
},
+ {
+ desc = 'valid version with leading "v" and trailing whitespace',
+ version = 'v1.2.3 ',
+ want = { major = 1, minor = 2, patch = 3 },
+ },
+ {
+ desc = 'valid version with leading "v" and whitespace',
+ version = ' v1.2.3',
+ want = { major = 1, minor = 2, patch = 3 },
+ },
}
for _, tc in ipairs(testcases) do
it(