1 /*************************************************************************/
2 /* gdscript_extend_parser.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30
31 #include "gdscript_extend_parser.h"
32
33 #include "../gdscript.h"
34 #include "core/io/json.h"
35 #include "gdscript_language_protocol.h"
36 #include "gdscript_workspace.h"
37
update_diagnostics()38 void ExtendGDScriptParser::update_diagnostics() {
39
40 diagnostics.clear();
41
42 if (has_error()) {
43 lsp::Diagnostic diagnostic;
44 diagnostic.severity = lsp::DiagnosticSeverity::Error;
45 diagnostic.message = get_error();
46 diagnostic.source = "gdscript";
47 diagnostic.code = -1;
48 lsp::Range range;
49 lsp::Position pos;
50 int line = LINE_NUMBER_TO_INDEX(get_error_line());
51 const String &line_text = get_lines()[line];
52 pos.line = line;
53 pos.character = line_text.length() - line_text.strip_edges(true, false).length();
54 range.start = pos;
55 range.end = range.start;
56 range.end.character = line_text.strip_edges(false).length();
57 diagnostic.range = range;
58 diagnostics.push_back(diagnostic);
59 }
60
61 const List<GDScriptWarning> &warnings = get_warnings();
62 for (const List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {
63 const GDScriptWarning &warning = E->get();
64 lsp::Diagnostic diagnostic;
65 diagnostic.severity = lsp::DiagnosticSeverity::Warning;
66 diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message();
67 diagnostic.source = "gdscript";
68 diagnostic.code = warning.code;
69 lsp::Range range;
70 lsp::Position pos;
71 int line = LINE_NUMBER_TO_INDEX(warning.line);
72 const String &line_text = get_lines()[line];
73 pos.line = line;
74 pos.character = line_text.length() - line_text.strip_edges(true, false).length();
75 range.start = pos;
76 range.end = pos;
77 range.end.character = line_text.strip_edges(false).length();
78 diagnostic.range = range;
79 diagnostics.push_back(diagnostic);
80 }
81 }
82
update_symbols()83 void ExtendGDScriptParser::update_symbols() {
84
85 members.clear();
86
87 const GDScriptParser::Node *head = get_parse_tree();
88 if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
89
90 parse_class_symbol(gdclass, class_symbol);
91
92 for (int i = 0; i < class_symbol.children.size(); i++) {
93 const lsp::DocumentSymbol &symbol = class_symbol.children[i];
94 members.set(symbol.name, &symbol);
95
96 // cache level one inner classes
97 if (symbol.kind == lsp::SymbolKind::Class) {
98 ClassMembers inner_class;
99 for (int j = 0; j < symbol.children.size(); j++) {
100 const lsp::DocumentSymbol &s = symbol.children[j];
101 inner_class.set(s.name, &s);
102 }
103 inner_classes.set(symbol.name, inner_class);
104 }
105 }
106 }
107 }
108
update_document_links(const String & p_code)109 void ExtendGDScriptParser::update_document_links(const String &p_code) {
110 document_links.clear();
111
112 GDScriptTokenizerText tokenizer;
113 FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES);
114 tokenizer.set_code(p_code);
115 while (true) {
116 GDScriptTokenizerText::Token token = tokenizer.get_token();
117 if (token == GDScriptTokenizer::TK_EOF || token == GDScriptTokenizer::TK_ERROR) {
118 break;
119 } else if (token == GDScriptTokenizer::TK_CONSTANT) {
120 const Variant &const_val = tokenizer.get_token_constant();
121 if (const_val.get_type() == Variant::STRING) {
122 String path = const_val;
123 bool exists = fs->file_exists(path);
124 if (!exists) {
125 path = get_path().get_base_dir() + "/" + path;
126 exists = fs->file_exists(path);
127 }
128 if (exists) {
129 String value = const_val;
130 lsp::DocumentLink link;
131 link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
132 link.range.start.line = LINE_NUMBER_TO_INDEX(tokenizer.get_token_line());
133 link.range.end.line = link.range.start.line;
134 link.range.end.character = LINE_NUMBER_TO_INDEX(tokenizer.get_token_column());
135 link.range.start.character = link.range.end.character - value.length();
136 document_links.push_back(link);
137 }
138 }
139 }
140 tokenizer.advance();
141 }
142 }
143
parse_class_symbol(const GDScriptParser::ClassNode * p_class,lsp::DocumentSymbol & r_symbol)144 void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) {
145
146 const String uri = get_uri();
147
148 r_symbol.uri = uri;
149 r_symbol.script_path = path;
150 r_symbol.children.clear();
151 r_symbol.name = p_class->name;
152 if (r_symbol.name.empty())
153 r_symbol.name = path.get_file();
154 r_symbol.kind = lsp::SymbolKind::Class;
155 r_symbol.deprecated = false;
156 r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->line);
157 r_symbol.range.start.character = p_class->column;
158 r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line);
159 r_symbol.selectionRange.start.line = r_symbol.range.start.line;
160 r_symbol.detail = "class " + r_symbol.name;
161 bool is_root_class = &r_symbol == &class_symbol;
162 r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class);
163
164 for (int i = 0; i < p_class->variables.size(); ++i) {
165
166 const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
167
168 lsp::DocumentSymbol symbol;
169 symbol.name = m.identifier;
170 symbol.kind = lsp::SymbolKind::Variable;
171 symbol.deprecated = false;
172 const int line = LINE_NUMBER_TO_INDEX(m.line);
173 symbol.range.start.line = line;
174 symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
175 symbol.range.end.line = line;
176 symbol.range.end.character = lines[line].length();
177 symbol.selectionRange.start.line = symbol.range.start.line;
178 if (m._export.type != Variant::NIL) {
179 symbol.detail += "export ";
180 }
181 symbol.detail += "var " + m.identifier;
182 if (m.data_type.kind != GDScriptParser::DataType::UNRESOLVED) {
183 symbol.detail += ": " + m.data_type.to_string();
184 }
185 if (m.default_value.get_type() != Variant::NIL) {
186 symbol.detail += " = " + JSON::print(m.default_value);
187 }
188
189 symbol.documentation = parse_documentation(line);
190 symbol.uri = uri;
191 symbol.script_path = path;
192
193 r_symbol.children.push_back(symbol);
194 }
195
196 for (int i = 0; i < p_class->_signals.size(); ++i) {
197 const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
198
199 lsp::DocumentSymbol symbol;
200 symbol.name = signal.name;
201 symbol.kind = lsp::SymbolKind::Event;
202 symbol.deprecated = false;
203 const int line = LINE_NUMBER_TO_INDEX(signal.line);
204 symbol.range.start.line = line;
205 symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
206 symbol.range.end.line = symbol.range.start.line;
207 symbol.range.end.character = lines[line].length();
208 symbol.selectionRange.start.line = symbol.range.start.line;
209 symbol.documentation = parse_documentation(line);
210 symbol.uri = uri;
211 symbol.script_path = path;
212 symbol.detail = "signal " + signal.name + "(";
213 for (int j = 0; j < signal.arguments.size(); j++) {
214 if (j > 0) {
215 symbol.detail += ", ";
216 }
217 symbol.detail += signal.arguments[j];
218 }
219 symbol.detail += ")";
220
221 r_symbol.children.push_back(symbol);
222 }
223
224 for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
225 lsp::DocumentSymbol symbol;
226 const GDScriptParser::ClassNode::Constant &c = E->value();
227 const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
228 ERR_FAIL_COND(!node);
229 symbol.name = E->key();
230 symbol.kind = lsp::SymbolKind::Constant;
231 symbol.deprecated = false;
232 const int line = LINE_NUMBER_TO_INDEX(E->get().expression->line);
233 symbol.range.start.line = line;
234 symbol.range.start.character = E->get().expression->column;
235 symbol.range.end.line = symbol.range.start.line;
236 symbol.range.end.character = lines[line].length();
237 symbol.selectionRange.start.line = symbol.range.start.line;
238 symbol.documentation = parse_documentation(line);
239 symbol.uri = uri;
240 symbol.script_path = path;
241
242 symbol.detail = "const " + symbol.name;
243 if (c.type.kind != GDScriptParser::DataType::UNRESOLVED) {
244 symbol.detail += ": " + c.type.to_string();
245 }
246
247 String value_text;
248 if (node->value.get_type() == Variant::OBJECT) {
249 RES res = node->value;
250 if (res.is_valid() && !res->get_path().empty()) {
251 value_text = "preload(\"" + res->get_path() + "\")";
252 if (symbol.documentation.empty()) {
253 if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) {
254 symbol.documentation = S->get()->class_symbol.documentation;
255 }
256 }
257 } else {
258 value_text = JSON::print(node->value);
259 }
260 } else {
261 value_text = JSON::print(node->value);
262 }
263 if (!value_text.empty()) {
264 symbol.detail += " = " + value_text;
265 }
266
267 r_symbol.children.push_back(symbol);
268 }
269
270 for (int i = 0; i < p_class->functions.size(); ++i) {
271 const GDScriptParser::FunctionNode *func = p_class->functions[i];
272 lsp::DocumentSymbol symbol;
273 parse_function_symbol(func, symbol);
274 r_symbol.children.push_back(symbol);
275 }
276
277 for (int i = 0; i < p_class->static_functions.size(); ++i) {
278 const GDScriptParser::FunctionNode *func = p_class->static_functions[i];
279 lsp::DocumentSymbol symbol;
280 parse_function_symbol(func, symbol);
281 r_symbol.children.push_back(symbol);
282 }
283
284 for (int i = 0; i < p_class->subclasses.size(); ++i) {
285 const GDScriptParser::ClassNode *subclass = p_class->subclasses[i];
286 lsp::DocumentSymbol symbol;
287 parse_class_symbol(subclass, symbol);
288 r_symbol.children.push_back(symbol);
289 }
290 }
291
parse_function_symbol(const GDScriptParser::FunctionNode * p_func,lsp::DocumentSymbol & r_symbol)292 void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) {
293
294 const String uri = get_uri();
295
296 r_symbol.name = p_func->name;
297 r_symbol.kind = lsp::SymbolKind::Function;
298 r_symbol.detail = "func " + p_func->name + "(";
299 r_symbol.deprecated = false;
300 const int line = LINE_NUMBER_TO_INDEX(p_func->line);
301 r_symbol.range.start.line = line;
302 r_symbol.range.start.character = p_func->column;
303 r_symbol.range.end.line = MAX(p_func->body->end_line - 2, r_symbol.range.start.line);
304 r_symbol.range.end.character = lines[r_symbol.range.end.line].length();
305 r_symbol.selectionRange.start.line = r_symbol.range.start.line;
306 r_symbol.documentation = parse_documentation(line);
307 r_symbol.uri = uri;
308 r_symbol.script_path = path;
309
310 String arguments;
311 for (int i = 0; i < p_func->arguments.size(); i++) {
312 lsp::DocumentSymbol symbol;
313 symbol.kind = lsp::SymbolKind::Variable;
314 symbol.name = p_func->arguments[i];
315 symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->body->line);
316 symbol.range.start.character = p_func->body->column;
317 symbol.range.end = symbol.range.start;
318 symbol.uri = uri;
319 symbol.script_path = path;
320 r_symbol.children.push_back(symbol);
321 if (i > 0) {
322 arguments += ", ";
323 }
324 arguments += String(p_func->arguments[i]);
325 if (p_func->argument_types[i].kind != GDScriptParser::DataType::UNRESOLVED) {
326 arguments += ": " + p_func->argument_types[i].to_string();
327 }
328 int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
329 if (default_value_idx >= 0) {
330 const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
331 if (const_node == NULL) {
332 const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
333 if (operator_node) {
334 const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
335 }
336 }
337
338 if (const_node) {
339 String value = JSON::print(const_node->value);
340 arguments += " = " + value;
341 }
342 }
343 }
344 r_symbol.detail += arguments + ")";
345 if (p_func->return_type.kind != GDScriptParser::DataType::UNRESOLVED) {
346 r_symbol.detail += " -> " + p_func->return_type.to_string();
347 }
348
349 for (const Map<StringName, LocalVarNode *>::Element *E = p_func->body->variables.front(); E; E = E->next()) {
350 lsp::DocumentSymbol symbol;
351 const GDScriptParser::LocalVarNode *var = E->value();
352 symbol.name = E->key();
353 symbol.kind = lsp::SymbolKind::Variable;
354 symbol.range.start.line = LINE_NUMBER_TO_INDEX(E->get()->line);
355 symbol.range.start.character = E->get()->column;
356 symbol.range.end.line = symbol.range.start.line;
357 symbol.range.end.character = lines[symbol.range.end.line].length();
358 symbol.uri = uri;
359 symbol.script_path = path;
360 symbol.detail = "var " + symbol.name;
361 if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) {
362 symbol.detail += ": " + var->datatype.to_string();
363 }
364 symbol.documentation = parse_documentation(line);
365 r_symbol.children.push_back(symbol);
366 }
367 }
368
parse_documentation(int p_line,bool p_docs_down)369 String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) {
370 ERR_FAIL_INDEX_V(p_line, lines.size(), String());
371
372 List<String> doc_lines;
373
374 if (!p_docs_down) { // inline comment
375 String inline_comment = lines[p_line];
376 int comment_start = inline_comment.find("#");
377 if (comment_start != -1) {
378 inline_comment = inline_comment.substr(comment_start, inline_comment.length()).strip_edges();
379 if (inline_comment.length() > 1) {
380 doc_lines.push_back(inline_comment.substr(1, inline_comment.length()));
381 }
382 }
383 }
384
385 int step = p_docs_down ? 1 : -1;
386 int start_line = p_docs_down ? p_line : p_line - 1;
387 for (int i = start_line; true; i += step) {
388
389 if (i < 0 || i >= lines.size()) break;
390
391 String line_comment = lines[i].strip_edges(true, false);
392 if (line_comment.begins_with("#")) {
393 line_comment = line_comment.substr(1, line_comment.length());
394 if (p_docs_down) {
395 doc_lines.push_back(line_comment);
396 } else {
397 doc_lines.push_front(line_comment);
398 }
399 } else {
400 break;
401 }
402 }
403
404 String doc;
405 for (List<String>::Element *E = doc_lines.front(); E; E = E->next()) {
406 doc += E->get() + "\n";
407 }
408 return doc;
409 }
410
get_text_for_completion(const lsp::Position & p_cursor) const411 String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_cursor) const {
412
413 String longthing;
414 int len = lines.size();
415 for (int i = 0; i < len; i++) {
416
417 if (i == p_cursor.line) {
418 longthing += lines[i].substr(0, p_cursor.character);
419 longthing += String::chr(0xFFFF); //not unicode, represents the cursor
420 longthing += lines[i].substr(p_cursor.character, lines[i].size());
421 } else {
422
423 longthing += lines[i];
424 }
425
426 if (i != len - 1)
427 longthing += "\n";
428 }
429
430 return longthing;
431 }
432
get_text_for_lookup_symbol(const lsp::Position & p_cursor,const String & p_symbol,bool p_func_requred) const433 String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_requred) const {
434 String longthing;
435 int len = lines.size();
436 for (int i = 0; i < len; i++) {
437
438 if (i == p_cursor.line) {
439 String line = lines[i];
440 String first_part = line.substr(0, p_cursor.character);
441 String last_part = line.substr(p_cursor.character + 1, lines[i].length());
442 if (!p_symbol.empty()) {
443 String left_cursor_text;
444 for (int c = p_cursor.character - 1; c >= 0; c--) {
445 left_cursor_text = line.substr(c, p_cursor.character - c);
446 if (p_symbol.begins_with(left_cursor_text)) {
447 first_part = line.substr(0, c);
448 first_part += p_symbol;
449 break;
450 }
451 }
452 }
453
454 longthing += first_part;
455 longthing += String::chr(0xFFFF); //not unicode, represents the cursor
456 if (p_func_requred) {
457 longthing += "("; // tell the parser this is a function call
458 }
459 longthing += last_part;
460 } else {
461
462 longthing += lines[i];
463 }
464
465 if (i != len - 1)
466 longthing += "\n";
467 }
468
469 return longthing;
470 }
471
get_identifier_under_position(const lsp::Position & p_position,Vector2i & p_offset) const472 String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const {
473
474 ERR_FAIL_INDEX_V(p_position.line, lines.size(), "");
475 String line = lines[p_position.line];
476 ERR_FAIL_INDEX_V(p_position.character, line.size(), "");
477
478 int start_pos = p_position.character;
479 for (int c = p_position.character; c >= 0; c--) {
480 start_pos = c;
481 CharType ch = line[c];
482 bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
483 if (!valid_char) {
484 break;
485 }
486 }
487
488 int end_pos = p_position.character;
489 for (int c = p_position.character; c < line.length(); c++) {
490 CharType ch = line[c];
491 bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
492 if (!valid_char) {
493 break;
494 }
495 end_pos = c;
496 }
497 if (start_pos < end_pos) {
498 p_offset.x = start_pos - p_position.character;
499 p_offset.y = end_pos - p_position.character;
500 return line.substr(start_pos + 1, end_pos - start_pos);
501 }
502
503 return "";
504 }
505
get_uri() const506 String ExtendGDScriptParser::get_uri() const {
507 return GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
508 }
509
search_symbol_defined_at_line(int p_line,const lsp::DocumentSymbol & p_parent) const510 const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const {
511 const lsp::DocumentSymbol *ret = NULL;
512 if (p_line < p_parent.range.start.line) {
513 return ret;
514 } else if (p_parent.range.start.line == p_line) {
515 return &p_parent;
516 } else {
517 for (int i = 0; i < p_parent.children.size(); i++) {
518 ret = search_symbol_defined_at_line(p_line, p_parent.children[i]);
519 if (ret) {
520 break;
521 }
522 }
523 }
524 return ret;
525 }
526
get_left_function_call(const lsp::Position & p_position,lsp::Position & r_func_pos,int & r_arg_index) const527 Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const {
528
529 ERR_FAIL_INDEX_V(p_position.line, lines.size(), ERR_INVALID_PARAMETER);
530
531 int bracket_stack = 0;
532 int index = 0;
533
534 bool found = false;
535 for (int l = p_position.line; l >= 0; --l) {
536 String line = lines[l];
537 int c = line.length() - 1;
538 if (l == p_position.line) {
539 c = MIN(c, p_position.character - 1);
540 }
541
542 while (c >= 0) {
543 const CharType &character = line[c];
544 if (character == ')') {
545 ++bracket_stack;
546 } else if (character == '(') {
547 --bracket_stack;
548 if (bracket_stack < 0) {
549 found = true;
550 }
551 }
552 if (bracket_stack <= 0 && character == ',') {
553 ++index;
554 }
555 --c;
556 if (found) {
557 r_func_pos.character = c;
558 break;
559 }
560 }
561
562 if (found) {
563 r_func_pos.line = l;
564 r_arg_index = index;
565 return OK;
566 }
567 }
568
569 return ERR_METHOD_NOT_FOUND;
570 }
571
get_symbol_defined_at_line(int p_line) const572 const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const {
573 if (p_line <= 0) {
574 return &class_symbol;
575 }
576 return search_symbol_defined_at_line(p_line, class_symbol);
577 }
578
get_member_symbol(const String & p_name,const String & p_subclass) const579 const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const {
580
581 if (p_subclass.empty()) {
582 const lsp::DocumentSymbol *const *ptr = members.getptr(p_name);
583 if (ptr) {
584 return *ptr;
585 }
586 } else {
587 if (const ClassMembers *_class = inner_classes.getptr(p_subclass)) {
588 const lsp::DocumentSymbol *const *ptr = _class->getptr(p_name);
589 if (ptr) {
590 return *ptr;
591 }
592 }
593 }
594
595 return NULL;
596 }
597
get_document_links() const598 const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const {
599 return document_links;
600 }
601
get_member_completions()602 const Array &ExtendGDScriptParser::get_member_completions() {
603
604 if (member_completions.empty()) {
605
606 const String *name = members.next(NULL);
607 while (name) {
608
609 const lsp::DocumentSymbol *symbol = members.get(*name);
610 lsp::CompletionItem item = symbol->make_completion_item();
611 item.data = JOIN_SYMBOLS(path, *name);
612 member_completions.push_back(item.to_json());
613
614 name = members.next(name);
615 }
616
617 const String *_class = inner_classes.next(NULL);
618 while (_class) {
619
620 const ClassMembers *inner_class = inner_classes.getptr(*_class);
621 const String *member_name = inner_class->next(NULL);
622 while (member_name) {
623 const lsp::DocumentSymbol *symbol = inner_class->get(*member_name);
624 lsp::CompletionItem item = symbol->make_completion_item();
625 item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(*_class, *member_name));
626 member_completions.push_back(item.to_json());
627
628 member_name = inner_class->next(member_name);
629 }
630
631 _class = inner_classes.next(_class);
632 }
633 }
634
635 return member_completions;
636 }
637
dump_function_api(const GDScriptParser::FunctionNode * p_func) const638 Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const {
639 Dictionary func;
640 ERR_FAIL_NULL_V(p_func, func);
641 func["name"] = p_func->name;
642 func["return_type"] = p_func->return_type.to_string();
643 func["rpc_mode"] = p_func->rpc_mode;
644 Array arguments;
645 for (int i = 0; i < p_func->arguments.size(); i++) {
646 Dictionary arg;
647 arg["name"] = p_func->arguments[i];
648 arg["type"] = p_func->argument_types[i].to_string();
649 int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
650 if (default_value_idx >= 0) {
651 const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
652 if (const_node == NULL) {
653 const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
654 if (operator_node) {
655 const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
656 }
657 }
658 if (const_node) {
659 arg["default_value"] = const_node->value;
660 }
661 }
662 arguments.push_back(arg);
663 }
664 if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->line))) {
665 func["signature"] = symbol->detail;
666 func["description"] = symbol->documentation;
667 }
668 func["arguments"] = arguments;
669 return func;
670 }
671
dump_class_api(const GDScriptParser::ClassNode * p_class) const672 Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode *p_class) const {
673 Dictionary class_api;
674
675 ERR_FAIL_NULL_V(p_class, class_api);
676
677 class_api["name"] = String(p_class->name);
678 class_api["path"] = path;
679 Array extends_class;
680 for (int i = 0; i < p_class->extends_class.size(); i++) {
681 extends_class.append(String(p_class->extends_class[i]));
682 }
683 class_api["extends_class"] = extends_class;
684 class_api["extends_file"] = String(p_class->extends_file);
685 class_api["icon"] = String(p_class->icon_path);
686
687 if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->line))) {
688 class_api["signature"] = symbol->detail;
689 class_api["description"] = symbol->documentation;
690 }
691
692 Array subclasses;
693 for (int i = 0; i < p_class->subclasses.size(); i++) {
694 subclasses.push_back(dump_class_api(p_class->subclasses[i]));
695 }
696 class_api["sub_classes"] = subclasses;
697
698 Array constants;
699 for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
700
701 const GDScriptParser::ClassNode::Constant &c = E->value();
702 const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
703 ERR_FAIL_COND_V(!node, class_api);
704
705 Dictionary api;
706 api["name"] = E->key();
707 api["value"] = node->value;
708 api["data_type"] = node->datatype.to_string();
709 if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(node->line))) {
710 api["signature"] = symbol->detail;
711 api["description"] = symbol->documentation;
712 }
713 constants.push_back(api);
714 }
715 class_api["constants"] = constants;
716
717 Array members;
718 for (int i = 0; i < p_class->variables.size(); ++i) {
719 const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
720 Dictionary api;
721 api["name"] = m.identifier;
722 api["data_type"] = m.data_type.to_string();
723 api["default_value"] = m.default_value;
724 api["setter"] = String(m.setter);
725 api["getter"] = String(m.getter);
726 api["export"] = m._export.type != Variant::NIL;
727 if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))) {
728 api["signature"] = symbol->detail;
729 api["description"] = symbol->documentation;
730 }
731 members.push_back(api);
732 }
733 class_api["members"] = members;
734
735 Array signals;
736 for (int i = 0; i < p_class->_signals.size(); ++i) {
737 const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
738 Dictionary api;
739 api["name"] = signal.name;
740 Array args;
741 for (int j = 0; j < signal.arguments.size(); j++) {
742 args.append(signal.arguments[j]);
743 }
744 api["arguments"] = args;
745 if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(signal.line))) {
746 api["signature"] = symbol->detail;
747 api["description"] = symbol->documentation;
748 }
749 signals.push_back(api);
750 }
751 class_api["signals"] = signals;
752
753 Array methods;
754 for (int i = 0; i < p_class->functions.size(); ++i) {
755 methods.append(dump_function_api(p_class->functions[i]));
756 }
757 class_api["methods"] = methods;
758
759 Array static_functions;
760 for (int i = 0; i < p_class->static_functions.size(); ++i) {
761 static_functions.append(dump_function_api(p_class->static_functions[i]));
762 }
763 class_api["static_functions"] = static_functions;
764
765 return class_api;
766 }
767
generate_api() const768 Dictionary ExtendGDScriptParser::generate_api() const {
769
770 Dictionary api;
771 const GDScriptParser::Node *head = get_parse_tree();
772 if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
773 api = dump_class_api(gdclass);
774 }
775 return api;
776 }
777
parse(const String & p_code,const String & p_path)778 Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
779 path = p_path;
780 lines = p_code.split("\n");
781
782 Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, NULL, false);
783 update_diagnostics();
784 update_symbols();
785 update_document_links(p_code);
786 return err;
787 }
788