Forráskód Böngészése

Merge pull request #62713 from YuriSizov/docs-scripting-annotations

Rémi Verschelde 3 éve
szülő
commit
635d447a69

+ 1 - 0
core/doc_data.h

@@ -165,6 +165,7 @@ public:
 		Vector<ConstantDoc> constants;
 		HashMap<String, String> enums;
 		Vector<PropertyDoc> properties;
+		Vector<MethodDoc> annotations;
 		Vector<ThemeItemDoc> theme_properties;
 		bool is_script_doc = false;
 		String script_path;

+ 2 - 0
core/object/script_language.h

@@ -350,6 +350,7 @@ public:
 		LOOKUP_RESULT_CLASS_SIGNAL,
 		LOOKUP_RESULT_CLASS_ENUM,
 		LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE,
+		LOOKUP_RESULT_CLASS_ANNOTATION,
 		LOOKUP_RESULT_MAX
 	};
 
@@ -402,6 +403,7 @@ public:
 	virtual void get_recognized_extensions(List<String> *p_extensions) const = 0;
 	virtual void get_public_functions(List<MethodInfo> *p_functions) const = 0;
 	virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const = 0;
+	virtual void get_public_annotations(List<MethodInfo> *p_annotations) const = 0;
 
 	struct ProfilingInfo {
 		StringName signature;

+ 2 - 0
core/object/script_language_extension.cpp

@@ -134,6 +134,7 @@ void ScriptLanguageExtension::_bind_methods() {
 	GDVIRTUAL_BIND(_get_recognized_extensions);
 	GDVIRTUAL_BIND(_get_public_functions);
 	GDVIRTUAL_BIND(_get_public_constants);
+	GDVIRTUAL_BIND(_get_public_annotations);
 
 	GDVIRTUAL_BIND(_profiling_start);
 	GDVIRTUAL_BIND(_profiling_stop);
@@ -160,6 +161,7 @@ void ScriptLanguageExtension::_bind_methods() {
 	BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_SIGNAL);
 	BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_ENUM);
 	BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE);
+	BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_ANNOTATION);
 	BIND_ENUM_CONSTANT(LOOKUP_RESULT_MAX);
 
 	BIND_ENUM_CONSTANT(LOCATION_LOCAL);

+ 9 - 0
core/object/script_language_extension.h

@@ -580,6 +580,15 @@ public:
 			p_constants->push_back(Pair<String, Variant>(d["name"], d["value"]));
 		}
 	}
+	GDVIRTUAL0RC(TypedArray<Dictionary>, _get_public_annotations)
+	virtual void get_public_annotations(List<MethodInfo> *p_annotations) const override {
+		TypedArray<Dictionary> ret;
+		GDVIRTUAL_REQUIRED_CALL(_get_public_annotations, ret);
+		for (int i = 0; i < ret.size(); i++) {
+			MethodInfo mi = MethodInfo::from_dict(ret[i]);
+			p_annotations->push_back(mi);
+		}
+	}
 
 	EXBIND0(profiling_start)
 	EXBIND0(profiling_stop)

+ 36 - 0
doc/class.xsd

@@ -163,6 +163,42 @@
 						</xs:sequence>
 					</xs:complexType>
 				</xs:element>
+				<xs:element name="annotations" minOccurs="0">
+					<xs:complexType>
+						<xs:sequence>
+							<xs:element name="annotation" maxOccurs="unbounded" minOccurs="0">
+								<xs:complexType>
+									<xs:sequence>
+										<xs:element name="return" minOccurs="0">
+											<xs:complexType>
+												<xs:sequence>
+													<xs:sequence />
+												</xs:sequence>
+												<xs:attribute type="xs:string" name="type" />
+												<xs:attribute type="xs:string" name="enum" use="optional" />
+											</xs:complexType>
+										</xs:element>
+										<xs:element name="argument" maxOccurs="unbounded" minOccurs="0">
+											<xs:complexType>
+												<xs:sequence>
+													<xs:sequence />
+												</xs:sequence>
+												<xs:attribute type="xs:byte" name="index" />
+												<xs:attribute type="xs:string" name="name" />
+												<xs:attribute type="xs:string" name="type" />
+												<xs:attribute type="xs:string" name="enum" use="optional" />
+												<xs:attribute type="xs:string" name="default" use="optional" />
+											</xs:complexType>
+										</xs:element>
+										<xs:element type="xs:string" name="description" />
+									</xs:sequence>
+									<xs:attribute type="xs:string" name="name" use="optional" />
+									<xs:attribute type="xs:string" name="qualifiers" use="optional" />
+								</xs:complexType>
+							</xs:element>
+						</xs:sequence>
+					</xs:complexType>
+				</xs:element>
 				<xs:element name="theme_items" minOccurs="0">
 					<xs:complexType>
 						<xs:sequence>

