Skip to content

Commit

Permalink
Add GDScriptParser::make_refactor_context
Browse files Browse the repository at this point in the history
  • Loading branch information
adamscott committed Dec 18, 2024
1 parent 055b6a3 commit 52a5345
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 5 deletions.
78 changes: 73 additions & 5 deletions modules/gdscript/gdscript_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,18 @@ void GDScriptParser::set_last_completion_call_arg(int p_argument) {
completion_call_stack.back()->get().argument = p_argument;
}

void GDScriptParser::make_refactor_context(GDScriptParser::RefactorSymbolType p_type, Node *p_node) {
if (!for_refactor) {
return;
}
RefactorContext context;
context.type = p_type;
context.current_line = tokenizer->get_cursor_line();
context.node = p_node;
context.parser = this;
refactor_context = context;
}

Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, ParserContext p_context, bool p_parse_body) {
clear();

Expand Down Expand Up @@ -486,6 +498,9 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
passed_cursor = true;
}
}
if (for_refactor && tokenizer->is_past_cursor()) {
passed_cursor = true;
}
previous = current;
current = tokenizer->scan();
while (current.type == GDScriptTokenizer::Token::ERROR) {
Expand Down Expand Up @@ -926,6 +941,7 @@ void GDScriptParser::parse_extends() {
}

make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_INHERIT_TYPE, current_class);

if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) {
return;
Expand All @@ -934,6 +950,8 @@ void GDScriptParser::parse_extends() {

while (match(GDScriptTokenizer::Token::PERIOD)) {
make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_INHERIT_TYPE, current_class);

if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) {
return;
}
Expand Down Expand Up @@ -1090,6 +1108,8 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
default:
// Display a completion with identifiers.
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_IDENTIFIER, nullptr);

advance();
push_error(vformat(R"(Unexpected %s in class body.)", previous.get_debug_name()));
break;
Expand Down Expand Up @@ -1138,6 +1158,8 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, b
} else {
if (p_allow_property) {
make_completion_context(COMPLETION_PROPERTY_DECLARATION_OR_TYPE, variable);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_PROPERTY_DECLARATION_OR_TYPE, variable);

if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
// Check if get or set.
if (current.get_identifier() == "get" || current.get_identifier() == "set") {
Expand Down Expand Up @@ -1185,6 +1207,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var
VariableNode *property = p_variable;

make_completion_context(COMPLETION_PROPERTY_DECLARATION, property);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_PROPERTY_DECLARATION, property);

if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) {
complete_extents(p_variable);
Expand Down Expand Up @@ -1295,7 +1318,10 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
}
case VariableNode::PROP_SETGET:
consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")");

make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_PROPERTY_METHOD, p_variable);

if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)")) {
p_variable->setter_pointer = parse_identifier();
}
Expand Down Expand Up @@ -1336,7 +1362,10 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
}
case VariableNode::PROP_SETGET:
consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")");

make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_PROPERTY_METHOD, p_variable);

if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)")) {
p_variable->getter_pointer = parse_identifier();
}
Expand Down Expand Up @@ -1401,6 +1430,8 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
} else {
// Parse type.
make_completion_context(COMPLETION_TYPE_NAME, parameter);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_TYPE_NAME, parameter);

parameter->datatype_specifier = parse_type();
}
}
Expand Down Expand Up @@ -1592,6 +1623,8 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod

if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, p_function);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_TYPE_NAME_OR_VOID, p_function);

p_function->return_type = parse_type(true);
if (p_function->return_type == nullptr) {
push_error(R"(Expected return type or "void" after "->".)");
Expand All @@ -1616,6 +1649,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {
FunctionNode *function = alloc_node<FunctionNode>();

make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_OVERRIDE_METHOD, function);

if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
complete_extents(function);
Expand Down Expand Up @@ -1656,6 +1690,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
annotation->name = previous.literal;

make_completion_context(COMPLETION_ANNOTATION, annotation);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ANNOTATION, annotation);

bool valid = true;

Expand Down Expand Up @@ -1693,6 +1728,8 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
int argument_index = 0;
do {
make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ANNOTATION_ARGUMENTS, annotation);

set_last_completion_call_arg(argument_index);
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
Expand Down Expand Up @@ -2574,6 +2611,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr

// Completion can appear whenever an expression is expected.
make_completion_context(COMPLETION_IDENTIFIER, nullptr, -1, false);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_IDENTIFIER, nullptr);

