From 40b7dd0cf3657fa4a90fee2ca54d87145cb8ab66 Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Fri, 23 Feb 2024 18:29:37 +0100 Subject: [PATCH] C#: Add deprecated message to generated bindings Uses the `DocData` deprecated message in the C# `[Obsolete]` attribute added to deprecated members. --- modules/mono/editor/bindings_generator.cpp | 671 +++++++++++++++++++-- modules/mono/editor/bindings_generator.h | 21 +- 2 files changed, 653 insertions(+), 39 deletions(-) diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 675651ccf77..7960a1ad5bb 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -150,8 +150,268 @@ static String fix_doc_description(const String &p_bbcode) { .strip_edges(); } +String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInterface *p_itype) { + // Based on the version in EditorHelp. + + if (p_bbcode.is_empty()) { + return String(); + } + + DocTools *doc = EditorHelp::get_doc_data(); + + String bbcode = p_bbcode; + + StringBuilder output; + + List tag_stack; + bool code_tag = false; + + int pos = 0; + while (pos < bbcode.length()) { + int brk_pos = bbcode.find("[", pos); + + if (brk_pos < 0) { + brk_pos = bbcode.length(); + } + + if (brk_pos > pos) { + String text = bbcode.substr(pos, brk_pos - pos); + if (code_tag || tag_stack.size() > 0) { + output.append("'" + text + "'"); + } else { + output.append(text); + } + } + + if (brk_pos == bbcode.length()) { + // Nothing else to add. + break; + } + + int brk_end = bbcode.find("]", brk_pos + 1); + + if (brk_end == -1) { + String text = bbcode.substr(brk_pos, bbcode.length() - brk_pos); + if (code_tag || tag_stack.size() > 0) { + output.append("'" + text + "'"); + } + + break; + } + + String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1); + + if (tag.begins_with("/")) { + bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1, tag.length()); + + if (!tag_ok) { + output.append("]"); + pos = brk_pos + 1; + continue; + } + + tag_stack.pop_front(); + pos = brk_end + 1; + code_tag = false; + } else if (code_tag) { + output.append("["); + pos = brk_pos + 1; + } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) { + const int tag_end = tag.find(" "); + const String link_tag = tag.substr(0, tag_end); + const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" "); + + const Vector link_target_parts = link_target.split("."); + + if (link_target_parts.size() <= 0 || link_target_parts.size() > 2) { + ERR_PRINT("Invalid reference format: '" + tag + "'."); + + output.append(tag); + + pos = brk_end + 1; + continue; + } + + const TypeInterface *target_itype; + StringName target_cname; + + if (link_target_parts.size() == 2) { + target_itype = _get_type_or_null(TypeReference(link_target_parts[0])); + if (!target_itype) { + target_itype = _get_type_or_null(TypeReference("_" + link_target_parts[0])); + } + target_cname = link_target_parts[1]; + } else { + target_itype = p_itype; + target_cname = link_target_parts[0]; + } + + if (link_tag == "method") { + _append_text_method(output, target_itype, target_cname, link_target, link_target_parts); + } else if (link_tag == "constructor") { + // TODO: Support constructors? + _append_text_undeclared(output, link_target); + } else if (link_tag == "operator") { + // TODO: Support operators? + _append_text_undeclared(output, link_target); + } else if (link_tag == "member") { + _append_text_member(output, target_itype, target_cname, link_target, link_target_parts); + } else if (link_tag == "signal") { + _append_text_signal(output, target_itype, target_cname, link_target, link_target_parts); + } else if (link_tag == "enum") { + _append_text_enum(output, target_itype, target_cname, link_target, link_target_parts); + } else if (link_tag == "constant") { + _append_text_constant(output, target_itype, target_cname, link_target, link_target_parts); + } else if (link_tag == "param") { + _append_text_param(output, link_target); + } else if (link_tag == "theme_item") { + // We do not declare theme_items in any way in C#, so there is nothing to reference. + _append_text_undeclared(output, link_target); + } + + pos = brk_end + 1; + } else if (doc->class_list.has(tag)) { + if (tag == "Array" || tag == "Dictionary") { + output.append("'" BINDINGS_NAMESPACE_COLLECTIONS "."); + output.append(tag); + output.append("'"); + } else if (tag == "bool" || tag == "int") { + output.append(tag); + } else if (tag == "float") { + output.append( +#ifdef REAL_T_IS_DOUBLE + "double" +#else + "float" +#endif + ); + } else if (tag == "Variant") { + output.append("'Godot.Variant'"); + } else if (tag == "String") { + output.append("string"); + } else if (tag == "Nil") { + output.append("null"); + } else if (tag.begins_with("@")) { + // @GlobalScope, @GDScript, etc. + output.append("'" + tag + "'"); + } else if (tag == "PackedByteArray") { + output.append("byte[]"); + } else if (tag == "PackedInt32Array") { + output.append("int[]"); + } else if (tag == "PackedInt64Array") { + output.append("long[]"); + } else if (tag == "PackedFloat32Array") { + output.append("float[]"); + } else if (tag == "PackedFloat64Array") { + output.append("double[]"); + } else if (tag == "PackedStringArray") { + output.append("string[]"); + } else if (tag == "PackedVector2Array") { + output.append("'" BINDINGS_NAMESPACE ".Vector2[]'"); + } else if (tag == "PackedVector3Array") { + output.append("'" BINDINGS_NAMESPACE ".Vector3[]'"); + } else if (tag == "PackedColorArray") { + output.append("'" BINDINGS_NAMESPACE ".Color[]'"); + } else { + const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag)); + + if (!target_itype) { + target_itype = _get_type_or_null(TypeReference("_" + tag)); + } + + if (target_itype) { + output.append("'" + target_itype->proxy_name + "'"); + } else { + ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'."); + output.append("'" + tag + "'"); + } + } + + pos = brk_end + 1; + } else if (tag == "b") { + // Bold is not supported. + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "i") { + // Italic is not supported. + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "code" || tag.begins_with("code ")) { + code_tag = true; + pos = brk_end + 1; + tag_stack.push_front("code"); + } else if (tag == "kbd") { + // Keyboard combinations are not supported. + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "center") { + // Center alignment is not supported. + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "br") { + // Break is not supported. + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "u") { + // Underline is not supported. + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "s") { + // Strikethrough is not supported. + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "url") { + int end = bbcode.find("[", brk_end); + if (end == -1) { + end = bbcode.length(); + } + String url = bbcode.substr(brk_end + 1, end - brk_end - 1); + // Not supported. Just append the url. + output.append(url); + + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag.begins_with("url=")) { + String url = tag.substr(4, tag.length()); + // Not supported. Just append the url. + output.append(url); + + pos = brk_end + 1; + tag_stack.push_front("url"); + } else if (tag == "img") { + int end = bbcode.find("[", brk_end); + if (end == -1) { + end = bbcode.length(); + } + String image = bbcode.substr(brk_end + 1, end - brk_end - 1); + + // Not supported. Just append the bbcode. + output.append("[img]"); + output.append(image); + output.append("[/img]"); + + pos = end; + tag_stack.push_front(tag); + } else if (tag.begins_with("color=")) { + // Not supported. + pos = brk_end + 1; + tag_stack.push_front("color"); + } else if (tag.begins_with("font=")) { + // Not supported. + pos = brk_end + 1; + tag_stack.push_front("font"); + } else { + // Ignore unrecognized tag. + output.append("["); + pos = brk_pos + 1; + } + } + + return output.as_string(); +} + String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal) { - // Based on the version in EditorHelp + // Based on the version in EditorHelp. if (p_bbcode.is_empty()) { return String(); @@ -200,7 +460,8 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } if (brk_pos == bbcode.length()) { - break; // nothing else to add + // Nothing else to add. + break; } int brk_end = bbcode.find("]", brk_pos + 1); @@ -316,7 +577,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } else if (link_tag == "param") { _append_xml_param(xml_output, link_target, p_is_signal); } else if (link_tag == "theme_item") { - // We do not declare theme_items in any way in C#, so there is nothing to reference + // We do not declare theme_items in any way in C#, so there is nothing to reference. _append_xml_undeclared(xml_output, link_target); } @@ -345,7 +606,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } else if (tag == "Nil") { xml_output.append(""); } else if (tag.begins_with("@")) { - // @GlobalScope, @GDScript, etc + // @GlobalScope, @GDScript, etc. xml_output.append(""); xml_output.append(tag); xml_output.append(""); @@ -432,22 +693,22 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf pos = brk_end + 1; tag_stack.push_front("csharp"); } else if (tag == "kbd") { - // keyboard combinations are not supported in xml comments + // Keyboard combinations are not supported in xml comments. pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "center") { - // center alignment is not supported in xml comments + // Center alignment is not supported in xml comments. pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "br") { xml_output.append("\n"); // FIXME: Should use instead. Luckily this tag isn't used for now. pos = brk_end + 1; } else if (tag == "u") { - // underline is not supported in Rider xml comments + // Underline is not supported in Rider xml comments. pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "s") { - // strikethrough is not supported in xml comments + // Strikethrough is not supported in xml comments. pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "url") { @@ -495,7 +756,8 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf tag_stack.push_front("font"); } else { if (!line_del) { - xml_output.append("["); // ignore + // Ignore unrecognized tag. + xml_output.append("["); } pos = brk_pos + 1; } @@ -506,6 +768,285 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf return xml_output.as_string(); } +void BindingsGenerator::_append_text_method(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts) { + if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) { + if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Cannot resolve @GlobalScope method reference in documentation: %s\n", p_link_target.utf8().get_data()); + } + + // TODO Map what we can + _append_text_undeclared(p_output, p_link_target); + } else if (!p_target_itype || !p_target_itype->is_object_type) { + if (OS::get_singleton()->is_stdout_verbose()) { + if (p_target_itype) { + OS::get_singleton()->print("Cannot resolve method reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data()); + } else { + OS::get_singleton()->print("Cannot resolve type from method reference in documentation: %s\n", p_link_target.utf8().get_data()); + } + } + + // TODO Map what we can + _append_text_undeclared(p_output, p_link_target); + } else { + if (p_target_cname == "_init") { + // The _init method is not declared in C#, reference the constructor instead + p_output.append("'new " BINDINGS_NAMESPACE "."); + p_output.append(p_target_itype->proxy_name); + p_output.append("()'"); + } else { + const MethodInterface *target_imethod = p_target_itype->find_method_by_name(p_target_cname); + + if (target_imethod) { + p_output.append("'" BINDINGS_NAMESPACE "."); + p_output.append(p_target_itype->proxy_name); + p_output.append("."); + p_output.append(target_imethod->proxy_name); + p_output.append("("); + bool first_key = true; + for (const ArgumentInterface &iarg : target_imethod->arguments) { + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + + if (first_key) { + first_key = false; + } else { + p_output.append(", "); + } + if (!arg_type) { + ERR_PRINT("Cannot resolve argument type in documentation: '" + p_link_target + "'."); + p_output.append(iarg.type.cname); + continue; + } + if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) { + p_output.append("Nullable<"); + } + String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters); + p_output.append(arg_cs_type.replacen("params ", "")); + if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) { + p_output.append(">"); + } + } + p_output.append(")'"); + } else { + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'."); + } + + _append_text_undeclared(p_output, p_link_target); + } + } + } +} + +void BindingsGenerator::_append_text_member(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts) { + if (p_link_target.find("/") >= 0) { + // Properties with '/' (slash) in the name are not declared in C#, so there is nothing to reference. + _append_text_undeclared(p_output, p_link_target); + } else if (!p_target_itype || !p_target_itype->is_object_type) { + if (OS::get_singleton()->is_stdout_verbose()) { + if (p_target_itype) { + OS::get_singleton()->print("Cannot resolve member reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data()); + } else { + OS::get_singleton()->print("Cannot resolve type from member reference in documentation: %s\n", p_link_target.utf8().get_data()); + } + } + + // TODO Map what we can + _append_text_undeclared(p_output, p_link_target); + } else { + const TypeInterface *current_itype = p_target_itype; + const PropertyInterface *target_iprop = nullptr; + + while (target_iprop == nullptr && current_itype != nullptr) { + target_iprop = current_itype->find_property_by_name(p_target_cname); + if (target_iprop == nullptr) { + current_itype = _get_type_or_null(TypeReference(current_itype->base_name)); + } + } + + if (target_iprop) { + p_output.append("'" BINDINGS_NAMESPACE "."); + p_output.append(current_itype->proxy_name); + p_output.append("."); + p_output.append(target_iprop->proxy_name); + p_output.append("'"); + } else { + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'."); + } + + _append_text_undeclared(p_output, p_link_target); + } + } +} + +void BindingsGenerator::_append_text_signal(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts) { + if (!p_target_itype || !p_target_itype->is_object_type) { + if (OS::get_singleton()->is_stdout_verbose()) { + if (p_target_itype) { + OS::get_singleton()->print("Cannot resolve signal reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data()); + } else { + OS::get_singleton()->print("Cannot resolve type from signal reference in documentation: %s\n", p_link_target.utf8().get_data()); + } + } + + // TODO Map what we can + _append_text_undeclared(p_output, p_link_target); + } else { + const SignalInterface *target_isignal = p_target_itype->find_signal_by_name(p_target_cname); + + if (target_isignal) { + p_output.append("'" BINDINGS_NAMESPACE "."); + p_output.append(p_target_itype->proxy_name); + p_output.append("."); + p_output.append(target_isignal->proxy_name); + p_output.append("'"); + } else { + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'."); + } + + _append_text_undeclared(p_output, p_link_target); + } + } +} + +void BindingsGenerator::_append_text_enum(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts) { + const StringName search_cname = !p_target_itype ? p_target_cname : StringName(p_target_itype->name + "." + (String)p_target_cname); + + HashMap::ConstIterator enum_match = enum_types.find(search_cname); + + if (!enum_match && search_cname != p_target_cname) { + enum_match = enum_types.find(p_target_cname); + } + + if (enum_match) { + const TypeInterface &target_enum_itype = enum_match->value; + + p_output.append("'" BINDINGS_NAMESPACE "."); + p_output.append(target_enum_itype.proxy_name); // Includes nesting class if any + p_output.append("'"); + } else { + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'."); + } + + _append_text_undeclared(p_output, p_link_target); + } +} + +void BindingsGenerator::_append_text_constant(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts) { + if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) { + _append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target); + } else if (!p_target_itype || !p_target_itype->is_object_type) { + // Search in @GlobalScope as a last resort if no class was specified + if (p_link_target_parts.size() == 1) { + _append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target); + return; + } + + if (OS::get_singleton()->is_stdout_verbose()) { + if (p_target_itype) { + OS::get_singleton()->print("Cannot resolve constant reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data()); + } else { + OS::get_singleton()->print("Cannot resolve type from constant reference in documentation: %s\n", p_link_target.utf8().get_data()); + } + } + + // TODO Map what we can + _append_text_undeclared(p_output, p_link_target); + } else { + // Try to find the constant in the current class + if (p_target_itype->is_singleton_instance) { + // Constants and enums are declared in the static singleton class. + p_target_itype = &obj_types[p_target_itype->cname]; + } + + const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants); + + if (target_iconst) { + // Found constant in current class + p_output.append("'" BINDINGS_NAMESPACE "."); + p_output.append(p_target_itype->proxy_name); + p_output.append("."); + p_output.append(target_iconst->proxy_name); + p_output.append("'"); + } else { + // Try to find as enum constant in the current class + const EnumInterface *target_ienum = nullptr; + + for (const EnumInterface &ienum : p_target_itype->enums) { + target_ienum = &ienum; + target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants); + if (target_iconst) { + break; + } + } + + if (target_iconst) { + p_output.append("'" BINDINGS_NAMESPACE "."); + p_output.append(p_target_itype->proxy_name); + p_output.append("."); + p_output.append(target_ienum->proxy_name); + p_output.append("."); + p_output.append(target_iconst->proxy_name); + p_output.append("'"); + } else if (p_link_target_parts.size() == 1) { + // Also search in @GlobalScope as a last resort if no class was specified + _append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target); + } else { + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'."); + } + + _append_xml_undeclared(p_output, p_link_target); + } + } + } +} + +void BindingsGenerator::_append_text_constant_in_global_scope(StringBuilder &p_output, const String &p_target_cname, const String &p_link_target) { + // Try to find as a global constant + const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, global_constants); + + if (target_iconst) { + // Found global constant + p_output.append("'" BINDINGS_NAMESPACE "." BINDINGS_GLOBAL_SCOPE_CLASS "."); + p_output.append(target_iconst->proxy_name); + p_output.append("'"); + } else { + // Try to find as global enum constant + const EnumInterface *target_ienum = nullptr; + + for (const EnumInterface &ienum : global_enums) { + target_ienum = &ienum; + target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants); + if (target_iconst) { + break; + } + } + + if (target_iconst) { + p_output.append("'" BINDINGS_NAMESPACE "."); + p_output.append(target_ienum->proxy_name); + p_output.append("."); + p_output.append(target_iconst->proxy_name); + p_output.append("'"); + } else { + ERR_PRINT("Cannot resolve global constant reference in documentation: '" + p_link_target + "'."); + _append_text_undeclared(p_output, p_link_target); + } + } +} + +void BindingsGenerator::_append_text_param(StringBuilder &p_output, const String &p_link_target) { + const String link_target = snake_to_camel_case(p_link_target); + p_output.append("'" + link_target + "'"); +} + +void BindingsGenerator::_append_text_undeclared(StringBuilder &p_output, const String &p_link_target) { + p_output.append("'" + p_link_target + "'"); +} + void BindingsGenerator::_append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts) { if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) { if (OS::get_singleton()->is_stdout_verbose()) { @@ -1429,10 +1970,12 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append("/// \n"); } + } - if (class_doc->is_deprecated) { - output.append("[Obsolete(\"This class is deprecated.\")]\n"); - } + if (itype.is_deprecated) { + output.append("[Obsolete(\""); + output.append(bbcode_to_text(itype.deprecation_message, &itype)); + output.append("\")]\n"); } // We generate a `GodotClassName` attribute if the engine class name is not the same as the @@ -1489,10 +2032,12 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(INDENT1 "/// "); } + } - if (iconstant.const_doc->is_deprecated) { - output.append(MEMBER_BEGIN "[Obsolete(\"This constant is deprecated.\")]"); - } + if (iconstant.is_deprecated) { + output.append(MEMBER_BEGIN "[Obsolete(\""); + output.append(bbcode_to_text(iconstant.deprecation_message, &itype)); + output.append("\")]"); } output.append(MEMBER_BEGIN "public const long "); @@ -1537,10 +2082,12 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(INDENT2 "/// \n"); } + } - if (iconstant.const_doc->is_deprecated) { - output.append(INDENT2 "[Obsolete(\"This enum member is deprecated.\")]\n"); - } + if (iconstant.is_deprecated) { + output.append(INDENT2 "[Obsolete(\""); + output.append(bbcode_to_text(iconstant.deprecation_message, &itype)); + output.append("\")]\n"); } output.append(INDENT2); @@ -1992,10 +2539,12 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append(INDENT1 "/// "); } + } - if (p_iprop.prop_doc->is_deprecated) { - p_output.append(MEMBER_BEGIN "[Obsolete(\"This property is deprecated.\")]"); - } + if (p_iprop.is_deprecated) { + p_output.append(MEMBER_BEGIN "[Obsolete(\""); + p_output.append(bbcode_to_text(p_iprop.deprecation_message, &p_itype)); + p_output.append("\")]"); } p_output.append(MEMBER_BEGIN "public "); @@ -2248,15 +2797,9 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf } if (p_imethod.is_deprecated) { - if (p_imethod.deprecation_message.is_empty()) { - WARN_PRINT("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'."); - } - p_output.append(MEMBER_BEGIN "[Obsolete(\""); - p_output.append(p_imethod.deprecation_message); + p_output.append(bbcode_to_text(p_imethod.deprecation_message, &p_itype)); p_output.append("\")]"); - } else if (p_imethod.method_doc && p_imethod.method_doc->is_deprecated) { - p_output.append(MEMBER_BEGIN "[Obsolete(\"This method is deprecated.\")]"); } if (p_imethod.is_compat) { @@ -2401,12 +2944,8 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append(INDENT1 "/// "); if (p_isignal.is_deprecated) { - if (p_isignal.deprecation_message.is_empty()) { - WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); - } - p_output.append(MEMBER_BEGIN "[Obsolete(\""); - p_output.append(p_isignal.deprecation_message); + p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype)); p_output.append("\")]"); } @@ -2459,15 +2998,11 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append(INDENT1 "/// "); } - - if (p_isignal.method_doc->is_deprecated) { - p_output.append(MEMBER_BEGIN "[Obsolete(\"This signal is deprecated.\")]"); - } } if (p_isignal.is_deprecated) { p_output.append(MEMBER_BEGIN "[Obsolete(\""); - p_output.append(p_isignal.deprecation_message); + p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype)); p_output.append("\")]"); } @@ -3029,6 +3564,16 @@ bool BindingsGenerator::_populate_object_type_interfaces() { itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted); itype.memory_own = itype.is_ref_counted; + if (itype.class_doc) { + itype.is_deprecated = itype.class_doc->is_deprecated; + itype.deprecation_message = itype.class_doc->deprecated_message; + + if (itype.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Type: '" + itype.proxy_name + "'."); + itype.deprecation_message = "This class is deprecated."; + } + } + if (itype.is_singleton && compat_singletons.has(itype.cname)) { itype.is_singleton = false; itype.is_compat_singleton = true; @@ -3103,6 +3648,16 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + if (iprop.prop_doc) { + iprop.is_deprecated = iprop.prop_doc->is_deprecated; + iprop.deprecation_message = iprop.prop_doc->deprecated_message; + + if (iprop.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Property: '" + itype.proxy_name + "." + iprop.proxy_name + "'."); + iprop.deprecation_message = "This property is deprecated."; + } + } + itype.properties.push_back(iprop); } @@ -3282,6 +3837,16 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + if (imethod.method_doc) { + imethod.is_deprecated = imethod.method_doc->is_deprecated; + imethod.deprecation_message = imethod.method_doc->deprecated_message; + + if (imethod.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Method: '" + itype.proxy_name + "." + imethod.proxy_name + "'."); + imethod.deprecation_message = "This method is deprecated."; + } + } + ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false, "Method name conflicts with property: '" + itype.name + "." + imethod.name + "'."); @@ -3388,6 +3953,16 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + if (isignal.method_doc) { + isignal.is_deprecated = isignal.method_doc->is_deprecated; + isignal.deprecation_message = isignal.method_doc->deprecated_message; + + if (isignal.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + itype.proxy_name + "." + isignal.proxy_name + "'."); + isignal.deprecation_message = "This signal is deprecated."; + } + } + itype.signals_.push_back(isignal); } @@ -3428,6 +4003,16 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + if (iconstant.const_doc) { + iconstant.is_deprecated = iconstant.const_doc->is_deprecated; + iconstant.deprecation_message = iconstant.const_doc->deprecated_message; + + if (iconstant.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Enum member: '" + itype.proxy_name + "." + ienum.proxy_name + "." + iconstant.proxy_name + "'."); + iconstant.deprecation_message = "This enum member is deprecated."; + } + } + ienum.constants.push_back(iconstant); } @@ -3470,6 +4055,16 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + if (iconstant.const_doc) { + iconstant.is_deprecated = iconstant.const_doc->is_deprecated; + iconstant.deprecation_message = iconstant.const_doc->deprecated_message; + + if (iconstant.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Constant: '" + itype.proxy_name + "." + iconstant.proxy_name + "'."); + iconstant.deprecation_message = "This constant is deprecated."; + } + } + itype.constants.push_back(iconstant); } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index aa4e5ea093b..bb0ba0cb009 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -47,7 +47,10 @@ class BindingsGenerator { String name; String proxy_name; int64_t value = 0; - const DocData::ConstantDoc *const_doc; + const DocData::ConstantDoc *const_doc = nullptr; + + bool is_deprecated = false; + String deprecation_message; ConstantInterface() {} @@ -86,6 +89,9 @@ class BindingsGenerator { StringName getter; const DocData::PropertyDoc *prop_doc; + + bool is_deprecated = false; + String deprecation_message; }; struct TypeReference { @@ -427,6 +433,9 @@ class BindingsGenerator { const DocData::ClassDoc *class_doc = nullptr; + bool is_deprecated = false; + String deprecation_message; + List constants; List enums; List properties; @@ -765,8 +774,18 @@ class BindingsGenerator { return p_type->name; } + String bbcode_to_text(const String &p_bbcode, const TypeInterface *p_itype); String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal = false); + void _append_text_method(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts); + void _append_text_member(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts); + void _append_text_signal(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts); + void _append_text_enum(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts); + void _append_text_constant(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts); + void _append_text_constant_in_global_scope(StringBuilder &p_output, const String &p_target_cname, const String &p_link_target); + void _append_text_param(StringBuilder &p_output, const String &p_link_target); + void _append_text_undeclared(StringBuilder &p_output, const String &p_link_target); + void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts); void _append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts); void _append_xml_signal(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector &p_link_target_parts);