Prechádzať zdrojové kódy

GDScript: Add warning if non-`@tool` class extends `@tool` class

Danil Alexeev 1 rok pred
rodič
commit
3f52871f70

+ 3 - 0
doc/classes/ProjectSettings.xml

@@ -533,6 +533,9 @@
 		<member name="debug/gdscript/warnings/integer_division" type="int" setter="" getter="" default="1">
 			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when dividing an integer by another integer (the decimal part will be discarded).
 		</member>
+		<member name="debug/gdscript/warnings/missing_tool" type="int" setter="" getter="" default="1">
+			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the base class script has the [code]@tool[/code] annotation, but the current class script does not have it.
+		</member>
 		<member name="debug/gdscript/warnings/narrowing_conversion" type="int" setter="" getter="" default="1">
 			When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when passing a floating-point value to a function that expects an integer (it will be converted and lose precision).
 		</member>

+ 20 - 0
modules/gdscript/gdscript_analyzer.cpp

@@ -411,6 +411,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
 				return err;
 			}
 
+#ifdef DEBUG_ENABLED
+			if (!parser->_is_tool && ext_parser->get_parser()->_is_tool) {
+				parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
+			}
+#endif
+
 			base = ext_parser->get_parser()->head->get_datatype();
 		} else {
 			if (p_class->extends.is_empty()) {
@@ -438,6 +444,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
 						push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
 						return err;
 					}
+
+#ifdef DEBUG_ENABLED
+					if (!parser->_is_tool && base_parser->get_parser()->_is_tool) {
+						parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
+					}
+#endif
+
 					base = base_parser->get_parser()->head->get_datatype();
 				}
 			} else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) {
@@ -458,6 +471,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
 					push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
 					return err;
 				}
+
+#ifdef DEBUG_ENABLED
+				if (!parser->_is_tool && info_parser->get_parser()->_is_tool) {
+					parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
+				}
+#endif
+
 				base = info_parser->get_parser()->head->get_datatype();
 			} else if (class_exists(name)) {
 				if (Engine::get_singleton()->has_singleton(name)) {

+ 3 - 0
modules/gdscript/gdscript_warning.cpp

@@ -109,6 +109,8 @@ String GDScriptWarning::get_message() const {
 		case STATIC_CALLED_ON_INSTANCE:
 			CHECK_SYMBOLS(2);
 			return vformat(R"*(The function "%s()" is a static function but was called from an instance. Instead, it should be directly called from the type: "%s.%s()".)*", symbols[0], symbols[1], symbols[0]);
+		case MISSING_TOOL:
+			return R"(The base class script has the "@tool" annotation, but this script does not have it.)";
 		case REDUNDANT_STATIC_UNLOAD:
 			return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
 		case REDUNDANT_AWAIT:
@@ -219,6 +221,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
 		"UNSAFE_VOID_RETURN",
 		"RETURN_VALUE_DISCARDED",
 		"STATIC_CALLED_ON_INSTANCE",
+		"MISSING_TOOL",
 		"REDUNDANT_STATIC_UNLOAD",
 		"REDUNDANT_AWAIT",
 		"ASSERT_ALWAYS_TRUE",

+ 2 - 0
modules/gdscript/gdscript_warning.h

@@ -70,6 +70,7 @@ public:
 		UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked.
 		RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used.
 		STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
+		MISSING_TOOL, // The base class script has the "@tool" annotation, but this script does not have it.
 		REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
 		REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
 		ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
@@ -123,6 +124,7 @@ public:
 		WARN, // UNSAFE_VOID_RETURN
 		IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.).
 		WARN, // STATIC_CALLED_ON_INSTANCE
+		WARN, // MISSING_TOOL
 		WARN, // REDUNDANT_STATIC_UNLOAD
 		WARN, // REDUNDANT_AWAIT
 		WARN, // ASSERT_ALWAYS_TRUE

+ 7 - 0
modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd

@@ -0,0 +1,7 @@
+extends "./non_tool_extends_tool.notest.gd"
+
+class InnerClass extends "./non_tool_extends_tool.notest.gd":
+	pass
+
+func test():
+	pass

+ 1 - 0
modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd

@@ -0,0 +1 @@
+@tool

+ 9 - 0
modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out

@@ -0,0 +1,9 @@
+GDTEST_OK
+>> WARNING
+>> Line: 1
+>> MISSING_TOOL
+>> The base class script has the "@tool" annotation, but this script does not have it.
+>> WARNING
+>> Line: 3
+>> MISSING_TOOL
+>> The base class script has the "@tool" annotation, but this script does not have it.

+ 9 - 0
modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd

@@ -0,0 +1,9 @@
+@warning_ignore("missing_tool")
+extends "./non_tool_extends_tool.notest.gd"
+
+@warning_ignore("missing_tool")
+class InnerClass extends "./non_tool_extends_tool.notest.gd":
+	pass
+
+func test():
+	pass

+ 1 - 0
modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out

@@ -0,0 +1 @@
+GDTEST_OK