diff options
-rw-r--r-- | runtime/doc/lua.txt | 21 | ||||
-rw-r--r-- | runtime/lua/vim/shared.lua | 76 | ||||
-rw-r--r-- | test/functional/lua/vim_spec.lua | 20 |
3 files changed, 101 insertions, 16 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 64a3014388..fd0cd3252f 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2366,7 +2366,26 @@ vim.trim({s}) *vim.trim()* • https://www.lua.org/pil/20.2.html vim.validate({opt}) *vim.validate()* - Validates a parameter specification (types and values). Specs are + Validate function arguments. + + This function has two valid forms: + 1. vim.validate(name: str, value: any, type: string, optional?: bool) + 2. vim.validate(spec: table) + + Form 1 validates that argument {name} with value {value} has the type + {type}. {type} must be a value returned by |lua-type()|. If {optional} is + true, then {value} may be null. This form is significantly faster and + should be preferred for simple cases. + + Example: >lua + function vim.startswith(s, prefix) + vim.validate('s', s, 'string') + vim.validate('prefix', prefix, 'string') + ... + end +< + + Form 2 validates a parameter specification (types and values). Specs are evaluated in alphanumeric order, until the first failure. Usage example: >lua diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index e9e4326057..2641d1feb0 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -214,7 +214,7 @@ end ---@param t table<T, any> (table) Table ---@return T[] : List of keys function vim.tbl_keys(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') --- @cast t table<any,any> local keys = {} @@ -231,7 +231,7 @@ end ---@param t table<any, T> (table) Table ---@return T[] : List of values function vim.tbl_values(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') local values = {} for _, v in @@ -332,7 +332,7 @@ end ---@param value any Value to compare ---@return boolean `true` if `t` contains `value` function vim.list_contains(t, value) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') --- @cast t table<any,any> for _, v in ipairs(t) do @@ -350,7 +350,7 @@ end ---@param t table Table to check ---@return boolean `true` if `t` is empty function vim.tbl_isempty(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') return next(t) == nil end @@ -580,7 +580,7 @@ end ---@return fun(table: table<K, V>, index?: K):K, V # |for-in| iterator over sorted keys and their values ---@return T function vim.spairs(t) - assert(type(t) == 'table', ('expected table, got %s'):format(type(t))) + vim.validate('t', t, 'table') --- @cast t table<any,any> -- collect the keys @@ -691,7 +691,7 @@ end ---@param t table Table ---@return integer : Number of non-nil values in table function vim.tbl_count(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') --- @cast t table<any,any> local count = 0 @@ -723,7 +723,7 @@ end ---@param s string String to trim ---@return string String with whitespace removed from its beginning and end function vim.trim(s) - vim.validate({ s = { s, 's' } }) + vim.validate('s', s, 'string') return s:match('^%s*(.*%S)') or '' end @@ -733,7 +733,7 @@ end ---@param s string String to escape ---@return string %-escaped pattern string function vim.pesc(s) - vim.validate({ s = { s, 's' } }) + vim.validate('s', s, 'string') return (s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')) end @@ -743,7 +743,8 @@ end ---@param prefix string Prefix to match ---@return boolean `true` if `prefix` is a prefix of `s` function vim.startswith(s, prefix) - vim.validate({ s = { s, 's' }, prefix = { prefix, 's' } }) + vim.validate('s', s, 'string') + vim.validate('prefix', prefix, 'string') return s:sub(1, #prefix) == prefix end @@ -753,7 +754,8 @@ end ---@param suffix string Suffix to match ---@return boolean `true` if `suffix` is a suffix of `s` function vim.endswith(s, suffix) - vim.validate({ s = { s, 's' }, suffix = { suffix, 's' } }) + vim.validate('s', s, 'string') + vim.validate('suffix', suffix, 'string') return #suffix == 0 or s:sub(-#suffix) == suffix end @@ -877,8 +879,30 @@ do return true end - --- Validates a parameter specification (types and values). Specs are evaluated in alphanumeric - --- order, until the first failure. + --- Validate function arguments. + --- + --- This function has two valid forms: + --- + --- 1. vim.validate(name: str, value: any, type: string, optional?: bool) + --- 2. vim.validate(spec: table) + --- + --- Form 1 validates that argument {name} with value {value} has the type + --- {type}. {type} must be a value returned by |lua-type()|. If {optional} is + --- true, then {value} may be null. This form is significantly faster and + --- should be preferred for simple cases. + --- + --- Example: + --- + --- ```lua + --- function vim.startswith(s, prefix) + --- vim.validate('s', s, 'string') + --- vim.validate('prefix', prefix, 'string') + --- ... + --- end + --- ``` + --- + --- Form 2 validates a parameter specification (types and values). Specs are + --- evaluated in alphanumeric order, until the first failure. --- --- Usage example: --- @@ -930,8 +954,32 @@ do --- only if the argument is valid. Can optionally return an additional --- informative error message as the second returned value. --- - msg: (optional) error string if validation fails - function vim.validate(opt) - local ok, err_msg = is_valid(opt) + --- @overload fun(name: string, val: any, expected: string, optional?: boolean) + function vim.validate(opt, ...) + local ok = false + local err_msg ---@type string? + local narg = select('#', ...) + if narg == 0 then + ok, err_msg = is_valid(opt) + elseif narg >= 2 then + -- Overloaded signature for fast/simple cases + local name = opt --[[@as string]] + local v, expected, optional = ... ---@type string, string, boolean? + local actual = type(v) + + ok = (actual == expected) or (v == nil and optional == true) + if not ok then + err_msg = ('%s: expected %s, got %s%s'):format( + name, + expected, + actual, + v and (' (%s)'):format(v) or '' + ) + end + else + error('invalid arguments') + end + if not ok then error(err_msg, 2) end diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index c8f94c6ffa..d50b646085 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -1408,7 +1408,25 @@ describe('lua stdlib', function() exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}") exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}") exec_lua("vim.validate{arg1={5, {'n', 's'} }, arg2={ 'foo', {'n', 's'} }}") - + vim.validate('arg1', 5, 'number') + vim.validate('arg1', '5', 'string') + vim.validate('arg1', { 5 }, 'table') + vim.validate('arg1', function() + return 5 + end, 'function') + vim.validate('arg1', nil, 'number', true) + vim.validate('arg1', nil, 'string', true) + vim.validate('arg1', nil, 'table', true) + vim.validate('arg1', nil, 'function', true) + + matches('arg1: expected number, got nil', pcall_err(vim.validate, 'arg1', nil, 'number')) + matches('arg1: expected string, got nil', pcall_err(vim.validate, 'arg1', nil, 'string')) + matches('arg1: expected table, got nil', pcall_err(vim.validate, 'arg1', nil, 'table')) + matches('arg1: expected function, got nil', pcall_err(vim.validate, 'arg1', nil, 'function')) + matches('arg1: expected string, got number', pcall_err(vim.validate, 'arg1', 5, 'string')) + matches('arg1: expected table, got number', pcall_err(vim.validate, 'arg1', 5, 'table')) + matches('arg1: expected function, got number', pcall_err(vim.validate, 'arg1', 5, 'function')) + matches('arg1: expected number, got string', pcall_err(vim.validate, 'arg1', '5', 'number')) matches('expected table, got number', pcall_err(exec_lua, "vim.validate{ 1, 'x' }")) matches('invalid type name: x', pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}")) matches('invalid type name: 1', pcall_err(exec_lua, 'vim.validate{ arg1={ 1, 1 }}')) |