ソースを参照

Merge pull request #78941 from dalexeev/gds-doc-comments-deprecated-and-experimental

GDScript: Add `@deprecated` and `@experimental` doc comment tags
Yuri Sizov 2 年 前
コミット
41efc7cb86

+ 40 - 4
core/doc_data.h

@@ -532,6 +532,42 @@ public:
 		}
 		}
 	};
 	};
 
 
+	struct EnumDoc {
+		String description;
+		bool is_deprecated = false;
+		bool is_experimental = false;
+		static EnumDoc from_dict(const Dictionary &p_dict) {
+			EnumDoc doc;
+
+			if (p_dict.has("description")) {
+				doc.description = p_dict["description"];
+			}
+
+			if (p_dict.has("is_deprecated")) {
+				doc.is_deprecated = p_dict["is_deprecated"];
+			}
+
+			if (p_dict.has("is_experimental")) {
+				doc.is_experimental = p_dict["is_experimental"];
+			}
+
+			return doc;
+		}
+		static Dictionary to_dict(const EnumDoc &p_doc) {
+			Dictionary dict;
+
+			if (!p_doc.description.is_empty()) {
+				dict["description"] = p_doc.description;
+			}
+
+			dict["is_deprecated"] = p_doc.is_deprecated;
+
+			dict["is_experimental"] = p_doc.is_experimental;
+
+			return dict;
+		}
+	};
+
 	struct ClassDoc {
 	struct ClassDoc {
 		String name;
 		String name;
 		String inherits;
 		String inherits;
@@ -543,7 +579,7 @@ public:
 		Vector<MethodDoc> operators;
 		Vector<MethodDoc> operators;
 		Vector<MethodDoc> signals;
 		Vector<MethodDoc> signals;
 		Vector<ConstantDoc> constants;
 		Vector<ConstantDoc> constants;
-		HashMap<String, String> enums;
+		HashMap<String, EnumDoc> enums;
 		Vector<PropertyDoc> properties;
 		Vector<PropertyDoc> properties;
 		Vector<MethodDoc> annotations;
 		Vector<MethodDoc> annotations;
 		Vector<ThemeItemDoc> theme_properties;
 		Vector<ThemeItemDoc> theme_properties;
@@ -626,7 +662,7 @@ public:
 				enums = p_dict["enums"];
 				enums = p_dict["enums"];
 			}
 			}
 			for (int i = 0; i < enums.size(); i++) {
 			for (int i = 0; i < enums.size(); i++) {
-				doc.enums[enums.get_key_at_index(i)] = enums.get_value_at_index(i);
+				doc.enums[enums.get_key_at_index(i)] = EnumDoc::from_dict(enums.get_value_at_index(i));
 			}
 			}
 
 
 			Array properties;
 			Array properties;
@@ -740,8 +776,8 @@ public:
 
 
 			if (!p_doc.enums.is_empty()) {
 			if (!p_doc.enums.is_empty()) {
 				Dictionary enums;
 				Dictionary enums;
-				for (const KeyValue<String, String> &E : p_doc.enums) {
-					enums[E.key] = E.value;
+				for (const KeyValue<String, EnumDoc> &E : p_doc.enums) {
+					enums[E.key] = EnumDoc::to_dict(E.value);
 				}
 				}
 				dict["enums"] = enums;
 				dict["enums"] = enums;
 			}
 			}

+ 23 - 6
editor/editor_help.cpp

