Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/gdscript/editor/gdscript_docgen.cpp
10278 views
1
/**************************************************************************/
2
/* gdscript_docgen.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
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_docgen.h"
32
33
#include "../gdscript.h"
34
35
#include "core/config/project_settings.h"
36
37
HashMap<String, String> GDScriptDocGen::singletons;
38
39
String GDScriptDocGen::_get_script_name(const String &p_path) {
40
const HashMap<String, String>::ConstIterator E = singletons.find(p_path);
41
if (E) {
42
return E->value;
43
}
44
return p_path.trim_prefix("res://").quote();
45
}
46
47
String GDScriptDocGen::_get_class_name(const GDP::ClassNode &p_class) {
48
const GDP::ClassNode *curr_class = &p_class;
49
if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
50
return _get_script_name(curr_class->fqcn);
51
}
52
53
String full_name = curr_class->identifier->name;
54
while (curr_class->outer) {
55
curr_class = curr_class->outer;
56
if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
57
return vformat("%s.%s", _get_script_name(curr_class->fqcn), full_name);
58
}
59
full_name = vformat("%s.%s", curr_class->identifier->name, full_name);
60
}
61
return full_name;
62
}
63
64
void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) {
65
if (!p_gdtype.is_hard_type()) {
66
r_type = "Variant";
67
return;
68
}
69
switch (p_gdtype.kind) {
70
case GDType::BUILTIN:
71
if (p_gdtype.builtin_type == Variant::NIL) {
72
r_type = p_is_return ? "void" : "null";
73
return;
74
}
75
if (p_gdtype.builtin_type == Variant::ARRAY && p_gdtype.has_container_element_type(0)) {
76
_doctype_from_gdtype(p_gdtype.get_container_element_type(0), r_type, r_enum);
77
if (!r_enum.is_empty()) {
78
r_type = "int[]";
79
r_enum += "[]";
80
return;
81
}
82
if (!r_type.is_empty() && r_type != "Variant") {
83
r_type += "[]";
84
return;
85
}
86
}
87
if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) {
88
String key, value;
89
_doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum);
90
_doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum);
91
if (key != "Variant" || value != "Variant") {
92
r_type = "Dictionary[" + key + ", " + value + "]";
93
return;
94
}
95
}
96
r_type = Variant::get_type_name(p_gdtype.builtin_type);
97
return;
98
case GDType::NATIVE:
99
if (p_gdtype.is_meta_type) {
100
//r_type = GDScriptNativeClass::get_class_static();
101
r_type = "Object"; // "GDScriptNativeClass" refers to a blank page.
102
return;
103
}
104
r_type = p_gdtype.native_type;
105
return;
106
case GDType::SCRIPT:
107
if (p_gdtype.is_meta_type) {
108
r_type = p_gdtype.script_type.is_valid() ? p_gdtype.script_type->get_class_name() : Script::get_class_static();
109
return;
110
}
111
if (p_gdtype.script_type.is_valid()) {
112
if (p_gdtype.script_type->get_global_name() != StringName()) {
113
r_type = p_gdtype.script_type->get_global_name();
114
return;
115
}
116
if (!p_gdtype.script_type->get_path().is_empty()) {
117
r_type = _get_script_name(p_gdtype.script_type->get_path());
118
return;
119
}
120
}
121
if (!p_gdtype.script_path.is_empty()) {
122
r_type = _get_script_name(p_gdtype.script_path);
123
return;
124
}
125
r_type = "Object";
126
return;
127
case GDType::CLASS:
128
if (p_gdtype.is_meta_type) {
129
r_type = GDScript::get_class_static();
130
return;
131
}
132
r_type = _get_class_name(*p_gdtype.class_type);
133
return;
134
case GDType::ENUM:
135
if (p_gdtype.is_meta_type) {
136
r_type = "Dictionary";
137
return;
138
}
139
r_type = "int";
140
r_enum = String(p_gdtype.native_type).replace("::", ".");
141
if (r_enum.begins_with("res://")) {
142
int dot_pos = r_enum.rfind_char('.');
143
if (dot_pos >= 0) {
144
r_enum = _get_script_name(r_enum.left(dot_pos)) + r_enum.substr(dot_pos);
145
} else {
146
r_enum = _get_script_name(r_enum);
147
}
148
}
149
return;
150
case GDType::VARIANT:
151
case GDType::RESOLVING:
152
case GDType::UNRESOLVED:
153
r_type = "Variant";
154
return;
155
}
156
}
157
158
String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_recursion_level) {
159
constexpr int MAX_RECURSION_LEVEL = 2;
160
161
switch (p_variant.get_type()) {
162
case Variant::STRING:
163
return String(p_variant).c_escape().quote();
164
case Variant::OBJECT:
165
return "<Object>";
166
case Variant::DICTIONARY: {
167
const Dictionary dict = p_variant;
168
String result;
169
170
if (dict.is_typed()) {
171
result += "Dictionary[";
172
173
Ref<Script> key_script = dict.get_typed_key_script();
174
if (key_script.is_valid()) {
175
if (key_script->get_global_name() != StringName()) {
176
result += key_script->get_global_name();
177
} else if (!key_script->get_path().get_file().is_empty()) {
178
result += key_script->get_path().get_file();
179
} else {
180
result += dict.get_typed_key_class_name();
181
}
182
} else if (dict.get_typed_key_class_name() != StringName()) {
183
result += dict.get_typed_key_class_name();
184
} else if (dict.is_typed_key()) {
185
result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin());
186
} else {
187
result += "Variant";
188
}
189
190
result += ", ";
191
192
Ref<Script> value_script = dict.get_typed_value_script();
193
if (value_script.is_valid()) {
194
if (value_script->get_global_name() != StringName()) {
195
result += value_script->get_global_name();
196
} else if (!value_script->get_path().get_file().is_empty()) {
197
result += value_script->get_path().get_file();
198
} else {
199
result += dict.get_typed_value_class_name();
200
}
201
} else if (dict.get_typed_value_class_name() != StringName()) {
202
result += dict.get_typed_value_class_name();
203
} else if (dict.is_typed_value()) {
204
result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin());
205
} else {
206
result += "Variant";
207
}
208
209
result += "](";
210
}
211
212
if (dict.is_empty()) {
213
result += "{}";
214
} else if (p_recursion_level > MAX_RECURSION_LEVEL) {
215
result += "{...}";
216
} else {
217
result += "{";
218
219
LocalVector<Variant> keys = dict.get_key_list();
220
keys.sort_custom<StringLikeVariantOrder>();
221
222
for (uint32_t i = 0; i < keys.size(); i++) {
223
const Variant &key = keys[i];
224
if (i > 0) {
225
result += ", ";
226
}
227
result += _docvalue_from_variant(key, p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[key], p_recursion_level + 1);
228
}
229
230
result += "}";
231
}
232
233
if (dict.is_typed()) {
234
result += ")";
235
}
236
237
return result;
238
} break;
239
case Variant::ARRAY: {
240
const Array array = p_variant;
241
String result;
242
243
if (array.is_typed()) {
244
result += "Array[";
245
246
Ref<Script> script = array.get_typed_script();
247
if (script.is_valid()) {
248
if (script->get_global_name() != StringName()) {
249
result += script->get_global_name();
250
} else if (!script->get_path().get_file().is_empty()) {
251
result += script->get_path().get_file();
252
} else {
253
result += array.get_typed_class_name();
254
}
255
} else if (array.get_typed_class_name() != StringName()) {
256
result += array.get_typed_class_name();
257
} else {
258
result += Variant::get_type_name((Variant::Type)array.get_typed_builtin());
259
}
260
261
result += "](";
262
}
263
264
if (array.is_empty()) {
265
result += "[]";
266
} else if (p_recursion_level > MAX_RECURSION_LEVEL) {
267
result += "[...]";
268
} else {
269
result += "[";
270
271
for (int i = 0; i < array.size(); i++) {
272
if (i > 0) {
273
result += ", ";
274
}
275
result += _docvalue_from_variant(array[i], p_recursion_level + 1);
276
}
277
278
result += "]";
279
}
280
281
if (array.is_typed()) {
282
result += ")";
283
}
284
285
return result;
286
} break;
287
default:
288
return p_variant.get_construct_string();
289
}
290
}
291
292
String GDScriptDocGen::docvalue_from_expression(const GDP::ExpressionNode *p_expression) {
293
ERR_FAIL_NULL_V(p_expression, String());
294
295
if (p_expression->is_constant) {
296
return _docvalue_from_variant(p_expression->reduced_value);
297
}
298
299
switch (p_expression->type) {
300
case GDP::Node::ARRAY: {
301
const GDP::ArrayNode *array = static_cast<const GDP::ArrayNode *>(p_expression);
302
return array->elements.is_empty() ? "[]" : "[...]";
303
} break;
304
case GDP::Node::CALL: {
305
const GDP::CallNode *call = static_cast<const GDP::CallNode *>(p_expression);
306
if (call->get_callee_type() == GDP::Node::IDENTIFIER) {
307
return call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)");
308
}
309
} break;
310
case GDP::Node::DICTIONARY: {
311
const GDP::DictionaryNode *dict = static_cast<const GDP::DictionaryNode *>(p_expression);
312
return dict->elements.is_empty() ? "{}" : "{...}";
313
} break;
314
case GDP::Node::IDENTIFIER: {
315
const GDP::IdentifierNode *id = static_cast<const GDP::IdentifierNode *>(p_expression);
316
return id->name;
317
} break;
318
default: {
319
// Nothing to do.
320
} break;
321
}
322
323
return "<unknown>";
324
}
325
326
void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
327
p_script->_clear_doc();
328
329
DocData::ClassDoc &doc = p_script->doc;
330
331
doc.is_script_doc = true;
332
333
if (p_script->local_name == StringName()) {
334
// This is an outer unnamed class.
335
doc.name = _get_script_name(p_script->get_script_path());
336
} else {
337
// This is an inner or global outer class.
338
doc.name = p_script->local_name;
339
if (p_script->_owner) {
340
doc.name = p_script->_owner->doc.name + "." + doc.name;
341
}
342
}
343
344
doc.script_path = p_script->get_script_path();
345
346
if (p_script->base.is_valid() && p_script->base->is_valid()) {
347
if (!p_script->base->doc.name.is_empty()) {
348
doc.inherits = p_script->base->doc.name;
349
} else {
350
doc.inherits = p_script->base->get_instance_base_type();
351
}
352
} else if (p_script->native.is_valid()) {
353
doc.inherits = p_script->native->get_name();
354
}
355
356
doc.brief_description = p_class->doc_data.brief;
357
doc.description = p_class->doc_data.description;
358
for (const Pair<String, String> &p : p_class->doc_data.tutorials) {
359
DocData::TutorialDoc td;
360
td.title = p.first;
361
td.link = p.second;
362
doc.tutorials.append(td);
363
}
364
doc.is_deprecated = p_class->doc_data.is_deprecated;
365
doc.deprecated_message = p_class->doc_data.deprecated_message;
366
doc.is_experimental = p_class->doc_data.is_experimental;
367
doc.experimental_message = p_class->doc_data.experimental_message;
368
369
for (const GDP::ClassNode::Member &member : p_class->members) {
370
switch (member.type) {
371
case GDP::ClassNode::Member::CLASS: {
372
const GDP::ClassNode *inner_class = member.m_class;
373
const StringName &class_name = inner_class->identifier->name;
374
375
p_script->member_lines[class_name] = inner_class->start_line;
376
377
// Recursively generate inner class docs.
378
// Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts().
379
GDScriptDocGen::_generate_docs(*p_script->subclasses[class_name], inner_class);
380
} break;
381
382
case GDP::ClassNode::Member::CONSTANT: {
383
const GDP::ConstantNode *m_const = member.constant;
384
const StringName &const_name = member.constant->identifier->name;
385
386
p_script->member_lines[const_name] = m_const->start_line;
387
388
DocData::ConstantDoc const_doc;
389
const_doc.name = const_name;
390
const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value);
391
const_doc.is_value_valid = true;
392
_doctype_from_gdtype(m_const->get_datatype(), const_doc.type, const_doc.enumeration);
393
const_doc.description = m_const->doc_data.description;
394
const_doc.is_deprecated = m_const->doc_data.is_deprecated;
395
const_doc.deprecated_message = m_const->doc_data.deprecated_message;
396
const_doc.is_experimental = m_const->doc_data.is_experimental;
397
const_doc.experimental_message = m_const->doc_data.experimental_message;
398
doc.constants.push_back(const_doc);
399
} break;
400
401
case GDP::ClassNode::Member::FUNCTION: {
402
const GDP::FunctionNode *m_func = member.function;
403
const StringName &func_name = m_func->identifier->name;
404
405
p_script->member_lines[func_name] = m_func->start_line;
406
407
DocData::MethodDoc method_doc;
408
method_doc.name = func_name;
409
method_doc.description = m_func->doc_data.description;
410
method_doc.is_deprecated = m_func->doc_data.is_deprecated;
411
method_doc.deprecated_message = m_func->doc_data.deprecated_message;
412
method_doc.is_experimental = m_func->doc_data.is_experimental;
413
method_doc.experimental_message = m_func->doc_data.experimental_message;
414
415
if (m_func->is_vararg()) {
416
if (!method_doc.qualifiers.is_empty()) {
417
method_doc.qualifiers += " ";
418
}
419
method_doc.qualifiers += "vararg";
420
method_doc.rest_argument.name = m_func->rest_parameter->identifier->name;
421
_doctype_from_gdtype(m_func->rest_parameter->get_datatype(), method_doc.rest_argument.type, method_doc.rest_argument.enumeration);
422
}
423
if (m_func->is_abstract) {
424
if (!method_doc.qualifiers.is_empty()) {
425
method_doc.qualifiers += " ";
426
}
427
method_doc.qualifiers += "abstract";
428
}
429
if (m_func->is_static) {
430
if (!method_doc.qualifiers.is_empty()) {
431
method_doc.qualifiers += " ";
432
}
433
method_doc.qualifiers += "static";
434
}
435
436
if (func_name == "_init") {
437
method_doc.return_type = "void";
438
} else if (m_func->return_type) {
439
// `m_func->return_type->get_datatype()` is a metatype.
440
_doctype_from_gdtype(m_func->get_datatype(), method_doc.return_type, method_doc.return_enum, true);
441
} else if (!m_func->body->has_return) {
442
// If no `return` statement, then return type is `void`, not `Variant`.
443
method_doc.return_type = "void";
444
} else {
445
method_doc.return_type = "Variant";
446
}
447
448
for (const GDP::ParameterNode *p : m_func->parameters) {
449
DocData::ArgumentDoc arg_doc;
450
arg_doc.name = p->identifier->name;
451
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
452
if (p->initializer != nullptr) {
453
arg_doc.default_value = docvalue_from_expression(p->initializer);
454
}
455
method_doc.arguments.push_back(arg_doc);
456
}
457
458
doc.methods.push_back(method_doc);
459
} break;
460
461
case GDP::ClassNode::Member::SIGNAL: {
462
const GDP::SignalNode *m_signal = member.signal;
463
const StringName &signal_name = m_signal->identifier->name;
464
465
p_script->member_lines[signal_name] = m_signal->start_line;
466
467
DocData::MethodDoc signal_doc;
468
signal_doc.name = signal_name;
469
signal_doc.description = m_signal->doc_data.description;
470
signal_doc.is_deprecated = m_signal->doc_data.is_deprecated;
471
signal_doc.deprecated_message = m_signal->doc_data.deprecated_message;
472
signal_doc.is_experimental = m_signal->doc_data.is_experimental;
473
signal_doc.experimental_message = m_signal->doc_data.experimental_message;
474
475
for (const GDP::ParameterNode *p : m_signal->parameters) {
476
DocData::ArgumentDoc arg_doc;
477
arg_doc.name = p->identifier->name;
478
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
479
signal_doc.arguments.push_back(arg_doc);
480
}
481
482
doc.signals.push_back(signal_doc);
483
} break;
484
485
case GDP::ClassNode::Member::VARIABLE: {
486
const GDP::VariableNode *m_var = member.variable;
487
const StringName &var_name = m_var->identifier->name;
488
489
p_script->member_lines[var_name] = m_var->start_line;
490
491
DocData::PropertyDoc prop_doc;
492
prop_doc.name = var_name;
493
prop_doc.description = m_var->doc_data.description;
494
prop_doc.is_deprecated = m_var->doc_data.is_deprecated;
495
prop_doc.deprecated_message = m_var->doc_data.deprecated_message;
496
prop_doc.is_experimental = m_var->doc_data.is_experimental;
497
prop_doc.experimental_message = m_var->doc_data.experimental_message;
498
_doctype_from_gdtype(m_var->get_datatype(), prop_doc.type, prop_doc.enumeration);
499
500
switch (m_var->property) {
501
case GDP::VariableNode::PROP_NONE:
502
break;
503
case GDP::VariableNode::PROP_INLINE:
504
if (m_var->setter != nullptr) {
505
prop_doc.setter = m_var->setter->identifier->name;
506
}
507
if (m_var->getter != nullptr) {
508
prop_doc.getter = m_var->getter->identifier->name;
509
}
510
break;
511
case GDP::VariableNode::PROP_SETGET:
512
if (m_var->setter_pointer != nullptr) {
513
prop_doc.setter = m_var->setter_pointer->name;
514
}
515
if (m_var->getter_pointer != nullptr) {
516
prop_doc.getter = m_var->getter_pointer->name;
517
}
518
break;
519
}
520
521
if (m_var->initializer != nullptr) {
522
prop_doc.default_value = docvalue_from_expression(m_var->initializer);
523
}
524
525
prop_doc.overridden = false;
526
527
doc.properties.push_back(prop_doc);
528
} break;
529
530
case GDP::ClassNode::Member::ENUM: {
531
const GDP::EnumNode *m_enum = member.m_enum;
532
StringName name = m_enum->identifier->name;
533
534
p_script->member_lines[name] = m_enum->start_line;
535
536
DocData::EnumDoc enum_doc;
537
enum_doc.description = m_enum->doc_data.description;
538
enum_doc.is_deprecated = m_enum->doc_data.is_deprecated;
539
enum_doc.deprecated_message = m_enum->doc_data.deprecated_message;
540
enum_doc.is_experimental = m_enum->doc_data.is_experimental;
541
enum_doc.experimental_message = m_enum->doc_data.experimental_message;
542
doc.enums[name] = enum_doc;
543
544
for (const GDP::EnumNode::Value &val : m_enum->values) {
545
DocData::ConstantDoc const_doc;
546
const_doc.name = val.identifier->name;
547
const_doc.value = _docvalue_from_variant(val.value);
548
const_doc.is_value_valid = true;
549
const_doc.type = "int";
550
const_doc.enumeration = name;
551
const_doc.description = val.doc_data.description;
552
const_doc.is_deprecated = val.doc_data.is_deprecated;
553
const_doc.deprecated_message = val.doc_data.deprecated_message;
554
const_doc.is_experimental = val.doc_data.is_experimental;
555
const_doc.experimental_message = val.doc_data.experimental_message;
556
557
doc.constants.push_back(const_doc);
558
}
559
560
} break;
561
562
case GDP::ClassNode::Member::ENUM_VALUE: {
563
const GDP::EnumNode::Value &m_enum_val = member.enum_value;
564
const StringName &name = m_enum_val.identifier->name;
565
566
p_script->member_lines[name] = m_enum_val.identifier->start_line;
567
568
DocData::ConstantDoc const_doc;
569
const_doc.name = name;
570
const_doc.value = _docvalue_from_variant(m_enum_val.value);
571
const_doc.is_value_valid = true;
572
const_doc.type = "int";
573
const_doc.enumeration = "@unnamed_enums";
574
const_doc.description = m_enum_val.doc_data.description;
575
const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated;
576
const_doc.deprecated_message = m_enum_val.doc_data.deprecated_message;
577
const_doc.is_experimental = m_enum_val.doc_data.is_experimental;
578
const_doc.experimental_message = m_enum_val.doc_data.experimental_message;
579
doc.constants.push_back(const_doc);
580
} break;
581
582
default:
583
break;
584
}
585
}
586
587
// Add doc to the outer-most class.
588
p_script->_add_doc(doc);
589
}
590
591
void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
592
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
593
if (E.value.is_singleton) {
594
singletons[E.value.path] = E.key;
595
}
596
}
597
_generate_docs(p_script, p_class);
598
singletons.clear();
599
}
600
601
// This method is needed for the editor, since during autocompletion the script is not compiled, only analyzed.
602
void GDScriptDocGen::doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) {
603
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
604
if (E.value.is_singleton) {
605
singletons[E.value.path] = E.key;
606
}
607
}
608
_doctype_from_gdtype(p_gdtype, r_type, r_enum, p_is_return);
609
singletons.clear();
610
}
611
612