Make script annotations placed before class_name and extends

This commit is contained in:
Aaron Franke 2022-10-22 18:32:21 -05:00
parent dcb3754db0
commit 6590e1fd8a
No known key found for this signature in database
GPG key ID: 40A1750B977E56BF
4 changed files with 15 additions and 47 deletions

View file

@ -540,43 +540,28 @@ void GDScriptParser::parse_program() {
head = alloc_node<ClassNode>();
head->fqcn = script_path;
current_class = head;
bool can_have_class_or_extends = true;
// If we happen to parse an annotation before extends or class_name keywords, track it.
// @tool is allowed, but others should fail.
AnnotationNode *premature_annotation = nullptr;
if (match(GDScriptTokenizer::Token::ANNOTATION)) {
// Check for @tool, script-level, or standalone annotation.
while (match(GDScriptTokenizer::Token::ANNOTATION)) {
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
if (annotation != nullptr) {
if (annotation->name == SNAME("@tool")) {
// TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
_is_tool = true;
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
push_error(R"(Expected newline after "@tool" annotation.)");
}
// @tool annotation has no specific target.
annotation->apply(this, nullptr);
} else if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) {
premature_annotation = annotation;
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
push_error(R"(Expected newline after a standalone annotation.)");
}
if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
annotation->apply(this, head);
} else {
premature_annotation = annotation;
annotation_stack.push_back(annotation);
// This annotation must appear after script-level annotations
// and class_name/extends (ex: could be @onready or @export),
// so we stop looking for script-level stuff.
can_have_class_or_extends = false;
break;
}
}
}
for (bool should_break = false; !should_break;) {
while (can_have_class_or_extends) {
// Order here doesn't matter, but there should be only one of each at most.
switch (current.type) {
case GDScriptTokenizer::Token::CLASS_NAME:
if (premature_annotation != nullptr) {
push_error(R"("class_name" should be used before annotations (except @tool).)");
}
advance();
if (head->identifier != nullptr) {
push_error(R"("class_name" can only be used once.)");
@ -585,9 +570,6 @@ void GDScriptParser::parse_program() {
}
break;
case GDScriptTokenizer::Token::EXTENDS:
if (premature_annotation != nullptr) {
push_error(R"("extends" should be used before annotations (except @tool).)");
}
advance();
if (head->extends_used) {
push_error(R"("extends" can only be used once.)");
@ -597,7 +579,8 @@ void GDScriptParser::parse_program() {
}
break;
default:
should_break = true;
// No tokens are allowed between script annotations and class/extends.
can_have_class_or_extends = false;
break;
}
@ -606,21 +589,6 @@ void GDScriptParser::parse_program() {
}
}
if (match(GDScriptTokenizer::Token::ANNOTATION)) {
// Check for a script-level, or standalone annotation.
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
if (annotation != nullptr) {
if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) {
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
push_error(R"(Expected newline after a standalone annotation.)");
}
annotation->apply(this, head);
} else {
annotation_stack.push_back(annotation);
}
}
}
parse_class_body(true);
complete_extents(head);