diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2023-03-16 14:50:20 +0100 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2023-03-20 13:36:55 +0100 |
commit | 990c481551af2b346f315d75aa0815e9b65051f3 (patch) | |
tree | c7d99c099853be3fd886b153a2ebde1e5307fe15 | |
parent | d8f9dcd5deb72f1fbc706d7b3bb5b823c7998e38 (diff) | |
download | rneovim-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.lua | 191 | ||||
-rw-r--r-- | test/functional/lua/version_spec.lua | 120 |
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( |