Pārlūkot izejas kodu

Merge pull request #52068 from ThreeRhinosInAnElephantCostume/fixgdscript

Fix parse error on statement-less files with only newlines, add a warning for empty files.
Rémi Verschelde 4 gadi atpakaļ
vecāks
revīzija
4059cf2f02

+ 3 - 0
doc/classes/ProjectSettings.xml

@@ -328,6 +328,9 @@
 		<member name="debug/gdscript/warnings/deprecated_keyword" type="bool" setter="" getter="" default="true">
 			If [code]true[/code], enables warnings when deprecated keywords are used.
 		</member>
+		<member name="debug/gdscript/warnings/empty_file" type="bool" setter="" getter="" default="true">
+			If [code]true[/code], enables warnings when an empty file is parsed.
+		</member>
 		<member name="debug/gdscript/warnings/enable" type="bool" setter="" getter="" default="true">
 			If [code]true[/code], enables specific GDScript warnings (see [code]debug/gdscript/warnings/*[/code] settings). If [code]false[/code], disables all GDScript warnings.
 		</member>

+ 20 - 3
modules/gdscript/gdscript_parser.cpp

@@ -337,12 +337,29 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
 	tokenizer.set_cursor_position(cursor_line, cursor_column);
 	script_path = p_script_path;
 	current = tokenizer.scan();
-	// Avoid error as the first token.
-	while (current.type == GDScriptTokenizer::Token::ERROR) {
-		push_error(current.literal);
+	// Avoid error or newline as the first token.
+	// The latter can mess with the parser when opening files filled exclusively with comments and newlines.
+	while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {
+		if (current.type == GDScriptTokenizer::Token::ERROR) {
+			push_error(current.literal);
+		}
 		current = tokenizer.scan();
 	}
 
+#ifdef DEBUG_ENABLED
+	// Warn about parsing an empty script file:
+	if (current.type == GDScriptTokenizer::Token::TK_EOF) {
+		// Create a dummy Node for the warning, pointing to the very beginning of the file
+		Node *nd = alloc_node<PassNode>();
+		nd->start_line = 1;
+		nd->start_column = 0;
+		nd->end_line = 1;
+		nd->leftmost_column = 0;
+		nd->rightmost_column = 0;
+		push_warning(nd, GDScriptWarning::EMPTY_FILE);
+	}
+#endif
+
 	push_multiline(false); // Keep one for the whole parsing.
 	parse_program();
 	pop_multiline();

+ 4 - 0
modules/gdscript/gdscript_warning.cpp

@@ -145,6 +145,9 @@ String GDScriptWarning::get_message() const {
 		case REDUNDANT_AWAIT: {
 			return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)";
 		}
+		case EMPTY_FILE: {
+			return "Empty script file.";
+		}
 		case WARNING_MAX:
 			break; // Can't happen, but silences warning
 	}
@@ -190,6 +193,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
 		"ASSERT_ALWAYS_TRUE",
 		"ASSERT_ALWAYS_FALSE",
 		"REDUNDANT_AWAIT",
+		"EMPTY_FILE",
 	};
 
 	static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");

+ 1 - 0
modules/gdscript/gdscript_warning.h

@@ -68,6 +68,7 @@ public:
 		ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
 		ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false.
 		REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
+		EMPTY_FILE, // A script file is empty.
 		WARNING_MAX,
 	};
 

+ 4 - 1
modules/gdscript/tests/gdscript_test_runner.cpp

@@ -496,7 +496,10 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
 		}
 		return result;
 	}
-
+	// Script files matching this pattern are allowed to not contain a test() function.
+	if (source_file.match("*.notest.gd")) {
+		return result;
+	}
 	// Test running.
 	const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name);
 	if (test_function_element == nullptr) {

+ 1 - 0
modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd

@@ -0,0 +1 @@
+

+ 4 - 0
modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out

@@ -0,0 +1,4 @@
+>> WARNING
+>> Line: 1
+>> EMPTY_FILE
+>> Empty script file.

+ 1 - 0
modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd

@@ -0,0 +1 @@
+#a comment

+ 4 - 0
modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out

@@ -0,0 +1,4 @@
+>> WARNING
+>> Line: 1
+>> EMPTY_FILE
+>> Empty script file.

+ 3 - 0
modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd

@@ -0,0 +1,3 @@
+
+
+

+ 4 - 0
modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out

@@ -0,0 +1,4 @@
+>> WARNING
+>> Line: 1
+>> EMPTY_FILE
+>> Empty script file.

+ 4 - 0
modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd

@@ -0,0 +1,4 @@
+#a comment, followed by a bunch of newlines
+
+
+

+ 4 - 0
modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out

@@ -0,0 +1,4 @@
+>> WARNING
+>> Line: 1
+>> EMPTY_FILE
+>> Empty script file.