Remove underscore hacks
Way less cruft. :) Co-authored-by: Ignacio Roldán Etcheverry <neikeq@users.noreply.github.com>
This commit is contained in:
parent
81f7d1890b
commit
5161c97c9c
12 changed files with 38 additions and 132 deletions
|
@ -1031,9 +1031,6 @@ def make_enum(t, state): # type: (str, State) -> str
|
|||
if c in state.classes and e not in state.classes[c].enums:
|
||||
c = "@GlobalScope"
|
||||
|
||||
if not c in state.classes and c.startswith("_"):
|
||||
c = c[1:] # Remove the underscore prefix
|
||||
|
||||
if c in state.classes and e in state.classes[c].enums:
|
||||
return ":ref:`{0}<enum_{1}_{0}>`".format(e, c)
|
||||
|
||||
|
|
|
@ -245,9 +245,6 @@ void DocTools::generate(bool p_basic_types) {
|
|||
}
|
||||
|
||||
String cname = name;
|
||||
if (cname.begins_with("_")) { //proxy class
|
||||
cname = cname.substr(1, name.length());
|
||||
}
|
||||
|
||||
class_list[cname] = DocData::ClassDoc();
|
||||
DocData::ClassDoc &c = class_list[cname];
|
||||
|
@ -740,9 +737,6 @@ void DocTools::generate(bool p_basic_types) {
|
|||
while (String(ClassDB::get_parent_class(pd.type)) != "Object") {
|
||||
pd.type = ClassDB::get_parent_class(pd.type);
|
||||
}
|
||||
if (pd.type.begins_with("_")) {
|
||||
pd.type = pd.type.substr(1, pd.type.length());
|
||||
}
|
||||
c.properties.push_back(pd);
|
||||
}
|
||||
|
||||
|
|
|
@ -104,11 +104,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
|
|||
List<StringName> types;
|
||||
ClassDB::get_class_list(&types);
|
||||
for (const StringName &E : types) {
|
||||
String n = E;
|
||||
if (n.begins_with("_")) {
|
||||
n = n.substr(1, n.length());
|
||||
}
|
||||
highlighter->add_keyword_color(n, type_color);
|
||||
highlighter->add_keyword_color(E, type_color);
|
||||
}
|
||||
|
||||
/* User types. */
|
||||
|
|
|
@ -756,8 +756,6 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c
|
|||
} else if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK) {
|
||||
_goto_line(p_row);
|
||||
|
||||
result.class_name = result.class_name.trim_prefix("_");
|
||||
|
||||
switch (result.type) {
|
||||
case ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION: {
|
||||
if (result.script.is_valid()) {
|
||||
|
|
|
@ -242,13 +242,9 @@ List<ClassAPI> generate_c_api_classes() {
|
|||
class_api.class_name = class_name;
|
||||
class_api.super_class_name = ClassDB::get_parent_class(class_name);
|
||||
{
|
||||
String name = class_name;
|
||||
if (name.begins_with("_")) {
|
||||
name.remove(0);
|
||||
}
|
||||
class_api.is_singleton = Engine::get_singleton()->has_singleton(name);
|
||||
class_api.is_singleton = Engine::get_singleton()->has_singleton(class_name);
|
||||
if (class_api.is_singleton) {
|
||||
class_api.singleton_name = name;
|
||||
class_api.singleton_name = class_name;
|
||||
}
|
||||
}
|
||||
class_api.is_instantiable = !class_api.is_singleton && ClassDB::can_instantiate(class_name);
|
||||
|
|
|
@ -459,11 +459,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
|
|||
List<StringName> types;
|
||||
ClassDB::get_class_list(&types);
|
||||
for (const StringName &E : types) {
|
||||
String n = E;
|
||||
if (n.begins_with("_")) {
|
||||
n = n.substr(1, n.length());
|
||||
}
|
||||
keywords[n] = types_color;
|
||||
keywords[E] = types_color;
|
||||
}
|
||||
|
||||
/* User types. */
|
||||
|
|
|
@ -1644,16 +1644,11 @@ void GDScriptLanguage::init() {
|
|||
List<StringName> class_list;
|
||||
ClassDB::get_class_list(&class_list);
|
||||
for (const StringName &n : class_list) {
|
||||
String s = String(n);
|
||||
if (s.begins_with("_")) {
|
||||
s = s.substr(1, s.length());
|
||||
}
|
||||
|
||||
if (globals.has(s)) {
|
||||
if (globals.has(n)) {
|
||||
continue;
|
||||
}
|
||||
Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n));
|
||||
_add_global(s, nc);
|
||||
_add_global(n, nc);
|
||||
}
|
||||
|
||||
//populate singletons
|
||||
|
|
|
@ -112,11 +112,10 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native
|
|||
type.is_meta_type = true;
|
||||
|
||||
List<StringName> enum_values;
|
||||
StringName real_native_name = GDScriptParser::get_real_class_name(p_native_class);
|
||||
ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values);
|
||||
ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values);
|
||||
|
||||
for (const StringName &E : enum_values) {
|
||||
type.enum_values[E] = ClassDB::get_integer_constant(real_native_name, E);
|
||||
type.enum_values[E] = ClassDB::get_integer_constant(p_native_class, E);
|
||||
}
|
||||
|
||||
return type;
|
||||
|
@ -229,7 +228,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
|
|||
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
|
||||
return err;
|
||||
}
|
||||
} else if (class_exists(name) && ClassDB::can_instantiate(GDScriptParser::get_real_class_name(name))) {
|
||||
} else if (class_exists(name) && ClassDB::can_instantiate(name)) {
|
||||
base.kind = GDScriptParser::DataType::NATIVE;
|
||||
base.native_type = name;
|
||||
} else {
|
||||
|
@ -406,7 +405,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
|||
return GDScriptParser::DataType();
|
||||
}
|
||||
result = ref->get_parser()->head->get_datatype();
|
||||
} else if (ClassDB::has_enum(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), first)) {
|
||||
} else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
|
||||
// Native enum in current class.
|
||||
result = make_native_enum_type(parser->current_class->base_type.native_type, first);
|
||||
} else {
|
||||
|
@ -469,7 +468,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
|||
}
|
||||
} else if (result.kind == GDScriptParser::DataType::NATIVE) {
|
||||
// Only enums allowed for native.
|
||||
if (ClassDB::has_enum(GDScriptParser::get_real_class_name(result.native_type), p_type->type_chain[1]->name)) {
|
||||
if (ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) {
|
||||
if (p_type->type_chain.size() > 2) {
|
||||
push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]);
|
||||
} else {
|
||||
|
@ -2252,7 +2251,7 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node)
|
|||
result.native_type = "Node";
|
||||
result.builtin_type = Variant::OBJECT;
|
||||
|
||||
if (!ClassDB::is_parent_class(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) {
|
||||
if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) {
|
||||
push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node);
|
||||
} else if (!lambda_stack.is_empty()) {
|
||||
push_error(R"*(Cannot use shorthand "get_node()" notation ("$") inside a lambda. Use a captured variable instead.)*", p_get_node);
|
||||
|
@ -2421,7 +2420,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
|
|||
}
|
||||
|
||||
// Check native members.
|
||||
const StringName &native = GDScriptParser::get_real_class_name(base.native_type);
|
||||
const StringName &native = base.native_type;
|
||||
|
||||
if (class_exists(native)) {
|
||||
PropertyInfo prop_info;
|
||||
|
@ -3303,10 +3302,8 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD
|
|||
return true;
|
||||
}
|
||||
|
||||
StringName real_native = GDScriptParser::get_real_class_name(base_native);
|
||||
|
||||
MethodInfo info;
|
||||
if (ClassDB::get_method_info(real_native, function_name, &info)) {
|
||||
if (ClassDB::get_method_info(base_native, function_name, &info)) {
|
||||
return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
|
||||
}
|
||||
|
||||
|
@ -3398,24 +3395,23 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con
|
|||
|
||||
StringName parent = base_native;
|
||||
while (parent != StringName()) {
|
||||
StringName real_class_name = GDScriptParser::get_real_class_name(parent);
|
||||
if (ClassDB::has_method(real_class_name, name, true)) {
|
||||
if (ClassDB::has_method(parent, name, true)) {
|
||||
parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent);
|
||||
return true;
|
||||
} else if (ClassDB::has_signal(real_class_name, name, true)) {
|
||||
} else if (ClassDB::has_signal(parent, name, true)) {
|
||||
parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "signal", parent);
|
||||
return true;
|
||||
} else if (ClassDB::has_property(real_class_name, name, true)) {
|
||||
} else if (ClassDB::has_property(parent, name, true)) {
|
||||
parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "property", parent);
|
||||
return true;
|
||||
} else if (ClassDB::has_integer_constant(real_class_name, name, true)) {
|
||||
} else if (ClassDB::has_integer_constant(parent, name, true)) {
|
||||
parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "constant", parent);
|
||||
return true;
|
||||
} else if (ClassDB::has_enum(real_class_name, name, true)) {
|
||||
} else if (ClassDB::has_enum(parent, name, true)) {
|
||||
parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "enum", parent);
|
||||
return true;
|
||||
}
|
||||
parent = ClassDB::get_parent_class(real_class_name);
|
||||
parent = ClassDB::get_parent_class(parent);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -3560,16 +3556,12 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
|
|||
break; // Already solved before.
|
||||
}
|
||||
|
||||
// Get underscore-prefixed version for some classes.
|
||||
src_native = GDScriptParser::get_real_class_name(src_native);
|
||||
|
||||
switch (p_target.kind) {
|
||||
case GDScriptParser::DataType::NATIVE: {
|
||||
if (p_target.is_meta_type) {
|
||||
return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static());
|
||||
}
|
||||
StringName tgt_native = GDScriptParser::get_real_class_name(p_target.native_type);
|
||||
return ClassDB::is_parent_class(src_native, tgt_native);
|
||||
return ClassDB::is_parent_class(src_native, p_target.native_type);
|
||||
}
|
||||
case GDScriptParser::DataType::SCRIPT:
|
||||
if (p_target.is_meta_type) {
|
||||
|
@ -3618,8 +3610,7 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) {
|
|||
}
|
||||
|
||||
bool GDScriptAnalyzer::class_exists(const StringName &p_class) const {
|
||||
StringName real_name = GDScriptParser::get_real_class_name(p_class);
|
||||
return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name);
|
||||
return ClassDB::class_exists(p_class) && ClassDB::is_class_exposed(p_class);
|
||||
}
|
||||
|
||||
Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
|
||||
|
|
|
@ -912,7 +912,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
|
|||
}
|
||||
} break;
|
||||
case GDScriptParser::DataType::NATIVE: {
|
||||
StringName type = GDScriptParser::get_real_class_name(base_type.native_type);
|
||||
StringName type = base_type.native_type;
|
||||
if (!ClassDB::class_exists(type)) {
|
||||
return;
|
||||
}
|
||||
|
@ -1326,10 +1326,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
|
|||
native_type.kind = GDScriptParser::DataType::NATIVE;
|
||||
native_type.native_type = native_type.script_type->get_instance_base_type();
|
||||
if (!ClassDB::class_exists(native_type.native_type)) {
|
||||
native_type.native_type = String("_") + native_type.native_type;
|
||||
if (!ClassDB::class_exists(native_type.native_type)) {
|
||||
native_type.kind = GDScriptParser::DataType::UNRESOLVED;
|
||||
}
|
||||
native_type.kind = GDScriptParser::DataType::UNRESOLVED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1765,9 +1762,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
|
|||
base_type = GDScriptParser::DataType();
|
||||
break;
|
||||
}
|
||||
StringName real_native = GDScriptParser::get_real_class_name(base_type.native_type);
|
||||
MethodInfo info;
|
||||
if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) {
|
||||
if (ClassDB::get_method_info(base_type.native_type, p_context.current_function->identifier->name, &info)) {
|
||||
for (const PropertyInfo &E : info.arguments) {
|
||||
if (E.name == p_identifier) {
|
||||
r_type = _type_from_property(E);
|
||||
|
@ -1836,13 +1832,12 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
|
|||
}
|
||||
|
||||
// Check ClassDB.
|
||||
StringName class_name = GDScriptParser::get_real_class_name(p_identifier);
|
||||
if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) {
|
||||
if (ClassDB::class_exists(p_identifier) && ClassDB::is_class_exposed(p_identifier)) {
|
||||
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||
r_type.type.kind = GDScriptParser::DataType::NATIVE;
|
||||
r_type.type.native_type = p_identifier;
|
||||
r_type.type.is_constant = true;
|
||||
r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(class_name);
|
||||
r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(p_identifier);
|
||||
r_type.value = Variant();
|
||||
}
|
||||
|
||||
|
@ -1951,7 +1946,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
|
|||
}
|
||||
} break;
|
||||
case GDScriptParser::DataType::NATIVE: {
|
||||
StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type);
|
||||
StringName class_name = base_type.native_type;
|
||||
if (!ClassDB::class_exists(class_name)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2113,11 +2108,10 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex
|
|||
}
|
||||
} break;
|
||||
case GDScriptParser::DataType::NATIVE: {
|
||||
StringName native = GDScriptParser::get_real_class_name(base_type.native_type);
|
||||
if (!ClassDB::class_exists(native)) {
|
||||
if (!ClassDB::class_exists(base_type.native_type)) {
|
||||
return false;
|
||||
}
|
||||
MethodBind *mb = ClassDB::get_method(native, p_method);
|
||||
MethodBind *mb = ClassDB::get_method(base_type.native_type, p_method);
|
||||
if (mb) {
|
||||
r_type = _type_from_property(mb->get_return_info());
|
||||
return true;
|
||||
|
@ -2209,7 +2203,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
|
|||
base_type = base_type.class_type->base_type;
|
||||
} break;
|
||||
case GDScriptParser::DataType::NATIVE: {
|
||||
StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type);
|
||||
StringName class_name = base_type.native_type;
|
||||
if (!ClassDB::class_exists(class_name)) {
|
||||
base_type.kind = GDScriptParser::DataType::UNRESOLVED;
|
||||
break;
|
||||
|
@ -2592,7 +2586,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
|
|||
break;
|
||||
}
|
||||
|
||||
StringName class_name = GDScriptParser::get_real_class_name(native_type.native_type);
|
||||
StringName class_name = native_type.native_type;
|
||||
if (!ClassDB::class_exists(class_name)) {
|
||||
break;
|
||||
}
|
||||
|
@ -2821,7 +2815,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
|
|||
}
|
||||
} break;
|
||||
case GDScriptParser::DataType::NATIVE: {
|
||||
StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type);
|
||||
StringName class_name = base_type.native_type;
|
||||
if (!ClassDB::class_exists(class_name)) {
|
||||
base_type.kind = GDScriptParser::DataType::UNRESOLVED;
|
||||
break;
|
||||
|
@ -2873,11 +2867,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
|
|||
|
||||
StringName parent = ClassDB::get_parent_class(class_name);
|
||||
if (parent != StringName()) {
|
||||
if (String(parent).begins_with("_")) {
|
||||
base_type.native_type = String(parent).substr(1);
|
||||
} else {
|
||||
base_type.native_type = parent;
|
||||
}
|
||||
base_type.native_type = parent;
|
||||
} else {
|
||||
base_type.kind = GDScriptParser::DataType::UNRESOLVED;
|
||||
}
|
||||
|
@ -2931,18 +2921,11 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
|
|||
}
|
||||
|
||||
::Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) {
|
||||
//before parsing, try the usual stuff
|
||||
// Before parsing, try the usual stuff
|
||||
if (ClassDB::class_exists(p_symbol)) {
|
||||
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
|
||||
r_result.class_name = p_symbol;
|
||||
return OK;
|
||||
} else {
|
||||
String under_prefix = "_" + p_symbol;
|
||||
if (ClassDB::class_exists(under_prefix)) {
|
||||
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
|
||||
r_result.class_name = p_symbol;
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
|
|
|
@ -111,11 +111,7 @@ public:
|
|||
}
|
||||
|
||||
if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) {
|
||||
// Try with underscore prefix
|
||||
StringName underscore_native_type = "_" + native_type;
|
||||
if (!ClassDB::is_parent_class(obj->get_class_name(), underscore_native_type)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} break;
|
||||
|
|
|
@ -94,43 +94,8 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
|
|||
return Variant::VARIANT_MAX;
|
||||
}
|
||||
|
||||
// TODO: Move this to a central location (maybe core?).
|
||||
static HashMap<StringName, StringName> underscore_map;
|
||||
static const char *underscore_classes[] = {
|
||||
"ClassDB",
|
||||
"Directory",
|
||||
"Engine",
|
||||
"File",
|
||||
"Geometry",
|
||||
"GodotSharp",
|
||||
"JSON",
|
||||
"Marshalls",
|
||||
"Mutex",
|
||||
"OS",
|
||||
"ResourceLoader",
|
||||
"ResourceSaver",
|
||||
"Semaphore",
|
||||
"Thread",
|
||||
"VisualScriptEditor",
|
||||
nullptr,
|
||||
};
|
||||
StringName GDScriptParser::get_real_class_name(const StringName &p_source) {
|
||||
if (underscore_map.is_empty()) {
|
||||
const char **class_name = underscore_classes;
|
||||
while (*class_name != nullptr) {
|
||||
underscore_map[*class_name] = String("_") + *class_name;
|
||||
class_name++;
|
||||
}
|
||||
}
|
||||
if (underscore_map.has(p_source)) {
|
||||
return underscore_map[p_source];
|
||||
}
|
||||
return p_source;
|
||||
}
|
||||
|
||||
void GDScriptParser::cleanup() {
|
||||
builtin_types.clear();
|
||||
underscore_map.clear();
|
||||
}
|
||||
|
||||
void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const {
|
||||
|
@ -3363,10 +3328,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
|
|||
variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type);
|
||||
break;
|
||||
case GDScriptParser::DataType::NATIVE:
|
||||
if (ClassDB::is_parent_class(get_real_class_name(export_type.native_type), "Resource")) {
|
||||
if (ClassDB::is_parent_class(export_type.native_type, "Resource")) {
|
||||
variable->export_info.type = Variant::OBJECT;
|
||||
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
||||
variable->export_info.hint_string = get_real_class_name(export_type.native_type);
|
||||
variable->export_info.hint_string = export_type.native_type;
|
||||
} else {
|
||||
push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable);
|
||||
return false;
|
||||
|
|
|
@ -1399,7 +1399,6 @@ public:
|
|||
ClassNode *get_tree() const { return head; }
|
||||
bool is_tool() const { return _is_tool; }
|
||||
static Variant::Type get_builtin_type(const StringName &p_type);
|
||||
static StringName get_real_class_name(const StringName &p_source);
|
||||
|
||||
CompletionContext get_completion_context() const { return completion_context; }
|
||||
CompletionCall get_completion_call() const { return completion_call; }
|
||||
|
|
Loading…
Reference in a new issue