GDScriptTokenizer::Token token = current;
GDScriptTokenizer::Token::Type token_type = token.type;
Expand All @@ -2598,6 +2636,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
// HACK: We can't create a context in parse_identifier since it is used in places were we don't want completion.
if (previous_operand != nullptr && previous_operand->type == GDScriptParser::Node::IDENTIFIER && prefix_rule == static_cast<ParseFunction>(&GDScriptParser::parse_identifier)) {
make_completion_context(COMPLETION_IDENTIFIER, previous_operand);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_IDENTIFIER, previous_operand);
}
#endif

Expand Down Expand Up @@ -2702,7 +2741,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_
literal->value = previous.literal;
reset_extents(literal, p_previous_operand);
update_extents(literal);

make_completion_context(COMPLETION_NONE, literal, -1);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_NONE, literal);

complete_extents(literal);
return literal;
}
Expand Down Expand Up @@ -2981,6 +3023,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
update_extents(assignment);

make_completion_context(COMPLETION_ASSIGN, assignment);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ASSIGN, assignment);

switch (previous.type) {
case GDScriptTokenizer::Token::EQUAL:
assignment->operation = AssignmentNode::OP_NONE;
Expand Down Expand Up @@ -3204,11 +3248,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
Variant::Type builtin_type = get_builtin_type(id->name);
if (builtin_type < Variant::VARIANT_MAX) {
make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, nullptr);

is_builtin = true;
}
}
if (!is_builtin) {
make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ATTRIBUTE, attribute);
}
}

Expand All @@ -3235,6 +3282,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *
update_extents(subscript);

make_completion_context(COMPLETION_SUBSCRIPT, subscript);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_SUBSCRIPT, subscript);

subscript->base = p_previous_operand;
subscript->index = parse_expression(false);
Expand Down Expand Up @@ -3296,7 +3344,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
}
} else {
consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)");

make_completion_context(COMPLETION_SUPER_METHOD, call, true);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_SUPER_METHOD, call);

if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {
pop_multiline();
complete_extents(call);
Expand All @@ -3314,14 +3365,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
} else if (call->callee->type == Node::IDENTIFIER) {
call->function_name = static_cast<IdentifierNode *>(call->callee)->name;

make_completion_context(COMPLETION_METHOD, call->callee);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_METHOD, call->callee);
} else if (call->callee->type == Node::SUBSCRIPT) {
SubscriptNode *attribute = static_cast<SubscriptNode *>(call->callee);
if (attribute->is_attribute) {
if (attribute->attribute) {
call->function_name = attribute->attribute->name;
}

make_completion_context(COMPLETION_ATTRIBUTE_METHOD, call->callee);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ATTRIBUTE_METHOD, call->callee);
} else {
// TODO: The analyzer can see if this is actually a Callable and give better error message.
push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
Expand All @@ -3332,14 +3387,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
}

// Arguments.
CompletionType ct = COMPLETION_CALL_ARGUMENTS;
CompletionType completion_type = COMPLETION_CALL_ARGUMENTS;
RefactorSymbolType symbol_type = REFACTOR_SYMBOL_CALL_ARGUMENTS;
if (call->function_name == SNAME("load")) {
ct = COMPLETION_RESOURCE_PATH;
completion_type = COMPLETION_RESOURCE_PATH;
symbol_type = REFACTOR_SYMBOL_CALL_ARGUMENTS;
}
push_completion_call(call);
int argument_index = 0;
do {
make_completion_context(ct, call, argument_index);
make_completion_context(completion_type, call, argument_index);
make_refactor_context(symbol_type, call);

if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
break;
Expand All @@ -3351,11 +3410,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
call->arguments.push_back(argument);

if (argument->type == Node::LITERAL) {
override_completion_context(argument, ct, call, argument_index);
override_completion_context(argument, completion_type, call, argument_index);
}
}

ct = COMPLETION_CALL_ARGUMENTS;
completion_type = COMPLETION_CALL_ARGUMENTS;
argument_index++;
} while (match(GDScriptTokenizer::Token::COMMA));
pop_completion_call();
Expand All @@ -3370,6 +3429,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) {
// We want code completion after a DOLLAR even if the current code is invalid.
make_completion_context(COMPLETION_GET_NODE, nullptr, -1);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_GET_NODE, nullptr);

if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {
push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name()));
Expand Down Expand Up @@ -3427,6 +3487,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
}

make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_GET_NODE, get_node);

if (match(GDScriptTokenizer::Token::LITERAL)) {
if (previous.literal.get_type() != Variant::STRING) {
Expand Down Expand Up @@ -3484,6 +3545,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)");

make_completion_context(COMPLETION_RESOURCE_PATH, preload);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_RESOURCE_PATH, preload);

push_completion_call(preload);

preload->path = parse_expression(false);
Expand Down Expand Up @@ -3649,7 +3712,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNo

GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
TypeNode *type = alloc_node<TypeNode>();

make_completion_context(p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME, type);
make_refactor_context(p_allow_void ? RefactorSymbolType::REFACTOR_SYMBOL_TYPE_NAME_OR_VOID : RefactorSymbolType::REFACTOR_SYMBOL_TYPE_NAME, type);

if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {
if (match(GDScriptTokenizer::Token::VOID)) {
if (p_allow_void) {
Expand Down Expand Up @@ -3696,6 +3762,8 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
int chain_index = 1;
while (match(GDScriptTokenizer::Token::PERIOD)) {
make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++);
make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ATTRIBUTE, type);

if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)")) {
type_element = parse_identifier();
type->type_chain.push_back(type_element);
Expand Down
45 changes: 45 additions & 0 deletions modules/gdscript/gdscript_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@ class GDScriptParser {
}
};

// Completion.
enum CompletionType {
COMPLETION_NONE,
COMPLETION_ANNOTATION, // Annotation (following @).
Expand Down Expand Up @@ -1329,6 +1330,46 @@ class GDScriptParser {
int argument = -1;
};

// Refactor.
enum RefactorSymbolType {
REFACTOR_SYMBOL_NONE,
REFACTOR_SYMBOL_ANNOTATION, // Annotation (following @).
REFACTOR_SYMBOL_ANNOTATION_ARGUMENTS, // Annotation arguments hint.
REFACTOR_SYMBOL_ASSIGN, // Assignment based on type (e.g. enum values).
REFACTOR_SYMBOL_ATTRIBUTE, // After id.| to look for members.
REFACTOR_SYMBOL_ATTRIBUTE_METHOD, // After id.| to look for methods.
REFACTOR_SYMBOL_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, // Constants inside a built-in type (e.g. Color.BLUE) or static methods (e.g. Color.html).
REFACTOR_SYMBOL_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions).
// TODO: REFACTOR_SYMBOL_DECLARATION, // Potential declaration (var, const, func).
REFACTOR_SYMBOL_GET_NODE, // Get node with $ notation.
REFACTOR_SYMBOL_IDENTIFIER, // List available identifiers in scope.
REFACTOR_SYMBOL_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index.
REFACTOR_SYMBOL_METHOD, // List available methods in scope.
REFACTOR_SYMBOL_OVERRIDE_METHOD, // Override implementation, also for native virtuals.
REFACTOR_SYMBOL_PROPERTY_DECLARATION, // Property declaration (get, set).
REFACTOR_SYMBOL_PROPERTY_DECLARATION_OR_TYPE, // Property declaration (get, set) or a type hint.
REFACTOR_SYMBOL_PROPERTY_METHOD, // Property setter or getter (list available methods).
REFACTOR_SYMBOL_RESOURCE_PATH, // For load/preload.
REFACTOR_SYMBOL_SUBSCRIPT, // Inside id[|].
REFACTOR_SYMBOL_SUPER_METHOD, // After super.
REFACTOR_SYMBOL_TYPE_ATTRIBUTE, // Attribute in type name (Type.|).
REFACTOR_SYMBOL_TYPE_NAME, // Name of type (after :).
REFACTOR_SYMBOL_TYPE_NAME_OR_VOID, // Same as TYPE_NAME, but allows void (in function return type).
};

struct RefactorContext {
RefactorSymbolType type = REFACTOR_SYMBOL_NONE;
ClassNode *current_class = nullptr;
FunctionNode *current_function = nullptr;
SuiteNode *current_suite = nullptr;
int current_line = -1;
int current_column = -1;
Node *node = nullptr;
Object *base = nullptr;
GDScriptParser *parser = nullptr;
};

// Parser context.
enum ParserContext {
PARSER_CONTEXT_STANDARD = 1 << 0,
PARSER_CONTEXT_COMPLETION = 1 << 1,
Expand Down Expand Up @@ -1386,6 +1427,8 @@ class GDScriptParser {
bool in_lambda = false;
bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed.

RefactorContext refactor_context;

typedef bool (GDScriptParser::*AnnotationAction)(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
struct AnnotationInfo {
enum TargetKind {
Expand Down Expand Up @@ -1482,6 +1525,8 @@ class GDScriptParser {
void pop_completion_call();
void set_last_completion_call_arg(int p_argument);

void make_refactor_context(RefactorSymbolType p_type, Node *p_node);

GDScriptTokenizer::Token advance();
bool match(GDScriptTokenizer::Token::Type p_token_type);
bool check(GDScriptTokenizer::Token::Type p_token_type) const;
Expand Down

0 comments on commit 52a5345

Please sign in to comment.