Merge pull request #56483 from vnen/gdscript-warning-annotation

Add annotation to ignore warnings
This commit is contained in:
Rémi Verschelde 2022-01-05 09:05:56 +01:00 committed by GitHub
commit 6d4ed65f4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 29 deletions

View file

@ -238,10 +238,6 @@ void ScriptTextEditor::_show_warnings_panel(bool p_show) {
void ScriptTextEditor::_warning_clicked(Variant p_line) {
if (p_line.get_type() == Variant::INT) {
goto_line_centered(p_line.operator int64_t());
} else if (p_line.get_type() == Variant::DICTIONARY) {
Dictionary meta = p_line.operator Dictionary();
code_editor->get_text_editor()->insert_line_at(meta["line"].operator int64_t() - 1, "# warning-ignore:" + meta["code"].operator String());
_validate_script();
}
}
@ -468,20 +464,8 @@ void ScriptTextEditor::_validate_script() {
}
// Add script warnings.
warnings_panel->push_table(3);
warnings_panel->push_table(2);
for (const ScriptLanguage::Warning &w : warnings) {
Dictionary ignore_meta;
ignore_meta["line"] = w.start_line;
ignore_meta["code"] = w.string_code.to_lower();
warnings_panel->push_cell();
warnings_panel->push_meta(ignore_meta);
warnings_panel->push_color(
warnings_panel->get_theme_color(SNAME("accent_color"), SNAME("Editor")).lerp(warnings_panel->get_theme_color(SNAME("mono_color"), SNAME("Editor")), 0.5));
warnings_panel->add_text(TTR("[Ignore]"));
warnings_panel->pop(); // Color.
warnings_panel->pop(); // Meta ignore.
warnings_panel->pop(); // Cell.
warnings_panel->push_cell();
warnings_panel->push_meta(w.start_line - 1);
warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor")));

View file

@ -881,12 +881,23 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
for (int i = 0; i < p_class->members.size(); i++) {
GDScriptParser::ClassNode::Member member = p_class->members[i];
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
resolve_function_body(member.function);
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.function->annotations) {
E->apply(parser, member.function);
}
#ifdef DEBUG_ENABLED
Set<uint32_t> previously_ignored = parser->ignored_warning_codes;
for (uint32_t ignored_warning : member.function->ignored_warnings) {
parser->ignored_warning_codes.insert(ignored_warning);
}
#endif // DEBUG_ENABLED
resolve_function_body(member.function);
#ifdef DEBUG_ENABLED
parser->ignored_warning_codes = previously_ignored;
#endif // DEBUG_ENABLED
} else if (member.type == GDScriptParser::ClassNode::Member::VARIABLE && member.variable->property != GDScriptParser::VariableNode::PROP_NONE) {
if (member.variable->property == GDScriptParser::VariableNode::PROP_INLINE) {
if (member.variable->getter != nullptr) {
@ -925,6 +936,10 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
GDScriptParser::ClassNode::Member member = p_class->members[i];
if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
#ifdef DEBUG_ENABLED
Set<uint32_t> previously_ignored = parser->ignored_warning_codes;
for (uint32_t ignored_warning : member.function->ignored_warnings) {
parser->ignored_warning_codes.insert(ignored_warning);
}
if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) {
parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name);
}
@ -992,6 +1007,9 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
push_error(vformat(R"(Getter with type "%s" cannot be used along with setter of type "%s".)", getter_function->datatype.to_string(), setter_function->parameters[0]->datatype.to_string()), member.variable);
}
}
#ifdef DEBUG_ENABLED
parser->ignored_warning_codes = previously_ignored;
#endif // DEBUG_ENABLED
}
}
}
@ -1186,7 +1204,23 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript
void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
for (int i = 0; i < p_suite->statements.size(); i++) {
GDScriptParser::Node *stmt = p_suite->statements[i];
for (GDScriptParser::AnnotationNode *&annotation : stmt->annotations) {
annotation->apply(parser, stmt);
}
#ifdef DEBUG_ENABLED
Set<uint32_t> previously_ignored = parser->ignored_warning_codes;
for (uint32_t ignored_warning : stmt->ignored_warnings) {
parser->ignored_warning_codes.insert(ignored_warning);
}
#endif // DEBUG_ENABLED
resolve_node(stmt);
#ifdef DEBUG_ENABLED
parser->ignored_warning_codes = previously_ignored;
#endif // DEBUG_ENABLED
decide_suite_type(p_suite, stmt);
}
}

View file

@ -646,6 +646,11 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_CLASS);
r_result.insert(option.display, option);
}
} else if (p_annotation->name == "@warning_ignore") {
for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
ScriptCodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
r_result.insert(warning.display, warning);
}
}
}

