From 1cee67d6e24fd3515a1ef09e5dd95d5982b8c211 Mon Sep 17 00:00:00 2001 From: Danil Alexeev Date: Tue, 20 Dec 2022 14:29:06 +0300 Subject: [PATCH] Expose `String.validate_identifier` method --- core/string/ustring.cpp | 22 +++++++++++++++------- core/variant/variant_call.cpp | 1 + doc/classes/String.xml | 11 +++++++++++ doc/classes/StringName.xml | 11 +++++++++++ editor/connections_dialog.cpp | 19 ++----------------- tests/core/string/test_string.h | 20 ++++++++++++++++---- 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index adab6d07c7f0..05d04079a7f7 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -3805,20 +3805,28 @@ static _FORCE_INLINE_ bool _is_valid_identifier_bit(int p_index, char32_t p_char String String::validate_identifier() const { if (is_empty()) { - return "_"; // Empty string is not a valid identifier; + return "_"; // Empty string is not a valid identifier. } - String result = *this; - int len = result.length(); - char32_t *buffer = result.ptrw(); + String new_string; + int len = length(); for (int i = 0; i < len; i++) { - if (!_is_valid_identifier_bit(i, buffer[i])) { - buffer[i] = '_'; + const char32_t c = operator[](i); + if (is_ascii_identifier_char(c)) { + new_string += c; } } - return result; + if (new_string.is_empty()) { + return "_"; + } + + if (is_digit(new_string[0])) { + return "_" + new_string; + } + + return new_string; } bool String::is_valid_identifier() const { diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index ac569941bf01..778b7eaa82b7 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1692,6 +1692,7 @@ static void _register_variant_builtin_methods() { bind_string_method(c_unescape, sarray(), varray()); bind_string_method(json_escape, sarray(), varray()); + bind_string_method(validate_identifier, sarray(), varray()); bind_string_method(validate_node_name, sarray(), varray()); bind_string_method(is_valid_identifier, sarray(), varray()); diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 97466e786038..d08637b79bba 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -437,11 +437,14 @@ Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]), and the first character may not be a digit. [codeblock] + print("".is_valid_identifier()) # Prints false + print("_".is_valid_identifier()) # Prints true print("node_2d".is_valid_identifier()) # Prints true print("TYPE_FLOAT".is_valid_identifier()) # Prints true print("1st_method".is_valid_identifier()) # Prints false print("MyMethod#2".is_valid_identifier()) # Prints false [/codeblock] + [b]Note:[/b] [code]_[/code] is not a valid identifier in GDScript. @@ -1002,6 +1005,14 @@ [/codeblocks] + + + + Removes any characters from the string that are prohibited in identifier names (see [method is_valid_identifier]). If the string is empty, returns [code]_[/code]. If the string begins with a digit, the [code]_[/code] prefix will be added. + [b]Note:[/b] [code]_[/code] is not a valid identifier in GDScript. + [b]Note:[/b] This method removes spaces. Use [code]replace(" ", "_")[/code] or [method to_snake_case] before [code]validate_identifier()[/code] if needed. + + diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index b46e39b8d7fb..0b2db78b894d 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -412,11 +412,14 @@ Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]), and the first character may not be a digit. [codeblock] + print("".is_valid_identifier()) # Prints false + print("_".is_valid_identifier()) # Prints true print("node_2d".is_valid_identifier()) # Prints true print("TYPE_FLOAT".is_valid_identifier()) # Prints true print("1st_method".is_valid_identifier()) # Prints false print("MyMethod#2".is_valid_identifier()) # Prints false [/codeblock] + [b]Note:[/b] [code]_[/code] is not a valid identifier in GDScript. @@ -909,6 +912,14 @@ [/codeblocks] + + + + Removes any characters from the string that are prohibited in identifier names (see [method is_valid_identifier]). If the string is empty, returns [code]_[/code]. If the string begins with a digit, the [code]_[/code] prefix will be added. + [b]Note:[/b] [code]_[/code] is not a valid identifier in GDScript. + [b]Note:[/b] This method removes spaces. Use [code]replace(" ", "_")[/code] or [method to_snake_case] before [code]validate_identifier()[/code] if needed. + + diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 1f0cc1dc77b7..21f1997b8ad5 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -214,22 +214,7 @@ void ConnectDialog::_remove_bind() { * Automatically generates a name for the callback method. */ StringName ConnectDialog::generate_method_callback_name(Node *p_source, String p_signal_name, Node *p_target) { - String node_name = p_source->get_name(); - for (int i = 0; i < node_name.length(); i++) { // TODO: Regex filter may be cleaner. - char32_t c = node_name[i]; - if (!is_ascii_identifier_char(c)) { - if (c == ' ') { - // Replace spaces with underlines. - c = '_'; - } else { - // Remove any other characters. - node_name.remove_at(i); - i--; - continue; - } - } - node_name[i] = c; - } + String node_name = String(p_source->get_name()).replace("-", "_"); Dictionary subst; subst["NodeName"] = node_name.to_pascal_case(); @@ -247,7 +232,7 @@ StringName ConnectDialog::generate_method_callback_name(Node *p_source, String p dst_method = String(EDITOR_GET("interface/editors/default_signal_callback_name")).format(subst); } - return dst_method; + return dst_method.validate_identifier(); } /* diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index 659d451d76e6..7b2dcee1cdac 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -1678,14 +1678,26 @@ TEST_CASE("[String] validate_identifier") { String empty_string; CHECK(empty_string.validate_identifier() == "_"); - String numeric_only = "12345"; - CHECK(numeric_only.validate_identifier() == "_2345"); + String valid_identifier = "abc_123_Test4"; + CHECK(valid_identifier.validate_identifier() == "abc_123_Test4"); + + String numeric_prefix = "123abc"; + CHECK(numeric_prefix.validate_identifier() == "_123abc"); + + String invalid_prefix = String::utf8(":@*#&世界abc"); + CHECK(invalid_prefix.validate_identifier() == "abc"); + + String invalid_and_numeric_prefix = String::utf8(":@*#&世界123abc"); + CHECK(invalid_and_numeric_prefix.validate_identifier() == "_123abc"); String name_with_spaces = "Name with spaces"; - CHECK(name_with_spaces.validate_identifier() == "Name_with_spaces"); + CHECK(name_with_spaces.validate_identifier() == "Namewithspaces"); String name_with_invalid_chars = String::utf8("Invalid characters:@*#&世界"); - CHECK(name_with_invalid_chars.validate_identifier() == "Invalid_characters_______"); + CHECK(name_with_invalid_chars.validate_identifier() == "Invalidcharacters"); + + String invalid_chars_only = String::utf8(":@*#&世界"); + CHECK(invalid_chars_only.validate_identifier() == "_"); } TEST_CASE("[String] Variant indexed get") {