summaryrefslogtreecommitdiffstats
path: root/modules/gdscript/tests
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript/tests')
-rw-r--r--modules/gdscript/tests/README.md41
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp109
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.h13
-rw-r--r--modules/gdscript/tests/gdscript_test_runner_suite.h9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd14
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd15
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd14
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd15
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd15
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out10
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg4
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd6
-rw-r--r--modules/gdscript/tests/scripts/lsp/class.gd (renamed from modules/gdscript/tests/scripts/lsp/class.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/enums.gd (renamed from modules/gdscript/tests/scripts/lsp/enums.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/indentation.gd (renamed from modules/gdscript/tests/scripts/lsp/indentation.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/lambdas.gd (renamed from modules/gdscript/tests/scripts/lsp/lambdas.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/local_variables.gd (renamed from modules/gdscript/tests/scripts/lsp/local_variables.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/properties.gd (renamed from modules/gdscript/tests/scripts/lsp/properties.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/scopes.gd (renamed from modules/gdscript/tests/scripts/lsp/scopes.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/lsp/shadowing_initializer.gd (renamed from modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd)0
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.gd (renamed from modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd)0
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.out (renamed from modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out)0
-rw-r--r--modules/gdscript/tests/scripts/parser/features/annotations.gd31
-rw-r--r--modules/gdscript/tests/scripts/parser/features/annotations.out36
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_enum.gd19
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_enum.out18
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd35
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out35
-rw-r--r--modules/gdscript/tests/scripts/parser/features/is_not_operator.gd11
-rw-r--r--modules/gdscript/tests/scripts/parser/features/is_not_operator.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_if.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/property_setter_getter.out1
-rw-r--r--modules/gdscript/tests/scripts/project.godot2
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd13
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out9
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/free_is_callable.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/match_test_null.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/match_test_null.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out7
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd25
-rw-r--r--modules/gdscript/tests/test_completion.h203
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp61
-rw-r--r--modules/gdscript/tests/test_gdscript.h1
-rw-r--r--modules/gdscript/tests/test_lsp.h18
65 files changed, 742 insertions, 150 deletions
diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md
index 361d586d32..72b5316532 100644
--- a/modules/gdscript/tests/README.md
+++ b/modules/gdscript/tests/README.md
@@ -6,3 +6,44 @@ and output files.
See the
[Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html#integration-tests-for-gdscript)
for information about creating and running GDScript integration tests.
+
+# GDScript Autocompletion tests
+
+The `script/completion` folder contains test for the GDScript autocompletion.
+
+Each test case consists of at least one `.gd` file, which contains the code, and one `.cfg` file, which contains expected results and configuration. Inside of the GDScript file the character `➡` represents the cursor position, at which autocompletion is invoked.
+
+The config file contains two section:
+
+`[input]` contains keys that configure the test environment. The following keys are possible:
+
+- `cs: boolean = false`: If `true`, the test will be skipped when running a non C# build.
+- `use_single_quotes: boolean = false`: Configures the corresponding editor setting for the test.
+- `scene: String`: Allows to specify a scene which is opened while autocompletion is performed. If this is not set the test runner will search for a `.tscn` file with the same basename as the GDScript file. If that isn't found either, autocompletion will behave as if no scene was opened.
+
+`[output]` specifies the expected results for the test. The following key are supported:
+
+- `include: Array`: An unordered list of suggestions that should be in the result. Each entry is one dictionary with the following keys: `display`, `insert_text`, `kind`, `location`, which correspond to the suggestion struct which is used in the code. The runner only tests against specified keys, so in most cases `display` will suffice.
+- `exclude: Array`: An array of suggestions which should not be in the result. The entries take the same form as for `include`.
+- `call_hint: String`: The expected call hint returned by autocompletion.
+- `forced: boolean`: Whether autocompletion is expected to force opening a completion window.
+
+Tests will only test against entries in `[output]` that were specified.
+
+## Writing autocompletion tests
+
+To avoid failing edge cases a certain behavior needs to be tested multiple times. Some things that tests should account for:
+
+- All possible types: Test with all possible types that apply to the tested behavior. (For the last points testing against `SCRIPT` and `CLASS` should suffice. `CLASS` can be obtained through C#, `SCRIPT` through GDScript. Relying on autoloads to be of type `SCRIPT` is not good, since this might change in the future.)
+
+ - `BUILTIN`
+ - `NATIVE`
+ - GDScripts (with `class_name` as well as `preload`ed)
+ - C# (as standin for all other language bindings) (with `class_name` as well as `preload`ed)
+ - Autoloads
+
+- Possible contexts: the completion might be placed in different places of the program. e.g:
+ - initializers of class members
+ - directly inside a suite
+ - assignments inside a suite
+ - as parameter to a call
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index f91dc83f2c..a0329eb8d2 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -34,6 +34,7 @@
#include "../gdscript_analyzer.h"
#include "../gdscript_compiler.h"
#include "../gdscript_parser.h"
+#include "../gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/core_globals.h"
@@ -78,31 +79,30 @@ void init_autoloads() {
scn.instantiate();
scn->set_path(info.path);
scn->reload_from_file();
- ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Can't autoload: %s.", info.path));
+ ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
if (scn.is_valid()) {
n = scn->instantiate();
}
} else {
Ref<Resource> res = ResourceLoader::load(info.path);
- ERR_CONTINUE_MSG(res.is_null(), vformat("Can't autoload: %s.", info.path));
+ ERR_CONTINUE_MSG(res.is_null(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
Ref<Script> scr = res;
if (scr.is_valid()) {
StringName ibt = scr->get_instance_base_type();
bool valid_type = ClassDB::is_parent_class(ibt, "Node");
- ERR_CONTINUE_MSG(!valid_type, vformat("Script does not inherit from Node: %s.", info.path));
+ ERR_CONTINUE_MSG(!valid_type, vformat("Failed to instantiate an autoload, script '%s' does not inherit from 'Node'.", info.path));
Object *obj = ClassDB::instantiate(ibt);
-
- ERR_CONTINUE_MSG(!obj, vformat("Cannot instance script for Autoload, expected 'Node' inheritance, got: %s.", ibt));
+ ERR_CONTINUE_MSG(!obj, vformat("Failed to instantiate an autoload, cannot instantiate '%s'.", ibt));
n = Object::cast_to<Node>(obj);
n->set_script(scr);
}
}
- ERR_CONTINUE_MSG(!n, vformat("Path in autoload not a node or script: %s.", info.path));
+ ERR_CONTINUE_MSG(!n, vformat("Failed to instantiate an autoload, path is not pointing to a scene or a script: %s.", info.path));
n->set_name(info.name);
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
@@ -132,10 +132,11 @@ void finish_language() {
StringName GDScriptTestRunner::test_function_name;
-GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames) {
+GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames, bool p_use_binary_tokens) {
test_function_name = StaticCString::create("test");
do_init_languages = p_init_language;
print_filenames = p_print_filenames;
+ binary_tokens = p_use_binary_tokens;
source_dir = p_source_dir;
if (!source_dir.ends_with("/")) {
@@ -267,7 +268,7 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
while (!next.is_empty()) {
if (dir->current_is_dir()) {
- if (next == "." || next == "..") {
+ if (next == "." || next == ".." || next == "completion" || next == "lsp") {
next = dir->get_next();
continue;
}
@@ -278,6 +279,9 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
if (next.ends_with(".notest.gd")) {
next = dir->get_next();
continue;
+ } else if (binary_tokens && next.ends_with(".textonly.gd")) {
+ next = dir->get_next();
+ continue;
} else if (next.get_extension().to_lower() == "gd") {
#ifndef DEBUG_ENABLED
// On release builds, skip tests marked as debug only.
@@ -300,6 +304,9 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
ERR_FAIL_V_MSG(false, "Could not find output file for " + next);
}
GDScriptTest test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
+ if (binary_tokens) {
+ test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER);
+ }
tests.push_back(test);
}
}
@@ -322,24 +329,65 @@ bool GDScriptTestRunner::make_tests() {
return make_tests_for_dir(dir->get_current_dir());
}
-bool GDScriptTestRunner::generate_class_index() {
+static bool generate_class_index_recursive(const String &p_dir) {
+ Error err = OK;
+ Ref<DirAccess> dir(DirAccess::open(p_dir, &err));
+
+ if (err != OK) {
+ return false;
+ }
+
+ String current_dir = dir->get_current_dir();
+
+ dir->list_dir_begin();
+ String next = dir->get_next();
+
StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name();
- for (int i = 0; i < tests.size(); i++) {
- GDScriptTest test = tests[i];
- String base_type;
+ while (!next.is_empty()) {
+ if (dir->current_is_dir()) {
+ if (next == "." || next == ".." || next == "completion" || next == "lsp") {
+ next = dir->get_next();
+ continue;
+ }
+ if (!generate_class_index_recursive(current_dir.path_join(next))) {
+ return false;
+ }
+ } else {
+ if (!next.ends_with(".gd")) {
+ next = dir->get_next();
+ continue;
+ }
+ String base_type;
+ String source_file = current_dir.path_join(next);
+ String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type);
+ if (class_name.is_empty()) {
+ next = dir->get_next();
+ continue;
+ }
+ ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
+ "Class name '" + class_name + "' from " + source_file + " is already used in " + ScriptServer::get_global_class_path(class_name));
- String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(test.get_source_file(), &base_type);
- if (class_name.is_empty()) {
- continue;
+ ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file);
}
- ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
- "Class name '" + class_name + "' from " + test.get_source_file() + " is already used in " + ScriptServer::get_global_class_path(class_name));
- ScriptServer::add_global_class(class_name, base_type, gdscript_name, test.get_source_file());
+ next = dir->get_next();
}
+
+ dir->list_dir_end();
+
return true;
}
+bool GDScriptTestRunner::generate_class_index() {
+ Error err = OK;
+ Ref<DirAccess> dir(DirAccess::open(source_dir, &err));
+
+ ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
+
+ source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
+ return generate_class_index_recursive(dir->get_current_dir());
+}
+
GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir) {
source_file = p_source_path;
output_file = p_output_path;
@@ -485,7 +533,15 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
Ref<GDScript> script;
script.instantiate();
script->set_path(source_file);
- err = script->load_source_code(source_file);
+ if (tokenizer_mode == TOKENIZER_TEXT) {
+ err = script->load_source_code(source_file);
+ } else {
+ String code = FileAccess::get_file_as_string(source_file, &err);
+ if (!err) {
+ Vector<uint8_t> buffer = GDScriptTokenizerBuffer::parse_code_string(code, GDScriptTokenizerBuffer::COMPRESS_ZSTD);
+ script->set_binary_tokens_source(buffer);
+ }
+ }
if (err != OK) {
enable_stdout();
result.status = GDTEST_LOAD_ERROR;
@@ -495,7 +551,11 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
// Test parsing.
GDScriptParser parser;
- err = parser.parse(script->get_source_code(), source_file, false);
+ if (tokenizer_mode == TOKENIZER_TEXT) {
+ err = parser.parse(script->get_source_code(), source_file, false);
+ } else {
+ err = parser.parse_binary(script->get_binary_tokens_source(), source_file);
+ }
if (err != OK) {
enable_stdout();
result.status = GDTEST_PARSER_ERROR;
@@ -584,7 +644,14 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
add_print_handler(&_print_handler);
add_error_handler(&_error_handler);
- script->reload();
+ err = script->reload();
+ if (err) {
+ enable_stdout();
+ result.status = GDTEST_LOAD_ERROR;
+ result.output = "";
+ result.passed = false;
+ ERR_FAIL_V_MSG(result, "\nCould not reload script: '" + source_file + "'");
+ }
// Create object instance for test.
Object *obj = ClassDB::instantiate(script->get_native()->get_name());
diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h
index b1190604ad..57e3ac86f9 100644
--- a/modules/gdscript/tests/gdscript_test_runner.h
+++ b/modules/gdscript/tests/gdscript_test_runner.h
@@ -62,6 +62,11 @@ public:
bool passed;
};
+ enum TokenizerMode {
+ TOKENIZER_TEXT,
+ TOKENIZER_BUFFER,
+ };
+
private:
struct ErrorHandlerData {
TestResult *result = nullptr;
@@ -79,6 +84,8 @@ private:
PrintHandlerList _print_handler;
ErrorHandlerList _error_handler;
+ TokenizerMode tokenizer_mode = TOKENIZER_TEXT;
+
void enable_stdout();
void disable_stdout();
bool check_output(const String &p_output) const;
@@ -96,6 +103,9 @@ public:
const String get_source_relative_filepath() const { return source_file.trim_prefix(base_dir); }
const String &get_output_file() const { return output_file; }
+ void set_tokenizer_mode(TokenizerMode p_tokenizer_mode) { tokenizer_mode = p_tokenizer_mode; }
+ TokenizerMode get_tokenizer_mode() const { return tokenizer_mode; }
+
GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir);
GDScriptTest() :
GDScriptTest(String(), String(), String()) {} // Needed to use in Vector.
@@ -108,6 +118,7 @@ class GDScriptTestRunner {
bool is_generating = false;
bool do_init_languages = false;
bool print_filenames; // Whether filenames should be printed when generated/running tests
+ bool binary_tokens; // Test with buffer tokenizer.
bool make_tests();
bool make_tests_for_dir(const String &p_dir);
@@ -120,7 +131,7 @@ public:
int run_tests();
bool generate_outputs();
- GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames = false);
+ GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames = false, bool p_use_binary_tokens = false);
~GDScriptTestRunner();
};
diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h
index 5fd7d942d2..b2289ef9cc 100644
--- a/modules/gdscript/tests/gdscript_test_runner_suite.h
+++ b/modules/gdscript/tests/gdscript_test_runner_suite.h
@@ -37,13 +37,13 @@
namespace GDScriptTests {
+// TODO: Handle some cases failing on release builds. See: https://github.com/godotengine/godot/pull/88452
+#ifdef TOOLS_ENABLED
TEST_SUITE("[Modules][GDScript]") {
- // GDScript 2.0 is still under heavy construction.
- // Allow the tests to fail, but do not ignore errors during development.
- // Update the scripts and expected output as needed.
TEST_CASE("Script compilation and runtime") {
bool print_filenames = OS::get_singleton()->get_cmdline_args().find("--print-filenames") != nullptr;
- GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames);
+ bool use_binary_tokens = OS::get_singleton()->get_cmdline_args().find("--use-binary-tokens") != nullptr;
+ GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames, use_binary_tokens);
int fail_count = runner.run_tests();
INFO("Make sure `*.out` files have expected results.");
REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass.");
@@ -70,6 +70,7 @@ func _init():
ref_counted->set_script(gdscript);
CHECK_MESSAGE(int(ref_counted->get_meta("result")) == 42, "The script should assign object metadata successfully.");
}
+#endif // TOOLS_ENABLED
TEST_CASE("[Modules][GDScript] Validate built-in API") {
GDScriptLanguage *lang = GDScriptLanguage::get_singleton();
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out
index 73a54d7820..a6a3973255 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> analyzer/errors/outer_class_constants.gd
>> 8
->> Invalid get index 'OUTER_CONST' (on base: 'GDScript').
+>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'GDScript'.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out
index 92e7b9316e..70fdc5b62c 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> analyzer/errors/outer_class_constants_as_variant.gd
>> 9
->> Invalid get index 'OUTER_CONST' (on base: 'GDScript').
+>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'GDScript'.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out
index 892f8e2c3f..6632f056bd 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> analyzer/errors/outer_class_instance_constants.gd
>> 8
->> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)').
+>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'RefCounted (Inner)'.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out
index 8257e74f57..0459b756d1 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> analyzer/errors/outer_class_instance_constants_as_variant.gd
>> 9
->> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)').
+>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'RefCounted (Inner)'.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd
new file mode 100644
index 0000000000..a98f69f3ac
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd
@@ -0,0 +1,14 @@
+# GH-83468
+
+func non_static_func():
+ pass
+
+static func static_func():
+ var f := func ():
+ var g := func ():
+ non_static_func()
+ g.call()
+ f.call()
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out
new file mode 100644
index 0000000000..b78f131345
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-static function "non_static_func()" from static function "static_func()".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd
new file mode 100644
index 0000000000..7af9ff274c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd
@@ -0,0 +1,15 @@
+# GH-83468
+
+func non_static_func():
+ pass
+
+static func static_func(
+ f := func ():
+ var g := func ():
+ non_static_func()
+ g.call()
+):
+ f.call()
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out
new file mode 100644
index 0000000000..b78f131345
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-static function "non_static_func()" from static function "static_func()".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd
new file mode 100644
index 0000000000..5130973bd2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd
@@ -0,0 +1,14 @@
+# GH-83468
+
+func non_static_func():
+ pass
+
+static var static_var = func ():
+ var f := func ():
+ var g := func ():
+ non_static_func()
+ g.call()
+ f.call()
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out
new file mode 100644
index 0000000000..c0308c81f3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-static function "non_static_func()" from a static variable initializer.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd
new file mode 100644
index 0000000000..2d15b4e3e5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd
@@ -0,0 +1,15 @@
+# GH-83468
+
+func non_static_func():
+ pass
+
+static var static_var:
+ set(_value):
+ var f := func ():
+ var g := func ():
+ non_static_func()
+ g.call()
+ f.call()
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out
new file mode 100644
index 0000000000..cdf3ab2aeb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot call non-static function "non_static_func()" from static function "@static_var_setter()".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out
index f1e9ec34f2..81554ec707 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot call non-static function "non_static()" for static variable initializer.
+Cannot call non-static function "non_static()" from a static variable initializer.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
index dafd2ec0c8..39f490c4b3 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
@@ -4,14 +4,13 @@ enum MyEnum {A, B, C}
const Utils = preload("../../utils.notest.gd")
-@export var x1 = MyEnum
-@export var x2 = MyEnum.A
-@export var x3 := MyEnum
-@export var x4 := MyEnum.A
-@export var x5: MyEnum
+@export var test_1 = MyEnum
+@export var test_2 = MyEnum.A
+@export var test_3 := MyEnum
+@export var test_4 := MyEnum.A
+@export var test_5: MyEnum
func test():
for property in get_property_list():
- if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
- print(Utils.get_property_signature(property))
- print(" ", Utils.get_property_additional_info(property))
+ if str(property.name).begins_with("test_"):
+ Utils.print_property_extended_info(property)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out
index f1a13f1045..505af5f1f3 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out
@@ -1,11 +1,11 @@
GDTEST_OK
-@export var x1: Dictionary
+var test_1: Dictionary
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
-@export var x2: TestExportEnumAsDictionary.MyEnum
+var test_2: TestExportEnumAsDictionary.MyEnum
hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
-@export var x3: Dictionary
+var test_3: Dictionary
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
-@export var x4: TestExportEnumAsDictionary.MyEnum
+var test_4: TestExportEnumAsDictionary.MyEnum
hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
-@export var x5: TestExportEnumAsDictionary.MyEnum
+var test_5: TestExportEnumAsDictionary.MyEnum
hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
diff --git a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg
new file mode 100644
index 0000000000..27e695d245
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg
@@ -0,0 +1,4 @@
+[output]
+include=[
+ {"display": "autoplay"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd
new file mode 100644
index 0000000000..d41bbb970c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd
@@ -0,0 +1,6 @@
+extends Node
+
+var test: AnimationPlayer = $AnimationPlayer
+
+func _ready():
+ test.➡
diff --git a/modules/gdscript/tests/scripts/lsp/class.notest.gd b/modules/gdscript/tests/scripts/lsp/class.gd
index 53d0b14d72..53d0b14d72 100644
--- a/modules/gdscript/tests/scripts/lsp/class.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/class.gd
diff --git a/modules/gdscript/tests/scripts/lsp/enums.notest.gd b/modules/gdscript/tests/scripts/lsp/enums.gd
index 38b9ec110a..38b9ec110a 100644
--- a/modules/gdscript/tests/scripts/lsp/enums.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/enums.gd
diff --git a/modules/gdscript/tests/scripts/lsp/indentation.notest.gd b/modules/gdscript/tests/scripts/lsp/indentation.gd
index c25d73a719..c25d73a719 100644
--- a/modules/gdscript/tests/scripts/lsp/indentation.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/indentation.gd
diff --git a/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd b/modules/gdscript/tests/scripts/lsp/lambdas.gd
index 6f5d468eea..6f5d468eea 100644
--- a/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/lambdas.gd
diff --git a/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd b/modules/gdscript/tests/scripts/lsp/local_variables.gd
index b6cc46f7da..b6cc46f7da 100644
--- a/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/local_variables.gd
diff --git a/modules/gdscript/tests/scripts/lsp/properties.notest.gd b/modules/gdscript/tests/scripts/lsp/properties.gd
index 8dfaee2e5b..8dfaee2e5b 100644
--- a/modules/gdscript/tests/scripts/lsp/properties.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/properties.gd
diff --git a/modules/gdscript/tests/scripts/lsp/scopes.notest.gd b/modules/gdscript/tests/scripts/lsp/scopes.gd
index 20b8fb9bd7..20b8fb9bd7 100644
--- a/modules/gdscript/tests/scripts/lsp/scopes.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/scopes.gd
diff --git a/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.gd
index 338000fa0e..338000fa0e 100644
--- a/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd
+++ b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.gd
diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.gd
index 9ad77f1432..9ad77f1432 100644
--- a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.gd
diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.out
index 31bed2dbc7..31bed2dbc7 100644
--- a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out
+++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.out
diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd
index 13c89a0a09..7a7d6d953e 100644
--- a/modules/gdscript/tests/scripts/parser/features/annotations.gd
+++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd
@@ -1,48 +1,49 @@
extends Node
-@export_enum("A", "B", "C") var a0
-@export_enum("A", "B", "C",) var a1
+const Utils = preload("../../utils.notest.gd")
+
+@export_enum("A", "B", "C") var test_1
+@export_enum("A", "B", "C",) var test_2
@export_enum(
"A",
"B",
"C"
-) var a2
+) var test_3
@export_enum(
"A",
"B",
"C",
-) var a3
+) var test_4
@export
-var a4: int
+var test_5: int
@export()
-var a5: int
+var test_6: int
-@export() var a6: int
-@warning_ignore("onready_with_export") @onready @export var a7: int
-@warning_ignore("onready_with_export") @onready() @export() var a8: int
+@export() var test_7: int = 42
+@warning_ignore("onready_with_export") @onready @export var test_8: int = 42
+@warning_ignore("onready_with_export") @onready() @export() var test_9: int = 42
@warning_ignore("onready_with_export")
@onready
@export
-var a9: int
+var test_10: int = 42
@warning_ignore("onready_with_export")
@onready()
@export()
-var a10: int
+var test_11: int = 42
@warning_ignore("onready_with_export")
@onready()
@export()
-var a11: int
-
+var test_12: int = 42
func test():
for property in get_property_list():
- if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
- print(property)
+ if str(property.name).begins_with("test_"):
+ Utils.print_property_extended_info(property, self)
diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.out b/modules/gdscript/tests/scripts/parser/features/annotations.out
index 3af0436c53..2ba9dd7496 100644
--- a/modules/gdscript/tests/scripts/parser/features/annotations.out
+++ b/modules/gdscript/tests/scripts/parser/features/annotations.out
@@ -1,13 +1,25 @@
GDTEST_OK
-{ "name": "a0", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
-{ "name": "a1", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
-{ "name": "a2", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
-{ "name": "a3", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
-{ "name": "a4", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
-{ "name": "a5", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
-{ "name": "a6", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
-{ "name": "a7", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
-{ "name": "a8", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
-{ "name": "a9", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
-{ "name": "a10", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
-{ "name": "a11", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+var test_1: int = null
+ hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
+var test_2: int = null
+ hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
+var test_3: int = null
+ hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
+var test_4: int = null
+ hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
+var test_5: int = 0
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_6: int = 0
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_7: int = 42
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_8: int = 0
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_9: int = 0
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_10: int = 0
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_11: int = 0
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_12: int = 0
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
index 9b2c22dea1..4f2a43f4fe 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_enum.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
@@ -1,15 +1,16 @@
-@export_enum("Red", "Green", "Blue") var untyped
+const Utils = preload("../../utils.notest.gd")
-@export_enum("Red", "Green", "Blue") var weak_int = 0
-@export_enum("Red", "Green", "Blue") var weak_string = ""
+@export_enum("Red", "Green", "Blue") var test_untyped
-@export_enum("Red", "Green", "Blue") var hard_int: int
-@export_enum("Red", "Green", "Blue") var hard_string: String
+@export_enum("Red", "Green", "Blue") var test_weak_int = 0
+@export_enum("Red", "Green", "Blue") var test_weak_string = ""
-@export_enum("Red:10", "Green:20", "Blue:30") var with_values
+@export_enum("Red", "Green", "Blue") var test_hard_int: int
+@export_enum("Red", "Green", "Blue") var test_hard_string: String
+
+@export_enum("Red:10", "Green:20", "Blue:30") var test_with_values
func test():
for property in get_property_list():
- if property.name in ["untyped", "weak_int", "weak_string", "hard_int",
- "hard_string", "with_values"]:
- prints(property.name, property.type, property.hint_string)
+ if str(property.name).begins_with("test_"):
+ Utils.print_property_extended_info(property, self)
diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.out b/modules/gdscript/tests/scripts/parser/features/export_enum.out
index 330b7eaf01..43f5e197ad 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_enum.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_enum.out
@@ -1,7 +1,13 @@
GDTEST_OK
-untyped 2 Red,Green,Blue
-weak_int 2 Red,Green,Blue
-weak_string 4 Red,Green,Blue
-hard_int 2 Red,Green,Blue
-hard_string 4 Red,Green,Blue
-with_values 2 Red:10,Green:20,Blue:30
+var test_untyped: int = null
+ hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_int: int = 0
+ hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_string: String = ""
+ hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_hard_int: int = 0
+ hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_hard_string: String = ""
+ hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_with_values: int = null
+ hint=ENUM hint_string="Red:10,Green:20,Blue:30" usage=DEFAULT|SCRIPT_VARIABLE
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index c9d05a7e68..2a218774de 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -1,23 +1,22 @@
extends Node
-@export var example = 99
-@export_range(0, 100) var example_range = 100
-@export_range(0, 100, 1) var example_range_step = 101
-@export_range(0, 100, 1, "or_greater") var example_range_step_or_greater = 102
+const Utils = preload("../../utils.notest.gd")
-@export var color: Color
-@export_color_no_alpha var color_no_alpha: Color
-@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello"
-@export var node: Node
-@export var node_array: Array[Node]
+@export var test_weak_int = 1
+@export var test_hard_int: int = 2
+@export_storage var test_storage_untyped
+@export_storage var test_storage_weak_int = 3 # Property info still `Variant`, unlike `@export`.
+@export_storage var test_storage_hard_int: int = 4
+@export_range(0, 100) var test_range = 100
+@export_range(0, 100, 1) var test_range_step = 101
+@export_range(0, 100, 1, "or_greater") var test_range_step_or_greater = 102
+@export var test_color: Color
+@export_color_no_alpha var test_color_no_alpha: Color
+@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var test_node_path := ^"hello"
+@export var test_node: Node
+@export var test_node_array: Array[Node]
func test():
- print(example)
- print(example_range)
- print(example_range_step)
- print(example_range_step_or_greater)
- print(color)
- print(color_no_alpha)
- print(nodepath)
- print(node)
- print(var_to_str(node_array))
+ for property in get_property_list():
+ if str(property.name).begins_with("test_"):
+ Utils.print_property_extended_info(property, self)
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index 5430c975f4..baadcd4ee8 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -1,10 +1,27 @@
GDTEST_OK
-99
-100
-101
-102
-(0, 0, 0, 1)
-(0, 0, 0, 1)
-hello
-<null>
-Array[Node]([])
+var test_weak_int: int = 1
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_hard_int: int = 2
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_storage_untyped: Variant = null
+ hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE|NIL_IS_VARIANT
+var test_storage_weak_int: Variant = 3
+ hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE|NIL_IS_VARIANT
+var test_storage_hard_int: int = 4
+ hint=NONE hint_string="" usage=STORAGE|SCRIPT_VARIABLE
+var test_range: int = 100
+ hint=RANGE hint_string="0,100" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_step: int = 101
+ hint=RANGE hint_string="0,100,1" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_step_or_greater: int = 102
+ hint=RANGE hint_string="0,100,1,or_greater" usage=DEFAULT|SCRIPT_VARIABLE
+var test_color: Color = Color(0, 0, 0, 1)
+ hint=NONE hint_string="Color" usage=DEFAULT|SCRIPT_VARIABLE
+var test_color_no_alpha: Color = Color(0, 0, 0, 1)
+ hint=COLOR_NO_ALPHA hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
+var test_node_path: NodePath = NodePath("hello")
+ hint=NODE_PATH_VALID_TYPES hint_string="Sprite2D,Sprite3D,Control,Node" usage=DEFAULT|SCRIPT_VARIABLE
+var test_node: Node = null
+ hint=NODE_TYPE hint_string="Node" usage=DEFAULT|SCRIPT_VARIABLE
+var test_node_array: Array = Array[Node]([])
+ hint=TYPE_STRING hint_string="24/34:Node" usage=DEFAULT|SCRIPT_VARIABLE
diff --git a/modules/gdscript/tests/scripts/parser/features/is_not_operator.gd b/modules/gdscript/tests/scripts/parser/features/is_not_operator.gd
new file mode 100644
index 0000000000..b744e6170b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/is_not_operator.gd
@@ -0,0 +1,11 @@
+func test():
+ var i: Variant = 123
+ var s: Variant = "str"
+ prints(i is int, i is not int)
+ prints(s is int, s is not int)
+
+ var a: Variant = false
+ var b: Variant = true
+ prints(a == b is int, a == b is not int)
+ prints(a == (b is int), a == (b is not int))
+ prints((a == b) is int, (a == b) is not int)
diff --git a/modules/gdscript/tests/scripts/parser/features/is_not_operator.out b/modules/gdscript/tests/scripts/parser/features/is_not_operator.out
new file mode 100644
index 0000000000..f0535f9c83
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/is_not_operator.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+true false
+false true
+true false
+true false
+false true
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_if.gd b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
index 86152f4543..7b82d9b1da 100644
--- a/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
@@ -9,6 +9,7 @@ func test():
# Alternatively, backslashes can be used.
if 1 == 1 \
+ \
and 2 == 2 and \
3 == 3:
pass
diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd
index 9e4b360fb2..82616ee3cf 100644
--- a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd
+++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd
@@ -6,6 +6,9 @@ var property:
set(value):
_backing = value - 1000
+var property_2:
+ get(): # Allow parentheses.
+ return 123
func test():
print("Not using self:")
@@ -35,3 +38,5 @@ func test():
self.property = 5000
print(self.property)
print(self._backing)
+
+ print(property_2)
diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out
index 560e0c3bd7..23f98f44ab 100644
--- a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out
+++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out
@@ -17,3 +17,4 @@ Using self:
-50
5000
4000
+123
diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot
index 25b49c0abd..c500ef443d 100644
--- a/modules/gdscript/tests/scripts/project.godot
+++ b/modules/gdscript/tests/scripts/project.godot
@@ -3,7 +3,7 @@
; It also helps for opening Godot to edit the scripts, but please don't
; let the editor changes be saved.
-config_version=4
+config_version=5
[application]
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out
index 2a97eaea44..c524a1ae6b 100644
--- a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> runtime/errors/constant_array_is_deep.gd
>> 6
->> Invalid set index '0' (on base: 'Dictionary') with value of type 'int'
+>> Invalid assignment of property or key '0' with value of type 'int' on a base object of type 'Dictionary'.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out
index c807db6b0c..cf51b0262d 100644
--- a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out
+++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out
@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
>> on function: test()
>> runtime/errors/constant_dictionary_is_deep.gd
>> 6
->> Invalid set index '0' (on base: 'Array') with value of type 'int'
+>> Invalid assignment of index '0' (on base: 'Array') with value of type 'int'.
diff --git a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd
new file mode 100644
index 0000000000..cb5ea827f6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd
@@ -0,0 +1,13 @@
+func test():
+ var array: Array = [1, 2, 3]
+ print(array)
+ var array_clear: Callable = array.clear
+ array_clear.call()
+ print(array)
+
+ var dictionary: Dictionary = {1: 2, 3: 4}
+ print(dictionary)
+ # `dictionary.clear` is treated as a key.
+ var dictionary_clear := Callable.create(dictionary, &"clear")
+ dictionary_clear.call()
+ print(dictionary)
diff --git a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out
new file mode 100644
index 0000000000..c12984ca37
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+[1, 2, 3]
+[]
+{ 1: 2, 3: 4 }
+{ }
diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
index e46f24cc5f..0133d7fcfc 100644
--- a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
@@ -1,17 +1,17 @@
-extends RefCounted # TODO: Fix standalone annotations parsing.
+const Utils = preload("../../utils.notest.gd")
# GH-73843
@export_group("Resource")
# GH-78252
-@export var prop_1: int
-@export_category("prop_1")
-@export var prop_2: int
+@export var test_1: int
+@export_category("test_1")
+@export var test_2: int
func test():
var resource := Resource.new()
prints("Not shadowed:", resource.get_class())
for property in get_property_list():
- if property.name in ["prop_1", "prop_2"]:
- print(property)
+ if str(property.name).begins_with("test_"):
+ Utils.print_property_extended_info(property, self)
diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out
index 96ae84e986..9387ec50d7 100644
--- a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out
+++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out
@@ -1,5 +1,8 @@
GDTEST_OK
Not shadowed: Resource
-{ "name": "prop_1", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
-{ "name": "prop_1", "class_name": &"", "type": 0, "hint": 0, "hint_string": "", "usage": 128 }
-{ "name": "prop_2", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+var test_1: int = 0
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
+@export_category("test_1")
+ hint=NONE hint_string="" usage=CATEGORY
+var test_2: int = 0
+ hint=NONE hint_string="int" usage=DEFAULT|SCRIPT_VARIABLE
diff --git a/modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd
new file mode 100644
index 0000000000..b9746a8207
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd
@@ -0,0 +1,10 @@
+func test():
+ var node := Node.new()
+ var callable: Callable = node.free
+ callable.call()
+ print(node)
+
+ node = Node.new()
+ callable = node["free"]
+ callable.call()
+ print(node)
diff --git a/modules/gdscript/tests/scripts/runtime/features/free_is_callable.out b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.out
new file mode 100644
index 0000000000..97bfc46d96
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+<Freed Object>
+<Freed Object>
diff --git a/modules/gdscript/tests/scripts/runtime/features/match_test_null.gd b/modules/gdscript/tests/scripts/runtime/features/match_test_null.gd
new file mode 100644
index 0000000000..9bb57b68ee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/match_test_null.gd
@@ -0,0 +1,6 @@
+func test():
+ match null:
+ null:
+ print('null matched')
+ _:
+ pass
diff --git a/modules/gdscript/tests/scripts/runtime/features/match_test_null.out b/modules/gdscript/tests/scripts/runtime/features/match_test_null.out
new file mode 100644
index 0000000000..7640cf42ab
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/match_test_null.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+null matched
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
index 805ea42455..6fe9647b4d 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
@@ -60,7 +60,7 @@ func test():
var script: Script = get_script()
for property in script.get_property_list():
if str(property.name).begins_with("test_"):
- print(Utils.get_property_signature(property, true))
+ print(Utils.get_property_signature(property, null, true))
for property in get_property_list():
if str(property.name).begins_with("test_"):
print(Utils.get_property_signature(property))
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out
index 3a91507da9..7c826ac05a 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.out
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out
@@ -6,13 +6,13 @@ static var test_static_var_hard_int: int
var test_var_untyped: Variant
var test_var_weak_null: Variant
var test_var_weak_int: Variant
-@export var test_var_weak_int_exported: int
+var test_var_weak_int_exported: int
var test_var_weak_variant_type: Variant
-@export var test_var_weak_variant_type_exported: Variant.Type
+var test_var_weak_variant_type_exported: Variant.Type
var test_var_hard_variant: Variant
var test_var_hard_int: int
var test_var_hard_variant_type: Variant.Type
-@export var test_var_hard_variant_type_exported: Variant.Type
+var test_var_hard_variant_type_exported: Variant.Type
var test_var_hard_node_process_mode: Node.ProcessMode
var test_var_hard_my_enum: TestMemberInfo.MyEnum
var test_var_hard_array: Array
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
index d0cbeeab85..563c6ce569 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
@@ -30,7 +30,7 @@ func test():
var b := B.new()
for property in (B as GDScript).get_property_list():
if str(property.name).begins_with("test_"):
- print(Utils.get_property_signature(property, true))
+ print(Utils.get_property_signature(property, null, true))
print("---")
for property in b.get_property_list():
if str(property.name).begins_with("test_"):
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd
index f6aa58737f..97e9da3b26 100644
--- a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd
@@ -1,12 +1,18 @@
-# GH-79521
+# GH-79521, GH-86032
class_name TestStaticMethodAsCallable
static func static_func() -> String:
return "Test"
+static func another_static_func():
+ prints("another_static_func:", static_func.call(), static_func.is_valid())
+
func test():
var a: Callable = TestStaticMethodAsCallable.static_func
var b: Callable = static_func
prints(a.call(), a.is_valid())
prints(b.call(), b.is_valid())
+ @warning_ignore("static_called_on_instance")
+ another_static_func()
+ TestStaticMethodAsCallable.another_static_func()
diff --git a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out
index e6d461b8f9..2b773ce8ee 100644
--- a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out
+++ b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out
@@ -1,3 +1,5 @@
GDTEST_OK
Test true
Test true
+another_static_func: Test true
+another_static_func: Test true
diff --git a/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd
new file mode 100644
index 0000000000..11f064bb83
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd
@@ -0,0 +1,10 @@
+func test():
+ print(print)
+ print(len)
+
+ prints.callv([1, 2, 3])
+ print(mini.call(1, 2))
+ print(len.bind("abc").call())
+
+ const ABSF = absf
+ print(ABSF.call(-1.2))
diff --git a/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out
new file mode 100644
index 0000000000..91549b9345
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+@GlobalScope::print (Callable)
+@GDScript::len (Callable)
+1 2 3
+1
+3
+1.2
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 781843b8e2..31818c9d01 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -20,24 +20,32 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
return type_string(property.type)
-static func get_property_signature(property: Dictionary, is_static: bool = false) -> String:
+static func get_property_signature(property: Dictionary, base: Object = null, is_static: bool = false) -> String:
+ if property.usage & PROPERTY_USAGE_CATEGORY:
+ return '@export_category("%s")' % str(property.name).c_escape()
+ if property.usage & PROPERTY_USAGE_GROUP:
+ return '@export_group("%s")' % str(property.name).c_escape()
+ if property.usage & PROPERTY_USAGE_SUBGROUP:
+ return '@export_subgroup("%s")' % str(property.name).c_escape()
+
var result: String = ""
if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE):
printerr("Missing `PROPERTY_USAGE_SCRIPT_VARIABLE` flag.")
- if property.usage & PROPERTY_USAGE_DEFAULT:
- result += "@export "
if is_static:
result += "static "
result += "var " + property.name + ": " + get_type(property)
+ if is_instance_valid(base):
+ result += " = " + var_to_str(base.get(property.name))
return result
-static func get_property_additional_info(property: Dictionary) -> String:
- return 'hint=%s hint_string="%s" usage=%s' % [
+static func print_property_extended_info(property: Dictionary, base: Object = null, is_static: bool = false) -> void:
+ print(get_property_signature(property, base, is_static))
+ print(' hint=%s hint_string="%s" usage=%s' % [
get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"),
str(property.hint_string).c_escape(),
get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""),
- ]
+ ])
static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String:
@@ -153,7 +161,6 @@ static func get_property_usage_string(usage: int) -> String:
return "PROPERTY_USAGE_NONE"
const FLAGS: Array[Array] = [
- [PROPERTY_USAGE_DEFAULT, "PROPERTY_USAGE_DEFAULT"],
[PROPERTY_USAGE_STORAGE, "PROPERTY_USAGE_STORAGE"],
[PROPERTY_USAGE_EDITOR, "PROPERTY_USAGE_EDITOR"],
[PROPERTY_USAGE_INTERNAL, "PROPERTY_USAGE_INTERNAL"],
@@ -187,6 +194,10 @@ static func get_property_usage_string(usage: int) -> String:
var result: String = ""
+ if (usage & PROPERTY_USAGE_DEFAULT) == PROPERTY_USAGE_DEFAULT:
+ result += "PROPERTY_USAGE_DEFAULT|"
+ usage &= ~PROPERTY_USAGE_DEFAULT
+
for flag in FLAGS:
if usage & flag[0]:
result += flag[1] + "|"
diff --git a/modules/gdscript/tests/test_completion.h b/modules/gdscript/tests/test_completion.h
new file mode 100644
index 0000000000..fd6b5321e6
--- /dev/null
+++ b/modules/gdscript/tests/test_completion.h
@@ -0,0 +1,203 @@
+/**************************************************************************/
+/* test_completion.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_COMPLETION_H
+#define TEST_COMPLETION_H
+
+#ifdef TOOLS_ENABLED
+
+#include "core/io/config_file.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+#include "core/object/script_language.h"
+#include "core/variant/dictionary.h"
+#include "core/variant/variant.h"
+#include "gdscript_test_runner.h"
+#include "modules/modules_enabled.gen.h" // For mono.
+#include "scene/resources/packed_scene.h"
+
+#include "../gdscript.h"
+#include "tests/test_macros.h"
+
+#include "editor/editor_settings.h"
+#include "scene/theme/theme_db.h"
+
+namespace GDScriptTests {
+
+static bool match_option(const Dictionary p_expected, const ScriptLanguage::CodeCompletionOption p_got) {
+ if (p_expected.get("display", p_got.display) != p_got.display) {
+ return false;
+ }
+ if (p_expected.get("insert_text", p_got.insert_text) != p_got.insert_text) {
+ return false;
+ }
+ if (p_expected.get("kind", p_got.kind) != Variant(p_got.kind)) {
+ return false;
+ }
+ if (p_expected.get("location", p_got.location) != Variant(p_got.location)) {
+ return false;
+ }
+ return true;
+}
+
+static void to_dict_list(Variant p_variant, List<Dictionary> &p_list) {
+ ERR_FAIL_COND(!p_variant.is_array());
+
+ Array arr = p_variant;
+ for (int i = 0; i < arr.size(); i++) {
+ if (arr[i].get_type() == Variant::DICTIONARY) {
+ p_list.push_back(arr[i]);
+ }
+ }
+}
+
+static void test_directory(const String &p_dir) {
+ Error err = OK;
+ Ref<DirAccess> dir = DirAccess::open(p_dir, &err);
+
+ if (err != OK) {
+ FAIL("Invalid test directory.");
+ return;
+ }
+
+ String path = dir->get_current_dir();
+
+ dir->list_dir_begin();
+ String next = dir->get_next();
+
+ while (!next.is_empty()) {
+ if (dir->current_is_dir()) {
+ if (next == "." || next == "..") {
+ next = dir->get_next();
+ continue;
+ }
+ test_directory(path.path_join(next));
+ } else if (next.ends_with(".gd") && !next.ends_with(".notest.gd")) {
+ Ref<FileAccess> acc = FileAccess::open(path.path_join(next), FileAccess::READ, &err);
+
+ if (err != OK) {
+ next = dir->get_next();
+ continue;
+ }
+
+ String code = acc->get_as_utf8_string();
+ // For ease of reading ➡ (0x27A1) acts as sentinel char instead of 0xFFFF in the files.
+ code = code.replace_first(String::chr(0x27A1), String::chr(0xFFFF));
+ // Require pointer sentinel char in scripts.
+ CHECK(code.find_char(0xFFFF) != -1);
+
+ ConfigFile conf;
+ if (conf.load(path.path_join(next.get_basename() + ".cfg")) != OK) {
+ FAIL("No config file found.");
+ }
+
+#ifndef MODULE_MONO_ENABLED
+ if (conf.get_value("input", "cs", false)) {
+ next = dir->get_next();
+ continue;
+ }
+#endif
+
+ EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", conf.get_value("input", "use_single_quotes", false));
+
+ List<Dictionary> include;
+ to_dict_list(conf.get_value("output", "include", Array()), include);
+
+ List<Dictionary> exclude;
+ to_dict_list(conf.get_value("output", "exclude", Array()), exclude);
+
+ List<ScriptLanguage::CodeCompletionOption> options;
+ String call_hint;
+ bool forced;
+
+ Node *owner = nullptr;
+ if (conf.has_section_key("input", "scene")) {
+ Ref<PackedScene> scene = ResourceLoader::load(conf.get_value("input", "scene"), "PackedScene");
+ if (scene.is_valid()) {
+ owner = scene->instantiate();
+ }
+ } else if (dir->file_exists(next.get_basename() + ".tscn")) {
+ Ref<PackedScene> scene = ResourceLoader::load(path.path_join(next.get_basename() + ".tscn"), "PackedScene");
+ if (scene.is_valid()) {
+ owner = scene->instantiate();
+ }
+ }
+
+ GDScriptLanguage::get_singleton()->complete_code(code, path.path_join(next), owner, &options, forced, call_hint);
+ String contains_excluded;
+ for (ScriptLanguage::CodeCompletionOption &option : options) {
+ for (const Dictionary &E : exclude) {
+ if (match_option(E, option)) {
+ contains_excluded = option.display;
+ break;
+ }
+ }
+ if (!contains_excluded.is_empty()) {
+ break;
+ }
+
+ for (const Dictionary &E : include) {
+ if (match_option(E, option)) {
+ include.erase(E);
+ break;
+ }
+ }
+ }
+ CHECK_MESSAGE(contains_excluded.is_empty(), "Autocompletion suggests illegal option '", contains_excluded, "' for '", path.path_join(next), "'.");
+ CHECK(include.is_empty());
+
+ String expected_call_hint = conf.get_value("output", "call_hint", call_hint);
+ bool expected_forced = conf.get_value("output", "forced", forced);
+
+ CHECK(expected_call_hint == call_hint);
+ CHECK(expected_forced == forced);
+
+ if (owner) {
+ memdelete(owner);
+ }
+ }
+ next = dir->get_next();
+ }
+}
+
+TEST_SUITE("[Modules][GDScript][Completion]") {
+ TEST_CASE("[Editor] Check suggestion list") {
+ // Set all editor settings that code completion relies on.
+ EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", false);
+ init_language("modules/gdscript/tests/scripts");
+
+ test_directory("modules/gdscript/tests/scripts/completion");
+ }
+}
+} // namespace GDScriptTests
+
+#endif
+
+#endif // TEST_COMPLETION_H
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index 467bedc4b2..f6965cf7cf 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -34,6 +34,7 @@
#include "../gdscript_compiler.h"
#include "../gdscript_parser.h"
#include "../gdscript_tokenizer.h"
+#include "../gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
@@ -50,7 +51,7 @@
namespace GDScriptTests {
static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
- GDScriptTokenizer tokenizer;
+ GDScriptTokenizerText tokenizer;
tokenizer.set_source_code(p_code);
int tab_size = 4;
@@ -107,6 +108,53 @@ static void test_tokenizer(const String &p_code, const Vector<String> &p_lines)
print_line(current.get_name()); // Should be EOF
}
+static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines);
+
+static void test_tokenizer_buffer(const String &p_code, const Vector<String> &p_lines) {
+ Vector<uint8_t> binary = GDScriptTokenizerBuffer::parse_code_string(p_code, GDScriptTokenizerBuffer::COMPRESS_NONE);
+ test_tokenizer_buffer(binary, p_lines);
+}
+
+static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines) {
+ GDScriptTokenizerBuffer tokenizer;
+ tokenizer.set_code_buffer(p_buffer);
+
+ int tab_size = 4;
+#ifdef TOOLS_ENABLED
+ if (EditorSettings::get_singleton()) {
+ tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
+ }
+#endif // TOOLS_ENABLED
+ String tab = String(" ").repeat(tab_size);
+
+ GDScriptTokenizer::Token current = tokenizer.scan();
+ while (current.type != GDScriptTokenizer::Token::TK_EOF) {
+ StringBuilder token;
+ token += " --> "; // Padding for line number.
+
+ for (int l = current.start_line; l <= current.end_line && l <= p_lines.size(); l++) {
+ print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
+ }
+
+ token += current.get_name();
+
+ if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {
+ token += "(";
+ token += Variant::get_type_name(current.literal.get_type());
+ token += ") ";
+ token += current.literal;
+ }
+
+ print_line(token.as_string());
+
+ print_line("-------------------------------------------------------");
+
+ current = tokenizer.scan();
+ }
+
+ print_line(current.get_name()); // Should be EOF
+}
+
static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
GDScriptParser parser;
Error err = parser.parse(p_code, p_script_path, false);
@@ -119,7 +167,7 @@ static void test_parser(const String &p_code, const String &p_script_path, const
}
GDScriptAnalyzer analyzer(&parser);
- analyzer.analyze();
+ err = analyzer.analyze();
if (err != OK) {
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
@@ -212,7 +260,7 @@ void test(TestType p_type) {
}
String test = cmdlargs.back()->get();
- if (!test.ends_with(".gd")) {
+ if (!test.ends_with(".gd") && !test.ends_with(".gdc")) {
print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
return;
}
@@ -255,6 +303,13 @@ void test(TestType p_type) {
case TEST_TOKENIZER:
test_tokenizer(code, lines);
break;
+ case TEST_TOKENIZER_BUFFER:
+ if (test.ends_with(".gdc")) {
+ test_tokenizer_buffer(buf, lines);
+ } else {
+ test_tokenizer_buffer(code, lines);
+ }
+ break;
case TEST_PARSER:
test_parser(code, test, lines);
break;
diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h
index b39dfe2b5a..32f278d5ce 100644
--- a/modules/gdscript/tests/test_gdscript.h
+++ b/modules/gdscript/tests/test_gdscript.h
@@ -39,6 +39,7 @@ namespace GDScriptTests {
enum TestType {
TEST_TOKENIZER,
+ TEST_TOKENIZER_BUFFER,
TEST_PARSER,
TEST_COMPILER,
TEST_BYTECODE,
diff --git a/modules/gdscript/tests/test_lsp.h b/modules/gdscript/tests/test_lsp.h
index e57df00e2d..6192272f80 100644
--- a/modules/gdscript/tests/test_lsp.h
+++ b/modules/gdscript/tests/test_lsp.h
@@ -76,7 +76,7 @@ namespace GDScriptTests {
// LSP GDScript test scripts are located inside project of other GDScript tests:
// Cannot reset `ProjectSettings` (singleton) -> Cannot load another workspace and resources in there.
// -> Reuse GDScript test project. LSP specific scripts are then placed inside `lsp` folder.
-// Access via `res://lsp/my_script.notest.gd`.
+// Access via `res://lsp/my_script.gd`.
const String root = "modules/gdscript/tests/scripts/";
/*
@@ -394,7 +394,7 @@ func f():
Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace();
{
- String path = "res://lsp/local_variables.notest.gd";
+ String path = "res://lsp/local_variables.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -413,7 +413,7 @@ func f():
}
SUBCASE("Can get correct ranges for indented variables") {
- String path = "res://lsp/indentation.notest.gd";
+ String path = "res://lsp/indentation.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -421,7 +421,7 @@ func f():
}
SUBCASE("Can get correct ranges for scopes") {
- String path = "res://lsp/scopes.notest.gd";
+ String path = "res://lsp/scopes.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -429,7 +429,7 @@ func f():
}
SUBCASE("Can get correct ranges for lambda") {
- String path = "res://lsp/lambdas.notest.gd";
+ String path = "res://lsp/lambdas.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -437,7 +437,7 @@ func f():
}
SUBCASE("Can get correct ranges for inner class") {
- String path = "res://lsp/class.notest.gd";
+ String path = "res://lsp/class.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -445,7 +445,7 @@ func f():
}
SUBCASE("Can get correct ranges for inner class") {
- String path = "res://lsp/enums.notest.gd";
+ String path = "res://lsp/enums.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -453,7 +453,7 @@ func f():
}
SUBCASE("Can get correct ranges for shadowing & shadowed variables") {
- String path = "res://lsp/shadowing_initializer.notest.gd";
+ String path = "res://lsp/shadowing_initializer.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);
@@ -461,7 +461,7 @@ func f():
}
SUBCASE("Can get correct ranges for properties and getter/setter") {
- String path = "res://lsp/properties.notest.gd";
+ String path = "res://lsp/properties.gd";
assert_no_errors_in(path);
String uri = workspace->get_file_uri(path);
Vector<InlineTestData> all_test_data = read_tests(path);