diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2023-02-14 08:07:38 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-14 05:07:38 -0800 |
commit | ff3d04b75b4a9314815c37d53ebc4d035a043335 (patch) | |
tree | 435a61efa5c036060b72aa945166e76bee34d7de | |
parent | 3a6a7add57d2ac141f474b54659bbbf596b76059 (diff) | |
download | rneovim-ff3d04b75b4a9314815c37d53ebc4d035a043335.tar.gz rneovim-ff3d04b75b4a9314815c37d53ebc4d035a043335.tar.bz2 rneovim-ff3d04b75b4a9314815c37d53ebc4d035a043335.zip |
refactor(api): VALIDATE macros #22256
- VALIDATE() takes a format string
- deduplicate check_string_array
- VALIDATE_RANGE
- validate UI args
-rw-r--r-- | src/nvim/api/autocmd.c | 74 | ||||
-rw-r--r-- | src/nvim/api/buffer.c | 53 | ||||
-rw-r--r-- | src/nvim/api/command.c | 62 | ||||
-rw-r--r-- | src/nvim/api/extmark.c | 117 | ||||
-rw-r--r-- | src/nvim/api/options.c | 46 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 3 | ||||
-rw-r--r-- | src/nvim/api/private/validate.c | 28 | ||||
-rw-r--r-- | src/nvim/api/private/validate.h | 27 | ||||
-rw-r--r-- | src/nvim/api/ui.c | 66 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 59 | ||||
-rw-r--r-- | src/nvim/highlight.c | 60 | ||||
-rw-r--r-- | test/functional/api/autocmd_spec.lua | 104 | ||||
-rw-r--r-- | test/functional/api/buffer_spec.lua | 20 | ||||
-rw-r--r-- | test/functional/api/extmark_spec.lua | 6 | ||||
-rw-r--r-- | test/functional/api/highlight_spec.lua | 67 | ||||
-rw-r--r-- | test/functional/api/proc_spec.lua | 4 | ||||
-rw-r--r-- | test/functional/api/ui_spec.lua | 26 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 10 | ||||
-rw-r--r-- | test/functional/lua/api_spec.lua | 2 |
19 files changed, 426 insertions, 408 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 9aadd48a13..36d3e04f54 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -125,7 +125,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) }); } - if (opts->event.type != kObjectTypeNil) { + if (HAS_KEY(opts->event)) { check_event = true; Object v = opts->event; @@ -148,13 +148,13 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) } } - VALIDATE((opts->pattern.type == kObjectTypeNil || opts->buffer.type == kObjectTypeNil), - "Cannot use both 'pattern' and 'buffer'", { + VALIDATE((!HAS_KEY(opts->pattern) || !HAS_KEY(opts->buffer)), + "%s", "Cannot use both 'pattern' and 'buffer'", { goto cleanup; }); int pattern_filter_count = 0; - if (opts->pattern.type != kObjectTypeNil) { + if (HAS_KEY(opts->pattern)) { Object v = opts->pattern; if (v.type == kObjectTypeString) { pattern_filters[pattern_filter_count] = v.data.string.data; @@ -210,7 +210,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) snprintf(pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle); ADD(buffers, CSTR_TO_OBJ(pattern_buflocal)); }); - } else if (opts->buffer.type != kObjectTypeNil) { + } else if (HAS_KEY(opts->buffer)) { VALIDATE_EXP(false, "buffer", "Integer or Array", api_typename(opts->buffer.type), { goto cleanup; }); @@ -413,10 +413,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc { int64_t autocmd_id = -1; char *desc = NULL; - Array patterns = ARRAY_DICT_INIT; Array event_array = ARRAY_DICT_INIT; - AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT; Callback cb = CALLBACK_NONE; @@ -424,12 +422,12 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc goto cleanup; } - VALIDATE((opts->callback.type == kObjectTypeNil || opts->command.type == kObjectTypeNil), - "Cannot use both 'callback' and 'command'", { + VALIDATE((!HAS_KEY(opts->callback) || !HAS_KEY(opts->command)), + "%s", "Cannot use both 'callback' and 'command'", { goto cleanup; }); - if (opts->callback.type != kObjectTypeNil) { + if (HAS_KEY(opts->callback)) { // NOTE: We could accept callable tables, but that isn't common in the API. Object *callback = &opts->callback; @@ -458,7 +456,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc aucmd.type = CALLABLE_CB; aucmd.callable.cb = cb; - } else if (opts->command.type != kObjectTypeNil) { + } else if (HAS_KEY(opts->command)) { Object *command = &opts->command; VALIDATE_T("command", kObjectTypeString, command->type, { goto cleanup; @@ -466,7 +464,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc aucmd.type = CALLABLE_EX; aucmd.callable.cmd = string_to_cstr(command->data.string); } else { - VALIDATE_S(false, "'command' or 'callback' is required", "", { + VALIDATE(false, "%s", "Required: 'command' or 'callback'", { goto cleanup; }); } @@ -483,7 +481,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc goto cleanup; } - if (opts->desc.type != kObjectTypeNil) { + if (HAS_KEY(opts->desc)) { VALIDATE_T("desc", kObjectTypeString, opts->desc.type, { goto cleanup; }); @@ -494,7 +492,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*"))); } - VALIDATE_R((event_array.size != 0), "event", { + VALIDATE_R((event_array.size > 0), "event", { goto cleanup; }); @@ -586,8 +584,8 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err) goto cleanup; } - VALIDATE((opts->pattern.type == kObjectTypeNil || opts->buffer.type == kObjectTypeNil), - "Cannot use both 'pattern' and 'buffer'", { + VALIDATE((!HAS_KEY(opts->pattern) || !HAS_KEY(opts->buffer)), + "%s", "Cannot use both 'pattern' and 'buffer'", { goto cleanup; }); @@ -764,7 +762,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) }); } - if (opts->buffer.type != kObjectTypeNil) { + if (HAS_KEY(opts->buffer)) { Object buf_obj = opts->buffer; VALIDATE_EXP((buf_obj.type == kObjectTypeInteger || buf_obj.type == kObjectTypeBuffer), "buffer", "Integer", api_typename(buf_obj.type), { @@ -786,7 +784,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING(""))); } - if (opts->data.type != kObjectTypeNil) { + if (HAS_KEY(opts->data)) { data = &opts->data; } @@ -797,7 +795,7 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) GET_ONE_EVENT(event_nr, event_str, cleanup) FOREACH_ITEM(patterns, pat, { - char *fname = opts->buffer.type == kObjectTypeNil ? pat.data.string.data : NULL; + char *fname = !HAS_KEY(opts->buffer) ? pat.data.string.data : NULL; did_aucmd |= apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data); }) @@ -812,41 +810,19 @@ cleanup: api_free_array(patterns); } -static bool check_autocmd_string_array(Array arr, char *k, Error *err) -{ - FOREACH_ITEM(arr, entry, { - if (entry.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "Invalid '%s' item type: expected String, got %s", - k, api_typename(entry.type)); - return false; - } - - // Disallow newlines in the middle of the line. - const String l = entry.data.string; - if (memchr(l.data, NL, l.size)) { - api_set_error(err, kErrorTypeValidation, "'%s' item cannot contain newlines", k); - return false; - } - }) - return true; -} - static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err) { if (v->type == kObjectTypeString) { ADD(*array, copy_object(*v, NULL)); } else if (v->type == kObjectTypeArray) { - if (!check_autocmd_string_array(v->data.array, k, err)) { + if (!check_string_array(v->data.array, k, true, err)) { return false; } *array = copy_array(v->data.array, NULL); } else { - if (required) { - api_set_error(err, kErrorTypeValidation, - "Invalid '%s' type: expected Array or String, got %s", - k, api_typename(v->type)); + VALIDATE_EXP(!required, k, "Array or String", api_typename(v->type), { return false; - } + }); } return true; @@ -886,12 +862,12 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob { const char pattern_buflocal[BUFLOCAL_PAT_LEN]; - VALIDATE((pattern.type == kObjectTypeNil || buffer.type == kObjectTypeNil), - "Cannot use both 'pattern' and 'buffer' for the same autocmd", { + VALIDATE((!HAS_KEY(pattern) || !HAS_KEY(buffer)), + "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", { return false; }); - if (pattern.type != kObjectTypeNil) { + if (HAS_KEY(pattern)) { Object *v = &pattern; if (v->type == kObjectTypeString) { @@ -904,7 +880,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob patlen = aucmd_pattern_length(pat); } } else if (v->type == kObjectTypeArray) { - if (!check_autocmd_string_array(v->data.array, "pattern", err)) { + if (!check_string_array(v->data.array, "pattern", true, err)) { return false; } @@ -924,7 +900,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob return false; }); } - } else if (buffer.type != kObjectTypeNil) { + } else if (HAS_KEY(buffer)) { VALIDATE_EXP((buffer.type == kObjectTypeInteger || buffer.type == kObjectTypeBuffer), "buffer", "Integer", api_typename(buffer.type), { return false; diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index b2e78db278..9c2d14d30f 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -293,7 +293,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, start = normalize_index(buf, start, true, &oob); end = normalize_index(buf, end, true, &oob); - VALIDATE((!strict_indexing || !oob), "Index out of bounds", { + VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", { return rv; }); @@ -320,23 +320,6 @@ end: return rv; } -static bool check_string_array(Array arr, bool disallow_nl, Error *err) -{ - for (size_t i = 0; i < arr.size; i++) { - VALIDATE_T("replacement item", kObjectTypeString, arr.items[i].type, { - return false; - }); - // Disallow newlines in the middle of the line. - if (disallow_nl) { - const String l = arr.items[i].data.string; - VALIDATE(!memchr(l.data, NL, l.size), "String cannot contain newlines", { - return false; - }); - } - } - return true; -} - /// Sets (replaces) a line-range in the buffer. /// /// Indexing is zero-based, end-exclusive. Negative indices are interpreted @@ -373,15 +356,15 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ start = normalize_index(buf, start, true, &oob); end = normalize_index(buf, end, true, &oob); - VALIDATE((!strict_indexing || !oob), "Index out of bounds", { + VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", { return; }); - VALIDATE((start <= end), "\"start\" is higher than \"end\"", { + VALIDATE((start <= end), "%s", "'start' is higher than 'end'", { return; }); bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); - if (!check_string_array(replacement, disallow_nl, err)) { + if (!check_string_array(replacement, "replacement string", disallow_nl, err)) { return; } @@ -438,7 +421,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ for (size_t i = 0; i < to_replace; i++) { int64_t lnum = start + (int64_t)i; - VALIDATE(lnum < MAXLNUM, "Index value is too high", { + VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { goto end; }); @@ -457,7 +440,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ for (size_t i = to_replace; i < new_len; i++) { int64_t lnum = start + (int64_t)i - 1; - VALIDATE(lnum < MAXLNUM, "Index value is too high", { + VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { goto end; }); @@ -546,12 +529,12 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // check range is ordered and everything! // start_row, end_row within buffer len (except add text past the end?) start_row = normalize_index(buf, start_row, false, &oob); - VALIDATE((!oob), "start_row out of bounds", { + VALIDATE_RANGE((!oob), "start_row", { return; }); end_row = normalize_index(buf, end_row, false, &oob); - VALIDATE((!oob), "end_row out of bounds", { + VALIDATE_RANGE((!oob), "end_row", { return; }); @@ -561,24 +544,24 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // Another call to ml_get_buf() may free the line, so make a copy. str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false)); size_t len_at_start = strlen(str_at_start); - VALIDATE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col out of bounds", { + VALIDATE_RANGE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col", { goto early_end; }); // Another call to ml_get_buf() may free the line, so make a copy. str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false)); size_t len_at_end = strlen(str_at_end); - VALIDATE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col out of bounds", { + VALIDATE_RANGE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col", { goto early_end; }); VALIDATE((start_row <= end_row && !(end_row == start_row && start_col > end_col)), - "start is higher than end", { + "%s", "'start' is higher than 'end'", { goto early_end; }); bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); - if (!check_string_array(replacement, disallow_nl, err)) { + if (!check_string_array(replacement, "replacement string", disallow_nl, err)) { goto early_end; } @@ -681,7 +664,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In for (size_t i = 0; i < to_replace; i++) { int64_t lnum = start_row + (int64_t)i; - VALIDATE((lnum < MAXLNUM), "Index value is too high", { + VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { goto end; }); @@ -698,7 +681,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In for (size_t i = to_replace; i < new_len; i++) { int64_t lnum = start_row + (int64_t)i - 1; - VALIDATE((lnum < MAXLNUM), "Index value is too high", { + VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { goto end; }); @@ -777,7 +760,7 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, { Array rv = ARRAY_DICT_INIT; - VALIDATE((opts.size == 0), "opts dict isn't empty", { + VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", { return rv; }); @@ -796,14 +779,14 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, start_row = normalize_index(buf, start_row, false, &oob); end_row = normalize_index(buf, end_row, false, &oob); - VALIDATE((!oob), "Index out of bounds", { + VALIDATE((!oob), "%s", "Index out of bounds", { return rv; }); // nvim_buf_get_lines doesn't care if the start row is greater than the end // row (it will just return an empty array), but nvim_buf_get_text does in // order to maintain symmetry with nvim_buf_set_text. - VALIDATE((start_row <= end_row), "start is higher than end", { + VALIDATE((start_row <= end_row), "%s", "'start' is higher than 'end'", { return rv; }); @@ -881,7 +864,7 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err) return -1; } - VALIDATE((index >= 0 && index <= buf->b_ml.ml_line_count), "Index out of bounds", { + VALIDATE((index >= 0 && index <= buf->b_ml.ml_line_count), "%s", "Index out of bounds", { return 0; }); diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 0a95833256..cae3927dfd 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -100,7 +100,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) { Dictionary result = ARRAY_DICT_INIT; - VALIDATE((opts.size == 0), "opts dict isn't empty", { + VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", { return result; }); @@ -1021,7 +1021,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, name.data, { goto err; }); - VALIDATE((!HAS_KEY(opts->range) || !HAS_KEY(opts->count)), + VALIDATE((!HAS_KEY(opts->range) || !HAS_KEY(opts->count)), "%s", "Cannot use both 'range' and 'count'", { goto err; }); @@ -1065,7 +1065,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, }); } - VALIDATE((!HAS_KEY(opts->complete) || argt), "'complete' used without 'nargs'", { + VALIDATE((!HAS_KEY(opts->complete) || argt), "%s", "'complete' used without 'nargs'", { goto err; }); @@ -1086,8 +1086,9 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, def = opts->range.data.integer; addr_type_arg = ADDR_LINES; } else if (HAS_KEY(opts->range)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'"); - goto err; + VALIDATE_S(false, "range", "", { + goto err; + }); } if (opts->count.type == kObjectTypeBoolean) { @@ -1101,23 +1102,25 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, addr_type_arg = ADDR_OTHER; def = opts->count.data.integer; } else if (HAS_KEY(opts->count)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'"); - goto err; + VALIDATE_S(false, "count", "", { + goto err; + }); } - if (opts->addr.type == kObjectTypeString) { - if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size, - &addr_type_arg) != OK) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); + if (HAS_KEY(opts->addr)) { + VALIDATE_T("addr", kObjectTypeString, opts->addr.type, { goto err; - } + }); + + VALIDATE_S(OK == parse_addr_type_arg(opts->addr.data.string.data, + (int)opts->addr.data.string.size, &addr_type_arg), "addr", + opts->addr.data.string.data, { + goto err; + }); if (addr_type_arg != ADDR_LINES) { argt |= EX_ZEROR; } - } else if (HAS_KEY(opts->addr)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); - goto err; } if (api_object_to_bool(opts->bang, "bang", false, err)) { @@ -1153,23 +1156,25 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, compl = EXPAND_USER_LUA; compl_luaref = api_new_luaref(opts->complete.data.luaref); } else if (opts->complete.type == kObjectTypeString) { - if (parse_compl_arg(opts->complete.data.string.data, - (int)opts->complete.data.string.size, &compl, &argt, - &compl_arg) != OK) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); + VALIDATE_S(OK == parse_compl_arg(opts->complete.data.string.data, + (int)opts->complete.data.string.size, &compl, &argt, + &compl_arg), + "complete", opts->complete.data.string.data, { goto err; - } + }); } else if (HAS_KEY(opts->complete)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); - goto err; + VALIDATE(false, "%s", "Invalid complete: expected Function or String", { + goto err; + }); } - if (opts->preview.type == kObjectTypeLuaRef) { + if (HAS_KEY(opts->preview)) { + VALIDATE_T("preview", kObjectTypeLuaRef, opts->preview.type, { + goto err; + }); + argt |= EX_PREVIEW; preview_luaref = api_new_luaref(opts->preview.data.luaref); - } else if (HAS_KEY(opts->preview)) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'"); - goto err; } switch (command.type) { @@ -1185,8 +1190,9 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, rep = command.data.string.data; break; default: - api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function"); - goto err; + VALIDATE(false, "%s", "Invalid command: expected Function or String", { + goto err; + }); } if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref, diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 71ba9cfd4c..15f759eddf 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -520,7 +520,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer // For backward compatibility we support "end_line" as an alias for "end_row" if (HAS_KEY(opts->end_line)) { - VALIDATE(!HAS_KEY(opts->end_row), "cannot use both end_row and end_line", { + VALIDATE(!HAS_KEY(opts->end_row), "%s", "cannot use both 'end_row' and 'end_line'", { goto error; }); opts->end_row = opts->end_line; @@ -535,30 +535,29 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool strict = true; OPTION_TO_BOOL(strict, strict, true); - if (opts->end_row.type == kObjectTypeInteger) { - Integer val = opts->end_row.data.integer; - VALIDATE((val >= 0 && !(val > buf->b_ml.ml_line_count && strict)), - "end_row value outside range", { + if (HAS_KEY(opts->end_row)) { + VALIDATE_T("end_row", kObjectTypeInteger, opts->end_row.type, { goto error; }); - line2 = (int)val; - } else if (HAS_KEY(opts->end_row)) { - VALIDATE_T("end_row", kObjectTypeInteger, opts->end_row.type, { + + Integer val = opts->end_row.data.integer; + VALIDATE_RANGE((val >= 0 && !(val > buf->b_ml.ml_line_count && strict)), "end_row", { goto error; }); + line2 = (int)val; } colnr_T col2 = -1; - if (opts->end_col.type == kObjectTypeInteger) { - Integer val = opts->end_col.data.integer; - VALIDATE((val >= 0 && val <= MAXCOL), "end_col value outside range", { + if (HAS_KEY(opts->end_col)) { + VALIDATE_T("end_col", kObjectTypeInteger, opts->end_col.type, { goto error; }); - col2 = (int)val; - } else if (HAS_KEY(opts->end_col)) { - VALIDATE_T("end_col", kObjectTypeInteger, opts->end_col.type, { + + Integer val = opts->end_col.data.integer; + VALIDATE_RANGE((val >= 0 && val <= MAXCOL), "end_col", { goto error; }); + col2 = (int)val; } // uncrustify:off @@ -588,33 +587,37 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } } - if (opts->conceal.type == kObjectTypeString) { + if (HAS_KEY(opts->conceal)) { + VALIDATE_T("conceal", kObjectTypeString, opts->conceal.type, { + goto error; + }); + String c = opts->conceal.data.string; decor.conceal = true; if (c.size) { decor.conceal_char = utf_ptr2char(c.data); } has_decor = true; - } else if (HAS_KEY(opts->conceal)) { - VALIDATE_T("conceal", kObjectTypeString, opts->conceal.type, { + } + + if (HAS_KEY(opts->virt_text)) { + VALIDATE_T("virt_text", kObjectTypeArray, opts->virt_text.type, { goto error; }); - } - if (opts->virt_text.type == kObjectTypeArray) { decor.virt_text = parse_virt_text(opts->virt_text.data.array, err, &decor.virt_text_width); has_decor = true; if (ERROR_SET(err)) { goto error; } - } else if (HAS_KEY(opts->virt_text)) { - VALIDATE_T("virt_text", kObjectTypeArray, opts->virt_text.type, { + } + + if (HAS_KEY(opts->virt_text_pos)) { + VALIDATE_T("virt_text_pos", kObjectTypeString, opts->virt_text_pos.type, { goto error; }); - } - if (opts->virt_text_pos.type == kObjectTypeString) { String str = opts->virt_text_pos.data.string; if (strequal("eol", str.data)) { decor.virt_text_pos = kVTEndOfLine; @@ -627,19 +630,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; }); } - } else if (HAS_KEY(opts->virt_text_pos)) { - VALIDATE_T("virt_text_pos", kObjectTypeString, opts->virt_text_pos.type, { - goto error; - }); } - if (opts->virt_text_win_col.type == kObjectTypeInteger) { - decor.col = (int)opts->virt_text_win_col.data.integer; - decor.virt_text_pos = kVTWinCol; - } else if (HAS_KEY(opts->virt_text_win_col)) { + if (HAS_KEY(opts->virt_text_win_col)) { VALIDATE_T("virt_text_win_col", kObjectTypeInteger, opts->virt_text_win_col.type, { goto error; }); + + decor.col = (int)opts->virt_text_win_col.data.integer; + decor.virt_text_pos = kVTWinCol; } OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false); @@ -667,7 +666,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool virt_lines_leftcol = false; OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false); - if (opts->virt_lines.type == kObjectTypeArray) { + if (HAS_KEY(opts->virt_lines)) { + VALIDATE_T("virt_lines", kObjectTypeArray, opts->virt_lines.type, { + goto error; + }); + Array a = opts->virt_lines.data.array; for (size_t j = 0; j < a.size; j++) { VALIDATE_T("virt_text_line", kObjectTypeArray, a.items[j].type, { @@ -681,37 +684,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } has_decor = true; } - } else if (HAS_KEY(opts->virt_lines)) { - VALIDATE_T("virt_lines", kObjectTypeArray, opts->virt_lines.type, { - goto error; - }); } OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false); - if (opts->priority.type == kObjectTypeInteger) { + if (HAS_KEY(opts->priority)) { + VALIDATE_T("priority", kObjectTypeInteger, opts->priority.type, { + goto error; + }); + Integer val = opts->priority.data.integer; - VALIDATE_S((val >= 0 && val <= UINT16_MAX), "priority", "(out of range)", { + VALIDATE_RANGE((val >= 0 && val <= UINT16_MAX), "priority", { goto error; }); decor.priority = (DecorPriority)val; - } else if (HAS_KEY(opts->priority)) { - VALIDATE_T("priority", kObjectTypeInteger, opts->priority.type, { + } + + if (HAS_KEY(opts->sign_text)) { + VALIDATE_T("sign_text", kObjectTypeString, opts->sign_text.type, { goto error; }); - } - if (opts->sign_text.type == kObjectTypeString) { VALIDATE_S(init_sign_text(&decor.sign_text, opts->sign_text.data.string.data), "sign_text", "", { goto error; }); has_decor = true; - } else if (HAS_KEY(opts->sign_text)) { - VALIDATE_T("sign_text", kObjectTypeString, opts->sign_text.type, { - goto error; - }); } bool right_gravity = true; @@ -720,7 +719,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer // Only error out if they try to set end_right_gravity without // setting end_col or end_row VALIDATE(!(line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)), - "cannot set end_right_gravity without setting end_row or end_col", { + "%s", "cannot set end_right_gravity without end_row or end_col", { goto error; }); @@ -732,7 +731,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool ephemeral = false; OPTION_TO_BOOL(ephemeral, ephemeral, false); - if (opts->spell.type == kObjectTypeNil) { + if (!HAS_KEY(opts->spell)) { decor.spell = kNone; } else { bool spell = false; @@ -746,12 +745,12 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer has_decor = true; } - VALIDATE_S((line >= 0), "line", "(out of range)", { + VALIDATE_RANGE((line >= 0), "line", { goto error; }); if (line > buf->b_ml.ml_line_count) { - VALIDATE_S(!strict, "line", "(out of range)", { + VALIDATE_RANGE(!strict, "line", { goto error; }); line = buf->b_ml.ml_line_count; @@ -762,12 +761,12 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer if (col == -1) { col = (Integer)len; } else if (col > (Integer)len) { - VALIDATE_S(!strict, "col", "(out of range)", { + VALIDATE_RANGE(!strict, "col", { goto error; }); col = (Integer)len; } else if (col < -1) { - VALIDATE_S(false, "col", "(out of range)", { + VALIDATE_RANGE(false, "col", { goto error; }); } @@ -783,7 +782,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer line2 = (int)line; } if (col2 > (Integer)len) { - VALIDATE_S(!strict, "end_col", "(out of range)", { + VALIDATE_RANGE(!strict, "end_col", { goto error; }); col2 = (int)len; @@ -886,10 +885,10 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In return 0; } - VALIDATE_S((line >= 0 && line < MAXLNUM), "line number", "(out of range)", { + VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", { return 0; }); - VALIDATE_S((col_start >= 0 && col_start <= MAXCOL), "column", "(out of range)", { + VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", { return 0; }); @@ -948,7 +947,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, return; } - VALIDATE_S((line_start >= 0 && line_start < MAXLNUM), "line number", "(out of range)", { + VALIDATE_RANGE((line_start >= 0 && line_start < MAXLNUM), "line number", { return; }); @@ -1092,7 +1091,7 @@ static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, in VALIDATE((pos.size == 2 && pos.items[0].type == kObjectTypeInteger && pos.items[1].type == kObjectTypeInteger), - "Invalid position: expected 2 Integer items", { + "%s", "Invalid position: expected 2 Integer items", { return false; }); @@ -1102,7 +1101,7 @@ static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, in *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL); return true; } else { - VALIDATE(false, "Invalid position: expected mark id Integer or 2-item Array", { + VALIDATE(false, "%s", "Invalid position: expected mark id Integer or 2-item Array", { return false; }); } @@ -1153,7 +1152,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width) }); Array chunk = chunks.items[i].data.array; VALIDATE((chunk.size > 0 && chunk.size <= 2 && chunk.items[0].type == kObjectTypeString), - "Invalid chunk: expected Array with 1 or 2 Strings", { + "%s", "Invalid chunk: expected Array with 1 or 2 Strings", { goto free_exit; }); diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 5b57b49ad6..40720c31c7 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -26,54 +26,54 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_type, void **from, Error *err) { - if (opts->scope.type == kObjectTypeString) { + if (HAS_KEY(opts->scope)) { + VALIDATE_T("scope", kObjectTypeString, opts->scope.type, { + return FAIL; + }); + if (!strcmp(opts->scope.data.string.data, "local")) { *scope = OPT_LOCAL; } else if (!strcmp(opts->scope.data.string.data, "global")) { *scope = OPT_GLOBAL; } else { - VALIDATE(false, "Invalid scope (expected 'local' or 'global')", { + VALIDATE(false, "%s", "Invalid scope: expected 'local' or 'global'", { return FAIL; }); } - } else if (HAS_KEY(opts->scope)) { - VALIDATE_T("scope", kObjectTypeString, opts->scope.type, { - return FAIL; - }); - return FAIL; } *opt_type = SREQ_GLOBAL; - if (opts->win.type == kObjectTypeInteger) { + if (HAS_KEY(opts->win)) { + VALIDATE_T("win", kObjectTypeInteger, opts->win.type, { + return FAIL; + }); + *opt_type = SREQ_WIN; *from = find_window_by_handle((int)opts->win.data.integer, err); if (ERROR_SET(err)) { return FAIL; } - } else if (HAS_KEY(opts->win)) { - VALIDATE_T("win", kObjectTypeInteger, opts->win.type, { + } + + if (HAS_KEY(opts->buf)) { + VALIDATE_T("buf", kObjectTypeInteger, opts->buf.type, { return FAIL; }); - } - if (opts->buf.type == kObjectTypeInteger) { *scope = OPT_LOCAL; *opt_type = SREQ_BUF; *from = find_buffer_by_handle((int)opts->buf.data.integer, err); if (ERROR_SET(err)) { return FAIL; } - } else if (HAS_KEY(opts->buf)) { - VALIDATE_T("buf", kObjectTypeInteger, opts->buf.type, { - return FAIL; - }); } - VALIDATE((!HAS_KEY(opts->scope) || !HAS_KEY(opts->buf)), "cannot use both 'scope' and 'buf'", { + VALIDATE((!HAS_KEY(opts->scope) || !HAS_KEY(opts->buf)), "%s", + "cannot use both 'scope' and 'buf'", { return FAIL; }); - VALIDATE((!HAS_KEY(opts->win) || !HAS_KEY(opts->buf)), "cannot use both 'buf' and 'win'", { + VALIDATE((!HAS_KEY(opts->win) || !HAS_KEY(opts->buf)), "%s", "cannot use both 'buf' and 'win'", { return FAIL; }); @@ -427,21 +427,21 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object char *stringval = NULL; if (flags & SOPT_BOOL) { - VALIDATE_FMT(value.type == kObjectTypeBoolean, "Option '%s' value must be Boolean", name.data, { + VALIDATE(value.type == kObjectTypeBoolean, "Option '%s' value must be Boolean", name.data, { return; }); numval = value.data.boolean; } else if (flags & SOPT_NUM) { - VALIDATE_FMT(value.type == kObjectTypeInteger, "Option '%s' value must be Integer", name.data, { + VALIDATE(value.type == kObjectTypeInteger, "Option '%s' value must be Integer", name.data, { return; }); - VALIDATE_FMT((value.data.integer <= INT_MAX && value.data.integer >= INT_MIN), - "Option '%s' value is out of range", name.data, { + VALIDATE((value.data.integer <= INT_MAX && value.data.integer >= INT_MIN), + "Option '%s' value is out of range", name.data, { return; }); numval = (int)value.data.integer; } else { - VALIDATE_FMT(value.type == kObjectTypeString, "Option '%s' value must be String", name.data, { + VALIDATE(value.type == kObjectTypeString, "Option '%s' value must be String", name.data, { return; }); stringval = value.data.string.data; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 6beb3d8683..c996e19eb9 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -821,8 +821,7 @@ int object_to_hl_id(Object obj, const char *what, Error *err) } else if (obj.type == kObjectTypeInteger) { return MAX((int)obj.data.integer, 0); } else { - api_set_error(err, kErrorTypeValidation, - "%s is not a valid highlight", what); + api_set_error(err, kErrorTypeValidation, "Invalid highlight: %s", what); return 0; } } diff --git a/src/nvim/api/private/validate.c b/src/nvim/api/private/validate.c new file mode 100644 index 0000000000..41c9472a39 --- /dev/null +++ b/src/nvim/api/private/validate.c @@ -0,0 +1,28 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/private/validate.c.generated.h" +#endif + +bool check_string_array(Array arr, char *name, bool disallow_nl, Error *err) +{ + snprintf(IObuff, sizeof(IObuff), "'%s' item", name); + for (size_t i = 0; i < arr.size; i++) { + VALIDATE_T(IObuff, kObjectTypeString, arr.items[i].type, { + return false; + }); + // Disallow newlines in the middle of the line. + if (disallow_nl) { + const String l = arr.items[i].data.string; + VALIDATE(!memchr(l.data, NL, l.size), "'%s' item contains newlines", name, { + return false; + }); + } + } + return true; +} diff --git a/src/nvim/api/private/validate.h b/src/nvim/api/private/validate.h index 8448b416be..4a1b99408e 100644 --- a/src/nvim/api/private/validate.h +++ b/src/nvim/api/private/validate.h @@ -24,19 +24,11 @@ } \ } while (0) -#define VALIDATE_R(cond, name, code) \ - do { \ - if (!(cond)) { \ - api_set_error(err, kErrorTypeValidation, "'" name "' is required"); \ - code; \ - } \ - } while (0) - #define VALIDATE_EXP(cond, name, expected, actual, code) \ do { \ if (!(cond)) { \ - api_set_error(err, kErrorTypeValidation, "Invalid " name ": expected %s, got %s", \ - expected, actual); \ + api_set_error(err, kErrorTypeValidation, "Invalid %s: expected %s, got %s", \ + name, expected, actual); \ code; \ } \ } while (0) @@ -50,20 +42,27 @@ } \ } while (0) -#define VALIDATE(cond, msg_, code) \ +#define VALIDATE(cond, fmt_, fmt_arg1, code) \ do { \ if (!(cond)) { \ - api_set_error(err, kErrorTypeValidation, "%s", msg_); \ + api_set_error(err, kErrorTypeValidation, fmt_, fmt_arg1); \ code; \ } \ } while (0) -#define VALIDATE_FMT(cond, fmt_, msg_, code) \ +#define VALIDATE_RANGE(cond, name, code) \ do { \ if (!(cond)) { \ - api_set_error(err, kErrorTypeValidation, fmt_, msg_); \ + api_set_error(err, kErrorTypeValidation, "Invalid '%s': out of range", name); \ code; \ } \ } while (0) +#define VALIDATE_R(cond, name, code) \ + VALIDATE(cond, "Required: '%s'", name, code); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/private/validate.h.generated.h" +#endif + #endif // NVIM_API_PRIVATE_VALIDATE_H diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 8f5465db77..61f2c881b3 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -12,6 +12,7 @@ #include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" #include "nvim/api/ui.h" #include "nvim/autocmd.h" #include "nvim/channel.h" @@ -291,22 +292,20 @@ void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *e ui_set_option(ui, false, name, value, error); } -static void ui_set_option(UI *ui, bool init, String name, Object value, Error *error) +static void ui_set_option(UI *ui, bool init, String name, Object value, Error *err) { if (strequal(name.data, "override")) { - if (value.type != kObjectTypeBoolean) { - api_set_error(error, kErrorTypeValidation, "override must be a Boolean"); + VALIDATE_T("override", kObjectTypeBoolean, value.type, { return; - } + }); ui->override = value.data.boolean; return; } if (strequal(name.data, "rgb")) { - if (value.type != kObjectTypeBoolean) { - api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean"); + VALIDATE_T("rgb", kObjectTypeBoolean, value.type, { return; - } + }); ui->rgb = value.data.boolean; // A little drastic, but only takes effect for legacy uis. For linegrid UI // only changes metadata for nvim_list_uis(), no refresh needed. @@ -317,62 +316,56 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e } if (strequal(name.data, "term_name")) { - if (value.type != kObjectTypeString) { - api_set_error(error, kErrorTypeValidation, "term_name must be a String"); + VALIDATE_T("term_name", kObjectTypeString, value.type, { return; - } + }); set_tty_option("term", string_to_cstr(value.data.string)); return; } if (strequal(name.data, "term_colors")) { - if (value.type != kObjectTypeInteger) { - api_set_error(error, kErrorTypeValidation, "term_colors must be a Integer"); + VALIDATE_T("term_colors", kObjectTypeInteger, value.type, { return; - } + }); t_colors = (int)value.data.integer; return; } if (strequal(name.data, "term_background")) { - if (value.type != kObjectTypeString) { - api_set_error(error, kErrorTypeValidation, "term_background must be a String"); + VALIDATE_T("term_background", kObjectTypeString, value.type, { return; - } + }); set_tty_background(value.data.string.data); return; } if (strequal(name.data, "stdin_fd")) { - if (value.type != kObjectTypeInteger || value.data.integer < 0) { - api_set_error(error, kErrorTypeValidation, "stdin_fd must be a non-negative Integer"); + VALIDATE_T("stdin_fd", kObjectTypeInteger, value.type, { return; - } - - if (starting != NO_SCREEN) { - api_set_error(error, kErrorTypeValidation, - "stdin_fd can only be used with first attached ui"); + }); + VALIDATE_INT((value.data.integer >= 0), "stdin_fd", value.data.integer, { return; - } + }); + VALIDATE((starting == NO_SCREEN), "%s", "stdin_fd can only be used with first attached UI", { + return; + }); stdin_fd = (int)value.data.integer; return; } if (strequal(name.data, "stdin_tty")) { - if (value.type != kObjectTypeBoolean) { - api_set_error(error, kErrorTypeValidation, "stdin_tty must be a Boolean"); + VALIDATE_T("stdin_tty", kObjectTypeBoolean, value.type, { return; - } + }); stdin_isatty = value.data.boolean; return; } if (strequal(name.data, "stdout_tty")) { - if (value.type != kObjectTypeBoolean) { - api_set_error(error, kErrorTypeValidation, "stdout_tty must be a Boolean"); + VALIDATE_T("stdout_tty", kObjectTypeBoolean, value.type, { return; - } + }); stdout_isatty = value.data.boolean; return; } @@ -383,17 +376,15 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e for (UIExtension i = 0; i < kUIExtCount; i++) { if (strequal(name.data, ui_ext_names[i]) || (i == kUIPopupmenu && is_popupmenu)) { - if (value.type != kObjectTypeBoolean) { - api_set_error(error, kErrorTypeValidation, "%s must be a Boolean", - name.data); + VALIDATE_EXP((value.type == kObjectTypeBoolean), name.data, "Boolean", + api_typename(value.type), { return; - } + }); bool boolval = value.data.boolean; if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) { // There shouldn't be a reason for an UI to do this ever // so explicitly don't support this. - api_set_error(error, kErrorTypeValidation, - "ext_linegrid option cannot be changed"); + api_set_error(err, kErrorTypeValidation, "ext_linegrid option cannot be changed"); } ui->ui_ext[i] = boolval; if (!init) { @@ -403,8 +394,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e } } - api_set_error(error, kErrorTypeValidation, "No such UI option: %s", - name.data); + api_set_error(err, kErrorTypeValidation, "No such UI option: %s", name.data); } /// Tell Nvim to resize a grid. Triggers a grid_resize event with the requested diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 333749c42f..50683ac403 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -87,10 +87,9 @@ Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *er Dictionary result = ARRAY_DICT_INIT; int id = syn_name2id(name.data); - if (id == 0) { - api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data); + VALIDATE_S((id != 0), "highlight name", name.data, { return result; - } + }); return nvim_get_hl_by_id(id, rgb, arena, err); } @@ -104,10 +103,9 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *er FUNC_API_SINCE(3) { Dictionary dic = ARRAY_DICT_INIT; - if (syn_get_final_id((int)hl_id) == 0) { - api_set_error(err, kErrorTypeException, "Invalid highlight id: %" PRId64, hl_id); + VALIDATE_INT((syn_get_final_id((int)hl_id) != 0), "highlight id", hl_id, { return dic; - } + }); int attrcode = syn_id2attr((int)hl_id); return hl_get_attr_by_id(attrcode, rgb, arena, err); } @@ -174,10 +172,9 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) FUNC_API_SINCE(7) { int hl_id = syn_check_group(name.data, name.size); - if (hl_id == 0) { - api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data); + VALIDATE_S((hl_id != 0), "highlight name", name.data, { return; - } + }); int link_id = -1; HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); @@ -403,11 +400,9 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri continue; } int mod = name_to_mod_mask(byte); - if (mod == 0) { - api_set_error(err, kErrorTypeValidation, - "invalid modifier %c", byte); + VALIDATE((mod != 0), "Invalid modifier: %c", byte, { return; - } + }); modmask |= mod; } @@ -500,7 +495,7 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err) Integer nvim_strwidth(String text, Error *err) FUNC_API_SINCE(1) { - VALIDATE((text.size <= INT_MAX), "text length (too long)", { + VALIDATE_S((text.size <= INT_MAX), "text length", "(too long)", { return 0; }); @@ -574,8 +569,7 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E { bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err); bool source = api_object_to_bool(opts->do_source, "do_source", false, err); - VALIDATE((!source || nlua_is_deferred_safe()), "'do_source' used in fast callback", {}); - + VALIDATE((!source || nlua_is_deferred_safe()), "%s", "'do_source' used in fast callback", {}); if (ERROR_SET(err)) { return (Array)ARRAY_DICT_INIT; } @@ -599,7 +593,7 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E void nvim_set_current_dir(String dir, Error *err) FUNC_API_SINCE(1) { - VALIDATE((dir.size < MAXPATHL), "directory name (too long)", { + VALIDATE_S((dir.size < MAXPATHL), "directory name", "(too long)", { return; }); @@ -1067,7 +1061,7 @@ void nvim_chan_send(Integer chan, String data, Error *err) channel_send((uint64_t)chan, data.data, data.size, false, &error); - VALIDATE(!error, error, {}); + VALIDATE(!error, "%s", error, {}); } /// Gets the current list of tabpage handles. @@ -1334,12 +1328,11 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err) FUNC_API_SINCE(6) { Array types = ARRAY_DICT_INIT; - if (opts->types.type == kObjectTypeArray) { - types = opts->types.data.array; - } else if (opts->types.type != kObjectTypeNil) { + if (HAS_KEY(opts->types)) { VALIDATE_T("types", kObjectTypeArray, opts->types.type, { return (Dictionary)ARRAY_DICT_INIT; }); + types = opts->types.data.array; } int int_types = types.size > 0 ? 0 : kCtxAll; @@ -1643,7 +1636,7 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *er goto theend; }); Array call = calls.items[i].data.array; - VALIDATE((call.size == 2), "Items in calls array must be arrays of size 2", { + VALIDATE((call.size == 2), "%s", "calls item must be a 2-item Array", { goto theend; }); VALIDATE_T("name", kObjectTypeString, call.items[0].type, { @@ -1824,10 +1817,9 @@ Array nvim_get_proc_children(Integer pid, Error *err) Array rvobj = ARRAY_DICT_INIT; int *proc_list = NULL; - if (pid <= 0 || pid > INT_MAX) { - api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, { goto end; - } + }); size_t proc_count; int rv = os_proc_children((int)pid, &proc_list, &proc_count); @@ -1866,10 +1858,10 @@ Object nvim_get_proc(Integer pid, Error *err) rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT; rvobj.type = kObjectTypeDictionary; - if (pid <= 0 || pid > INT_MAX) { - api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, { return NIL; - } + }); + #ifdef MSWIN rvobj.data.dictionary = os_proc_info((int)pid); if (rvobj.data.dictionary.size == 0) { // Process not found. @@ -1911,7 +1903,7 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Di Error *err) FUNC_API_SINCE(6) { - VALIDATE((opts.size == 0), "opts dict isn't empty", { + VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", { return; }); @@ -2100,7 +2092,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) { const char *const errmsg = check_stl_option(str.data); - VALIDATE(!errmsg, errmsg, { + VALIDATE(!errmsg, "%s", errmsg, { return result; }); } @@ -2119,7 +2111,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * VALIDATE((opts->fillchar.data.string.size != 0 && ((size_t)utf_ptr2len(opts->fillchar.data.string.data) == opts->fillchar.data.string.size)), - "Invalid fillchar (not a single character)", { + "%s", "Invalid fillchar: expected single character", { return result; }); fillchar = utf_ptr2char(opts->fillchar.data.string.data); @@ -2145,10 +2137,9 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * return result; } } - if (use_winbar && use_tabline) { - api_set_error(err, kErrorTypeValidation, "use_winbar and use_tabline are mutually exclusive"); + VALIDATE(!(use_winbar && use_tabline), "%s", "Cannot use both 'use_winbar' and 'use_tabline'", { return result; - } + }); win_T *wp, *ewp; diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 5936e4ff2b..5e53bf273f 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -12,6 +12,7 @@ #include "lauxlib.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" #include "nvim/api/ui.h" #include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" @@ -971,35 +972,33 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e return hlattrs; } - if (dict->blend.type == kObjectTypeInteger) { + if (HAS_KEY(dict->blend)) { + VALIDATE_T("blend", kObjectTypeInteger, dict->blend.type, { + return hlattrs; + }); + Integer blend0 = dict->blend.data.integer; - if (blend0 < 0 || blend0 > 100) { - api_set_error(err, kErrorTypeValidation, "'blend' is not between 0 to 100"); - } else { - blend = (int)blend0; - } - } else if (HAS_KEY(dict->blend)) { - api_set_error(err, kErrorTypeValidation, "'blend' must be an integer"); - } - if (ERROR_SET(err)) { - return hlattrs; + VALIDATE_RANGE((blend0 >= 0 && blend0 <= 100), "blend", { + return hlattrs; + }); + blend = (int)blend0; } if (HAS_KEY(dict->link) || HAS_KEY(dict->global_link)) { - if (link_id) { - if (HAS_KEY(dict->global_link)) { - *link_id = object_to_hl_id(dict->global_link, "link", err); - mask |= HL_GLOBAL; - } else { - *link_id = object_to_hl_id(dict->link, "link", err); - } - - if (ERROR_SET(err)) { - return hlattrs; - } - } else { + if (!link_id) { api_set_error(err, kErrorTypeValidation, "Invalid Key: '%s'", HAS_KEY(dict->global_link) ? "global_link" : "link"); + return hlattrs; + } + if (HAS_KEY(dict->global_link)) { + *link_id = object_to_hl_id(dict->global_link, "link", err); + mask |= HL_GLOBAL; + } else { + *link_id = object_to_hl_id(dict->link, "link", err); + } + + if (ERROR_SET(err)) { + return hlattrs; } } @@ -1026,7 +1025,9 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e // TODO(clason): handle via gen_api_dispatch cterm_mask_provided = true; } else if (HAS_KEY(dict->cterm)) { - api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary."); + VALIDATE_T("cterm", kObjectTypeDictionary, dict->cterm.type, { + return hlattrs; + }); } #undef CHECK_FLAG @@ -1083,13 +1084,14 @@ int object_to_color(Object val, char *key, bool rgb, Error *err) } else { color = name_to_ctermcolor(str.data); } - if (color < 0) { - api_set_error(err, kErrorTypeValidation, "'%s' is not a valid color", str.data); - } + VALIDATE_S((color >= 0), "highlight color", str.data, { + return color; + }); return color; } else { - api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key); - return 0; + VALIDATE(false, "Invalid %s: expected String or Integer", key, { + return 0; + }); } } diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index 56b0a4d9b2..f10f18174e 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -14,14 +14,31 @@ before_each(clear) describe('autocmd api', function() describe('nvim_create_autocmd', function() - it('"command" and "callback" are mutually exclusive', function() - local rv = pcall_err(meths.create_autocmd, "BufReadPost", { - pattern = "*.py,*.pyi", + it('validation', function() + eq("Cannot use both 'callback' and 'command'", pcall_err(meths.create_autocmd, 'BufReadPost', { + pattern = '*.py,*.pyi', command = "echo 'Should Have Errored", - callback = "NotAllowed", - }) - - eq("Cannot use both 'callback' and 'command'", rv) + callback = 'NotAllowed', + })) + eq("Cannot use both 'pattern' and 'buffer' for the same autocmd", pcall_err(meths.create_autocmd, 'FileType', { + command = 'let g:called = g:called + 1', + buffer = 0, + pattern = '*.py', + })) + eq("Required: 'event'", pcall_err(meths.create_autocmd, {}, { + command = 'ls', + })) + eq("Required: 'command' or 'callback'", pcall_err(meths.create_autocmd, 'FileType', { + })) + eq('Invalid desc: expected String, got Integer', pcall_err(meths.create_autocmd, 'FileType', { + command = 'ls', + desc = 42, + })) + eq('Invalid callback: expected Lua function or Vim function name, got Integer', pcall_err(meths.create_autocmd, 'FileType', { + callback = 0, + })) + eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.create_autocmd, + {'FileType', {}}, {})) end) it('doesnt leak when you use ++once', function() @@ -59,18 +76,8 @@ describe('autocmd api', function() eq(1, meths.get_var('called')) end) - it('does not allow passing buffer and patterns', function() - local rv = pcall_err(meths.create_autocmd, "Filetype", { - command = "let g:called = g:called + 1", - buffer = 0, - pattern = "*.py", - }) - - eq("Cannot use both 'pattern' and 'buffer' for the same autocmd", rv) - end) - it('does not allow passing invalid buffers', function() - local ok, msg = pcall(meths.create_autocmd, "Filetype", { + local ok, msg = pcall(meths.create_autocmd, 'FileType', { command = "let g:called = g:called + 1", buffer = -1, }) @@ -295,7 +302,7 @@ describe('autocmd api', function() describe('nvim_get_autocmds', function() describe('events', function() - it('should return one autocmd when there is only one for an event', function() + it('returns one autocmd when there is only one for an event', function() command [[au! InsertEnter]] command [[au InsertEnter * :echo "1"]] @@ -303,7 +310,7 @@ describe('autocmd api', function() eq(1, #aus) end) - it('should return two autocmds when there are two for an event', function() + it('returns two autocmds when there are two for an event', function() command [[au! InsertEnter]] command [[au InsertEnter * :echo "1"]] command [[au InsertEnter * :echo "2"]] @@ -312,7 +319,7 @@ describe('autocmd api', function() eq(2, #aus) end) - it('should return the same thing if you use string or list', function() + it('returns the same thing if you use string or list', function() command [[au! InsertEnter]] command [[au InsertEnter * :echo "1"]] command [[au InsertEnter * :echo "2"]] @@ -322,7 +329,7 @@ describe('autocmd api', function() eq(string_aus, array_aus) end) - it('should return two autocmds when there are two for an event', function() + it('returns two autocmds when there are two for an event', function() command [[au! InsertEnter]] command [[au! InsertLeave]] command [[au InsertEnter * :echo "1"]] @@ -332,7 +339,7 @@ describe('autocmd api', function() eq(2, #aus) end) - it('should return different IDs for different autocmds', function() + it('returns different IDs for different autocmds', function() command [[au! InsertEnter]] command [[au! InsertLeave]] command [[au InsertEnter * :echo "1"]] @@ -356,7 +363,7 @@ describe('autocmd api', function() eq(first, new_aus[1]) end) - it('should return event name', function() + it('returns event name', function() command [[au! InsertEnter]] command [[au InsertEnter * :echo "1"]] @@ -364,7 +371,7 @@ describe('autocmd api', function() eq({ { buflocal = false, command = ':echo "1"', event = "InsertEnter", once = false, pattern = "*" } }, aus) end) - it('should work with buffer numbers', function() + it('works with buffer numbers', function() command [[new]] command [[au! InsertEnter]] command [[au InsertEnter <buffer=1> :echo "1"]] @@ -419,7 +426,7 @@ describe('autocmd api', function() eq("Too many buffers (maximum of 256)", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs })) end) - it('should return autocmds when group is specified by id', function() + it('returns autocmds when group is specified by id', function() local auid = meths.create_augroup("nvim_test_augroup", { clear = true }) meths.create_autocmd("FileType", { group = auid, command = 'echo "1"' }) meths.create_autocmd("FileType", { group = auid, command = 'echo "2"' }) @@ -431,7 +438,7 @@ describe('autocmd api', function() eq(0, #aus2) end) - it('should return autocmds when group is specified by name', function() + it('returns autocmds when group is specified by name', function() local auname = "nvim_test_augroup" meths.create_augroup(auname, { clear = true }) meths.create_autocmd("FileType", { group = auname, command = 'echo "1"' }) @@ -531,7 +538,7 @@ describe('autocmd api', function() command [[augroup END]] end) - it('should return all groups if no group is specified', function() + it('returns all groups if no group is specified', function() local aus = meths.get_autocmds { event = "InsertEnter" } if #aus ~= 4 then eq({}, aus) @@ -540,7 +547,7 @@ describe('autocmd api', function() eq(4, #aus) end) - it('should return only the group specified', function() + it('returns only the group specified', function() local aus = meths.get_autocmds { event = "InsertEnter", group = "GroupOne", @@ -551,7 +558,7 @@ describe('autocmd api', function() eq("GroupOne", aus[1].group_name) end) - it('should return only the group specified, multiple values', function() + it('returns only the group specified, multiple values', function() local aus = meths.get_autocmds { event = "InsertEnter", group = "GroupTwo", @@ -625,7 +632,7 @@ describe('autocmd api', function() ]], {})) eq(false, success) - matches("Invalid 'pattern' item type: expected String, got Array", code) + matches("Invalid 'pattern' item: expected String, got Array", code) end) end) @@ -640,7 +647,7 @@ describe('autocmd api', function() command [[au InsertEnter <buffer> :echo "Buffer"]] end) - it('should should return for literal match', function() + it('returns for literal match', function() local aus = meths.get_autocmds { event = "InsertEnter", pattern = "*" @@ -650,7 +657,7 @@ describe('autocmd api', function() eq([[:echo "No Group"]], aus[1].command) end) - it('should return for multiple matches', function() + it('returns for multiple matches', function() -- vim.api.nvim_get_autocmds local aus = meths.get_autocmds { event = "InsertEnter", @@ -687,6 +694,23 @@ describe('autocmd api', function() end) describe('nvim_exec_autocmds', function() + it('validation', function() + eq('Invalid group: 9997999', pcall_err(meths.exec_autocmds, 'FileType', { + group = 9997999, + })) + eq("Invalid group: 'bogus'", pcall_err(meths.exec_autocmds, 'FileType', { + group = 'bogus', + })) + eq('Invalid group: expected String or Integer, got Array', pcall_err(meths.exec_autocmds, 'FileType', { + group = {}, + })) + eq('Invalid buffer: expected Integer, got Array', pcall_err(meths.exec_autocmds, 'FileType', { + buffer = {}, + })) + eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.exec_autocmds, + {'FileType', {}}, {})) + end) + it("can trigger builtin autocmds", function() meths.set_var("autocmd_executed", false) @@ -1036,7 +1060,7 @@ describe('autocmd api', function() local augroup = "WillBeDeleted" meths.create_augroup(augroup, { clear = true }) - meths.create_autocmd({"Filetype"}, { + meths.create_autocmd({"FileType"}, { pattern = "*", command = "echo 'does not matter'", }) @@ -1055,7 +1079,7 @@ describe('autocmd api', function() meths.set_var("value_set", false) meths.create_augroup(augroup, { clear = true }) - meths.create_autocmd("Filetype", { + meths.create_autocmd("FileType", { pattern = "<buffer>", command = "let g:value_set = v:true", }) @@ -1171,6 +1195,16 @@ describe('autocmd api', function() end) describe('nvim_clear_autocmds', function() + it('validation', function() + eq("Cannot use both 'pattern' and 'buffer'", pcall_err(meths.clear_autocmds, { + pattern = '*', + buffer = 42, + })) + eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.clear_autocmds, { + event = {'FileType', {}} + })) + end) + it('should clear based on event + pattern', function() command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"') command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"') diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 6b13729994..d454765edb 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -193,7 +193,7 @@ describe('api/buf', function() it('fails correctly when input is not valid', function() eq(1, api.curbufmeths.get_number()) - eq([[String cannot contain newlines]], + eq([['replacement string' item contains newlines]], pcall_err(bufmeths.set_lines, 1, 1, 2, false, {'b\na'})) end) @@ -553,20 +553,20 @@ describe('api/buf', function() insert([[ hello foo! text]]) - eq('start_row out of bounds', pcall_err(set_text, 2, 0, 3, 0, {})) - eq('start_row out of bounds', pcall_err(set_text, -3, 0, 0, 0, {})) - eq('end_row out of bounds', pcall_err(set_text, 0, 0, 2, 0, {})) - eq('end_row out of bounds', pcall_err(set_text, 0, 0, -3, 0, {})) - eq('start_col out of bounds', pcall_err(set_text, 1, 5, 1, 5, {})) - eq('end_col out of bounds', pcall_err(set_text, 1, 0, 1, 5, {})) + eq("Invalid 'start_row': out of range", pcall_err(set_text, 2, 0, 3, 0, {})) + eq("Invalid 'start_row': out of range", pcall_err(set_text, -3, 0, 0, 0, {})) + eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, 2, 0, {})) + eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, -3, 0, {})) + eq("Invalid 'start_col': out of range", pcall_err(set_text, 1, 5, 1, 5, {})) + eq("Invalid 'end_col': out of range", pcall_err(set_text, 1, 0, 1, 5, {})) end) it('errors when start is greater than end', function() insert([[ hello foo! text]]) - eq('start is higher than end', pcall_err(set_text, 1, 0, 0, 0, {})) - eq('start is higher than end', pcall_err(set_text, 0, 1, 0, 0, {})) + eq("'start' is higher than 'end'", pcall_err(set_text, 1, 0, 0, 0, {})) + eq("'start' is higher than 'end'", pcall_err(set_text, 0, 1, 0, 0, {})) end) it('no heap-use-after-free when called consecutively #19643', function() @@ -611,7 +611,7 @@ describe('api/buf', function() end) it('errors when start is greater than end', function() - eq('start is higher than end', pcall_err(get_text, 1, 0, 0, 0, {})) + eq("'start' is higher than 'end'", pcall_err(get_text, 1, 0, 0, 0, {})) eq('start_col must be less than end_col', pcall_err(get_text, 0, 1, 0, 0, {})) end) end) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index fc68f11a67..fab1557204 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -106,7 +106,7 @@ describe('API/extmarks', function() end_col = 0, end_row = 1 }) - eq("Invalid end_col: '(out of range)'", + eq("Invalid 'end_col': out of range", pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = 1 })) end) @@ -1362,13 +1362,13 @@ describe('API/extmarks', function() it('when col = line-length, set the mark on eol', function() local invalid_col = init_text:len() + 1 - eq("Invalid col: '(out of range)'", pcall_err(set_extmark, ns, marks[1], 0, invalid_col)) + eq("Invalid 'col': out of range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col)) end) it('fails when line > line_count', function() local invalid_col = init_text:len() + 1 local invalid_lnum = 3 - eq("Invalid line: '(out of range)'", pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col)) + eq("Invalid 'line': out of range", pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col)) eq({}, get_extmark_by_id(ns, marks[1])) end) diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 7f044977cb..73551de42e 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -57,9 +57,7 @@ describe('API: highlight',function() eq(expected_rgb, nvim("get_hl_by_id", hl_id, true)) -- Test invalid id. - local err, emsg = pcall(meths.get_hl_by_id, 30000, false) - eq(false, err) - eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*')) + eq('Invalid highlight id: 30000', pcall_err(meths.get_hl_by_id, 30000, false)) -- Test all highlight properties. command('hi NewHighlight gui=underline,bold,italic,reverse,strikethrough,altfont,nocombine') @@ -70,22 +68,14 @@ describe('API: highlight',function() eq(expected_undercurl, nvim("get_hl_by_id", hl_id, true)) -- Test nil argument. - err, emsg = pcall(meths.get_hl_by_id, { nil }, false) - eq(false, err) eq('Wrong type for argument 1 when calling nvim_get_hl_by_id, expecting Integer', - string.match(emsg, 'Wrong.*')) + pcall_err(meths.get_hl_by_id, { nil }, false)) -- Test 0 argument. - err, emsg = pcall(meths.get_hl_by_id, 0, false) - eq(false, err) - eq('Invalid highlight id: 0', - string.match(emsg, 'Invalid.*')) + eq('Invalid highlight id: 0', pcall_err(meths.get_hl_by_id, 0, false)) -- Test -1 argument. - err, emsg = pcall(meths.get_hl_by_id, -1, false) - eq(false, err) - eq('Invalid highlight id: -1', - string.match(emsg, 'Invalid.*')) + eq('Invalid highlight id: -1', pcall_err(meths.get_hl_by_id, -1, false)) -- Test highlight group without ctermbg value. command('hi Normal ctermfg=red ctermbg=yellow') @@ -119,22 +109,16 @@ describe('API: highlight',function() eq(expected_normal, nvim("get_hl_by_name", 'Normal', true)) -- Test invalid name. - local err, emsg = pcall(meths.get_hl_by_name , 'unknown_highlight', false) - eq(false, err) - eq('Invalid highlight name: unknown_highlight', - string.match(emsg, 'Invalid.*')) + eq("Invalid highlight name: 'unknown_highlight'", + pcall_err(meths.get_hl_by_name , 'unknown_highlight', false)) -- Test nil argument. - err, emsg = pcall(meths.get_hl_by_name , { nil }, false) - eq(false, err) eq('Wrong type for argument 1 when calling nvim_get_hl_by_name, expecting String', - string.match(emsg, 'Wrong.*')) + pcall_err(meths.get_hl_by_name , { nil }, false)) -- Test empty string argument. - err, emsg = pcall(meths.get_hl_by_name , '', false) - eq(false, err) - eq('Invalid highlight name: ', - string.match(emsg, 'Invalid.*')) + eq('Invalid highlight name', + pcall_err(meths.get_hl_by_name , '', false)) -- Test "standout" attribute. #8054 eq({ underline = true, }, @@ -155,7 +139,7 @@ describe('API: highlight',function() it('nvim_get_hl_id_by_name', function() -- precondition: use a hl group that does not yet exist - eq('Invalid highlight name: Shrubbery', pcall_err(meths.get_hl_by_name, "Shrubbery", true)) + eq("Invalid highlight name: 'Shrubbery'", pcall_err(meths.get_hl_by_name, "Shrubbery", true)) eq(0, funcs.hlID("Shrubbery")) local hl_id = meths.get_hl_id_by_name("Shrubbery") @@ -253,25 +237,32 @@ describe("API: set highlight", function() before_each(clear) - it ("can set gui highlight", function() + it('validation', function() + eq("Invalid 'blend': out of range", + pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='#FF00FF', blend=999})) + eq("Invalid blend: expected Integer, got Array", + pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='#FF00FF', blend={}})) + end) + + it("can set gui highlight", function() local ns = get_ns() meths.set_hl(ns, 'Test_hl', highlight1) eq(highlight1, meths.get_hl_by_name('Test_hl', true)) end) - it ("can set cterm highlight", function() + it("can set cterm highlight", function() local ns = get_ns() meths.set_hl(ns, 'Test_hl', highlight2_config) eq(highlight2_result, meths.get_hl_by_name('Test_hl', false)) end) - it ("can set empty cterm attr", function() + it("can set empty cterm attr", function() local ns = get_ns() meths.set_hl(ns, 'Test_hl', { cterm = {} }) eq({}, meths.get_hl_by_name('Test_hl', false)) end) - it ("cterm attr defaults to gui attr", function() + it("cterm attr defaults to gui attr", function() local ns = get_ns() meths.set_hl(ns, 'Test_hl', highlight1) eq({ @@ -280,14 +271,14 @@ describe("API: set highlight", function() }, meths.get_hl_by_name('Test_hl', false)) end) - it ("can overwrite attr for cterm", function() + it("can overwrite attr for cterm", function() local ns = get_ns() meths.set_hl(ns, 'Test_hl', highlight3_config) eq(highlight3_result_gui, meths.get_hl_by_name('Test_hl', true)) eq(highlight3_result_cterm, meths.get_hl_by_name('Test_hl', false)) end) - it ("can set a highlight in the global namespace", function() + it("can set a highlight in the global namespace", function() meths.set_hl(0, 'Test_hl', highlight2_config) eq('Test_hl xxx cterm=underline,reverse ctermfg=8 ctermbg=15 gui=underline,reverse', exec_capture('highlight Test_hl')) @@ -307,7 +298,7 @@ describe("API: set highlight", function() exec_capture('highlight Test_hl3')) end) - it ("can modify a highlight in the global namespace", function() + it("can modify a highlight in the global namespace", function() meths.set_hl(0, 'Test_hl3', { bg = 'red', fg = 'blue'}) eq('Test_hl3 xxx guifg=Blue guibg=Red', exec_capture('highlight Test_hl3')) @@ -328,17 +319,17 @@ describe("API: set highlight", function() eq('Test_hl3 xxx ctermbg=9', exec_capture('highlight Test_hl3')) - eq("'redd' is not a valid color", + eq("Invalid highlight color: 'redd'", pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='redd'})) - eq("'bleu' is not a valid color", + eq("Invalid highlight color: 'bleu'", pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='bleu'})) meths.set_hl(0, 'Test_hl3', {fg='#FF00FF'}) eq('Test_hl3 xxx guifg=#ff00ff', exec_capture('highlight Test_hl3')) - eq("'#FF00FF' is not a valid color", + eq("Invalid highlight color: '#FF00FF'", pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='#FF00FF'})) for _, fg_val in ipairs{ nil, 'NONE', 'nOnE', '', -1 } do @@ -353,14 +344,14 @@ describe("API: set highlight", function() end) - it ("correctly sets 'Normal' internal properties", function() + it("correctly sets 'Normal' internal properties", function() -- Normal has some special handling internally. #18024 meths.set_hl(0, 'Normal', {fg='#000083', bg='#0000F3'}) eq({foreground = 131, background = 243}, nvim("get_hl_by_name", 'Normal', true)) end) it('does not segfault on invalid group name #20009', function() - eq('Invalid highlight name: foo bar', pcall_err(meths.set_hl, 0, 'foo bar', {bold = true})) + eq("Invalid highlight name: 'foo bar'", pcall_err(meths.set_hl, 0, 'foo bar', {bold = true})) assert_alive() end) end) diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua index 2028a8fba5..315653af14 100644 --- a/test/functional/api/proc_spec.lua +++ b/test/functional/api/proc_spec.lua @@ -42,7 +42,7 @@ describe('API', function() end) end) - it('validates input', function() + it('validation', function() local status, rv = pcall(request, "nvim_get_proc_children", -1) eq(false, status) eq("Invalid pid: -1", string.match(rv, "Invalid.*")) @@ -68,7 +68,7 @@ describe('API', function() neq(pid, pinfo.ppid) end) - it('validates input', function() + it('validation', function() local status, rv = pcall(request, "nvim_get_proc", -1) eq(false, status) eq("Invalid pid: -1", string.match(rv, "Invalid.*")) diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua index 279cd1856d..5654ef31ef 100644 --- a/test/functional/api/ui_spec.lua +++ b/test/functional/api/ui_spec.lua @@ -11,17 +11,37 @@ describe('nvim_ui_attach()', function() before_each(function() clear() end) + it('handles very large width/height #2180', function() local screen = Screen.new(999, 999) screen:attach() eq(999, eval('&lines')) eq(999, eval('&columns')) end) - it('invalid option returns error', function() + + it('validation', function() eq('No such UI option: foo', pcall_err(meths.ui_attach, 80, 24, { foo={'foo'} })) - end) - it('validates channel arg', function() + + eq('Invalid ext_linegrid: expected Boolean, got Array', + pcall_err(meths.ui_attach, 80, 24, { ext_linegrid={} })) + eq('Invalid override: expected Boolean, got Array', + pcall_err(meths.ui_attach, 80, 24, { override={} })) + eq('Invalid rgb: expected Boolean, got Array', + pcall_err(meths.ui_attach, 80, 24, { rgb={} })) + eq('Invalid term_name: expected String, got Boolean', + pcall_err(meths.ui_attach, 80, 24, { term_name=true })) + eq('Invalid term_colors: expected Integer, got Boolean', + pcall_err(meths.ui_attach, 80, 24, { term_colors=true })) + eq('Invalid term_background: expected String, got Boolean', + pcall_err(meths.ui_attach, 80, 24, { term_background=true })) + eq('Invalid stdin_fd: expected Integer, got String', + pcall_err(meths.ui_attach, 80, 24, { stdin_fd='foo' })) + eq('Invalid stdin_tty: expected Boolean, got String', + pcall_err(meths.ui_attach, 80, 24, { stdin_tty='foo' })) + eq('Invalid stdout_tty: expected Boolean, got String', + pcall_err(meths.ui_attach, 80, 24, { stdout_tty='foo' })) + eq('UI not attached to channel: 1', pcall_err(request, 'nvim_ui_try_resize', 40, 10)) eq('UI not attached to channel: 1', diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 5c56c90952..469d8e8b6a 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1411,9 +1411,9 @@ describe('API', function() end) it('validation', function() - eq("Invalid scope (expected 'local' or 'global')", + eq("Invalid scope: expected 'local' or 'global'", pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 'bogus'})) - eq("Invalid scope (expected 'local' or 'global')", + eq("Invalid scope: expected 'local' or 'global'", pcall_err(nvim, 'set_option_value', 'scrolloff', 1, {scope = 'bogus'})) eq("Invalid scope: expected String, got Integer", pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 42})) @@ -2238,7 +2238,7 @@ describe('API', function() {'nvim_set_var'}, {'nvim_set_var', {'avar', 2}}, } - eq('Items in calls array must be arrays of size 2', + eq('calls item must be a 2-item Array', pcall_err(meths.call_atomic, req)) -- call before was done, but not after eq(1, meths.get_var('avar')) @@ -3119,11 +3119,11 @@ describe('API', function() meths.eval_statusline('a%=b', { fillchar = '\031', maxwidth = 5 })) end) it('rejects multiple-character fillchar', function() - eq('Invalid fillchar (not a single character)', + eq('Invalid fillchar: expected single character', pcall_err(meths.eval_statusline, '', { fillchar = 'aa' })) end) it('rejects empty string fillchar', function() - eq('Invalid fillchar (not a single character)', + eq('Invalid fillchar: expected single character', pcall_err(meths.eval_statusline, '', { fillchar = '' })) end) it('rejects non-string fillchar', function() diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua index 03832978a4..dc6452dd62 100644 --- a/test/functional/lua/api_spec.lua +++ b/test/functional/lua/api_spec.lua @@ -33,7 +33,7 @@ describe('luaeval(vim.api.…)', function() describe('with errors', function() it('transforms API error from nvim_buf_set_lines into lua error', function() funcs.setline(1, {"abc", "def", "a\nb", "ttt"}) - eq({false, 'String cannot contain newlines'}, + eq({false, "'replacement string' item contains newlines"}, funcs.luaeval('{pcall(vim.api.nvim_buf_set_lines, 1, 1, 2, false, {"b\\na"})}')) end) |