From beceba85da9d53dad33c1a0d3f59c24e966b1c35 Mon Sep 17 00:00:00 2001 From: Yuri Sizov Date: Sat, 6 Aug 2022 02:52:27 +0300 Subject: [PATCH 1/2] Add checks for empty/unnamed arguments to make_rst.py --- core/object/method_bind.cpp | 2 +- doc/tools/make_rst.py | 53 ++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/core/object/method_bind.cpp b/core/object/method_bind.cpp index a4474ea53b6..0edc7a90c23 100644 --- a/core/object/method_bind.cpp +++ b/core/object/method_bind.cpp @@ -63,7 +63,7 @@ PropertyInfo MethodBind::get_argument_info(int p_argument) const { PropertyInfo info = _gen_argument_type_info(p_argument); #ifdef DEBUG_METHODS_ENABLED - info.name = p_argument < arg_names.size() ? String(arg_names[p_argument]) : String("arg" + itos(p_argument)); + info.name = p_argument < arg_names.size() ? String(arg_names[p_argument]) : String("_unnamed_arg" + itos(p_argument)); #endif return info; } diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 196a26ef773..cf87990a110 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -72,6 +72,7 @@ class State: def parse_class(self, class_root: ET.Element, filepath: str) -> None: class_name = class_root.attrib["name"] + self.current_class = class_name class_def = ClassDef(class_name) self.classes[class_name] = class_def @@ -126,7 +127,7 @@ class State: else: return_type = TypeName("void") - params = parse_arguments(constructor) + params = self.parse_arguments(constructor, "constructor") desc_element = constructor.find("description") method_desc = None @@ -154,7 +155,7 @@ class State: else: return_type = TypeName("void") - params = parse_arguments(method) + params = self.parse_arguments(method, "method") desc_element = method.find("description") method_desc = None @@ -182,7 +183,7 @@ class State: else: return_type = TypeName("void") - params = parse_arguments(operator) + params = self.parse_arguments(operator, "operator") desc_element = operator.find("description") method_desc = None @@ -230,7 +231,7 @@ class State: annotation_name = annotation.attrib["name"] qualifiers = annotation.get("qualifiers") - params = parse_arguments(annotation) + params = self.parse_arguments(annotation, "annotation") desc_element = annotation.find("description") annotation_desc = None @@ -254,7 +255,7 @@ class State: print_error('{}.xml: Duplicate signal "{}".'.format(class_name, signal_name), self) continue - params = parse_arguments(signal) + params = self.parse_arguments(signal, "signal") desc_element = signal.find("description") signal_desc = None @@ -302,6 +303,32 @@ class State: if link.text is not None: class_def.tutorials.append((link.text.strip(), link.get("title", ""))) + self.current_class = "" + + def parse_arguments(self, root: ET.Element, context: str) -> List["ParameterDef"]: + param_elements = root.findall("argument") + params: Any = [None] * len(param_elements) + + for param_index, param_element in enumerate(param_elements): + param_name = param_element.attrib["name"] + index = int(param_element.attrib["index"]) + type_name = TypeName.from_element(param_element) + default = param_element.get("default") + + if param_name.strip() == "" or param_name.startswith("_unnamed_arg"): + print_error( + '{}.xml: Empty argument name in {} "{}" at position {}.'.format( + self.current_class, context, root.attrib["name"], param_index + ), + self, + ) + + params[index] = ParameterDef(param_name, type_name, default) + + cast: List[ParameterDef] = params + + return cast + def sort_classes(self) -> None: self.classes = OrderedDict(sorted(self.classes.items(), key=lambda t: t[0])) @@ -440,22 +467,6 @@ def print_error(error: str, state: State) -> None: state.num_errors += 1 -def parse_arguments(root: ET.Element) -> List[ParameterDef]: - param_elements = root.findall("argument") - params: Any = [None] * len(param_elements) - for param_element in param_elements: - param_name = param_element.attrib["name"] - index = int(param_element.attrib["index"]) - type_name = TypeName.from_element(param_element) - default = param_element.get("default") - - params[index] = ParameterDef(param_name, type_name, default) - - cast: List[ParameterDef] = params - - return cast - - def main() -> None: # Enable ANSI escape code support on Windows 10 and later (for colored console output). # From 1362bc22bd636190dfaf6876fbec86e2cd7a34b7 Mon Sep 17 00:00:00 2001 From: Yuri Sizov Date: Mon, 8 Aug 2022 15:18:26 +0300 Subject: [PATCH 2/2] Add tests for empty/unnamed arguments to ClassDB, Variant, GDScript --- core/io/packed_data_container.cpp | 2 +- core/string/translation.cpp | 2 +- editor/doc_tools.cpp | 559 +++++++++--------- .../tests/gdscript_test_runner_suite.h | 34 ++ scene/animation/animation_player.cpp | 2 +- scene/gui/dialogs.cpp | 2 +- scene/gui/option_button.cpp | 2 +- scene/gui/text_edit.cpp | 2 +- scene/main/resource_preloader.cpp | 2 +- scene/resources/bit_map.cpp | 2 +- scene/resources/curve.cpp | 4 +- scene/resources/multimesh.cpp | 8 +- scene/resources/packed_scene.cpp | 2 +- scene/resources/polygon_path_finder.cpp | 2 +- scene/resources/sprite_frames.cpp | 2 +- tests/core/object/test_class_db.h | 82 ++- tests/core/variant/test_variant.h | 62 ++ 17 files changed, 436 insertions(+), 335 deletions(-) diff --git a/core/io/packed_data_container.cpp b/core/io/packed_data_container.cpp index a4563181484..2dcb86cbc60 100644 --- a/core/io/packed_data_container.cpp +++ b/core/io/packed_data_container.cpp @@ -353,7 +353,7 @@ Variant PackedDataContainer::_iter_get(const Variant &p_iter) { } void PackedDataContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("_set_data"), &PackedDataContainer::_set_data); + ClassDB::bind_method(D_METHOD("_set_data", "data"), &PackedDataContainer::_set_data); ClassDB::bind_method(D_METHOD("_get_data"), &PackedDataContainer::_get_data); ClassDB::bind_method(D_METHOD("_iter_init"), &PackedDataContainer::_iter_init); ClassDB::bind_method(D_METHOD("_iter_get"), &PackedDataContainer::_iter_get); diff --git a/core/string/translation.cpp b/core/string/translation.cpp index cba2f090228..b83b7c786f8 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -141,7 +141,7 @@ void Translation::_bind_methods() { ClassDB::bind_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL("")); ClassDB::bind_method(D_METHOD("get_message_list"), &Translation::_get_message_list); ClassDB::bind_method(D_METHOD("get_message_count"), &Translation::get_message_count); - ClassDB::bind_method(D_METHOD("_set_messages"), &Translation::_set_messages); + ClassDB::bind_method(D_METHOD("_set_messages", "messages"), &Translation::_set_messages); ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages); GDVIRTUAL_BIND(_get_plural_message, "src_message", "src_plural_message", "n", "context"); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index a8194584176..46a2a0719a0 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -337,311 +337,318 @@ static Variant get_documentation_default_value(const StringName &p_class_name, c } void DocTools::generate(bool p_basic_types) { - List classes; - ClassDB::get_class_list(&classes); - classes.sort_custom(); - // Move ProjectSettings, so that other classes can register properties there. - classes.move_to_back(classes.find("ProjectSettings")); + // Add ClassDB-exposed classes. + { + List classes; + ClassDB::get_class_list(&classes); + classes.sort_custom(); + // Move ProjectSettings, so that other classes can register properties there. + classes.move_to_back(classes.find("ProjectSettings")); - bool skip_setter_getter_methods = true; + bool skip_setter_getter_methods = true; - while (classes.size()) { - HashSet setters_getters; - - String name = classes.front()->get(); - if (!ClassDB::is_class_exposed(name)) { - print_verbose(vformat("Class '%s' is not exposed, skipping.", name)); - classes.pop_front(); - continue; - } - - String cname = name; - - class_list[cname] = DocData::ClassDoc(); - DocData::ClassDoc &c = class_list[cname]; - c.name = cname; - c.inherits = ClassDB::get_parent_class(name); - - List properties; - List own_properties; - - // Special case for editor and project settings, so they can be documented. - if (name == "EditorSettings") { - // We don't create the full blown EditorSettings (+ config file) with `create()`, - // instead we just make a local instance to get default values. - Ref edset = memnew(EditorSettings); - edset->get_property_list(&properties); - own_properties = properties; - } else if (name == "ProjectSettings") { - ProjectSettings::get_singleton()->get_property_list(&properties); - own_properties = properties; - } else { - ClassDB::get_property_list(name, &properties); - ClassDB::get_property_list(name, &own_properties, true); - } - - properties.sort(); - own_properties.sort(); - - List::Element *EO = own_properties.front(); - for (const PropertyInfo &E : properties) { - bool inherited = true; - if (EO && EO->get() == E) { - inherited = false; - EO = EO->next(); - } - - if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL || (E.type == Variant::NIL && E.usage & PROPERTY_USAGE_ARRAY)) { + // Populate documentation data for each exposed class. + while (classes.size()) { + String name = classes.front()->get(); + if (!ClassDB::is_class_exposed(name)) { + print_verbose(vformat("Class '%s' is not exposed, skipping.", name)); + classes.pop_front(); continue; } - DocData::PropertyDoc prop; - prop.name = E.name; - prop.overridden = inherited; + String cname = name; + // Property setters and getters do not get exposed as individual methods. + HashSet setters_getters; - if (inherited) { - String parent = ClassDB::get_parent_class(c.name); - while (!ClassDB::has_property(parent, prop.name, true)) { - parent = ClassDB::get_parent_class(parent); - } - prop.overrides = parent; - } + class_list[cname] = DocData::ClassDoc(); + DocData::ClassDoc &c = class_list[cname]; + c.name = cname; + c.inherits = ClassDB::get_parent_class(name); - bool default_value_valid = false; - Variant default_value; + List properties; + List own_properties; + // Special case for editor and project settings, so they can be documented. if (name == "EditorSettings") { - if (E.name == "resource_local_to_scene" || E.name == "resource_name" || E.name == "resource_path" || E.name == "script") { - // Don't include spurious properties in the generated EditorSettings class reference. - continue; - } + // We don't create the full blown EditorSettings (+ config file) with `create()`, + // instead we just make a local instance to get default values. + Ref edset = memnew(EditorSettings); + edset->get_property_list(&properties); + own_properties = properties; + } else if (name == "ProjectSettings") { + ProjectSettings::get_singleton()->get_property_list(&properties); + own_properties = properties; + } else { + ClassDB::get_property_list(name, &properties); + ClassDB::get_property_list(name, &own_properties, true); } - if (name == "ProjectSettings") { - // Special case for project settings, so that settings are not taken from the current project's settings - if (E.name == "script" || !ProjectSettings::get_singleton()->is_builtin_setting(E.name)) { + properties.sort(); + own_properties.sort(); + + List::Element *EO = own_properties.front(); + for (const PropertyInfo &E : properties) { + bool inherited = true; + if (EO && EO->get() == E) { + inherited = false; + EO = EO->next(); + } + + if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL || (E.type == Variant::NIL && E.usage & PROPERTY_USAGE_ARRAY)) { continue; } - if (E.usage & PROPERTY_USAGE_EDITOR) { - if (!ProjectSettings::get_singleton()->get_ignore_value_in_docs(E.name)) { - default_value = ProjectSettings::get_singleton()->property_get_revert(E.name); - default_value_valid = true; - } - } - } else { - default_value = get_documentation_default_value(name, E.name, default_value_valid); + + DocData::PropertyDoc prop; + prop.name = E.name; + prop.overridden = inherited; + if (inherited) { - bool base_default_value_valid = false; - Variant base_default_value = get_documentation_default_value(ClassDB::get_parent_class(name), E.name, base_default_value_valid); - if (!default_value_valid || !base_default_value_valid || default_value == base_default_value) { + String parent = ClassDB::get_parent_class(c.name); + while (!ClassDB::has_property(parent, prop.name, true)) { + parent = ClassDB::get_parent_class(parent); + } + prop.overrides = parent; + } + + bool default_value_valid = false; + Variant default_value; + + if (name == "EditorSettings") { + if (E.name == "resource_local_to_scene" || E.name == "resource_name" || E.name == "resource_path" || E.name == "script") { + // Don't include spurious properties in the generated EditorSettings class reference. continue; } } - } - if (default_value_valid && default_value.get_type() != Variant::OBJECT) { - prop.default_value = default_value.get_construct_string().replace("\n", " "); - } - - StringName setter = ClassDB::get_property_setter(name, E.name); - StringName getter = ClassDB::get_property_getter(name, E.name); - - prop.setter = setter; - prop.getter = getter; - - bool found_type = false; - if (getter != StringName()) { - MethodBind *mb = ClassDB::get_method(name, getter); - if (mb) { - PropertyInfo retinfo = mb->get_return_info(); - - found_type = true; - if (retinfo.type == Variant::INT && retinfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { - prop.enumeration = retinfo.class_name; - prop.type = "int"; - } else if (retinfo.class_name != StringName()) { - prop.type = retinfo.class_name; - } else if (retinfo.type == Variant::ARRAY && retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) { - prop.type = retinfo.hint_string + "[]"; - } else if (retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { - prop.type = retinfo.hint_string; - } else if (retinfo.type == Variant::NIL && retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { - prop.type = "Variant"; - } else if (retinfo.type == Variant::NIL) { - prop.type = "void"; - } else { - prop.type = Variant::get_type_name(retinfo.type); + if (name == "ProjectSettings") { + // Special case for project settings, so that settings are not taken from the current project's settings + if (E.name == "script" || !ProjectSettings::get_singleton()->is_builtin_setting(E.name)) { + continue; + } + if (E.usage & PROPERTY_USAGE_EDITOR) { + if (!ProjectSettings::get_singleton()->get_ignore_value_in_docs(E.name)) { + default_value = ProjectSettings::get_singleton()->property_get_revert(E.name); + default_value_valid = true; + } } - } - - setters_getters.insert(getter); - } - - if (setter != StringName()) { - setters_getters.insert(setter); - } - - if (!found_type) { - if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_RESOURCE_TYPE) { - prop.type = E.hint_string; } else { - prop.type = Variant::get_type_name(E.type); - } - } - - c.properties.push_back(prop); - } - - List method_list; - ClassDB::get_method_list(name, &method_list, true); - method_list.sort(); - - for (const MethodInfo &E : method_list) { - if (E.name.is_empty() || (E.name[0] == '_' && !(E.flags & METHOD_FLAG_VIRTUAL))) { - continue; //hidden, don't count - } - - if (skip_setter_getter_methods && setters_getters.has(E.name)) { - // Don't skip parametric setters and getters, i.e. method which require - // one or more parameters to define what property should be set or retrieved. - // E.g. CPUParticles3D::set_param(Parameter param, float value). - if (E.arguments.size() == 0 /* getter */ || (E.arguments.size() == 1 && E.return_val.type == Variant::NIL /* setter */)) { - continue; - } - } - - DocData::MethodDoc method; - DocData::method_doc_from_methodinfo(method, E, ""); - - Vector errs = ClassDB::get_method_error_return_values(name, E.name); - if (errs.size()) { - if (!errs.has(OK)) { - errs.insert(0, OK); - } - for (int i = 0; i < errs.size(); i++) { - if (!method.errors_returned.has(errs[i])) { - method.errors_returned.push_back(errs[i]); + default_value = get_documentation_default_value(name, E.name, default_value_valid); + if (inherited) { + bool base_default_value_valid = false; + Variant base_default_value = get_documentation_default_value(ClassDB::get_parent_class(name), E.name, base_default_value_valid); + if (!default_value_valid || !base_default_value_valid || default_value == base_default_value) { + continue; + } } } - } - c.methods.push_back(method); - } - - List signal_list; - ClassDB::get_signal_list(name, &signal_list, true); - - if (signal_list.size()) { - for (List::Element *EV = signal_list.front(); EV; EV = EV->next()) { - DocData::MethodDoc signal; - signal.name = EV->get().name; - for (int i = 0; i < EV->get().arguments.size(); i++) { - const PropertyInfo &arginfo = EV->get().arguments[i]; - DocData::ArgumentDoc argument; - DocData::argument_doc_from_arginfo(argument, arginfo); - - signal.arguments.push_back(argument); + if (default_value_valid && default_value.get_type() != Variant::OBJECT) { + prop.default_value = default_value.get_construct_string().replace("\n", " "); } - c.signals.push_back(signal); + StringName setter = ClassDB::get_property_setter(name, E.name); + StringName getter = ClassDB::get_property_getter(name, E.name); + + prop.setter = setter; + prop.getter = getter; + + bool found_type = false; + if (getter != StringName()) { + MethodBind *mb = ClassDB::get_method(name, getter); + if (mb) { + PropertyInfo retinfo = mb->get_return_info(); + + found_type = true; + if (retinfo.type == Variant::INT && retinfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { + prop.enumeration = retinfo.class_name; + prop.type = "int"; + } else if (retinfo.class_name != StringName()) { + prop.type = retinfo.class_name; + } else if (retinfo.type == Variant::ARRAY && retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) { + prop.type = retinfo.hint_string + "[]"; + } else if (retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { + prop.type = retinfo.hint_string; + } else if (retinfo.type == Variant::NIL && retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { + prop.type = "Variant"; + } else if (retinfo.type == Variant::NIL) { + prop.type = "void"; + } else { + prop.type = Variant::get_type_name(retinfo.type); + } + } + + setters_getters.insert(getter); + } + + if (setter != StringName()) { + setters_getters.insert(setter); + } + + if (!found_type) { + if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_RESOURCE_TYPE) { + prop.type = E.hint_string; + } else { + prop.type = Variant::get_type_name(E.type); + } + } + + c.properties.push_back(prop); } + + List method_list; + ClassDB::get_method_list(name, &method_list, true); + method_list.sort(); + + for (const MethodInfo &E : method_list) { + if (E.name.is_empty() || (E.name[0] == '_' && !(E.flags & METHOD_FLAG_VIRTUAL))) { + continue; //hidden, don't count + } + + if (skip_setter_getter_methods && setters_getters.has(E.name)) { + // Don't skip parametric setters and getters, i.e. method which require + // one or more parameters to define what property should be set or retrieved. + // E.g. CPUParticles3D::set_param(Parameter param, float value). + if (E.arguments.size() == 0 /* getter */ || (E.arguments.size() == 1 && E.return_val.type == Variant::NIL /* setter */)) { + continue; + } + } + + DocData::MethodDoc method; + DocData::method_doc_from_methodinfo(method, E, ""); + + Vector errs = ClassDB::get_method_error_return_values(name, E.name); + if (errs.size()) { + if (!errs.has(OK)) { + errs.insert(0, OK); + } + for (int i = 0; i < errs.size(); i++) { + if (!method.errors_returned.has(errs[i])) { + method.errors_returned.push_back(errs[i]); + } + } + } + + c.methods.push_back(method); + } + + List signal_list; + ClassDB::get_signal_list(name, &signal_list, true); + + if (signal_list.size()) { + for (List::Element *EV = signal_list.front(); EV; EV = EV->next()) { + DocData::MethodDoc signal; + signal.name = EV->get().name; + for (int i = 0; i < EV->get().arguments.size(); i++) { + const PropertyInfo &arginfo = EV->get().arguments[i]; + DocData::ArgumentDoc argument; + DocData::argument_doc_from_arginfo(argument, arginfo); + + signal.arguments.push_back(argument); + } + + c.signals.push_back(signal); + } + } + + List constant_list; + ClassDB::get_integer_constant_list(name, &constant_list, true); + + for (const String &E : constant_list) { + DocData::ConstantDoc constant; + constant.name = E; + constant.value = itos(ClassDB::get_integer_constant(name, E)); + constant.is_value_valid = true; + constant.enumeration = ClassDB::get_integer_constant_enum(name, E); + constant.is_bitfield = ClassDB::is_enum_bitfield(name, constant.enumeration); + c.constants.push_back(constant); + } + + // Theme items. + { + List l; + + Theme::get_default()->get_color_list(cname, &l); + for (const StringName &E : l) { + DocData::ThemeItemDoc tid; + tid.name = E; + tid.type = "Color"; + tid.data_type = "color"; + tid.default_value = Variant(Theme::get_default()->get_color(E, cname)).get_construct_string().replace("\n", " "); + c.theme_properties.push_back(tid); + } + + l.clear(); + Theme::get_default()->get_constant_list(cname, &l); + for (const StringName &E : l) { + DocData::ThemeItemDoc tid; + tid.name = E; + tid.type = "int"; + tid.data_type = "constant"; + tid.default_value = itos(Theme::get_default()->get_constant(E, cname)); + c.theme_properties.push_back(tid); + } + + l.clear(); + Theme::get_default()->get_font_list(cname, &l); + for (const StringName &E : l) { + DocData::ThemeItemDoc tid; + tid.name = E; + tid.type = "Font"; + tid.data_type = "font"; + c.theme_properties.push_back(tid); + } + + l.clear(); + Theme::get_default()->get_font_size_list(cname, &l); + for (const StringName &E : l) { + DocData::ThemeItemDoc tid; + tid.name = E; + tid.type = "int"; + tid.data_type = "font_size"; + c.theme_properties.push_back(tid); + } + + l.clear(); + Theme::get_default()->get_icon_list(cname, &l); + for (const StringName &E : l) { + DocData::ThemeItemDoc tid; + tid.name = E; + tid.type = "Texture2D"; + tid.data_type = "icon"; + c.theme_properties.push_back(tid); + } + + l.clear(); + Theme::get_default()->get_stylebox_list(cname, &l); + for (const StringName &E : l) { + DocData::ThemeItemDoc tid; + tid.name = E; + tid.type = "StyleBox"; + tid.data_type = "style"; + c.theme_properties.push_back(tid); + } + + c.theme_properties.sort(); + } + + classes.pop_front(); } - - List constant_list; - ClassDB::get_integer_constant_list(name, &constant_list, true); - - for (const String &E : constant_list) { - DocData::ConstantDoc constant; - constant.name = E; - constant.value = itos(ClassDB::get_integer_constant(name, E)); - constant.is_value_valid = true; - constant.enumeration = ClassDB::get_integer_constant_enum(name, E); - constant.is_bitfield = ClassDB::is_enum_bitfield(name, constant.enumeration); - c.constants.push_back(constant); - } - - // Theme items. - { - List l; - - Theme::get_default()->get_color_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "Color"; - tid.data_type = "color"; - tid.default_value = Variant(Theme::get_default()->get_color(E, cname)).get_construct_string().replace("\n", " "); - c.theme_properties.push_back(tid); - } - - l.clear(); - Theme::get_default()->get_constant_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "int"; - tid.data_type = "constant"; - tid.default_value = itos(Theme::get_default()->get_constant(E, cname)); - c.theme_properties.push_back(tid); - } - - l.clear(); - Theme::get_default()->get_font_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "Font"; - tid.data_type = "font"; - c.theme_properties.push_back(tid); - } - - l.clear(); - Theme::get_default()->get_font_size_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "int"; - tid.data_type = "font_size"; - c.theme_properties.push_back(tid); - } - - l.clear(); - Theme::get_default()->get_icon_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "Texture2D"; - tid.data_type = "icon"; - c.theme_properties.push_back(tid); - } - - l.clear(); - Theme::get_default()->get_stylebox_list(cname, &l); - for (const StringName &E : l) { - DocData::ThemeItemDoc tid; - tid.name = E; - tid.type = "StyleBox"; - tid.data_type = "style"; - c.theme_properties.push_back(tid); - } - - c.theme_properties.sort(); - } - - classes.pop_front(); } + // Add a dummy Variant entry. { - // So we can document the concept of Variant even if it's not a usable class per se. + // This allows us to document the concept of Variant even though + // it's not a ClassDB-exposed class. class_list["Variant"] = DocData::ClassDoc(); class_list["Variant"].name = "Variant"; } + // If we don't want to populate basic types, break here. if (!p_basic_types) { return; } - // Add Variant types. + // Add Variant data types. for (int i = 0; i < Variant::VARIANT_MAX; i++) { if (i == Variant::NIL) { continue; // Not exposed outside of 'null', should not be in class list. @@ -809,14 +816,14 @@ void DocTools::generate(bool p_basic_types) { } } - //built in constants and functions - + // Add global API (servers, engine singletons, global constants) and Variant utility functions. { String cname = "@GlobalScope"; class_list[cname] = DocData::ClassDoc(); DocData::ClassDoc &c = class_list[cname]; c.name = cname; + // Global constants. for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { DocData::ConstantDoc cd; cd.name = CoreConstants::get_global_constant_name(i); @@ -830,10 +837,11 @@ void DocTools::generate(bool p_basic_types) { c.constants.push_back(cd); } + // Servers/engine singletons. List singletons; Engine::get_singleton()->get_singletons(&singletons); - //servers (this is kind of hackish) + // FIXME: this is kind of hackish... for (const Engine::Singleton &s : singletons) { DocData::PropertyDoc pd; if (!s.ptr) { @@ -847,13 +855,14 @@ void DocTools::generate(bool p_basic_types) { c.properties.push_back(pd); } + // Variant utility functions. List utility_functions; Variant::get_utility_function_list(&utility_functions); utility_functions.sort_custom(); for (const StringName &E : utility_functions) { DocData::MethodDoc md; md.name = E; - //return + // Utility function's return type. if (Variant::has_utility_function_return_value(E)) { PropertyInfo pi; pi.type = Variant::get_utility_function_return_type(E); @@ -865,6 +874,7 @@ void DocTools::generate(bool p_basic_types) { md.return_type = ad.type; } + // Utility function's arguments. if (Variant::is_utility_function_vararg(E)) { md.qualifiers = "vararg"; } else { @@ -885,11 +895,10 @@ void DocTools::generate(bool p_basic_types) { } } - // Built-in script reference. - // We only add a doc entry for languages which actually define any built-in - // methods or constants. - + // Add scripting language built-ins. { + // We only add a doc entry for languages which actually define any built-in + // methods, constants, or annotations. for (int i = 0; i < ScriptServer::get_language_count(); i++) { ScriptLanguage *lang = ScriptServer::get_language(i); String cname = "@" + lang->get_name(); diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h index 0722fb800ed..90f6d7f7e8a 100644 --- a/modules/gdscript/tests/gdscript_test_runner_suite.h +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -69,6 +69,40 @@ func _init(): CHECK_MESSAGE(int(ref_counted->get_meta("result")) == 42, "The script should assign object metadata successfully."); } +TEST_CASE("[Modules][GDScript] Validate built-in API") { + GDScriptLanguage *lang = GDScriptLanguage::get_singleton(); + + // Validate methods. + List builtin_methods; + lang->get_public_functions(&builtin_methods); + + SUBCASE("[Modules][GDScript] Validate built-in methods") { + for (const MethodInfo &mi : builtin_methods) { + for (int j = 0; j < mi.arguments.size(); j++) { + PropertyInfo arg = mi.arguments[j]; + + TEST_COND((arg.name.is_empty() || arg.name.begins_with("_unnamed_arg")), + vformat("Unnamed argument in position %d of built-in method '%s'.", j, mi.name)); + } + } + } + + // Validate annotations. + List builtin_annotations; + lang->get_public_annotations(&builtin_annotations); + + SUBCASE("[Modules][GDScript] Validate built-in annotations") { + for (const MethodInfo &ai : builtin_annotations) { + for (int j = 0; j < ai.arguments.size(); j++) { + PropertyInfo arg = ai.arguments[j]; + + TEST_COND((arg.name.is_empty() || arg.name.begins_with("_unnamed_arg")), + vformat("Unnamed argument in position %d of built-in annotation '%s'.", j, ai.name)); + } + } + } +} + } // namespace GDScriptTests #endif // GDSCRIPT_TEST_RUNNER_SUITE_H diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 636c9e26a55..09ec0865648 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -2124,7 +2124,7 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::set_method_call_mode); ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::get_method_call_mode); - ClassDB::bind_method(D_METHOD("set_movie_quit_on_finish_enabled"), &AnimationPlayer::set_movie_quit_on_finish_enabled); + ClassDB::bind_method(D_METHOD("set_movie_quit_on_finish_enabled", "enabled"), &AnimationPlayer::set_movie_quit_on_finish_enabled); ClassDB::bind_method(D_METHOD("is_movie_quit_on_finish_enabled"), &AnimationPlayer::is_movie_quit_on_finish_enabled); ClassDB::bind_method(D_METHOD("get_current_animation_position"), &AnimationPlayer::get_current_animation_position); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 942b7d0bf95..f075510aa4c 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -387,7 +387,7 @@ String ConfirmationDialog::get_cancel_button_text() const { void ConfirmationDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cancel_button"), &ConfirmationDialog::get_cancel_button); - ClassDB::bind_method(D_METHOD("set_cancel_button_text"), &ConfirmationDialog::set_cancel_button_text); + ClassDB::bind_method(D_METHOD("set_cancel_button_text", "text"), &ConfirmationDialog::set_cancel_button_text); ClassDB::bind_method(D_METHOD("get_cancel_button_text"), &ConfirmationDialog::get_cancel_button_text); ADD_PROPERTY(PropertyInfo(Variant::STRING, "cancel_button_text"), "set_cancel_button_text", "get_cancel_button_text"); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index c58513df172..c6d011d4ef2 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -498,7 +498,7 @@ void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_selected_id"), &OptionButton::get_selected_id); ClassDB::bind_method(D_METHOD("get_selected_metadata"), &OptionButton::get_selected_metadata); ClassDB::bind_method(D_METHOD("remove_item", "idx"), &OptionButton::remove_item); - ClassDB::bind_method(D_METHOD("_select_int"), &OptionButton::_select_int); + ClassDB::bind_method(D_METHOD("_select_int", "idx"), &OptionButton::_select_int); ClassDB::bind_method(D_METHOD("get_popup"), &OptionButton::get_popup); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index c023b068958..3755a8fa349 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -5294,7 +5294,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_v_scroll_speed", "speed"), &TextEdit::set_v_scroll_speed); ClassDB::bind_method(D_METHOD("get_v_scroll_speed"), &TextEdit::get_v_scroll_speed); - ClassDB::bind_method(D_METHOD("set_fit_content_height_enabled"), &TextEdit::set_fit_content_height_enabled); + ClassDB::bind_method(D_METHOD("set_fit_content_height_enabled", "enabled"), &TextEdit::set_fit_content_height_enabled); ClassDB::bind_method(D_METHOD("is_fit_content_height_enabled"), &TextEdit::is_fit_content_height_enabled); ClassDB::bind_method(D_METHOD("get_scroll_pos_for_line", "line", "wrap_index"), &TextEdit::get_scroll_pos_for_line, DEFVAL(0)); diff --git a/scene/main/resource_preloader.cpp b/scene/main/resource_preloader.cpp index 5512d0a84e5..a9b0285723b 100644 --- a/scene/main/resource_preloader.cpp +++ b/scene/main/resource_preloader.cpp @@ -138,7 +138,7 @@ void ResourcePreloader::get_resource_list(List *p_list) { } void ResourcePreloader::_bind_methods() { - ClassDB::bind_method(D_METHOD("_set_resources"), &ResourcePreloader::_set_resources); + ClassDB::bind_method(D_METHOD("_set_resources", "resources"), &ResourcePreloader::_set_resources); ClassDB::bind_method(D_METHOD("_get_resources"), &ResourcePreloader::_get_resources); ClassDB::bind_method(D_METHOD("add_resource", "name", "resource"), &ResourcePreloader::add_resource); diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index 1ff72825ac0..bef431e9808 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -669,7 +669,7 @@ void BitMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_size"), &BitMap::get_size); ClassDB::bind_method(D_METHOD("resize", "new_size"), &BitMap::resize); - ClassDB::bind_method(D_METHOD("_set_data"), &BitMap::_set_data); + ClassDB::bind_method(D_METHOD("_set_data", "data"), &BitMap::_set_data); ClassDB::bind_method(D_METHOD("_get_data"), &BitMap::_get_data); ClassDB::bind_method(D_METHOD("grow_mask", "pixels", "rect"), &BitMap::grow_mask); diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index da26a0261fd..5de92acb752 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -1190,7 +1190,7 @@ void Curve2D::_bind_methods() { ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve2D::tessellate, DEFVAL(5), DEFVAL(4)); ClassDB::bind_method(D_METHOD("_get_data"), &Curve2D::_get_data); - ClassDB::bind_method(D_METHOD("_set_data"), &Curve2D::_set_data); + ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve2D::_set_data); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); @@ -2002,7 +2002,7 @@ void Curve3D::_bind_methods() { ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve3D::tessellate, DEFVAL(5), DEFVAL(4)); ClassDB::bind_method(D_METHOD("_get_data"), &Curve3D::_get_data); - ClassDB::bind_method(D_METHOD("_set_data"), &Curve3D::_set_data); + ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve3D::_set_data); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp index e5fc61ade5c..ff4a7a45606 100644 --- a/scene/resources/multimesh.cpp +++ b/scene/resources/multimesh.cpp @@ -340,13 +340,13 @@ void MultiMesh::_bind_methods() { #ifndef DISABLE_DEPRECATED // Kept for compatibility from 3.x to 4.0. - ClassDB::bind_method(D_METHOD("_set_transform_array"), &MultiMesh::_set_transform_array); + ClassDB::bind_method(D_METHOD("_set_transform_array", "array"), &MultiMesh::_set_transform_array); ClassDB::bind_method(D_METHOD("_get_transform_array"), &MultiMesh::_get_transform_array); - ClassDB::bind_method(D_METHOD("_set_transform_2d_array"), &MultiMesh::_set_transform_2d_array); + ClassDB::bind_method(D_METHOD("_set_transform_2d_array", "array"), &MultiMesh::_set_transform_2d_array); ClassDB::bind_method(D_METHOD("_get_transform_2d_array"), &MultiMesh::_get_transform_2d_array); - ClassDB::bind_method(D_METHOD("_set_color_array"), &MultiMesh::_set_color_array); + ClassDB::bind_method(D_METHOD("_set_color_array", "array"), &MultiMesh::_set_color_array); ClassDB::bind_method(D_METHOD("_get_color_array"), &MultiMesh::_get_color_array); - ClassDB::bind_method(D_METHOD("_set_custom_data_array"), &MultiMesh::_set_custom_data_array); + ClassDB::bind_method(D_METHOD("_set_custom_data_array", "array"), &MultiMesh::_set_custom_data_array); ClassDB::bind_method(D_METHOD("_get_custom_data_array"), &MultiMesh::_get_custom_data_array); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "transform_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_transform_array", "_get_transform_array"); diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index ac67e6e5e9f..0e1b18d584f 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -1779,7 +1779,7 @@ void PackedScene::_bind_methods() { ClassDB::bind_method(D_METHOD("pack", "path"), &PackedScene::pack); ClassDB::bind_method(D_METHOD("instantiate", "edit_state"), &PackedScene::instantiate, DEFVAL(GEN_EDIT_STATE_DISABLED)); ClassDB::bind_method(D_METHOD("can_instantiate"), &PackedScene::can_instantiate); - ClassDB::bind_method(D_METHOD("_set_bundled_scene"), &PackedScene::_set_bundled_scene); + ClassDB::bind_method(D_METHOD("_set_bundled_scene", "scene"), &PackedScene::_set_bundled_scene); ClassDB::bind_method(D_METHOD("_get_bundled_scene"), &PackedScene::_get_bundled_scene); ClassDB::bind_method(D_METHOD("get_state"), &PackedScene::get_state); diff --git a/scene/resources/polygon_path_finder.cpp b/scene/resources/polygon_path_finder.cpp index 29135e30c99..5e18671c11a 100644 --- a/scene/resources/polygon_path_finder.cpp +++ b/scene/resources/polygon_path_finder.cpp @@ -553,7 +553,7 @@ void PolygonPathFinder::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_penalty", "idx"), &PolygonPathFinder::get_point_penalty); ClassDB::bind_method(D_METHOD("get_bounds"), &PolygonPathFinder::get_bounds); - ClassDB::bind_method(D_METHOD("_set_data"), &PolygonPathFinder::_set_data); + ClassDB::bind_method(D_METHOD("_set_data", "data"), &PolygonPathFinder::_set_data); ClassDB::bind_method(D_METHOD("_get_data"), &PolygonPathFinder::_get_data); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index 55c9d7397d5..3533e86c3a8 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -207,7 +207,7 @@ void SpriteFrames::_bind_methods() { // `animations` property is for serialization. - ClassDB::bind_method(D_METHOD("_set_animations"), &SpriteFrames::_set_animations); + ClassDB::bind_method(D_METHOD("_set_animations", "animations"), &SpriteFrames::_set_animations); ClassDB::bind_method(D_METHOD("_get_animations"), &SpriteFrames::_get_animations); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_animations", "_get_animations"); diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h index fc329ba0eb0..208923edb93 100644 --- a/tests/core/object/test_class_db.h +++ b/tests/core/object/test_class_db.h @@ -71,6 +71,7 @@ struct ArgumentData { String name; bool has_defval = false; Variant defval; + int position; }; struct MethodData { @@ -371,6 +372,39 @@ void validate_property(const Context &p_context, const ExposedClass &p_class, co } } +void validate_argument(const Context &p_context, const ExposedClass &p_class, const String &p_owner_name, const String &p_owner_type, const ArgumentData &p_arg) { + TEST_COND((p_arg.name.is_empty() || p_arg.name.begins_with("_unnamed_arg")), + vformat("Unnamed argument in position %d of %s '%s.%s'.", p_arg.position, p_owner_type, p_class.name, p_owner_name)); + + const ExposedClass *arg_class = p_context.find_exposed_class(p_arg.type); + if (arg_class) { + TEST_COND(arg_class->is_singleton, + vformat("Argument type is a singleton: '%s' of %s '%s.%s'.", p_arg.name, p_owner_type, p_class.name, p_owner_name)); + + if (p_class.api_type == ClassDB::API_CORE) { + TEST_COND(arg_class->api_type == ClassDB::API_EDITOR, + vformat("Argument '%s' of %s '%s.%s' has type '%s' from the editor API. Core API cannot have dependencies on the editor API.", + p_arg.name, p_owner_type, p_class.name, p_owner_name, arg_class->name)); + } + } else { + // Look for types that don't inherit Object. + TEST_FAIL_COND(!p_context.has_type(p_arg.type), + vformat("Argument type '%s' not found: '%s' of %s '%s.%s'.", p_arg.type.name, p_arg.name, p_owner_type, p_class.name, p_owner_name)); + } + + if (p_arg.has_defval) { + String type_error_msg; + bool arg_defval_assignable_to_type = arg_default_value_is_assignable_to_type(p_context, p_arg.defval, p_arg.type, &type_error_msg); + + String err_msg = vformat("Invalid default value for parameter '%s' of %s '%s.%s'.", p_arg.name, p_owner_type, p_class.name, p_owner_name); + if (!type_error_msg.is_empty()) { + err_msg += " " + type_error_msg; + } + + TEST_COND(!arg_defval_assignable_to_type, err_msg.utf8().get_data()); + } +} + void validate_method(const Context &p_context, const ExposedClass &p_class, const MethodData &p_method) { if (p_method.return_type.name != StringName()) { const ExposedClass *return_class = p_context.find_exposed_class(p_method.return_type); @@ -392,54 +426,14 @@ void validate_method(const Context &p_context, const ExposedClass &p_class, cons for (const ArgumentData &F : p_method.arguments) { const ArgumentData &arg = F; - - const ExposedClass *arg_class = p_context.find_exposed_class(arg.type); - if (arg_class) { - TEST_COND(arg_class->is_singleton, - "Argument type is a singleton: '", arg.name, "' of method '", p_class.name, ".", p_method.name, "'."); - - if (p_class.api_type == ClassDB::API_CORE) { - TEST_COND(arg_class->api_type == ClassDB::API_EDITOR, - "Argument '", arg.name, "' of method '", p_class.name, ".", p_method.name, "' has type '", - arg_class->name, "' from the editor API. Core API cannot have dependencies on the editor API."); - } - } else { - // Look for types that don't inherit Object - TEST_FAIL_COND(!p_context.has_type(arg.type), - "Argument type '", arg.type.name, "' not found: '", arg.name, "' of method", p_class.name, ".", p_method.name, "'."); - } - - if (arg.has_defval) { - String type_error_msg; - bool arg_defval_assignable_to_type = arg_default_value_is_assignable_to_type(p_context, arg.defval, arg.type, &type_error_msg); - String err_msg = vformat("Invalid default value for parameter '%s' of method '%s.%s'.", arg.name, p_class.name, p_method.name); - if (!type_error_msg.is_empty()) { - err_msg += " " + type_error_msg; - } - TEST_COND(!arg_defval_assignable_to_type, err_msg.utf8().get_data()); - } + validate_argument(p_context, p_class, p_method.name, "method", arg); } } void validate_signal(const Context &p_context, const ExposedClass &p_class, const SignalData &p_signal) { for (const ArgumentData &F : p_signal.arguments) { const ArgumentData &arg = F; - - const ExposedClass *arg_class = p_context.find_exposed_class(arg.type); - if (arg_class) { - TEST_COND(arg_class->is_singleton, - "Argument class is a singleton: '", arg.name, "' of signal '", p_class.name, ".", p_signal.name, "'."); - - if (p_class.api_type == ClassDB::API_CORE) { - TEST_COND(arg_class->api_type == ClassDB::API_EDITOR, - "Argument '", arg.name, "' of signal '", p_class.name, ".", p_signal.name, "' has type '", - arg_class->name, "' from the editor API. Core API cannot have dependencies on the editor API."); - } - } else { - // Look for types that don't inherit Object - TEST_FAIL_COND(!p_context.has_type(arg.type), - "Argument type '", arg.type.name, "' not found: '", arg.name, "' of signal", p_class.name, ".", p_signal.name, "'."); - } + validate_argument(p_context, p_class, p_signal.name, "signal", arg); } } @@ -625,6 +619,7 @@ void add_exposed_classes(Context &r_context) { ArgumentData arg; arg.name = orig_arg_name; + arg.position = i; if (arg_info.type == Variant::INT && arg_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { arg.type.name = arg_info.class_name; @@ -693,6 +688,7 @@ void add_exposed_classes(Context &r_context) { ArgumentData arg; arg.name = orig_arg_name; + arg.position = i; if (arg_info.type == Variant::INT && arg_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { arg.type.name = arg_info.class_name; @@ -841,7 +837,7 @@ TEST_SUITE("[ClassDB]") { add_builtin_types(context); add_global_enums(context); - SUBCASE("[ClassDB] Find exposed class") { + SUBCASE("[ClassDB] Validate exposed classes") { const ExposedClass *object_class = context.find_exposed_class(context.names_cache.object_class); TEST_FAIL_COND(!object_class, "Object class not found."); TEST_FAIL_COND(object_class->base != StringName(), diff --git a/tests/core/variant/test_variant.h b/tests/core/variant/test_variant.h index 916686d7c15..d6799928b48 100644 --- a/tests/core/variant/test_variant.h +++ b/tests/core/variant/test_variant.h @@ -719,6 +719,7 @@ TEST_CASE("[Variant] Assignment To Color from Bool,Int,Float,String,Vec2,Vec2i,V vec3i_v = col_v; CHECK(vec3i_v.get_type() == Variant::COLOR); } + TEST_CASE("[Variant] Writer and parser array") { Array a = build_array(1, String("hello"), build_array(Variant())); String a_str; @@ -911,6 +912,67 @@ TEST_CASE("[Variant] Nested dictionary comparison") { CHECK_FALSE(v_d1 == v_d_other_val); } +struct ArgumentData { + Variant::Type type; + String name; + bool has_defval = false; + Variant defval; + int position; +}; + +struct MethodData { + StringName name; + Variant::Type return_type; + List arguments; + bool is_virtual = false; + bool is_vararg = false; +}; + +TEST_CASE("[Variant] Utility functions") { + List functions; + + List function_names; + Variant::get_utility_function_list(&function_names); + function_names.sort_custom(); + + for (const StringName &E : function_names) { + MethodData md; + md.name = E; + + // Utility function's return type. + if (Variant::has_utility_function_return_value(E)) { + md.return_type = Variant::get_utility_function_return_type(E); + } + + // Utility function's arguments. + if (Variant::is_utility_function_vararg(E)) { + md.is_vararg = true; + } else { + for (int i = 0; i < Variant::get_utility_function_argument_count(E); i++) { + ArgumentData arg; + arg.type = Variant::get_utility_function_argument_type(E, i); + arg.name = Variant::get_utility_function_argument_name(E, i); + arg.position = i; + + md.arguments.push_back(arg); + } + } + + functions.push_back(md); + } + + SUBCASE("[Variant] Validate utility functions") { + for (const MethodData &E : functions) { + for (const ArgumentData &F : E.arguments) { + const ArgumentData &arg = F; + + TEST_COND((arg.name.is_empty() || arg.name.begins_with("_unnamed_arg")), + vformat("Unnamed argument in position %d of function '%s'.", arg.position, E.name)); + } + } + } +} + } // namespace TestVariant #endif // TEST_VARIANT_H