View file

@ -39,6 +39,7 @@
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
#include "core/string/string_builder.h"
#include "gdscript_warning.h"
#endif // DEBUG_ENABLED
#ifdef TOOLS_ENABLED
@ -132,9 +133,9 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>);
register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
register_annotation(MethodInfo("@warning_ignore", { Variant::STRING, "warning" }), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, 0, true);
// Networking.
register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true);
// TODO: Warning annotations.
}
GDScriptParser::~GDScriptParser() {
@ -196,6 +197,10 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
return;
}
if (ignored_warning_codes.has(p_code)) {
return;
}
String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower();
if (ignored_warnings.has(warn_name)) {
return;
@ -701,24 +706,21 @@ void GDScriptParser::parse_extends() {
template <class T>
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) {
advance();
T *member = (this->*p_parse_function)();
if (member == nullptr) {
return;
}
#ifdef TOOLS_ENABLED
int doc_comment_line = member->start_line - 1;
int doc_comment_line = previous.start_line - 1;
#endif // TOOLS_ENABLED
// Consume annotations.
List<AnnotationNode *> annotations;
while (!annotation_stack.is_empty()) {
AnnotationNode *last_annotation = annotation_stack.back()->get();
if (last_annotation->applies_to(p_target)) {
member->annotations.push_front(last_annotation);
annotations.push_front(last_annotation);
annotation_stack.pop_back();
} else {
push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));
clear_unused_annotations();
return;
}
#ifdef TOOLS_ENABLED
if (last_annotation->start_line == doc_comment_line) {
@ -727,6 +729,16 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
#endif // TOOLS_ENABLED
}
T *member = (this->*p_parse_function)();
if (member == nullptr) {
return;
}
// Apply annotations.
for (AnnotationNode *&annotation : annotations) {
member->annotations.push_back(annotation);
}
#ifdef TOOLS_ENABLED
// Consume doc comments.
class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
@ -1507,6 +1519,8 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code;
#endif
bool is_annotation = false;
switch (current.type) {
case GDScriptTokenizer::Token::PASS:
advance();
@ -1576,6 +1590,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
break;
case GDScriptTokenizer::Token::ANNOTATION: {
advance();
is_annotation = true;
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
if (annotation != nullptr) {
annotation_stack.push_back(annotation);
@ -1616,6 +1631,18 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
}
}
// Apply annotations to statement.
while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) {
AnnotationNode *last_annotation = annotation_stack.back()->get();
if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {
result->annotations.push_front(last_annotation);
annotation_stack.pop_back();
} else {
push_error(vformat(R"(Annotation "%s" cannot be applied to a statement.)", last_annotation->name));
clear_unused_annotations();
}
}
#ifdef DEBUG_ENABLED
if (unreachable && result != nullptr) {
current_suite->has_unreachable_code = true;
@ -3552,7 +3579,24 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_V_MSG(false, "Not implemented.");
#ifdef DEBUG_ENABLED
bool has_error = false;
for (const Variant &warning_name : p_annotation->resolved_arguments) {
GDScriptWarning::Code warning = GDScriptWarning::get_code_from_name(String(warning_name).to_upper());
if (warning == GDScriptWarning::WARNING_MAX) {
push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);
has_error = true;
} else {
p_node->ignored_warnings.push_back(warning);
}
}
return !has_error;
#else // ! DEBUG_ENABLED
// Only available in debug builds.
return true;
#endif // DEBUG_ENABLED
}
template <Multiplayer::RPCMode t_mode>

View file

@ -297,6 +297,7 @@ public:
int leftmost_column = 0, rightmost_column = 0;
Node *next = nullptr;
List<AnnotationNode *> annotations;
Vector<uint32_t> ignored_warnings;
DataType datatype;
@ -1204,6 +1205,7 @@ private:
#ifdef DEBUG_ENABLED
List<GDScriptWarning> warnings;
Set<String> ignored_warnings;
Set<uint32_t> ignored_warning_codes;
Set<int> unsafe_lines;
#endif

View file

@ -213,7 +213,7 @@ GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name)
}
}
ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name);
return WARNING_MAX;
}
#endif // DEBUG_ENABLED

View file

@ -0,0 +1,15 @@
@warning_ignore(unused_private_class_variable)
var _unused = 2
@warning_ignore(unused_variable)
func test():
print("test")
var unused = 3
@warning_ignore(redundant_await)
print(await regular_func())
print("done")
func regular_func():
return 0

View file

@ -0,0 +1,4 @@
GDTEST_OK
test
0
done