+ 8 - 1
doc/classes/ScriptLanguageExtension.xml

@@ -174,6 +174,11 @@
 			<description>
 			</description>
 		</method>
+		<method name="_get_public_annotations" qualifiers="virtual const">
+			<return type="Dictionary[]" />
+			<description>
+			</description>
+		</method>
 		<method name="_get_public_constants" qualifiers="virtual const">
 			<return type="Dictionary" />
 			<description>
@@ -378,7 +383,9 @@
 		</constant>
 		<constant name="LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE" value="7" enum="LookupResultType">
 		</constant>
-		<constant name="LOOKUP_RESULT_MAX" value="8" enum="LookupResultType">
+		<constant name="LOOKUP_RESULT_CLASS_ANNOTATION" value="8" enum="LookupResultType">
+		</constant>
+		<constant name="LOOKUP_RESULT_MAX" value="9" enum="LookupResultType">
 		</constant>
 		<constant name="LOCATION_LOCAL" value="0" enum="CodeCompletionLocation">
 			The option is local to the location of the code completion query - e.g. a local variable.

+ 48 - 0
doc/tools/make_rst.py

@@ -36,6 +36,7 @@ BASE_STRINGS = [
     "Signals",
     "Enumerations",
     "Constants",
+    "Annotations",
     "Property Descriptions",
     "Constructor Descriptions",
     "Method Descriptions",
@@ -157,6 +158,7 @@ class ClassDef:
         self.methods = OrderedDict()  # type: OrderedDict[str, List[MethodDef]]
         self.operators = OrderedDict()  # type: OrderedDict[str, List[MethodDef]]
         self.signals = OrderedDict()  # type: OrderedDict[str, SignalDef]
+        self.annotations = OrderedDict()  # type: OrderedDict[str, List[MethodDef]]
         self.theme_items = OrderedDict()  # type: OrderedDict[str, ThemeItemDef]
         self.inherits = None  # type: Optional[str]
         self.brief_description = None  # type: Optional[str]
@@ -326,6 +328,27 @@ class State:
 
                     enum_def.values[constant_name] = constant_def
 
+        annotations = class_root.find("annotations")
+        if annotations is not None:
+            for annotation in annotations:
+                assert annotation.tag == "annotation"
+
+                annotation_name = annotation.attrib["name"]
+                qualifiers = annotation.get("qualifiers")
+
+                params = parse_arguments(annotation)
+
+                desc_element = annotation.find("description")
+                annotation_desc = None
+                if desc_element is not None:
+                    annotation_desc = desc_element.text
+
+                annotation_def = MethodDef(annotation_name, return_type, params, annotation_desc, qualifiers)
+                if annotation_name not in class_def.annotations:
+                    class_def.annotations[annotation_name] = []
+
+                class_def.annotations[annotation_name].append(annotation_def)
+
         signals = class_root.find("signals")
         if signals is not None:
             for signal in signals:
@@ -739,6 +762,26 @@ def make_rst_class(class_def, state, dry_run, output_dir):  # type: (ClassDef, S
 
             f.write("\n\n")
 
+    if len(class_def.annotations) > 0:
+        f.write(make_heading("Annotations", "-"))
+        index = 0
+
+        for method_list in class_def.annotations.values():
+            for i, m in enumerate(method_list):
+                if index != 0:
+                    f.write("----\n\n")
+
+                if i == 0:
+                    f.write(".. _class_{}_annotation_{}:\n\n".format(class_name, m.name.strip("@")))
+
+                ret_type, signature = make_method_signature(class_def, m, "", state)
+                f.write("- {} {}\n\n".format(ret_type, signature))
+
+                if m.description is not None and m.description.strip() != "":
+                    f.write(rstize_text(m.description.strip(), state) + "\n\n")
+
+                index += 1
+
     # Property descriptions
     if any(not p.overrides for p in class_def.properties.values()) > 0:
         f.write(make_heading("Property Descriptions", "-"))
@@ -1072,6 +1115,11 @@ def rstize_text(text, state):  # type: (str, State) -> str
                             print_error('{}.xml: Unresolved signal "{}".'.format(state.current_class, param), state)
                         ref_type = "_signal"
 
+                    elif cmd.startswith("annotation"):
+                        if method_param not in class_def.annotations:
+                            print_error('{}.xml: Unresolved annotation "{}".'.format(state.current_class, param), state)
+                        ref_type = "_annotation"
+
                     elif cmd.startswith("constant"):
                         found = False
 

+ 53 - 1
editor/doc_tools.cpp

@@ -178,6 +178,20 @@ void DocTools::merge_from(const DocTools &p_data) {
 			}
 		}
 
