diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2023-03-06 13:23:03 +0100 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2023-03-06 14:51:56 +0100 |
commit | e31e49a8e3aac25e923dce15cc76dca4a447947f (patch) | |
tree | a00172499bce9c7b52a1591aa9bada742bbfb351 | |
parent | 0e7196438d8f856eecd7c90e160b79cbc8fb08dc (diff) | |
download | rneovim-e31e49a8e3aac25e923dce15cc76dca4a447947f.tar.gz rneovim-e31e49a8e3aac25e923dce15cc76dca4a447947f.tar.bz2 rneovim-e31e49a8e3aac25e923dce15cc76dca4a447947f.zip |
refactor(vim.version): cleanup
- version.cmp(): assert valid version
- add test for loading vim.version (the other tests use shared.lua in
the test runner)
- reduce test scopes, reword test descriptions
-rw-r--r-- | runtime/doc/lua.txt | 47 | ||||
-rw-r--r-- | runtime/doc/news.txt | 5 | ||||
-rw-r--r-- | runtime/lua/vim/_init_packages.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/version.lua | 134 | ||||
-rwxr-xr-x | scripts/gen_vimdoc.py | 1 | ||||
-rw-r--r-- | test/functional/lua/version_spec.lua | 690 |
6 files changed, 423 insertions, 462 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 3c48cd37a6..697cd86e8a 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2501,12 +2501,12 @@ trust({opts}) *vim.secure.trust()* ============================================================================== Lua module: version *lua-version* -cmp({v1}, {v2}, {opts}) *version.cmp()* +cmp({v1}, {v2}, {opts}) *vim.version.cmp()* Compares two strings ( `v1` and `v2` ) in semver format. Parameters: ~ • {v1} (string) Version. - • {v2} (string) Version to be compared with v1. + • {v2} (string) Version to compare with v1. • {opts} (table|nil) Optional keyword arguments: • strict (boolean): see `semver.parse` for details. Defaults to false. @@ -2514,59 +2514,54 @@ cmp({v1}, {v2}, {opts}) *version.cmp()* Return: ~ (integer) `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. -eq({version_1}, {version_2}) *version.eq()* +eq({v1}, {v2}) *vim.version.eq()* Returns `true` if `v1` are `v2` are equal versions. Parameters: ~ - • {version_1} (string) - • {version_2} (string) + • {v1} (string) + • {v2} (string) Return: ~ (boolean) -gt({version_1}, {version_2}) *version.gt()* +gt({v1}, {v2}) *vim.version.gt()* Returns `true` if `v1` is greater than `v2` . Parameters: ~ - • {version_1} (string) - • {version_2} (string) + • {v1} (string) + • {v2} (string) Return: ~ (boolean) -lt({version_1}, {version_2}) *version.lt()* +lt({v1}, {v2}) *vim.version.lt()* Returns `true` if `v1` is less than `v2` . Parameters: ~ - • {version_1} (string) - • {version_2} (string) + • {v1} (string) + • {v2} (string) Return: ~ (boolean) -parse({version}, {opts}) *version.parse()* - Parses a semantically formatted version string into a table. +parse({version}, {opts}) *vim.version.parse()* + Parses a semantic version string. - Supports leading "v" and leading and trailing whitespace in the version - string. e.g. `" v1.0.1-rc1+build.2"` , `"1.0.1-rc1+build.2"`, - `"v1.0.1-rc1+build.2"` and `"v1.0.1-rc1+build.2 "` will be parsed as: + Ignores leading "v" and surrounding whitespace, e.g. " + v1.0.1-rc1+build.2", "1.0.1-rc1+build.2", "v1.0.1-rc1+build.2" and + "v1.0.1-rc1+build.2 " are all parsed as: > - { major = 1, minor = 0, patch = 1, prerelease = 'rc1 , build = 'build.2' }` + { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" } +< Parameters: ~ • {version} (string) Version string to be parsed. • {opts} (table|nil) Optional keyword arguments: - • strict (boolean): when set to `true` an error will be - thrown for version strings that do not conform to the - semver specification (v2.0.0) (see - semver.org/spec/v2.0.0.html for details). This means that - `semver.parse('v1.2)` will throw an error. When set to - `false`, `semver.parse('v1.2)` will coerce 'v1.2' to - 'v1.2.0' and return the table: `{ major = 1, minor = 2, - patch = 0 }`. Defaults to false. + • strict (boolean): Default false. If `true` , no coercion is attempted on input not strictly + conforming to semver v2.0.0 ( https://semver.org/spec/v2.0.0.html ). E.g. `parse("v1.2")` returns nil. Return: ~ (table|nil) parsed_version Parsed version table or `nil` if `version` - is not valid. + is invalid. vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index b5cb975066..28fdaa770d 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -55,9 +55,8 @@ NEW FEATURES *news-features* The following new APIs or features were added. -• Added |version.parse()|, |version.cmp()|, |version.lt()|, |version.eq()| - and |version.gt()| to |vim.version| for parsing and comparing version numbers - according to the semver specification, see |lua-version|. +• Added |vim.version| for parsing and comparing version strings conforming to + the semver specification, see |lua-version|. • A new environment variable named NVIM_APPNAME enables configuring the directories where Neovim should find its configuration and state files. See diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index d032026796..57c0fc9122 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -51,7 +51,10 @@ end -- builtin functions which always should be available require('vim.shared') -vim._submodules = { inspect = true } +vim._submodules = { + inspect = true, + version = true, +} -- These are for loading runtime modules in the vim namespace lazily. setmetatable(vim, { @@ -69,9 +72,6 @@ setmetatable(vim, { t[key] = val return t[key] end - elseif key == 'version' then - t[key] = require('vim.version') - return t[key] end end, }) diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 49ef019295..ddbe228244 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -1,23 +1,41 @@ local M = {} ---@private +---@param version string +---@return string +local function create_err_msg(version) + return string.format('invalid version: "%s"', version) +end + +---@private +--- Throws an error if `version` cannot be parsed. +---@param version string +local function assert_version(version, opt) + local rv = M.parse(version, opt) + if rv == nil then + error(create_err_msg(version)) + end + return rv +end + +---@private --- Compares the prerelease component of the two versions. ----@param v1_parsed table Parsed version. ----@param v2_parsed table Parsed version. ----@return integer `-1` if `v1_parsed < v2_parsed`, `0` if `v1_parsed == v2_parsed`, `1` if `v1_parsed > v2_parsed`. -local function cmp_prerelease(v1_parsed, v2_parsed) - if v1_parsed.prerelease and not v2_parsed.prerelease then +---@param v1 table Parsed version. +---@param v2 table Parsed version. +---@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. +local function cmp_prerelease(v1, v2) + if v1.prerelease and not v2.prerelease then return -1 end - if not v1_parsed.prerelease and v2_parsed.prerelease then + if not v1.prerelease and v2.prerelease then return 1 end - if not v1_parsed.prerelease and not v2_parsed.prerelease then + if not v1.prerelease and not v2.prerelease then return 0 end - local v1_identifiers = vim.split(v1_parsed.prerelease, '.', { plain = true }) - local v2_identifiers = vim.split(v2_parsed.prerelease, '.', { plain = true }) + local v1_identifiers = vim.split(v1.prerelease, '.', { plain = true }) + local v2_identifiers = vim.split(v2.prerelease, '.', { plain = true }) local i = 1 local max = math.max(vim.tbl_count(v1_identifiers), vim.tbl_count(v2_identifiers)) while i <= max do @@ -71,23 +89,15 @@ end ---@private --- Compares the version core component of the two versions. ----@param v1_parsed table Parsed version. ----@param v2_parsed table Parsed version. ----@return integer `-1` if `v1_parsed < v2_parsed`, `0` if `v1_parsed == v2_parsed`, `1` if `v1_parsed > v2_parsed`. -local function cmp_version_core(v1_parsed, v2_parsed) - if - v1_parsed.major == v2_parsed.major - and v1_parsed.minor == v2_parsed.minor - and v1_parsed.patch == v2_parsed.patch - then +---@param v1 table Parsed version. +---@param v2 table Parsed version. +---@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. +local function cmp_version_core(v1, v2) + if v1.major == v2.major and v1.minor == v2.minor and v1.patch == v2.patch then return 0 end - if - v1_parsed.major > v2_parsed.major - or v1_parsed.minor > v2_parsed.minor - or v1_parsed.patch > v2_parsed.patch - then + if v1.major > v2.major or v1.minor > v2.minor or v1.patch > v2.patch then return 1 end @@ -96,14 +106,14 @@ end --- Compares two strings (`v1` and `v2`) in semver format. ---@param v1 string Version. ----@param v2 string Version to be compared with v1. +---@param v2 string Version to compare with v1. ---@param opts table|nil Optional keyword arguments: --- - strict (boolean): see `semver.parse` for details. Defaults to false. ---@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. function M.cmp(v1, v2, opts) opts = opts or { strict = false } - local v1_parsed = M.parse(v1, opts) - local v2_parsed = M.parse(v2, opts) + local v1_parsed = assert_version(v1, opts) + local v2_parsed = assert_version(v2, opts) local result = cmp_version_core(v1_parsed, v2_parsed) if result == 0 then @@ -188,30 +198,20 @@ local function is_prerelease_and_build_valid(prerelease_and_build) return has_build or has_prerelease or has_prerelease_and_build end ----@private ----@param version string ----@return string -local function create_err_msg(version) - return string.format('invalid version: "%s"', version) -end - ---- Parses a semantically formatted version string into a table. ---- ---- Supports leading "v" and leading and trailing whitespace in the version ---- string. e.g. `" v1.0.1-rc1+build.2"` , `"1.0.1-rc1+build.2"`, `"v1.0.1-rc1+build.2"` ---- and `"v1.0.1-rc1+build.2 "` will be parsed as: +--- Parses a semantic version string. --- ---- `{ major = 1, minor = 0, patch = 1, prerelease = 'rc1', build = 'build.2' }` +--- Ignores leading "v" and surrounding whitespace, e.g. " v1.0.1-rc1+build.2", +--- "1.0.1-rc1+build.2", "v1.0.1-rc1+build.2" and "v1.0.1-rc1+build.2 " are all parsed as: +--- <pre> +--- { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" } +--- </pre> --- ---@param version string Version string to be parsed. ---@param opts table|nil Optional keyword arguments: ---- - strict (boolean): when set to `true` an error will be thrown for version ---- strings that do not conform to the semver specification (v2.0.0) (see ---- semver.org/spec/v2.0.0.html for details). This means that ---- `semver.parse('v1.2)` will throw an error. When set to `false`, ---- `semver.parse('v1.2)` will coerce 'v1.2' to 'v1.2.0' and return the table: ---- `{ major = 1, minor = 2, patch = 0 }`. Defaults to false. ----@return table|nil parsed_version Parsed version table or `nil` if `version` is not valid. +--- - strict (boolean): Default false. If `true`, no coercion is attempted on +--- input not strictly conforming to semver v2.0.0 +--- (https://semver.org/spec/v2.0.0.html). E.g. `parse("v1.2")` returns nil. +---@return table|nil parsed_version Parsed version table or `nil` if `version` is invalid. function M.parse(version, opts) if type(version) ~= 'string' then error(create_err_msg(version)) @@ -246,46 +246,28 @@ function M.parse(version, opts) } end ----@private ---- Throws an error if `version` cannot be parsed. ----@param version string -local function assert_version(version) - if M.parse(version) == nil then - error(create_err_msg(version)) - end -end - ---Returns `true` if `v1` are `v2` are equal versions. ----@param version_1 string ----@param version_2 string +---@param v1 string +---@param v2 string ---@return boolean -function M.eq(version_1, version_2) - assert_version(version_1) - assert_version(version_2) - - return M.cmp(version_1, version_2) == 0 +function M.eq(v1, v2) + return M.cmp(v1, v2) == 0 end ---Returns `true` if `v1` is less than `v2`. ----@param version_1 string ----@param version_2 string +---@param v1 string +---@param v2 string ---@return boolean -function M.lt(version_1, version_2) - assert_version(version_1) - assert_version(version_2) - - return M.cmp(version_1, version_2) == -1 +function M.lt(v1, v2) + return M.cmp(v1, v2) == -1 end ---Returns `true` if `v1` is greater than `v2`. ----@param version_1 string ----@param version_2 string +---@param v1 string +---@param v2 string ---@return boolean -function M.gt(version_1, version_2) - assert_version(version_1) - assert_version(version_2) - - return M.cmp(version_1, version_2) == 1 +function M.gt(v1, v2) + return M.cmp(v1, v2) == 1 end setmetatable(M, { diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index 45a6d8e402..1e85fa49e9 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -194,6 +194,7 @@ CONFIG = { 'keymap': 'vim.keymap', 'fs': 'vim.fs', 'secure': 'vim.secure', + 'version': 'vim.version', }, 'append_only': [ 'shared.lua', diff --git a/test/functional/lua/version_spec.lua b/test/functional/lua/version_spec.lua index 23f3cec948..9e41330915 100644 --- a/test/functional/lua/version_spec.lua +++ b/test/functional/lua/version_spec.lua @@ -1,165 +1,176 @@ local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear local eq = helpers.eq -local pcall_err = helpers.pcall_err +local exec_lua = helpers.exec_lua local matches = helpers.matches +local pcall_err = helpers.pcall_err local version = require('vim.version') +local function quote_empty(s) + return tostring(s) == '' and '""' or tostring(s) +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('cmp()', function() local testcases = { { - desc = 'v1 < v2', + desc = '(v1 < v2)', v1 = 'v0.0.0', v2 = 'v9.0.0', want = -1, }, { - desc = 'v1 < v2', + desc = '(v1 < v2)', v1 = 'v0.0.0', v2 = 'v0.9.0', want = -1, }, { - desc = 'v1 < v2', + desc = '(v1 < v2)', v1 = 'v0.0.0', v2 = 'v0.0.9', want = -1, }, { - desc = 'v1 == v2', + desc = '(v1 == v2)', v1 = 'v0.0.0', v2 = 'v0.0.0', want = 0, }, { - desc = 'v1 > v2', + desc = '(v1 > v2)', v1 = 'v9.0.0', v2 = 'v0.0.0', want = 1, }, { - desc = 'v1 > v2', + desc = '(v1 > v2)', v1 = 'v0.9.0', v2 = 'v0.0.0', want = 1, }, { - desc = 'v1 > v2', + desc = '(v1 > v2)', v1 = 'v0.0.9', v2 = 'v0.0.0', want = 1, }, { - desc = 'v1 < v2 when v1 has prerelease', + desc = '(v1 < v2) when v1 has prerelease', v1 = 'v1.0.0-alpha', v2 = 'v1.0.0', want = -1, }, { - desc = 'v1 > v2 when v2 has prerelease', + desc = '(v1 > v2) when v2 has prerelease', v1 = '1.0.0', v2 = '1.0.0-alpha', want = 1, }, { - desc = 'v1 > v2 when v1 has a higher number identifier', + desc = '(v1 > v2) when v1 has a higher number identifier', v1 = '1.0.0-2', v2 = '1.0.0-1', want = 1, }, { - desc = 'v1 < v2 when v2 has a higher number identifier', + desc = '(v1 < v2) when v2 has a higher number identifier', v1 = '1.0.0-2', v2 = '1.0.0-9', want = -1, }, { - desc = 'v1 < v2 when v2 has more identifiers', + desc = '(v1 < v2) when v2 has more identifiers', v1 = '1.0.0-2', v2 = '1.0.0-2.0', want = -1, }, { - desc = 'v1 > v2 when v1 has more identifiers', + desc = '(v1 > v2) when v1 has more identifiers', v1 = '1.0.0-2.0', v2 = '1.0.0-2', want = 1, }, { - desc = 'v1 == v2 when v2 has same numeric identifiers', + desc = '(v1 == v2) when v2 has same numeric identifiers', v1 = '1.0.0-2.0', v2 = '1.0.0-2.0', want = 0, }, { - desc = 'v1 == v2 when v2 has same alphabet identifiers', + desc = '(v1 == v2) when v2 has same alphabet identifiers', v1 = '1.0.0-alpha', v2 = '1.0.0-alpha', want = 0, }, { - desc = 'v1 < v2 when v2 has an alphabet identifier with a higher ASCII sort order', + desc = '(v1 < v2) when v2 has an alphabet identifier with higher ASCII sort order', v1 = '1.0.0-alpha', v2 = '1.0.0-beta', want = -1, }, { - desc = 'v1 > v2 when v1 has an alphabet identifier with a higher ASCII sort order', + desc = '(v1 > v2) when v1 has an alphabet identifier with higher ASCII sort order', v1 = '1.0.0-beta', v2 = '1.0.0-alpha', want = 1, }, { - desc = 'v1 < v2 when v2 has prerelease and number identifer', + desc = '(v1 < v2) when v2 has prerelease and number identifer', v1 = '1.0.0-alpha', v2 = '1.0.0-alpha.1', want = -1, }, { - desc = 'v1 > v2 when v1 has prerelease and number identifer', + desc = '(v1 > v2) when v1 has prerelease and number identifer', v1 = '1.0.0-alpha.1', v2 = '1.0.0-alpha', want = 1, }, { - desc = 'v1 > v2 when v1 has an additional alphabet identifier', + desc = '(v1 > v2) when v1 has an additional alphabet identifier', v1 = '1.0.0-alpha.beta', v2 = '1.0.0-alpha', want = 1, }, { - desc = 'v1 < v2 when v2 has an additional alphabet identifier', + desc = '(v1 < v2) when v2 has an additional alphabet identifier', v1 = '1.0.0-alpha', v2 = '1.0.0-alpha.beta', want = -1, }, { - desc = 'v1 < v2 when v2 has an a first alphabet identifier with higher precedence', + desc = '(v1 < v2) when v2 has an a first alphabet identifier with higher precedence', v1 = '1.0.0-alpha.beta', v2 = '1.0.0-beta', want = -1, }, { - desc = 'v1 > v2 when v1 has an a first alphabet identifier with higher precedence', + desc = '(v1 > v2) when v1 has an a first alphabet identifier with higher precedence', v1 = '1.0.0-beta', v2 = '1.0.0-alpha.beta', want = 1, }, { - desc = 'v1 < v2 when v2 has an additional number identifer', + desc = '(v1 < v2) when v2 has an additional number identifer', v1 = '1.0.0-beta', v2 = '1.0.0-beta.2', want = -1, }, { - desc = 'v1 < v2 when v2 has same first alphabet identifier but has a higher number identifer', + desc = '(v1 < v2) when v2 has same first alphabet identifier but has a higher number identifer', v1 = '1.0.0-beta.2', v2 = '1.0.0-beta.11', want = -1, }, { - desc = 'v1 < v2 when v2 has higher alphabet precedence', + desc = '(v1 < v2) when v2 has higher alphabet precedence', v1 = '1.0.0-beta.11', v2 = '1.0.0-rc.1', want = -1, @@ -167,7 +178,7 @@ describe('version', function() } for _, tc in ipairs(testcases) do it( - string.format('returns %d if %s (v1 = %s, v2 = %s)', tc.want, tc.desc, tc.v1, tc.v2), + string.format('%d %s (v1 = %s, v2 = %s)', tc.want, tc.desc, tc.v1, tc.v2), function() eq(tc.want, version.cmp(tc.v1, tc.v2, { strict = true })) end @@ -176,410 +187,383 @@ describe('version', function() end) describe('parse()', function() - describe('parsing', function() - describe('strict = true', function() - local testcases = { - { - desc = 'a version without leading "v"', - version = '10.20.123', - want = { - major = 10, - minor = 20, - patch = 123, - prerelease = nil, - build = nil, - }, - }, - { - desc = 'a valid version with a leading "v"', - version = 'v1.2.3', - want = { major = 1, minor = 2, patch = 3 }, - }, - { - desc = 'a valid version with leading "v" and whitespace', - version = ' v1.2.3', - want = { major = 1, minor = 2, patch = 3 }, - }, - { - desc = 'a valid version with leading "v" and trailing whitespace', - version = 'v1.2.3 ', - want = { major = 1, minor = 2, patch = 3 }, - }, - { - desc = 'a version with a prerelease', - version = '1.2.3-alpha', - want = { major = 1, minor = 2, patch = 3, prerelease = 'alpha' }, - }, - { - desc = 'a version with a prerelease with additional identifiers', - version = '1.2.3-alpha.1', - want = { major = 1, minor = 2, patch = 3, prerelease = 'alpha.1' }, - }, - { - desc = 'a version with a build', - version = '1.2.3+build.15', - want = { major = 1, minor = 2, patch = 3, build = 'build.15' }, - }, - { - desc = 'a version with a prerelease and build', - version = '1.2.3-rc1+build.15', - want = { - major = 1, - minor = 2, - patch = 3, - prerelease = 'rc1', - build = 'build.15', - }, - }, - } - for _, tc in ipairs(testcases) do - it( - string.format('returns correct table for %q: version = %q', tc.desc, tc.version), - function() - eq(tc.want, version.parse(tc.version, { strict = true })) - end - ) - end - end) - - describe('strict = false', function() - local testcases = { - { - desc = 'a version missing patch version', - version = '1.2', - want = { major = 1, minor = 2, patch = 0 }, - }, - { - desc = 'a version missing minor and patch version', - version = '1', - want = { major = 1, minor = 0, patch = 0 }, - }, - { - desc = 'a version missing patch version with prerelease', - version = '1.1-0', - want = { major = 1, minor = 1, patch = 0, prerelease = '0' }, - }, - { - desc = 'a version missing minor and patch version with prerelease', - version = '1-1.0', - want = { major = 1, minor = 0, patch = 0, prerelease = '1.0' }, - }, - } - for _, tc in ipairs(testcases) do - it( - string.format('returns correct table for %q: version = %q', tc.desc, tc.version), - function() - eq(tc.want, version.parse(tc.version, { strict = false })) - end - ) - end - end) - end) - - describe('errors', function() - describe('returns nil', function() - local testcases = { - { desc = 'a word', version = 'foo' }, - { desc = 'an empty string', version = '' }, - { desc = 'trailing period character', version = '0.0.0.' }, - { desc = 'leading period character', version = '.0.0.0' }, - { desc = 'negative major version', version = '-1.0.0' }, - { desc = 'negative minor version', version = '0.-1.0' }, - { desc = 'negative patch version', version = '0.0.-1' }, - { desc = 'leading invalid string', version = 'foobar1.2.3' }, - { desc = 'trailing invalid string', version = '1.2.3foobar' }, - { desc = 'an invalid prerelease', version = '1.2.3-%?' }, - { desc = 'an invalid build', version = '1.2.3+%?' }, - { desc = 'build metadata before prerelease', version = '1.2.3+build.0-rc1' }, - } - for _, tc in ipairs(testcases) do - it(string.format('for %s: version = %s', tc.desc, tostring(tc.version)), function() - eq(nil, version.parse(tc.version, { strict = true })) - end) - end - end) - - describe('raises error', function() - local testcases = { - { desc = 'no parameters' }, - { desc = 'nil', version = nil }, - { desc = 'a number', version = 0 }, - { desc = 'a float', version = 0.01 }, - { desc = 'a table', version = {} }, - } - for _, tc in ipairs(testcases) do - it(string.format('for %s: version = %s', tc.desc, tostring(tc.version)), function() - matches( - string.format('invalid version: "%s"', tostring(tc.version)), - pcall_err(function() - version.parse(tc.version, { strict = true }) - end) - ) - end) - end - end) - end) - end) - - describe('eq', function() - describe('valid versions', function() + describe('strict=true', function() local testcases = { { - version_1 = '1.0.0', - version_2 = '1.0.0', - want = true, - }, - { - version_1 = '1.0.0', - version_2 = 'v1.0.0', - want = true, - }, - { - version_1 = '1.0.0', - version_2 = '1.0', - want = true, + desc = 'version without leading "v"', + version = '10.20.123', + want = { + major = 10, + minor = 20, + patch = 123, + prerelease = nil, + build = nil, + }, }, { - version_1 = '1.0.0', - version_2 = '1', - want = true, + desc = 'valid version with leading "v"', + version = 'v1.2.3', + want = { major = 1, minor = 2, patch = 3 }, }, { - version_1 = '1.0.0-alpha', - version_2 = '1.0.0-alpha', - want = true, + desc = 'valid version with leading "v" and whitespace', + version = ' v1.2.3', + want = { major = 1, minor = 2, patch = 3 }, }, { - version_1 = '1.0.0-alpha', - version_2 = 'v1.0.0-alpha', - want = true, + desc = 'valid version with leading "v" and trailing whitespace', + version = 'v1.2.3 ', + want = { major = 1, minor = 2, patch = 3 }, }, { - version_1 = '1.0.0-alpha', - version_2 = '1.0.0-alpha+build.5', - want = true, + desc = 'version with prerelease', + version = '1.2.3-alpha', + want = { major = 1, minor = 2, patch = 3, prerelease = 'alpha' }, }, { - version_1 = '1.0.0-alpha.1', - version_2 = '1.0.0-alpha.1+build.5', - want = true, + desc = 'version with prerelease with additional identifiers', + version = '1.2.3-alpha.1', + want = { major = 1, minor = 2, patch = 3, prerelease = 'alpha.1' }, }, { - version_1 = '1.0.0-alpha', - version_2 = '1.0.0-alpha.1', - want = false, + desc = 'version with build', + version = '1.2.3+build.15', + want = { major = 1, minor = 2, patch = 3, build = 'build.15' }, }, { - version_1 = '1.0.0', - version_2 = '2.0.0', - want = false, + desc = 'version with prerelease and build', + version = '1.2.3-rc1+build.15', + want = { + major = 1, + minor = 2, + patch = 3, + prerelease = 'rc1', + build = 'build.15', + }, }, } for _, tc in ipairs(testcases) do - it(string.format('returns %s for %s = %s', tc.want, tc.version_1, tc.version_2), function() - eq(tc.want, version.eq(tc.version_1, tc.version_2)) - end) + it( + string.format('for %q: version = %q', tc.desc, tc.version), + function() + eq(tc.want, version.parse(tc.version, { strict = true })) + end + ) end end) - describe('errors', function() + describe('strict=false', function() local testcases = { { - version_1 = '', - version_2 = '1.0.0', - err_version = '', + desc = 'version missing patch version', + version = '1.2', + want = { major = 1, minor = 2, patch = 0 }, }, { - version_1 = '1.0.0', - version_2 = '', - err_version = '', + desc = 'version missing minor and patch version', + version = '1', + want = { major = 1, minor = 0, patch = 0 }, }, { - version_1 = '', - version_2 = '', - err_version = '', + desc = 'version missing patch version with prerelease', + version = '1.1-0', + want = { major = 1, minor = 1, patch = 0, prerelease = '0' }, }, { - version_1 = '1.0.0', - version_2 = 'foo', - err_version = 'foo', + desc = 'version missing minor and patch version with prerelease', + version = '1-1.0', + want = { major = 1, minor = 0, patch = 0, prerelease = '1.0' }, }, } for _, tc in ipairs(testcases) do - it(string.format('for %s = %s', tc.version_1, tc.version_2), function() - matches( - string.format('invalid version: "%s"', tc.err_version), - pcall_err(function() - version.eq(tc.version_1, tc.version_2) - end) - ) - end) + it( + string.format('for %q: version = %q', tc.desc, tc.version), + function() + eq(tc.want, version.parse(tc.version, { strict = false })) + end + ) end end) - end) - describe('lt', function() - describe('valid versions', function() + describe('invalid semver', function() local testcases = { - { - version_1 = '1.0.0', - version_2 = '1.0.1', - want = true, - }, - { - version_1 = '1.0.0-alpha', - version_2 = '1.0.1', - want = true, - }, - { - version_1 = '1.0.0-alpha', - version_2 = '1.0.1-beta', - want = true, - }, - { - version_1 = '1.0.1', - version_2 = '1.0.0', - want = false, - }, - { - version_1 = '1.0.0-alpha', - version_2 = '1.0.0-alpha', - want = false, - }, - { - version_1 = '1.0.0-alpha', - version_2 = '1.0.0-alpha+build.5', - want = false, - }, - { - version_1 = '1.0.0-alpha+build.4', - version_2 = '1.0.0-alpha+build.5', - want = false, - }, + { desc = 'a word', version = 'foo' }, + { desc = 'empty string', version = '' }, + { desc = 'trailing period character', version = '0.0.0.' }, + { desc = 'leading period character', version = '.0.0.0' }, + { desc = 'negative major version', version = '-1.0.0' }, + { desc = 'negative minor version', version = '0.-1.0' }, + { desc = 'negative patch version', version = '0.0.-1' }, + { desc = 'leading invalid string', version = 'foobar1.2.3' }, + { desc = 'trailing invalid string', version = '1.2.3foobar' }, + { desc = 'an invalid prerelease', version = '1.2.3-%?' }, + { desc = 'an invalid build', version = '1.2.3+%?' }, + { desc = 'build metadata before prerelease', version = '1.2.3+build.0-rc1' }, } for _, tc in ipairs(testcases) do - it( - string.format('returns %s for %s < %s', tostring(tc.want), tc.version_1, tc.version_2), - function() - eq(tc.want, version.lt(tc.version_1, tc.version_2)) - end - ) + it(string.format('(%s): %s', tc.desc, quote_empty(tc.version)), function() + eq(nil, version.parse(tc.version, { strict = true })) + end) end end) - describe('errors', function() + describe('invalid shape', function() local testcases = { - { - version_1 = '', - version_2 = '1.0.0', - err_version = '', - }, - { - version_1 = '1.0.0', - version_2 = '', - err_version = '', - }, - { - version_1 = '', - version_2 = '', - err_version = '', - }, + { desc = 'no parameters' }, + { desc = 'nil', version = nil }, + { desc = 'number', version = 0 }, + { desc = 'float', version = 0.01 }, + { desc = 'table', version = {} }, } for _, tc in ipairs(testcases) do - it(string.format('for %s < %s', tc.version_1, tc.version_2), function() - matches( - string.format('invalid version: "%s"', tc.err_version), - pcall_err(function() - version.lt(tc.version_1, tc.version_2) - end) - ) + it(string.format('(%s): %s', tc.desc, tostring(tc.version)), function() + matches(string.format('invalid version: "%s"', tostring(tc.version)), + pcall_err(version.parse, tc.version, { strict = true })) end) end end) end) - describe('gt', function() - describe('valid versions', function() - local testcases = { - { - version_1 = '1.0.1', - version_2 = '1.0.0', - want = true, - }, - { - version_1 = '1.0.1', - version_2 = '1.0.1-alpha', - want = true, - }, + describe('eq()', function() + local testcases = { + { + v1 = '1.0.0', + v2 = '1.0.0', + want = true, + }, + { + v1 = '1.0.0', + v2 = 'v1.0.0', + want = true, + }, + { + v1 = '1.0.0', + v2 = '1.0', + want = true, + }, + { + v1 = '1.0.0', + v2 = '1', + want = true, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.0-alpha', + want = true, + }, + { + v1 = '1.0.0-alpha', + v2 = 'v1.0.0-alpha', + want = true, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.0-alpha+build.5', + want = true, + }, + { + v1 = '1.0.0-alpha.1', + v2 = '1.0.0-alpha.1+build.5', + want = true, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.0-alpha.1', + want = false, + }, + { + v1 = '1.0.0', + v2 = '2.0.0', + want = false, + }, + } + + for _, tc in ipairs(testcases) do + it(string.format('returns %s for %s = %s', tostring(tc.want), tc.v1, tc.v2), function() + eq(tc.want, version.eq(tc.v1, tc.v2)) + end) + end + + describe('fails', function() + local failtests = { { - version_1 = '1.0.0', - version_2 = '1.0.1', - want = false, + v1 = '', + v2 = '1.0.0', + err_version = '', }, { - version_1 = '1.0.0-alpha', - version_2 = '1.0.1', - want = false, + v1 = '1.0.0', + v2 = '', + err_version = '', }, { - version_1 = '1.0.0-alpha', - version_2 = '1.0.1-beta', - want = false, + v1 = '', + v2 = '', + err_version = '', }, { - version_1 = '1.0.0-alpha', - version_2 = '1.0.0-alpha', - want = false, + v1 = '1.0.0', + v2 = 'foo', + err_version = 'foo', }, + } + for _, tc in ipairs(failtests) do + it(string.format('for %s = %s', quote_empty(tc.v1), quote_empty(tc.v2)), function() + matches(string.format('invalid version: "%s"', tc.err_version), + pcall_err(version.eq, tc.v1, tc.v2)) + end) + end + end) + end) + + describe('lt()', function() + local testcases = { + { + v1 = '1.0.0', + v2 = '1.0.1', + want = true, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.1', + want = true, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.1-beta', + want = true, + }, + { + v1 = '1.0.1', + v2 = '1.0.0', + want = false, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.0-alpha', + want = false, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.0-alpha+build.5', + want = false, + }, + { + v1 = '1.0.0-alpha+build.4', + v2 = '1.0.0-alpha+build.5', + want = false, + }, + } + for _, tc in ipairs(testcases) do + it(string.format('returns %s for %s < %s', tostring(tc.want), tc.v1, tc.v2), function() + eq(tc.want, version.lt(tc.v1, tc.v2)) + end) + end + + describe('fails', function() + local failtests = { { - version_1 = '1.0.0-beta', - version_2 = '1.0.0-alpha', - want = true, + v1 = '', + v2 = '1.0.0', + err_version = '', }, { - version_1 = '1.0.0-alpha', - version_2 = '1.0.0-alpha+build.5', - want = false, + v1 = '1.0.0', + v2 = '', + err_version = '', }, { - version_1 = '1.0.0-alpha+build.4', - version_2 = '1.0.0-alpha+build.5', - want = false, + v1 = '', + v2 = '', + err_version = '', }, } - for _, tc in ipairs(testcases) do - it(string.format('returns %s for %s > %s', tc.want, tc.version_1, tc.version_2), function() - eq(tc.want, version.gt(tc.version_1, tc.version_2)) + for _, tc in ipairs(failtests) do + it(string.format('for %s < %s', quote_empty(tc.v1), quote_empty(tc.v2)), function() + matches(string.format('invalid version: "%s"', tc.err_version), + pcall_err(version.lt, tc.v1, tc.v2)) end) end end) + end) - describe('errors', function() - local testcases = { + describe('gt()', function() + local testcases = { + { + v1 = '1.0.1', + v2 = '1.0.0', + want = true, + }, + { + v1 = '1.0.1', + v2 = '1.0.1-alpha', + want = true, + }, + { + v1 = '1.0.0', + v2 = '1.0.1', + want = false, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.1', + want = false, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.1-beta', + want = false, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.0-alpha', + want = false, + }, + { + v1 = '1.0.0-beta', + v2 = '1.0.0-alpha', + want = true, + }, + { + v1 = '1.0.0-alpha', + v2 = '1.0.0-alpha+build.5', + want = false, + }, + { + v1 = '1.0.0-alpha+build.4', + v2 = '1.0.0-alpha+build.5', + want = false, + }, + } + + for _, tc in ipairs(testcases) do + it(string.format('returns %s for %s > %s', tostring(tc.want), tc.v1, tc.v2), function() + eq(tc.want, version.gt(tc.v1, tc.v2)) + end) + end + + describe('fails', function() + local failtests = { { - version_1 = '', - version_2 = '1.0.0', + v1 = '', + v2 = '1.0.0', err_version = '', }, { - version_1 = '1.0.0', - version_2 = '', + v1 = '1.0.0', + v2 = '', err_version = '', }, { - version_1 = '', - version_2 = '', + v1 = '', + v2 = '', err_version = '', }, } - for _, tc in ipairs(testcases) do - it(string.format('for %s < %s', tc.version_1, tc.version_2), function() - matches( - string.format('invalid version: "%s"', tc.err_version), - pcall_err(function() - version.gt(tc.version_1, tc.version_2) - end) - ) + for _, tc in ipairs(failtests) do + it(string.format('for %s < %s', quote_empty(tc.v1), quote_empty(tc.v2)), function() + matches(string.format('invalid version: "%s"', tc.err_version), + pcall_err(version.gt, tc.v1, tc.v2)) end) end end) |