diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 84f66c34797..8e9d3d68da8 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1768,7 +1768,7 @@ Object *Engine::get_singleton_object(const StringName &p_name) const { void Engine::register_singleton(const StringName &p_name, Object *p_object) { ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name)); - ERR_FAIL_COND_MSG(!String(p_name).is_valid_identifier(), "Singleton name is not a valid identifier: " + p_name); + ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), "Singleton name is not a valid identifier: " + p_name); ::Engine::Singleton s; s.class_name = p_name; s.name = p_name; diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 940a34396f3..c9e609cddc7 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -355,7 +355,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr StringName class_name = *reinterpret_cast(p_class_name); StringName parent_class_name = *reinterpret_cast(p_parent_class_name); - ERR_FAIL_COND_MSG(!String(class_name).is_valid_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier."); + ERR_FAIL_COND_MSG(!String(class_name).is_valid_ascii_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier."); ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered."); Extension *parent_extension = nullptr; diff --git a/core/object/object.cpp b/core/object/object.cpp index 161b1e3dbee..a2330ecd04e 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -997,7 +997,7 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) { if (E) { E->value = p_value; } else { - ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_identifier(), "Invalid metadata identifier: '" + p_name + "'."); + ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), "Invalid metadata identifier: '" + p_name + "'."); Variant *V = &metadata.insert(p_name, p_value)->value; const String &sname = p_name; diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 303998ab50a..2683addd4b3 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -4624,7 +4624,7 @@ bool String::is_absolute_path() const { } } -String String::validate_identifier() const { +String String::validate_ascii_identifier() const { if (is_empty()) { return "_"; // Empty string is not a valid identifier; } @@ -4647,7 +4647,7 @@ String String::validate_identifier() const { return result; } -bool String::is_valid_identifier() const { +bool String::is_valid_ascii_identifier() const { int len = length(); if (len == 0) { @@ -4669,6 +4669,26 @@ bool String::is_valid_identifier() const { return true; } +bool String::is_valid_unicode_identifier() const { + const char32_t *str = ptr(); + int len = length(); + + if (len == 0) { + return false; // Empty string. + } + + if (!is_unicode_identifier_start(str[0])) { + return false; + } + + for (int i = 1; i < len; i++) { + if (!is_unicode_identifier_continue(str[i])) { + return false; + } + } + return true; +} + bool String::is_valid_string() const { int l = length(); const char32_t *src = get_data(); diff --git a/core/string/ustring.h b/core/string/ustring.h index 9df2d56e800..11f15031f99 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -459,10 +459,11 @@ public: // node functions static String get_invalid_node_name_characters(bool p_allow_internal = false); String validate_node_name() const; - String validate_identifier() const; + String validate_ascii_identifier() const; String validate_filename() const; - bool is_valid_identifier() const; + bool is_valid_ascii_identifier() const; + bool is_valid_unicode_identifier() const; bool is_valid_int() const; bool is_valid_float() const; bool is_valid_hex_number(bool p_with_prefix) const; @@ -470,6 +471,9 @@ public: bool is_valid_ip_address() const; bool is_valid_filename() const; + // Use `is_valid_ascii_identifier()` instead. Kept for compatibility. + bool is_valid_identifier() const { return is_valid_ascii_identifier(); } + /** * The constructors must not depend on other overloads */ diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 695ad23bf5c..83f1f981b3e 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1724,6 +1724,8 @@ static void _register_variant_builtin_methods_string() { bind_string_method(validate_node_name, sarray(), varray()); bind_string_method(validate_filename, sarray(), varray()); + bind_string_method(is_valid_ascii_identifier, sarray(), varray()); + bind_string_method(is_valid_unicode_identifier, sarray(), varray()); bind_string_method(is_valid_identifier, sarray(), varray()); bind_string_method(is_valid_int, sarray(), varray()); bind_string_method(is_valid_float, sarray(), varray()); diff --git a/doc/classes/String.xml b/doc/classes/String.xml index de3d3e7cb92..d99eaa64a64 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -451,6 +451,19 @@ Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order, [b]ignoring case[/b]. + + + + Returns [code]true[/code] if this string is a valid ASCII identifier. A valid ASCII identifier may contain only letters, digits, and underscores ([code]_[/code]), and the first character may not be a digit. + [codeblock] + print("node_2d".is_valid_ascii_identifier()) # Prints true + print("TYPE_FLOAT".is_valid_ascii_identifier()) # Prints true + print("1st_method".is_valid_ascii_identifier()) # Prints false + print("MyMethod#2".is_valid_ascii_identifier()) # Prints false + [/codeblock] + See also [method is_valid_unicode_identifier]. + + @@ -490,7 +503,7 @@ Returns [code]true[/code] if this string is a valid color in hexadecimal HTML notation. The string must be a hexadecimal value (see [method is_valid_hex_number]) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). Other HTML notations for colors, such as names or [code]hsl()[/code], are not considered valid. See also [method Color.html]. - + 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. @@ -521,6 +534,23 @@ Returns [code]true[/code] if this string represents a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]"0.0.0.0"[/code] and [code]"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"[/code] as valid. + + + + Returns [code]true[/code] if this string is a valid Unicode identifier. + A valid Unicode identifier must begin with a Unicode character of class [code]XID_Start[/code] or [code]"_"[/code], and may contain Unicode characters of class [code]XID_Continue[/code] in the other positions. + [codeblock] + print("node_2d".is_valid_unicode_identifier()) # Prints true + print("1st_method".is_valid_unicode_identifier()) # Prints false + print("MyMethod#2".is_valid_unicode_identifier()) # Prints false + print("állóképesség".is_valid_unicode_identifier()) # Prints true + print("выносливость".is_valid_unicode_identifier()) # Prints true + print("体力".is_valid_unicode_identifier()) # Prints true + [/codeblock] + See also [method is_valid_ascii_identifier]. + [b]Note:[/b] This method checks identifiers the same way as GDScript. See [method TextServer.is_valid_identifier] for more advanced checks. + + diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index 836ca6b4ba0..1a891de05f8 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -420,6 +420,19 @@ Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order, [b]ignoring case[/b]. + + + + Returns [code]true[/code] if this string is a valid ASCII identifier. A valid ASCII identifier may contain only letters, digits, and underscores ([code]_[/code]), and the first character may not be a digit. + [codeblock] + print("node_2d".is_valid_ascii_identifier()) # Prints true + print("TYPE_FLOAT".is_valid_ascii_identifier()) # Prints true + print("1st_method".is_valid_ascii_identifier()) # Prints false + print("MyMethod#2".is_valid_ascii_identifier()) # Prints false + [/codeblock] + See also [method is_valid_unicode_identifier]. + + @@ -459,7 +472,7 @@ Returns [code]true[/code] if this string is a valid color in hexadecimal HTML notation. The string must be a hexadecimal value (see [method is_valid_hex_number]) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). Other HTML notations for colors, such as names or [code]hsl()[/code], are not considered valid. See also [method Color.html]. - + 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. @@ -490,6 +503,23 @@ Returns [code]true[/code] if this string represents a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]"0.0.0.0"[/code] and [code]"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"[/code] as valid. + + + + Returns [code]true[/code] if this string is a valid Unicode identifier. + A valid Unicode identifier must begin with a Unicode character of class [code]XID_Start[/code] or [code]"_"[/code], and may contain Unicode characters of class [code]XID_Continue[/code] in the other positions. + [codeblock] + print("node_2d".is_valid_unicode_identifier()) # Prints true + print("1st_method".is_valid_unicode_identifier()) # Prints false + print("MyMethod#2".is_valid_unicode_identifier()) # Prints false + print("állóképesség".is_valid_unicode_identifier()) # Prints true + print("выносливость".is_valid_unicode_identifier()) # Prints true + print("体力".is_valid_unicode_identifier()) # Prints true + [/codeblock] + See also [method is_valid_ascii_identifier]. + [b]Note:[/b] This method checks identifiers the same way as GDScript. See [method TextServer.is_valid_identifier] for more advanced checks. + + diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index 32b2133f234..fb007aee28b 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -88,7 +88,7 @@ void EditorAutoloadSettings::_notification(int p_what) { } bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, String *r_error) { - if (!p_name.is_valid_identifier()) { + if (!p_name.is_valid_ascii_identifier()) { if (r_error) { *r_error = TTR("Invalid name.") + " "; if (p_name.size() > 0 && p_name.left(1).is_numeric()) { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 199383c3918..1e5acce0329 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -4270,7 +4270,7 @@ void EditorInspector::_check_meta_name() { if (meta_name.is_empty()) { validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty."), EditorValidationPanel::MSG_ERROR); - } else if (!meta_name.is_valid_identifier()) { + } else if (!meta_name.is_valid_ascii_identifier()) { validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name must be a valid identifier."), EditorValidationPanel::MSG_ERROR); } else if (object->has_meta(meta_name)) { validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists."), meta_name), EditorValidationPanel::MSG_ERROR); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 070471f3f3e..7111d023c15 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1816,9 +1816,9 @@ static String _get_dropped_resource_line(const Ref &p_resource, bool p } if (is_script) { - variable_name = variable_name.to_pascal_case().validate_identifier(); + variable_name = variable_name.to_pascal_case().validate_ascii_identifier(); } else { - variable_name = variable_name.to_snake_case().to_upper().validate_identifier(); + variable_name = variable_name.to_snake_case().to_upper().validate_ascii_identifier(); } return vformat("const %s = preload(%s)", variable_name, _quote_drop_data(path)); } @@ -1932,13 +1932,13 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data path = sn->get_path_to(node); } for (const String &segment : path.split("/")) { - if (!segment.is_valid_identifier()) { + if (!segment.is_valid_ascii_identifier()) { path = _quote_drop_data(path); break; } } - String variable_name = String(node->get_name()).to_snake_case().validate_identifier(); + String variable_name = String(node->get_name()).to_snake_case().validate_ascii_identifier(); if (use_type) { StringName class_name = node->get_class_name(); Ref