+		for (int i = 0; i < c.annotations.size(); i++) {
+			DocData::MethodDoc &m = c.annotations.write[i];
+
+			for (int j = 0; j < cf.annotations.size(); j++) {
+				if (cf.annotations[j].name != m.name) {
+					continue;
+				}
+				const DocData::MethodDoc &mf = cf.annotations[j];
+
+				m.description = mf.description;
+				break;
+			}
+		}
+
 		for (int i = 0; i < c.properties.size(); i++) {
 			DocData::PropertyDoc &p = c.properties.write[i];
 
@@ -960,8 +974,41 @@ void DocTools::generate(bool p_basic_types) {
 				c.constants.push_back(cd);
 			}
 
+			// Get annotations.
+			List<MethodInfo> ainfo;
+			lang->get_public_annotations(&ainfo);
+
+			for (const MethodInfo &ai : ainfo) {
+				DocData::MethodDoc atd;
+				atd.name = ai.name;
+
+				if (ai.flags & METHOD_FLAG_VARARG) {
+					if (!atd.qualifiers.is_empty()) {
+						atd.qualifiers += " ";
+					}
+					atd.qualifiers += "vararg";
+				}
+
+				DocData::return_doc_from_retinfo(atd, ai.return_val);
+
+				for (int j = 0; j < ai.arguments.size(); j++) {
+					DocData::ArgumentDoc ad;
+					DocData::argument_doc_from_arginfo(ad, ai.arguments[j]);
+
+					int darg_idx = j - (ai.arguments.size() - ai.default_arguments.size());
+					if (darg_idx >= 0) {
+						Variant default_arg = ai.default_arguments[darg_idx];
+						ad.default_value = default_arg.get_construct_string().replace("\n", " ");
+					}
+
+					atd.arguments.push_back(ad);
+				}
+
+				c.annotations.push_back(atd);
+			}
+
 			// Skip adding the lang if it doesn't expose anything (e.g. C#).
-			if (c.methods.is_empty() && c.constants.is_empty()) {
+			if (c.methods.is_empty() && c.constants.is_empty() && c.annotations.is_empty()) {
 				continue;
 			}
 
@@ -1163,6 +1210,9 @@ Error DocTools::_load(Ref<XMLParser> parser) {
 				} else if (name2 == "signals") {
 					Error err2 = _parse_methods(parser, c.signals);
 					ERR_FAIL_COND_V(err2, err2);
+				} else if (name2 == "annotations") {
+					Error err2 = _parse_methods(parser, c.annotations);
+					ERR_FAIL_COND_V(err2, err2);
 				} else if (name2 == "members") {
 					while (parser->read() == OK) {
 						if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
@@ -1450,6 +1500,8 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap<String,
 			_write_string(f, 1, "</constants>");
 		}
 
+		_write_method_doc(f, "annotation", c.annotations);
+
 		if (!c.theme_properties.is_empty()) {
 			c.theme_properties.sort();
 

+ 128 - 15
editor/editor_help.cpp

@@ -125,6 +125,9 @@ void EditorHelp::_class_desc_select(const String &p_select) {
 		} else if (tag == "constant") {
 			topic = "class_constant";
 			table = &this->constant_line;
+		} else if (tag == "annotation") {
+			topic = "class_annotation";
+			table = &this->annotation_line;
 		} else if (tag == "theme_item") {
 			topic = "theme_item";
 			table = &this->theme_property_line;
@@ -274,7 +277,7 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview
 		class_desc->add_text(" ");
 	}
 
-	if (p_overview && !p_method.description.is_empty()) {
+	if (p_overview && !p_method.description.strip_edges().is_empty()) {
 		class_desc->push_meta("@method " + p_method.name);
 	}
 
@@ -282,7 +285,7 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview
 	_add_text(p_method.name);
 	class_desc->pop();
 
-	if (p_overview && !p_method.description.is_empty()) {
+	if (p_overview && !p_method.description.strip_edges().is_empty()) {
 		class_desc->pop(); //meta
 	}
 
@@ -412,7 +415,7 @@ void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods,
 				class_desc->pop(); //cell
 			}
 
-			if (!m[i].description.is_empty() || m[i].errors_returned.size() > 0) {
+			if (!m[i].description.strip_edges().is_empty() || m[i].errors_returned.size() > 0) {
 				r_method_descrpitons = true;
 			}
 
@@ -611,7 +614,7 @@ void EditorHelp::_update_doc() {
 	class_desc->add_newline();
 
 	// Brief description
-	if (!cd.brief_description.is_empty()) {
+	if (!cd.brief_description.strip_edges().is_empty()) {
 		class_desc->push_color(text_color);
 		class_desc->push_font(doc_bold_font);
 		class_desc->push_indent(1);
@@ -625,7 +628,7 @@ void EditorHelp::_update_doc() {
 	}
 
 	// Class description
-	if (!cd.description.is_empty()) {
+	if (!cd.description.strip_edges().is_empty()) {
 		section_line.push_back(Pair<String, int>(TTR("Description"), class_desc->get_paragraph_count() - 2));
 		description_line = class_desc->get_paragraph_count() - 2;
 		class_desc->push_color(title_color);
@@ -692,7 +695,7 @@ void EditorHelp::_update_doc() {
 	if (cd.is_script_doc) {
 		has_properties = false;
 		for (int i = 0; i < cd.properties.size(); i++) {
-			if (cd.properties[i].name.begins_with("_") && cd.properties[i].description.is_empty()) {
+			if (cd.properties[i].name.begins_with("_") && cd.properties[i].description.strip_edges().is_empty()) {
 				continue;
 			}
 			has_properties = true;
@@ -718,7 +721,7 @@ void EditorHelp::_update_doc() {
 
 		for (int i = 0; i < cd.properties.size(); i++) {
 			// Ignore undocumented private.
-			if (cd.properties[i].name.begins_with("_") && cd.properties[i].description.is_empty()) {
+			if (cd.properties[i].name.begins_with("_") && cd.properties[i].description.strip_edges().is_empty()) {
 				continue;
 			}
 			property_line[cd.properties[i].name] = class_desc->get_paragraph_count() - 2; //gets overridden if description
@@ -743,7 +746,7 @@ void EditorHelp::_update_doc() {
 				describe = true;
 			}
 
-			if (!cd.properties[i].description.is_empty()) {
+			if (!cd.properties[i].description.strip_edges().is_empty()) {
 				describe = true;
 			}
 
@@ -856,7 +859,7 @@ void EditorHelp::_update_doc() {
 			}
 		}
 		// Ignore undocumented non virtual private.
-		if (cd.methods[i].name.begins_with("_") && cd.methods[i].description.is_empty() && !cd.methods[i].qualifiers.contains("virtual")) {
+		if (cd.methods[i].name.begins_with("_") && cd.methods[i].description.strip_edges().is_empty() && !cd.methods[i].qualifiers.contains("virtual")) {
 			continue;
 		}
 		methods.push_back(cd.methods[i]);
@@ -976,7 +979,7 @@ void EditorHelp::_update_doc() {
 			class_desc->pop(); // monofont
 
 			// Theme item description.
-			if (!cd.theme_properties[i].description.is_empty()) {
+			if (!cd.theme_properties[i].description.strip_edges().is_empty()) {
 				class_desc->push_font(doc_font);
 				class_desc->push_color(comment_color);
 				class_desc->push_indent(1);
@@ -1018,8 +1021,8 @@ void EditorHelp::_update_doc() {
 			signal_line[cd.signals[i].name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
 
 			class_desc->push_font(doc_code_font); // monofont
-			class_desc->push_color(headline_color);
 			_add_bulletpoint();
+			class_desc->push_color(headline_color);
 			_add_text(cd.signals[i].name);
 			class_desc->pop();
 			class_desc->push_color(symbol_color);
@@ -1048,7 +1051,7 @@ void EditorHelp::_update_doc() {
 			class_desc->add_text(")");
 			class_desc->pop();
 			class_desc->pop(); // end monofont
-			if (!cd.signals[i].description.is_empty()) {
+			if (!cd.signals[i].description.strip_edges().is_empty()) {
 				class_desc->push_font(doc_font);
 				class_desc->push_color(comment_color);
 				class_desc->push_indent(1);
@@ -1079,7 +1082,7 @@ void EditorHelp::_update_doc() {
 				enums[cd.constants[i].enumeration].push_back(cd.constants[i]);
 			} else {
 				// Ignore undocumented private.
-				if (cd.constants[i].name.begins_with("_") && cd.constants[i].description.is_empty()) {
+				if (cd.constants[i].name.begins_with("_") && cd.constants[i].description.strip_edges().is_empty()) {
 					continue;
 				}
 				constants.push_back(cd.constants[i]);
@@ -1155,8 +1158,8 @@ void EditorHelp::_update_doc() {
 					constant_line[enum_list[i].name] = class_desc->get_paragraph_count() - 2;
 
 					class_desc->push_font(doc_code_font);
-					class_desc->push_color(headline_color);
 					_add_bulletpoint();
+					class_desc->push_color(headline_color);
 					_add_text(enum_list[i].name);
 					class_desc->pop();
 					class_desc->push_color(symbol_color);
@@ -1240,7 +1243,7 @@ void EditorHelp::_update_doc() {
 
 				class_desc->add_newline();
 
-				if (!constants[i].description.is_empty()) {
+				if (!constants[i].description.strip_edges().is_empty()) {
 					class_desc->push_font(doc_font);
 					class_desc->push_color(comment_color);
 					_add_text(DTR(constants[i].description));
@@ -1259,6 +1262,112 @@ void EditorHelp::_update_doc() {
 		}
 	}
 
+	// Annotations
+	if (!cd.annotations.is_empty()) {
+		if (sort_methods) {
+			cd.annotations.sort();
+		}
+
+		section_line.push_back(Pair<String, int>(TTR("Annotations"), class_desc->get_paragraph_count() - 2));
+		class_desc->push_color(title_color);
+		class_desc->push_font(doc_title_font);
+		class_desc->push_font_size(doc_title_font_size);
+		class_desc->add_text(TTR("Annotations"));
+		class_desc->pop(); // font size
+		class_desc->pop(); // font
+		class_desc->pop(); // color
+
+		class_desc->add_newline();
+		class_desc->add_newline();
+
+		class_desc->push_indent(1);
+
+		for (int i = 0; i < cd.annotations.size(); i++) {
+			annotation_line[cd.annotations[i].name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
+
+			class_desc->push_font(doc_code_font); // monofont
+			_add_bulletpoint();
+			class_desc->push_color(headline_color);
+			_add_text(cd.annotations[i].name);
+			class_desc->pop();
+
+			if (cd.annotations[i].arguments.size() > 0) {
+				class_desc->push_color(symbol_color);
+				class_desc->add_text("(");
+				class_desc->pop();
+				for (int j = 0; j < cd.annotations[i].arguments.size(); j++) {
+					class_desc->push_color(text_color);
+					if (j > 0) {
+						class_desc->add_text(", ");
+					}
+
+					_add_text(cd.annotations[i].arguments[j].name);
+					class_desc->add_text(": ");
+					_add_type(cd.annotations[i].arguments[j].type);
+					if (!cd.annotations[i].arguments[j].default_value.is_empty()) {
+						class_desc->push_color(symbol_color);
+						class_desc->add_text(" = ");
+						class_desc->pop();
+						_add_text(cd.annotations[i].arguments[j].default_value);
+					}
+
+					class_desc->pop();
+				}
+
+				if (cd.annotations[i].qualifiers.contains("vararg")) {
+					class_desc->push_color(text_color);
+					if (cd.annotations[i].arguments.size()) {
+						class_desc->add_text(", ");
+					}
+					class_desc->push_color(symbol_color);
+					class_desc->add_text("...");
+					class_desc->pop();
+					class_desc->pop();
+				}
+
+				class_desc->push_color(symbol_color);
+				class_desc->add_text(")");
+				class_desc->pop();
+			}
+
+			if (!cd.annotations[i].qualifiers.is_empty()) {
+				class_desc->push_color(qualifier_color);
+				class_desc->add_text(" ");
+				_add_text(cd.annotations[i].qualifiers);
+				class_desc->pop();
+			}
+
+			class_desc->pop(); // end monofont
+
+			if (!cd.annotations[i].description.strip_edges().is_empty()) {
+				class_desc->push_font(doc_font);
+				class_desc->push_color(comment_color);
+				class_desc->push_indent(1);
+				_add_text(DTR(cd.annotations[i].description));
+				class_desc->pop(); // indent
+				class_desc->pop();
+				class_desc->pop(); // font
+			} else {
+				class_desc->push_indent(1);
+				class_desc->add_image(get_theme_icon(SNAME("Error"), SNAME("EditorIcons")));
+				class_desc->add_text(" ");
+				class_desc->push_color(comment_color);
+				if (cd.is_script_doc) {
+					class_desc->append_text(TTR("There is currently no description for this annotation."));
+				} else {
+					class_desc->append_text(TTR("There is currently no description for this annotation. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text));
+				}
+				class_desc->pop();
+				class_desc->pop(); // indent
+			}
+			class_desc->add_newline();
+			class_desc->add_newline();
+		}
+
+		class_desc->pop();
+		class_desc->add_newline();
+	}
+
 	// Property descriptions
 	if (property_descr) {
 		section_line.push_back(Pair<String, int>(TTR("Property Descriptions"), class_desc->get_paragraph_count() - 2));
@@ -1505,6 +1614,10 @@ void EditorHelp::_help_callback(const String &p_topic) {
 		if (constant_line.has(name)) {
 			line = constant_line[name];
 		}
+	} else if (what == "class_annotation") {
+		if (annotation_line.has(name)) {
+			line = annotation_line[name];
+		}
 	} else if (what == "class_global") {
 		if (constant_line.has(name)) {
 			line = constant_line[name];

+ 1 - 0
editor/editor_help.h

@@ -110,6 +110,7 @@ class EditorHelp : public VBoxContainer {
 	HashMap<String, int> property_line;
 	HashMap<String, int> theme_property_line;
 	HashMap<String, int> constant_line;
+	HashMap<String, int> annotation_line;
 	HashMap<String, int> enum_line;
 	HashMap<String, HashMap<String, int>> enum_values_line;
 	int description_line = 0;

+ 3 - 0
editor/plugins/script_text_editor.cpp

@@ -886,6 +886,9 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c
 				emit_signal(SNAME("go_to_help"), "class_enum:" + result.class_name + ":" + result.class_member);
 
 			} break;
+			case ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION: {
+				emit_signal(SNAME("go_to_help"), "class_annotation:" + result.class_name + ":" + result.class_member);
+			} break;
 			case ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE: {
 				emit_signal(SNAME("go_to_help"), "class_global:" + result.class_name + ":" + result.class_member);
 			} break;

+ 141 - 0
modules/gdscript/doc_classes/@GDScript.xml

@@ -257,4 +257,145 @@
 			[b]Note:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead.
 		</constant>
 	</constants>
+	<annotations>
+		<annotation name="@export">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_color_no_alpha">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_dir">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_enum" qualifiers="vararg">
+			<return type="void" />
+			<argument index="0" name="names" type="String" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_exp_easing">
+			<return type="void" />
+			<argument index="0" name="hint1" type="String" default="null" />
+			<argument index="1" name="hint2" type="String" default="null" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_file" qualifiers="vararg">
+			<return type="void" />
+			<argument index="0" name="filter" type="String" default="null" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_flags" qualifiers="vararg">
+			<return type="void" />
+			<argument index="0" name="names" type="String" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_flags_2d_navigation">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_flags_2d_physics">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_flags_2d_render">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_flags_3d_navigation">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_flags_3d_physics">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_flags_3d_render">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_global_dir">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_global_file" qualifiers="vararg">
+			<return type="void" />
+			<argument index="0" name="filter" type="String" default="null" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_multiline">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_node_path" qualifiers="vararg">
+			<return type="void" />
+			<argument index="0" name="type" type="String" default="null" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_placeholder">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@export_range">
+			<return type="void" />
+			<argument index="0" name="min" type="float" />
+			<argument index="1" name="max" type="float" />
+			<argument index="2" name="step" type="float" default="null" />
+			<argument index="3" name="slider1" type="String" default="null" />
+			<argument index="4" name="slider2" type="String" default="null" />
+			<argument index="5" name="slider3" type="String" default="null" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@icon">
+			<return type="void" />
+			<argument index="0" name="icon_path" type="String" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@onready">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@rpc" qualifiers="vararg">
+			<return type="void" />
+			<argument index="0" name="mode" type="String" default="null" />
+			<argument index="1" name="sync" type="String" default="null" />
+			<argument index="2" name="transfer_mode" type="String" default="null" />
+			<argument index="3" name="transfer_channel" type="int" default="null" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@tool">
+			<return type="void" />
+			<description>
+			</description>
+		</annotation>
+		<annotation name="@warning_ignore" qualifiers="vararg">
+			<return type="void" />
+			<argument index="0" name="warning" type="String" />
+			<description>
+			</description>
+		</annotation>
+	</annotations>
 </class>

+ 1 - 0
modules/gdscript/gdscript.h

@@ -489,6 +489,7 @@ public:
 
 	virtual void get_public_functions(List<MethodInfo> *p_functions) const override;
 	virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const override;
+	virtual void get_public_annotations(List<MethodInfo> *p_annotations) const override;
 
 	virtual void profiling_start() override;
 	virtual void profiling_stop() override;

+ 19 - 0
modules/gdscript/gdscript_editor.cpp

@@ -445,6 +445,16 @@ void GDScriptLanguage::get_public_constants(List<Pair<String, Variant>> *p_const
 	p_constants->push_back(nan);
 }
 
+void GDScriptLanguage::get_public_annotations(List<MethodInfo> *p_annotations) const {
+	GDScriptParser parser;
+	List<MethodInfo> annotations;
+	parser.get_annotation_list(&annotations);
+
+	for (const MethodInfo &E : annotations) {
+		p_annotations->push_back(E);
+	}
+}
+
 String GDScriptLanguage::make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const {
 #ifdef TOOLS_ENABLED
 	bool th = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints");
@@ -3380,6 +3390,15 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
 				return OK;
 			}
 		} break;
+		case GDScriptParser::COMPLETION_ANNOTATION: {
+			const String annotation_symbol = "@" + p_symbol;
+			if (parser.annotation_exists(annotation_symbol)) {
+				r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION;
+				r_result.class_name = "@GDScript";
+				r_result.class_member = annotation_symbol;
+				return OK;
+			}
+		} break;
 		default: {
 		}
 	}

+ 4 - 0
modules/gdscript/gdscript_parser.cpp

@@ -105,6 +105,10 @@ void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const
 	}
 }
 
+bool GDScriptParser::annotation_exists(const String &p_annotation_name) const {
+	return valid_annotations.has(p_annotation_name);
+}
+
 GDScriptParser::GDScriptParser() {
 	// Register valid annotations.
 	// TODO: Should this be static?

+ 1 - 0
modules/gdscript/gdscript_parser.h

@@ -1434,6 +1434,7 @@ public:
 	CompletionContext get_completion_context() const { return completion_context; }
 	CompletionCall get_completion_call() const { return completion_call; }
 	void get_annotation_list(List<MethodInfo> *r_annotations) const;
+	bool annotation_exists(const String &p_annotation_name) const;
 
 	const List<ParserError> &get_errors() const { return errors; }
 	const List<String> get_dependencies() const {

+ 1 - 0
modules/mono/csharp_script.h

@@ -502,6 +502,7 @@ public:
 
 	/* TODO? */ void get_public_functions(List<MethodInfo> *p_functions) const override {}
 	/* TODO? */ void get_public_constants(List<Pair<String, Variant>> *p_constants) const override {}
+	/* TODO? */ void get_public_annotations(List<MethodInfo> *p_annotations) const override {}
 
 	void reload_all_scripts() override;
 	void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override;

+ 3 - 0
modules/visual_script/visual_script.cpp

@@ -2435,6 +2435,9 @@ void VisualScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) c
 void VisualScriptLanguage::get_public_constants(List<Pair<String, Variant>> *p_constants) const {
 }
 
+void VisualScriptLanguage::get_public_annotations(List<MethodInfo> *p_annotations) const {
+}
+
 void VisualScriptLanguage::profiling_start() {
 }
 

+ 1 - 0
modules/visual_script/visual_script.h

@@ -599,6 +599,7 @@ public:
 	virtual void get_recognized_extensions(List<String> *p_extensions) const override;
 	virtual void get_public_functions(List<MethodInfo> *p_functions) const override;
 	virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const override;
+	virtual void get_public_annotations(List<MethodInfo> *p_annotations) const override;
 
 	virtual void profiling_start() override;
 	virtual void profiling_stop() override;