@@ -1059,6 +1059,7 @@ void EditorHelp::_update_doc() {
 			if (cd.properties[i].is_deprecated) {
 			if (cd.properties[i].is_deprecated) {
 				DEPRECATED_DOC_TAG;
 				DEPRECATED_DOC_TAG;
 			}
 			}
+
 			if (cd.properties[i].is_experimental) {
 			if (cd.properties[i].is_experimental) {
 				EXPERIMENTAL_DOC_TAG;
 				EXPERIMENTAL_DOC_TAG;
 			}
 			}
@@ -1303,6 +1304,7 @@ void EditorHelp::_update_doc() {
 			if (cd.signals[i].is_deprecated) {
 			if (cd.signals[i].is_deprecated) {
 				DEPRECATED_DOC_TAG;
 				DEPRECATED_DOC_TAG;
 			}
 			}
+
 			if (cd.signals[i].is_experimental) {
 			if (cd.signals[i].is_experimental) {
 				EXPERIMENTAL_DOC_TAG;
 				EXPERIMENTAL_DOC_TAG;
 			}
 			}
@@ -1363,6 +1365,7 @@ void EditorHelp::_update_doc() {
 				enum_line[E.key] = class_desc->get_paragraph_count() - 2;
 				enum_line[E.key] = class_desc->get_paragraph_count() - 2;
 
 
 				_push_code_font();
 				_push_code_font();
+
 				class_desc->push_color(theme_cache.title_color);
 				class_desc->push_color(theme_cache.title_color);
 				if (E.value.size() && E.value[0].is_bitfield) {
 				if (E.value.size() && E.value[0].is_bitfield) {
 					class_desc->add_text("flags  ");
 					class_desc->add_text("flags  ");
@@ -1379,21 +1382,32 @@ void EditorHelp::_update_doc() {
 				class_desc->push_color(theme_cache.headline_color);
 				class_desc->push_color(theme_cache.headline_color);
 				class_desc->add_text(e);
 				class_desc->add_text(e);
 				class_desc->pop();
 				class_desc->pop();
-				_pop_code_font();
 
 
 				class_desc->push_color(theme_cache.symbol_color);
 				class_desc->push_color(theme_cache.symbol_color);
 				class_desc->add_text(":");
 				class_desc->add_text(":");
 				class_desc->pop();
 				class_desc->pop();
 
 
+				if (cd.enums.has(e)) {
+					if (cd.enums[e].is_deprecated) {
+						DEPRECATED_DOC_TAG;
+					}
+
+					if (cd.enums[e].is_experimental) {
+						EXPERIMENTAL_DOC_TAG;
+					}
+				}
+
+				_pop_code_font();
+
 				class_desc->add_newline();
 				class_desc->add_newline();
 				class_desc->add_newline();
 				class_desc->add_newline();
 
 
 				// Enum description.
 				// Enum description.
-				if (e != "@unnamed_enums" && cd.enums.has(e) && !cd.enums[e].strip_edges().is_empty()) {
+				if (e != "@unnamed_enums" && cd.enums.has(e) && !cd.enums[e].description.strip_edges().is_empty()) {
 					class_desc->push_color(theme_cache.text_color);
 					class_desc->push_color(theme_cache.text_color);
 					_push_normal_font();
 					_push_normal_font();
 					class_desc->push_indent(1);
 					class_desc->push_indent(1);
-					_add_text(cd.enums[e]);
+					_add_text(cd.enums[e].description);
 					class_desc->pop();
 					class_desc->pop();
 					_pop_normal_font();
 					_pop_normal_font();
 					class_desc->pop();
 					class_desc->pop();
@@ -1417,6 +1431,7 @@ void EditorHelp::_update_doc() {
 					constant_line[enum_list[i].name] = class_desc->get_paragraph_count() - 2;
 					constant_line[enum_list[i].name] = class_desc->get_paragraph_count() - 2;
 
 
 					_push_code_font();
 					_push_code_font();
+
 					_add_bulletpoint();
 					_add_bulletpoint();
 					class_desc->push_color(theme_cache.headline_color);
 					class_desc->push_color(theme_cache.headline_color);
 					_add_text(enum_list[i].name);
 					_add_text(enum_list[i].name);
@@ -1427,7 +1442,6 @@ void EditorHelp::_update_doc() {
 					class_desc->push_color(theme_cache.value_color);
 					class_desc->push_color(theme_cache.value_color);
 					_add_text(_fix_constant(enum_list[i].value));
 					_add_text(_fix_constant(enum_list[i].value));
 					class_desc->pop();
 					class_desc->pop();
-					_pop_code_font();
 
 
 					if (enum_list[i].is_deprecated) {
 					if (enum_list[i].is_deprecated) {
 						DEPRECATED_DOC_TAG;
 						DEPRECATED_DOC_TAG;
@@ -1437,6 +1451,8 @@ void EditorHelp::_update_doc() {
 						EXPERIMENTAL_DOC_TAG;
 						EXPERIMENTAL_DOC_TAG;
 					}
 					}
 
 
+					_pop_code_font();
+
 					class_desc->add_newline();
 					class_desc->add_newline();
 
 
 					if (!enum_list[i].description.strip_edges().is_empty()) {
 					if (!enum_list[i].description.strip_edges().is_empty()) {
@@ -1503,8 +1519,6 @@ void EditorHelp::_update_doc() {
 				_add_text(_fix_constant(constants[i].value));
 				_add_text(_fix_constant(constants[i].value));
 				class_desc->pop();
 				class_desc->pop();
 
 
-				_pop_code_font();
-
 				if (constants[i].is_deprecated) {
 				if (constants[i].is_deprecated) {
 					DEPRECATED_DOC_TAG;
 					DEPRECATED_DOC_TAG;
 				}
 				}
@@ -1513,6 +1527,8 @@ void EditorHelp::_update_doc() {
 					EXPERIMENTAL_DOC_TAG;
 					EXPERIMENTAL_DOC_TAG;
 				}
 				}
 
 
+				_pop_code_font();
+
 				class_desc->add_newline();
 				class_desc->add_newline();
 
 
 				if (!constants[i].description.strip_edges().is_empty()) {
 				if (!constants[i].description.strip_edges().is_empty()) {
@@ -1692,6 +1708,7 @@ void EditorHelp::_update_doc() {
 			if (cd.properties[i].is_deprecated) {
 			if (cd.properties[i].is_deprecated) {
 				DEPRECATED_DOC_TAG;
 				DEPRECATED_DOC_TAG;
 			}
 			}
+
 			if (cd.properties[i].is_experimental) {
 			if (cd.properties[i].is_experimental) {
 				EXPERIMENTAL_DOC_TAG;
 				EXPERIMENTAL_DOC_TAG;
 			}
 			}

+ 31 - 13
modules/gdscript/editor/gdscript_docgen.cpp

@@ -101,14 +101,16 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
 		doc.inherits = p_script->native->get_name();
 		doc.inherits = p_script->native->get_name();
 	}
 	}
 
 
-	doc.brief_description = p_class->doc_brief_description;
-	doc.description = p_class->doc_description;
-	for (const Pair<String, String> &p : p_class->doc_tutorials) {
+	doc.brief_description = p_class->doc_data.brief;
+	doc.description = p_class->doc_data.description;
+	for (const Pair<String, String> &p : p_class->doc_data.tutorials) {
 		DocData::TutorialDoc td;
 		DocData::TutorialDoc td;
 		td.title = p.first;
 		td.title = p.first;
 		td.link = p.second;
 		td.link = p.second;
 		doc.tutorials.append(td);
 		doc.tutorials.append(td);
 	}
 	}
+	doc.is_deprecated = p_class->doc_data.is_deprecated;
+	doc.is_experimental = p_class->doc_data.is_experimental;
 
 
 	for (const GDP::ClassNode::Member &member : p_class->members) {
 	for (const GDP::ClassNode::Member &member : p_class->members) {
 		switch (member.type) {
 		switch (member.type) {
@@ -130,7 +132,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
 				p_script->member_lines[const_name] = m_const->start_line;
 				p_script->member_lines[const_name] = m_const->start_line;
 
 
 				DocData::ConstantDoc const_doc;
 				DocData::ConstantDoc const_doc;
-				DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_description);
+				DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_data.description);
+				const_doc.is_deprecated = m_const->doc_data.is_deprecated;
+				const_doc.is_experimental = m_const->doc_data.is_experimental;
 				doc.constants.push_back(const_doc);
 				doc.constants.push_back(const_doc);
 			} break;
 			} break;
 
 
@@ -153,7 +157,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
 				}
 				}
 
 
 				DocData::MethodDoc method_doc;
 				DocData::MethodDoc method_doc;
-				DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_description);
+				DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_data.description);
+				method_doc.is_deprecated = m_func->doc_data.is_deprecated;
+				method_doc.is_experimental = m_func->doc_data.is_experimental;
 				doc.methods.push_back(method_doc);
 				doc.methods.push_back(method_doc);
 			} break;
 			} break;
 
 
@@ -172,7 +178,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
 				}
 				}
 
 
 				DocData::MethodDoc signal_doc;
 				DocData::MethodDoc signal_doc;
-				DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_description);
+				DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_data.description);
+				signal_doc.is_deprecated = m_signal->doc_data.is_deprecated;
+				signal_doc.is_experimental = m_signal->doc_data.is_experimental;
 				doc.signals.push_back(signal_doc);
 				doc.signals.push_back(signal_doc);
 			} break;
 			} break;
 
 
@@ -185,7 +193,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
 				DocData::PropertyDoc prop_doc;
 				DocData::PropertyDoc prop_doc;
 
 
 				prop_doc.name = var_name;
 				prop_doc.name = var_name;
-				prop_doc.description = m_var->doc_description;
+				prop_doc.description = m_var->doc_data.description;
+				prop_doc.is_deprecated = m_var->doc_data.is_deprecated;
+				prop_doc.is_experimental = m_var->doc_data.is_experimental;
 
 
 				GDType dt = m_var->get_datatype();
 				GDType dt = m_var->get_datatype();
 				switch (dt.kind) {
 				switch (dt.kind) {
@@ -236,15 +246,21 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
 
 
 				p_script->member_lines[name] = m_enum->start_line;
 				p_script->member_lines[name] = m_enum->start_line;
 
 
-				doc.enums[name] = m_enum->doc_description;
+				DocData::EnumDoc enum_doc;
+				enum_doc.description = m_enum->doc_data.description;
+				enum_doc.is_deprecated = m_enum->doc_data.is_deprecated;
+				enum_doc.is_experimental = m_enum->doc_data.is_experimental;
+				doc.enums[name] = enum_doc;
 
 
 				for (const GDP::EnumNode::Value &val : m_enum->values) {
 				for (const GDP::EnumNode::Value &val : m_enum->values) {
 					DocData::ConstantDoc const_doc;
 					DocData::ConstantDoc const_doc;
 					const_doc.name = val.identifier->name;
 					const_doc.name = val.identifier->name;
 					const_doc.value = String(Variant(val.value));
 					const_doc.value = String(Variant(val.value));
 					const_doc.is_value_valid = true;
 					const_doc.is_value_valid = true;
-					const_doc.description = val.doc_description;
 					const_doc.enumeration = name;
 					const_doc.enumeration = name;
+					const_doc.description = val.doc_data.description;
+					const_doc.is_deprecated = val.doc_data.is_deprecated;
+					const_doc.is_experimental = val.doc_data.is_experimental;
 
 
 					doc.constants.push_back(const_doc);
 					doc.constants.push_back(const_doc);
 				}
 				}
@@ -257,10 +273,12 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
 
 
 				p_script->member_lines[name] = m_enum_val.identifier->start_line;
 				p_script->member_lines[name] = m_enum_val.identifier->start_line;
 
 
-				DocData::ConstantDoc constant_doc;
-				constant_doc.enumeration = "@unnamed_enums";
-				DocData::constant_doc_from_variant(constant_doc, name, m_enum_val.value, m_enum_val.doc_description);
-				doc.constants.push_back(constant_doc);
+				DocData::ConstantDoc const_doc;
+				DocData::constant_doc_from_variant(const_doc, name, m_enum_val.value, m_enum_val.doc_data.description);
+				const_doc.enumeration = "@unnamed_enums";
+				const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated;
+				const_doc.is_experimental = m_enum_val.doc_data.is_experimental;
+				doc.constants.push_back(const_doc);
 			} break;
 			} break;
 			case GDP::ClassNode::Member::GROUP:
 			case GDP::ClassNode::Member::GROUP:
 			case GDP::ClassNode::Member::UNDEFINED:
 			case GDP::ClassNode::Member::UNDEFINED:

+ 89 - 62
modules/gdscript/gdscript_parser.cpp

@@ -571,8 +571,8 @@ void GDScriptParser::parse_program() {
 			class_doc_line = MIN(class_doc_line, E.key);
 			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
 #endif // TOOLS_ENABLED
 
 
@@ -771,12 +771,16 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
 
 
 	// Check whether current line has a doc comment
 	// Check whether current line has a doc comment
 	if (has_comment(previous.start_line, true)) {
 	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)) {
 	} else if (has_comment(doc_comment_line, true)) {
 		if constexpr (std::is_same_v<T, ClassNode>) {
 		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 {
 		} else {
-			member->doc_description = get_doc_comment(doc_comment_line);
+			member->doc_data = parse_doc_comment(doc_comment_line);
 		}
 		}
 	}
 	}
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
@@ -1314,25 +1318,34 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	// Enum values documentation.
 	// Enum values documentation.
 	for (int i = 0; i < enum_node->values.size(); i++) {
 	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 (i == enum_node->values.size() - 1) {
 			// If close bracket is same line as last value.
 			// 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 {
 		} else {
 			// If two values are same line.
 			// 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
 #endif // TOOLS_ENABLED
 
 
@@ -3411,19 +3424,20 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
 	return tokenizer.get_comments()[p_line].comment.begins_with("##");
 	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();
 	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 (p_single_line) {
 		if (comments[p_line].comment.begins_with("##")) {
 		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;
 	int line = p_line;
 	DocLineState state = DOC_LINE_NORMAL;
 	DocLineState state = DOC_LINE_NORMAL;
 
 
@@ -3451,29 +3465,42 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
 		}
 		}
 
 
 		String doc_line = comments[line].comment.trim_prefix("##");
 		String doc_line = comments[line].comment.trim_prefix("##");
-		doc += _process_doc_line(doc_line, doc, space_prefix, state);
 		line++;
 		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();
 	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;
 	int line = p_line;
 	DocLineState state = DOC_LINE_NORMAL;
 	DocLineState state = DOC_LINE_NORMAL;
-	enum Mode {
-		BRIEF,
-		DESC,
-		TUTORIALS,
-		DONE,
-	};
-	Mode mode = BRIEF;
+	bool is_in_brief = true;
 
 
 	if (p_inner_class) {
 	if (p_inner_class) {
 		while (comments.has(line - 1)) {
 		while (comments.has(line - 1)) {
@@ -3500,18 +3527,21 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
 			break;
 			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) {
 		if (state == DOC_LINE_NORMAL) {
-			// Set the read mode.
 			String stripped_line = doc_line.strip_edges();
 			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;
 				continue;
-			} else if (stripped_line.begins_with("@tutorial")) {
+			}
+
+			if (stripped_line.begins_with("@tutorial")) {
+				String title, link;
+
 				int begin_scan = String("@tutorial").length();
 				int begin_scan = String("@tutorial").length();
 				if (begin_scan >= stripped_line.length()) {
 				if (begin_scan >= stripped_line.length()) {
 					continue; // Invalid syntax.
 					continue; // Invalid syntax.
@@ -3553,24 +3583,21 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
 					link = stripped_line.substr(colon_pos).strip_edges();
 					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);
 		}
 		}
 	}
 	}
 
 
@@ -3578,11 +3605,11 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
 		const ClassNode::Member &m = current_class->members[0];
 		const ClassNode::Member &m = current_class->members[0];
 		int first_member_line = m.get_line();
 		int first_member_line = m.get_line();
 		if (first_member_line == line) {
 		if (first_member_line == line) {
-			p_brief = "";
-			p_desc = "";
-			p_tutorials.clear();
+			result = ClassDocData(); // Clear result.
 		}
 		}
 	}
 	}
+
+	return result;
 }
 }
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 

+ 28 - 14
modules/gdscript/gdscript_parser.h

@@ -257,6 +257,22 @@ public:
 		int line = 0, column = 0;
 		int line = 0, column = 0;
 	};
 	};
 
 
