diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 190 |
1 files changed, 111 insertions, 79 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index e17ee0668f..debc85ebbf 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -571,8 +571,8 @@ void GDScriptParser::parse_program() { class_doc_line = MIN(class_doc_line, E.key); } } - if (has_comment(class_doc_line)) { - get_class_doc_comment(class_doc_line, head->doc_brief_description, head->doc_description, head->doc_tutorials, false); + if (has_comment(class_doc_line, true)) { + head->doc_data = parse_class_doc_comment(class_doc_line, false); } #endif // TOOLS_ENABLED @@ -771,32 +771,22 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b // Check whether current line has a doc comment if (has_comment(previous.start_line, true)) { - member->doc_description = get_doc_comment(previous.start_line, true); + if constexpr (std::is_same_v<T, ClassNode>) { + member->doc_data = parse_class_doc_comment(previous.start_line, true, true); + } else { + member->doc_data = parse_doc_comment(previous.start_line, true); + } } else if (has_comment(doc_comment_line, true)) { if constexpr (std::is_same_v<T, ClassNode>) { - get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true); + member->doc_data = parse_class_doc_comment(doc_comment_line, true); } else { - member->doc_description = get_doc_comment(doc_comment_line); + member->doc_data = parse_doc_comment(doc_comment_line); } } #endif // TOOLS_ENABLED if (member->identifier != nullptr) { if (!((String)member->identifier->name).is_empty()) { // Enums may be unnamed. - -#ifdef DEBUG_ENABLED - List<MethodInfo> gdscript_funcs; - GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); - for (MethodInfo &info : gdscript_funcs) { - if (info.name == member->identifier->name) { - push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); - } - } - if (Variant::has_utility_function(member->identifier->name)) { - push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); - } -#endif - if (current_class->members_indices.has(member->identifier->name)) { push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); } else { @@ -1139,6 +1129,7 @@ GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) { ConstantNode *constant = alloc_node<ConstantNode>(); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) { + complete_extents(constant); return nullptr; } @@ -1327,25 +1318,34 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { #ifdef TOOLS_ENABLED // Enum values documentation. for (int i = 0; i < enum_node->values.size(); i++) { + int doc_comment_line = enum_node->values[i].line; + bool single_line = false; + + if (has_comment(doc_comment_line, true)) { + single_line = true; + } else if (has_comment(doc_comment_line - 1, true)) { + doc_comment_line--; + } else { + continue; + } + if (i == enum_node->values.size() - 1) { // If close bracket is same line as last value. - if (enum_node->values[i].line != previous.start_line && has_comment(enum_node->values[i].line)) { - if (named) { - enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); - } else { - current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); - } + if (doc_comment_line == previous.start_line) { + break; } } else { // If two values are same line. - if (enum_node->values[i].line != enum_node->values[i + 1].line && has_comment(enum_node->values[i].line)) { - if (named) { - enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); - } else { - current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); - } + if (doc_comment_line == enum_node->values[i + 1].line) { + continue; } } + + if (named) { + enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line); + } else { + current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line)); + } } #endif // TOOLS_ENABLED @@ -2147,6 +2147,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ ExpressionNode *expression = parse_expression(false); if (expression == nullptr) { push_error(R"(Expected expression for match pattern.)"); + complete_extents(pattern); return nullptr; } else { if (expression->type == GDScriptParser::Node::LITERAL) { @@ -2279,6 +2280,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode IdentifierNode *identifier = alloc_node<IdentifierNode>(); complete_extents(identifier); identifier->name = previous.get_identifier(); +#ifdef DEBUG_ENABLED + identifier->suite = current_suite; +#endif if (current_suite != nullptr && current_suite->has_local(identifier->name)) { const SuiteNode::Local &declaration = current_suite->get_local(identifier->name); @@ -3229,7 +3233,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode * } GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) { - push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)"); + push_error(R"("yield" was removed in Godot 4. Use "await" instead.)"); return nullptr; } @@ -3423,19 +3427,20 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { return tokenizer.get_comments()[p_line].comment.begins_with("##"); } -String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { +GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) { + MemberDocData result; + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - ERR_FAIL_COND_V(!comments.has(p_line), String()); + ERR_FAIL_COND_V(!comments.has(p_line), result); if (p_single_line) { if (comments[p_line].comment.begins_with("##")) { - return comments[p_line].comment.trim_prefix("##").strip_edges(); + result.description = comments[p_line].comment.trim_prefix("##").strip_edges(); + return result; } - return ""; + return result; } - String doc; - int line = p_line; DocLineState state = DOC_LINE_NORMAL; @@ -3463,29 +3468,42 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { } String doc_line = comments[line].comment.trim_prefix("##"); - doc += _process_doc_line(doc_line, doc, space_prefix, state); line++; + + if (state == DOC_LINE_NORMAL) { + String stripped_line = doc_line.strip_edges(); + if (stripped_line.begins_with("@deprecated")) { + result.is_deprecated = true; + continue; + } else if (stripped_line.begins_with("@experimental")) { + result.is_experimental = true; + continue; + } + } + + result.description += _process_doc_line(doc_line, result.description, space_prefix, state); } - return doc; + return result; } -void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class) { +GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) { + ClassDocData result; + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - if (!comments.has(p_line)) { - return; + ERR_FAIL_COND_V(!comments.has(p_line), result); + + if (p_single_line) { + if (comments[p_line].comment.begins_with("##")) { + result.brief = comments[p_line].comment.trim_prefix("##").strip_edges(); + return result; + } + return result; } - ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0); int line = p_line; DocLineState state = DOC_LINE_NORMAL; - enum Mode { - BRIEF, - DESC, - TUTORIALS, - DONE, - }; - Mode mode = BRIEF; + bool is_in_brief = true; if (p_inner_class) { while (comments.has(line - 1)) { @@ -3512,18 +3530,21 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & break; } - String doc_line = comments[line++].comment.trim_prefix("##"); - String title, link; // For tutorials. + String doc_line = comments[line].comment.trim_prefix("##"); + line++; if (state == DOC_LINE_NORMAL) { - // Set the read mode. String stripped_line = doc_line.strip_edges(); - if (stripped_line.is_empty()) { - if (mode == BRIEF && !p_brief.is_empty()) { - mode = DESC; - } + + // A blank line separates the description from the brief. + if (is_in_brief && !result.brief.is_empty() && stripped_line.is_empty()) { + is_in_brief = false; continue; - } else if (stripped_line.begins_with("@tutorial")) { + } + + if (stripped_line.begins_with("@tutorial")) { + String title, link; + int begin_scan = String("@tutorial").length(); if (begin_scan >= stripped_line.length()) { continue; // Invalid syntax. @@ -3565,24 +3586,21 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & link = stripped_line.substr(colon_pos).strip_edges(); } - mode = TUTORIALS; - } else if (mode == TUTORIALS) { // Tutorial docs are single line, we need a @tag after it. - mode = DONE; + result.tutorials.append(Pair<String, String>(title, link)); + continue; + } else if (stripped_line.begins_with("@deprecated")) { + result.is_deprecated = true; + continue; + } else if (stripped_line.begins_with("@experimental")) { + result.is_experimental = true; + continue; } } - switch (mode) { - case BRIEF: - p_brief += _process_doc_line(doc_line, p_brief, space_prefix, state); - break; - case DESC: - p_desc += _process_doc_line(doc_line, p_desc, space_prefix, state); - break; - case TUTORIALS: - p_tutorials.append(Pair<String, String>(title, link)); - break; - case DONE: - break; + if (is_in_brief) { + result.brief += _process_doc_line(doc_line, result.brief, space_prefix, state); + } else { + result.description += _process_doc_line(doc_line, result.description, space_prefix, state); } } @@ -3590,11 +3608,11 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & const ClassNode::Member &m = current_class->members[0]; int first_member_line = m.get_line(); if (first_member_line == line) { - p_brief = ""; - p_desc = ""; - p_tutorials.clear(); + result = ClassDocData(); // Clear result. } } + + return result; } #endif // TOOLS_ENABLED @@ -3879,6 +3897,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } } + // WARNING: Do not merge with the previous `if` because there `!=`, not `==`! if (p_annotation->name == SNAME("@export_flags")) { const int64_t max_flags = 32; Vector<String> t = arg_string.split(":", true, 1); @@ -3904,6 +3923,18 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Starting from argument %d, the flag value must be specified explicitly.)", i + 1, max_flags + 1), p_annotation->arguments[i]); return false; } + } else if (p_annotation->name == SNAME("@export_node_path")) { + String native_class = arg_string; + if (ScriptServer::is_global_class(arg_string)) { + native_class = ScriptServer::get_global_class_native_base(arg_string); + } + if (!ClassDB::class_exists(native_class)) { + push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" was not found in the global scope.)", i + 1, arg_string), p_annotation->arguments[i]); + return false; + } else if (!ClassDB::is_parent_class(native_class, SNAME("Node"))) { + push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" does not inherit "Node".)", i + 1, arg_string), p_annotation->arguments[i]); + return false; + } } if (i > 0) { @@ -3921,8 +3952,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node if (export_type.builtin_type == Variant::INT) { variable->export_info.type = Variant::INT; } - } - if (p_annotation->name == SNAME("@export_multiline")) { + } else if (p_annotation->name == SNAME("@export_multiline")) { if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) { DataType inner_type = export_type.get_container_element_type(); if (inner_type.builtin_type != Variant::STRING) { @@ -3950,6 +3980,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } } + // WARNING: Do not merge with the previous `else if`! Otherwise `else` (default variable type check) + // will not work for the above annotations. `@export` and `@export_enum` validate the type separately. if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); |