+#ifdef TOOLS_ENABLED
+	struct ClassDocData {
+		String brief;
+		String description;
+		Vector<Pair<String, String>> tutorials;
+		bool is_deprecated = false;
+		bool is_experimental = false;
+	};
+
+	struct MemberDocData {
+		String description;
+		bool is_deprecated = false;
+		bool is_experimental = false;
+	};
+#endif // TOOLS_ENABLED
+
 	struct Node {
 	struct Node {
 		enum Type {
 		enum Type {
 			NONE,
 			NONE,
@@ -505,7 +521,7 @@ public:
 			int leftmost_column = 0;
 			int leftmost_column = 0;
 			int rightmost_column = 0;
 			int rightmost_column = 0;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
-			String doc_description;
+			MemberDocData doc_data;
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 		};
 		};
 
 
@@ -513,7 +529,7 @@ public:
 		Vector<Value> values;
 		Vector<Value> values;
 		Variant dictionary;
 		Variant dictionary;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
-		String doc_description;
+		MemberDocData doc_data;
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
 		EnumNode() {
 		EnumNode() {
@@ -720,14 +736,12 @@ public:
 		DataType base_type;
 		DataType base_type;
 		String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project.
 		String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project.
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
-		String doc_description;
-		String doc_brief_description;
-		Vector<Pair<String, String>> doc_tutorials;
+		ClassDocData doc_data;
 
 
 		// EnumValue docs are parsed after itself, so we need a method to add/modify the doc property later.
 		// EnumValue docs are parsed after itself, so we need a method to add/modify the doc property later.
-		void set_enum_value_doc(const StringName &p_name, const String &p_doc_description) {
+		void set_enum_value_doc_data(const StringName &p_name, const MemberDocData &p_doc_data) {
 			ERR_FAIL_INDEX(members_indices[p_name], members.size());
 			ERR_FAIL_INDEX(members_indices[p_name], members.size());
-			members.write[members_indices[p_name]].enum_value.doc_description = p_doc_description;
+			members.write[members_indices[p_name]].enum_value.doc_data = p_doc_data;
 		}
 		}
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
@@ -766,7 +780,7 @@ public:
 
 
 	struct ConstantNode : public AssignableNode {
 	struct ConstantNode : public AssignableNode {
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
-		String doc_description;
+		MemberDocData doc_data;
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
 		ConstantNode() {
 		ConstantNode() {
@@ -821,7 +835,7 @@ public:
 		LambdaNode *source_lambda = nullptr;
 		LambdaNode *source_lambda = nullptr;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 		Vector<Variant> default_arg_values;
 		Vector<Variant> default_arg_values;
-		String doc_description;
+		MemberDocData doc_data;
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
 		bool resolved_signature = false;
 		bool resolved_signature = false;
@@ -1008,7 +1022,7 @@ public:
 		Vector<ParameterNode *> parameters;
 		Vector<ParameterNode *> parameters;
 		HashMap<StringName, int> parameters_indices;
 		HashMap<StringName, int> parameters_indices;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
-		String doc_description;
+		MemberDocData doc_data;
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
 		SignalNode() {
 		SignalNode() {
@@ -1213,7 +1227,7 @@ public:
 		int assignments = 0;
 		int assignments = 0;
 		bool is_static = false;
 		bool is_static = false;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
-		String doc_description;
+		MemberDocData doc_data;
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
 		VariableNode() {
 		VariableNode() {
@@ -1488,12 +1502,12 @@ private:
 	ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign);
 	ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign);
 	ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
 	ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
 	TypeNode *parse_type(bool p_allow_void = false);
 	TypeNode *parse_type(bool p_allow_void = false);
+
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
-	// Doc comments.
 	int class_doc_line = 0x7FFFFFFF;
 	int class_doc_line = 0x7FFFFFFF;
 	bool has_comment(int p_line, bool p_must_be_doc = false);
 	bool has_comment(int p_line, bool p_must_be_doc = false);
-	String get_doc_comment(int p_line, bool p_single_line = false);
-	void get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class);
+	MemberDocData parse_doc_comment(int p_line, bool p_single_line = false);
+	ClassDocData parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line = false);
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
 public:
 public: