diff --git a/example/scenes/tests_runner.tscn b/example/scenes/tests_runner.tscn index a61337c..9e8a20c 100644 --- a/example/scenes/tests_runner.tscn +++ b/example/scenes/tests_runner.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=4 format=3 uid="uid://5uvnhmsmksj8"] +[gd_scene load_steps=5 format=3 uid="uid://5uvnhmsmksj8"] [ext_resource type="Script" path="res://scripts/tests/tests_runner.gd" id="1_runner"] [ext_resource type="Script" path="res://scripts/tests/signal_test.ts" id="2_signalts"] [ext_resource type="Script" path="res://scripts/tests/runtime_integration_test.ts" id="3_runtimets"] +[ext_resource type="Script" path="res://scripts/tests/runtime_inheritance_test.ts" id="4_inheritancets"] [node name="TestsRunner" type="Node" unique_id=1924444567] script = ExtResource("1_runner") @@ -12,3 +13,6 @@ script = ExtResource("2_signalts") [node name="RuntimeIntegrationTest" type="Node" parent="." unique_id=1931083819] script = ExtResource("3_runtimets") + +[node name="RuntimeInheritanceTest" type="Node" parent="."] +script = ExtResource("4_inheritancets") diff --git a/example/scripts/tests/runtime_inheritance_external_base.ts b/example/scripts/tests/runtime_inheritance_external_base.ts new file mode 100644 index 0000000..ea22fd9 --- /dev/null +++ b/example/scripts/tests/runtime_inheritance_external_base.ts @@ -0,0 +1,31 @@ +import RuntimeIntegrationBase from "./runtime_base_test.js"; + +export interface ExternalRow { + title: string; + enabled: boolean; +} + +interface ExternalNestedSettings { + count: number; +} + +interface ExternalSettings { + label: string; + nested: ExternalNestedSettings; +} + +export abstract class RuntimeInheritanceExternalBase extends RuntimeIntegrationBase { + @Export() + external_count: number = 19; + + @Export() + external_dynamic: number = 18; + + @Export() + external_rows: ExternalRow[] = []; + + @Export() + external_settings: ExternalSettings = { label: "external", nested: { count: 4 } }; +} + +export default RuntimeInheritanceExternalBase; diff --git a/example/scripts/tests/runtime_inheritance_test.ts b/example/scripts/tests/runtime_inheritance_test.ts new file mode 100644 index 0000000..360d011 --- /dev/null +++ b/example/scripts/tests/runtime_inheritance_test.ts @@ -0,0 +1,209 @@ +import nodeAssert from "node:assert/strict"; +import { ClassDB, GodotObject, PropertyHint, Resource, type VariantArgument } from "godot"; +import * as InheritanceBaseModule from "./runtime_inheritance_external_base.js"; + +interface InheritedRow { + label: string; + count: number; +} + +interface DerivedRow { + label: string; + count: number; + tag: string; +} + +interface InheritedSettings { + label: string; + count: number; +} + +function Observe(_target: object, _property: string): void {} +function ExportIgnored(_target: object, _property: string): void {} + +function computedDefault(value: number): number { + return value; +} + +abstract class RuntimeInheritanceAbstractRoot extends InheritanceBaseModule.RuntimeInheritanceExternalBase { + @Export() + abstract_root_value: number = 20; +} + +class RuntimeInheritanceGrandparent extends RuntimeInheritanceAbstractRoot { + @Export({ hint: PropertyHint.PROPERTY_HINT_RANGE, hint_string: "0,100,1" }) + overridden_count: number = 11; + + @Export() + initializer_override: number = 12; + + @Export() + leaf_override: number = 13; + + @Export() + opaque_count: number = 14; +} + +export abstract class RuntimeInheritanceParent extends RuntimeInheritanceGrandparent { + static exports: ExportMap = { + inherited_static: { type: "int", hint: 1, hint_string: "0,20,1", default: 7 }, + inherited_static_initializer: { type: "int" }, + }; + + inherited_static: number = 7; + inherited_static_initializer: number = 9; + + @Export({ hint: PropertyHint.PROPERTY_HINT_RANGE, hint_string: "0,50,1" }) + override overridden_count: number = 23; + + override initializer_override: number = 24; + override opaque_count: number = computedDefault(44); + + @Export() + override leaf_override: number = 25; + + @Observe + @Export(PropertyHint.PROPERTY_HINT_RANGE, "0,10,0.5") + inherited_range: number = 5; + + @Export({ hint: PropertyHint.PROPERTY_HINT_RESOURCE_TYPE, hint_string: "Resource" }) + inherited_resource: Resource | null = null; + + @Export() + inherited_rows: InheritedRow[] = []; + + @Export() + overridden_rows: InheritedRow[] = []; + + @Export() + inherited_settings: InheritedSettings = { label: "parent", count: 6 }; + + @ExportIgnored + inherited_not_exported: number = 5; +} + +export default class RuntimeInheritanceTest extends RuntimeInheritanceParent { + static signals = { + test_finished: [ + { name: "success", type: "bool" }, + { name: "message", type: "String" }, + ], + } as const; + + static exports: ExportMap = { + direct_static: { type: "int", default: 3 }, + inherited_count: { type: "float", hint: 1, hint_string: "0,100,1" }, + inherited_static: { type: "int", hint: 1, hint_string: "0,30,1" }, + inherited_rows: { type: "Array", hint: 31, hint_string: "24/17:TypeScriptInterfaceResource" }, + external_rows: { type: "Array", hint: 31, hint_string: "24/17:TypeScriptInterfaceResource" }, + explicit_default: { type: "float", default: 99 }, + }; + + direct_static: number = 3; + explicit_default: number = computedDefault(27); + override leaf_override: number = 37; + override external_dynamic: number = computedDefault(38); + override inherited_settings: InheritedSettings = { label: "leaf", count: 8 }; + + @Export() + override overridden_rows: DerivedRow[] = []; + + @ExportIgnored + direct_not_exported: number = 6; + + run_test(): void { + try { + const properties = this.get_property_list() as Array<{ + name: VariantArgument; + type: VariantArgument; + hint: VariantArgument; + hint_string: VariantArgument; + usage: VariantArgument; + }>; + const property = (name: string) => { + const matches = properties.filter(entry => String(entry.name) === name); + nodeAssert.equal(matches.length, 1, `${name} must be exported exactly once`); + return matches[0]; + }; + const assertHint = (name: string, hint: number | bigint, hintString: string) => { + const metadata = property(name); + nodeAssert.equal(Number(metadata.hint), Number(hint), `${name} hint`); + nodeAssert.equal(String(metadata.hint_string), hintString, `${name} hint string`); + }; + assertHint("inherited_range", PropertyHint.PROPERTY_HINT_RANGE, "0,10,0.5"); + assertHint("overridden_count", PropertyHint.PROPERTY_HINT_RANGE, "0,50,1"); + assertHint("inherited_static", PropertyHint.PROPERTY_HINT_RANGE, "0,30,1"); + assertHint("inherited_count", PropertyHint.PROPERTY_HINT_RANGE, "0,100,1"); + assertHint("inherited_label", 20, "base label"); + assertHint("inherited_resource", PropertyHint.PROPERTY_HINT_RESOURCE_TYPE, "Resource"); + nodeAssert.equal(Number(property("inherited_resource").type), 24); + assertHint("inherited_rows", PropertyHint.PROPERTY_HINT_ARRAY_TYPE, "24/17:TypeScriptInterfaceResource"); + assertHint("external_rows", PropertyHint.PROPERTY_HINT_ARRAY_TYPE, "24/17:TypeScriptInterfaceResource"); + assertHint("overridden_rows", PropertyHint.PROPERTY_HINT_ARRAY_TYPE, "24/17:TypeScriptInterfaceResource"); + const propertyNames = properties.map(entry => String(entry.name)); + nodeAssert.ok(!propertyNames.includes("inherited_not_exported")); + nodeAssert.ok(!propertyNames.includes("direct_not_exported")); + nodeAssert.ok(propertyNames.indexOf("direct_static") < propertyNames.indexOf("inherited_range")); + nodeAssert.ok(propertyNames.indexOf("inherited_range") < propertyNames.indexOf("external_count")); + + for (const [name, expected] of Object.entries({ + overridden_count: 23, + initializer_override: 24, + leaf_override: 37, + inherited_range: 5, + inherited_static: 7, + inherited_static_initializer: 9, + direct_static: 3, + inherited_count: 11, + external_count: 19, + abstract_root_value: 20, + })) { + property(name); + nodeAssert.equal(this.get(name), expected, `${name} actual value`); + nodeAssert.equal(this.property_can_revert(name), true, `${name} can revert`); + nodeAssert.equal(this.property_get_revert(name), expected, `${name} default`); + } + nodeAssert.equal(this.property_get_revert("inherited_resource"), null); + nodeAssert.deepEqual(this.property_get_revert("inherited_rows"), []); + nodeAssert.deepEqual(this.property_get_revert("external_rows"), []); + nodeAssert.deepEqual(this.property_get_revert("overridden_rows"), []); + for (const [name, actual] of [["opaque_count", 44], ["external_dynamic", 38]] as const) { + property(name); + nodeAssert.equal(this.get(name), actual); + nodeAssert.equal(this.property_can_revert(name), false, `${name} must not use an ancestor's stale default`); + } + nodeAssert.equal(this.get("explicit_default"), 27); + nodeAssert.equal(this.property_get_revert("explicit_default"), 99); + property("inherited_settings::label"); + property("inherited_settings::count"); + nodeAssert.equal(this.get("inherited_settings::label"), "leaf"); + nodeAssert.equal(this.property_get_revert("inherited_settings::label"), "leaf"); + nodeAssert.equal(this.property_get_revert("inherited_settings::count"), 8); + property("external_settings::nested::count"); + nodeAssert.equal(this.property_get_revert("external_settings::nested::count"), 4); + for (const [name, prefix, usage] of [ + ["InheritedSettings", "inherited_settings::", 64], + ["ExternalSettings", "external_settings::", 64], + ["ExternalNestedSettings", "external_settings::nested::", 256], + ] as const) { + const group = property(name); + nodeAssert.equal(Number(group.usage), usage); + nodeAssert.equal(String(group.hint_string), prefix); + } + + for (const [name, fieldName] of [["inherited_rows", "label"], ["external_rows", "title"], ["overridden_rows", "tag"]] as const) { + const row = ClassDB.instantiate("TypeScriptInterfaceResource") as GodotObject; + this.set(name, [row]); + const fields = row.get_property_list() as Array<{ name: VariantArgument }>; + nodeAssert.ok(fields.some(field => String(field.name) === fieldName), `${name} schema missing`); + row.set(fieldName, "inherited"); + nodeAssert.equal(row.get(fieldName), "inherited"); + this.set(name, []); + } + this.emit_signal("test_finished", true, "runtime inheritance test passed"); + } catch (error) { + const message = error instanceof Error ? error.stack ?? error.message : String(error); + this.emit_signal("test_finished", false, message); + } + } +} diff --git a/example/scripts/tests/runtime_integration_test.ts b/example/scripts/tests/runtime_integration_test.ts index b2d0c2a..346b172 100644 --- a/example/scripts/tests/runtime_integration_test.ts +++ b/example/scripts/tests/runtime_integration_test.ts @@ -57,7 +57,15 @@ type GodeLoadEsm = (filepath: string, source: string) => Promise<{ [key: string] type GodeCompileEsm = (source: string, filepath: string) => Promise<{ [key: string]: VariantArgument }>; type RuntimeEditorStringEnum = "idle" | 'running' | null | "done"; -class RuntimeIntegrationTest extends RuntimeBaseModule.RuntimeIntegrationBase { +export abstract class RuntimeSameFileExportBase extends RuntimeBaseModule.RuntimeIntegrationBase { + @Export() + same_file_inherited_label: string = "same-file-base"; + + @Export() + same_file_inherited_count: number = 23; +} + +class RuntimeIntegrationTest extends RuntimeSameFileExportBase { static signals = { test_finished: [ { name: "success", type: "bool" }, @@ -357,6 +365,7 @@ class RuntimeIntegrationTest extends RuntimeBaseModule.RuntimeIntegrationBase { assert(this.property_can_revert("label"), "exported string property cannot revert"); assert(this.property_can_revert("inherited_label"), `inherited exported string property cannot revert; properties: ${propertyNames.join(", ")}`); + assert(this.property_can_revert("same_file_inherited_label"), `same-file inherited exported string property cannot revert; properties: ${propertyNames.join(", ")}`); assert(this.property_can_revert("spawn_offset"), "exported Vector3 property cannot revert"); assert(this.property_can_revert("resource_slot"), "exported Resource property cannot revert"); assert(this.property_can_revert("static_resource_default_first"), "static exported Resource property cannot revert"); @@ -370,6 +379,8 @@ class RuntimeIntegrationTest extends RuntimeBaseModule.RuntimeIntegrationBase { nodeAssert.equal(this.property_get_revert("label"), "runtime"); nodeAssert.equal(this.property_get_revert("inherited_label"), "base-runtime"); nodeAssert.equal(this.property_get_revert("inherited_count"), 11); + nodeAssert.equal(this.property_get_revert("same_file_inherited_label"), "same-file-base"); + nodeAssert.equal(this.property_get_revert("same_file_inherited_count"), 23); nodeAssert.equal(this.property_get_revert("resource_slot"), null); nodeAssert.equal(this.property_get_revert("static_resource_default_first"), null); nodeAssert.deepEqual(this.property_get_revert("static_number_array"), [3]); @@ -438,6 +449,8 @@ class RuntimeIntegrationTest extends RuntimeBaseModule.RuntimeIntegrationBase { "static_range_namespace", "inherited_label", "inherited_count", + "same_file_inherited_label", + "same_file_inherited_count", ]; for (const name of expectedExportProperties) { assert(propertyNames.includes(name), `exported property missing from property list: ${name}`); diff --git a/include/script/typescript_script.h b/include/script/typescript_script.h index e5643fa..7399c83 100644 --- a/include/script/typescript_script.h +++ b/include/script/typescript_script.h @@ -70,6 +70,7 @@ class TypeScriptScript : public godot::ScriptExtension { godot::Variant call_static_method(const godot::StringName &p_method, const godot::Variant **p_args, int32_t p_argcount, GDExtensionCallError &r_error) const; const godot::HashMap &get_exported_properties() const { return properties; } const godot::Vector &get_property_list_ordered() const { return property_list; } + const godot::HashMap &get_interface_array_schemas() const { return interface_array_schemas; } const godot::HashMap &get_property_defaults() const { return property_defaults; } godot::StringName get_base_class_name() const { compile(); diff --git a/src/script/typescript_language.cpp b/src/script/typescript_language.cpp index 1a3736f..30dffe9 100644 --- a/src/script/typescript_language.cpp +++ b/src/script/typescript_language.cpp @@ -277,6 +277,11 @@ StringName default_exported_class_name_from_statement(TSNode export_statement, c return StringName(); } +bool is_class_declaration_node(TSNode node) { + const char *node_type = ts_node_type(node); + return strcmp(node_type, "class_declaration") == 0 || strcmp(node_type, "abstract_class_declaration") == 0; +} + TSNode find_class_declaration_by_name(TSNode root_node, uint32_t child_count, const std::string &source, const StringName &name) { if (name.is_empty()) { return {}; @@ -284,7 +289,7 @@ TSNode find_class_declaration_by_name(TSNode root_node, uint32_t child_count, co for (uint32_t i = 0; i < child_count; i++) { TSNode child = ts_node_child(root_node, i); - if (strcmp(ts_node_type(child), "class_declaration") == 0) { + if (is_class_declaration_node(child)) { if (class_name_from_class_node(child, source) == name) { return child; } @@ -295,7 +300,7 @@ TSNode find_class_declaration_by_name(TSNode root_node, uint32_t child_count, co } for (uint32_t j = 0; j < ts_node_child_count(child); j++) { TSNode exported_child = ts_node_child(child, j); - if (strcmp(ts_node_type(exported_child), "class_declaration") == 0 && class_name_from_class_node(exported_child, source) == name) { + if (is_class_declaration_node(exported_child) && class_name_from_class_node(exported_child, source) == name) { return exported_child; } } @@ -455,7 +460,7 @@ TSNode find_default_class(TSNode root_node, uint32_t child_count, const std::str TSNode node = ts_node_child(child, j); if (strcmp(ts_node_type(node), "default") == 0) { is_default = true; - } else if (strcmp(ts_node_type(node), "class_declaration") == 0 && is_default) { + } else if (is_class_declaration_node(node) && is_default) { return node; } } diff --git a/src/script/typescript_script.cpp b/src/script/typescript_script.cpp index 6c9cd18..59b320e 100644 --- a/src/script/typescript_script.cpp +++ b/src/script/typescript_script.cpp @@ -279,6 +279,11 @@ static StringName default_exported_class_name_from_statement(TSNode export_state return StringName(); } +static bool is_class_declaration_node(TSNode node) { + const char *node_type = ts_node_type(node); + return strcmp(node_type, "class_declaration") == 0 || strcmp(node_type, "abstract_class_declaration") == 0; +} + static TSNode find_class_declaration_by_name(TSNode root_node, uint32_t child_count, const std::string &source, const StringName &name) { if (name.is_empty()) { return {}; @@ -286,7 +291,7 @@ static TSNode find_class_declaration_by_name(TSNode root_node, uint32_t child_co for (uint32_t i = 0; i < child_count; i++) { TSNode child = ts_node_child(root_node, i); - if (strcmp(ts_node_type(child), "class_declaration") == 0) { + if (is_class_declaration_node(child)) { if (class_name_from_class_node(child, source) == name) { return child; } @@ -297,7 +302,7 @@ static TSNode find_class_declaration_by_name(TSNode root_node, uint32_t child_co } for (uint32_t j = 0; j < ts_node_child_count(child); j++) { TSNode exported_child = ts_node_child(child, j); - if (strcmp(ts_node_type(exported_child), "class_declaration") == 0 && class_name_from_class_node(exported_child, source) == name) { + if (is_class_declaration_node(exported_child) && class_name_from_class_node(exported_child, source) == name) { return exported_child; } } @@ -1641,125 +1646,108 @@ static void finalize_explicit_object_hint(PropertyInfo &property) { } } +static void parse_class_members(TSNode class_node, const std::string &source, const String &file_path, TSNode root_node, uint32_t child_count, HashMap &properties, Vector &property_list, HashMap &interface_array_schemas, HashMap &property_defaults, HashMap &methods, HashMap &static_methods, HashMap &signals, HashMap &rpc_configs, HashMap &member_lines, const HashMap> &interfaces, bool properties_only = false); +static void parse_static_exports(TSNode class_node, const std::string &source, const String &file_path, TSNode root_node, uint32_t child_count, HashMap &properties, Vector &property_list, HashMap &property_defaults); +static HashSet parse_exported_field_defaults(TSNode class_node, const std::string &source, const HashMap &properties, HashMap &property_defaults); + static void collect_parent_properties( - const StringName &parent_name, - const StringName &parent_qualifier, + TSNode class_node, const std::string &source, TSNode root_node, uint32_t child_count, const String &file_path, HashMap &properties, Vector &property_list, - HashMap &property_defaults) { + HashMap &interface_array_schemas, + HashMap &property_defaults, + const HashMap> &interfaces, + HashSet &visited_classes) { + TSNode base_node = extends_class_node_from_class(class_node); + if (ts_node_is_null(base_node)) { + return; + } + const StringName parent_name = class_name_from_extends_node(base_node, source); + const StringName parent_qualifier = qualifier_from_extends_node(base_node, source); if (parent_name.is_empty()) { return; } - if (parent_qualifier.is_empty()) { - for (uint32_t i = 0; i < child_count; i++) { - TSNode child = ts_node_child(root_node, i); - TSNode parent_node = { 0 }; - if (strcmp(ts_node_type(child), "export_statement") == 0) { - for (uint32_t j = 0; j < ts_node_child_count(child); j++) { - TSNode en = ts_node_child(child, j); - if (strcmp(ts_node_type(en), "class_declaration") == 0) { - parent_node = en; + auto inherit_properties = [&](const HashMap &parent_properties, + const Vector &parent_property_list, + const HashMap &parent_schemas, + const HashMap &parent_defaults) { + HashSet inherited_properties; + for (const KeyValue &entry : parent_properties) { + if (!properties.has(entry.key)) { + properties[entry.key] = entry.value; + inherited_properties.insert(entry.key); + } + } + // A metadata-only override can retain the parent's interface array schema. + for (const KeyValue &entry : parent_schemas) { + const PropertyInfo *property = properties.getptr(entry.key); + const PropertyInfo *parent_property = parent_properties.getptr(entry.key); + if (!interface_array_schemas.has(entry.key) && property && parent_property && + property->type == parent_property->type && property->hint == parent_property->hint && + property->hint_string == parent_property->hint_string && property->class_name == parent_property->class_name) { + interface_array_schemas[entry.key] = entry.value; + } + } + for (const PropertyInfo &property : parent_property_list) { + bool include = inherited_properties.has(property.name); + // Inspector groups describe following fields; they are not property-map entries. + if ((property.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) != 0) { + include = false; + for (const StringName &name : inherited_properties) { + if (String(name).begins_with(property.hint_string)) { + include = true; break; } } - } else if (strcmp(ts_node_type(child), "class_declaration") == 0) { - parent_node = child; - } - if (!ts_node_is_null(parent_node)) { - TSNode pname = ts_node_child_by_field_name(parent_node, "name", 4); - if (!ts_node_is_null(pname)) { - uint32_t ps = ts_node_start_byte(pname); - uint32_t pe = ts_node_end_byte(pname); - if (source.substr(ps, pe - ps) == String(parent_name).utf8().get_data()) { - StringName grandparent; - StringName grandparent_qualifier; - TSNode grandparent_node = extends_class_node_from_class(parent_node); - if (!ts_node_is_null(grandparent_node)) { - grandparent = class_name_from_extends_node(grandparent_node, source); - grandparent_qualifier = qualifier_from_extends_node(grandparent_node, source); - } - collect_parent_properties(grandparent, grandparent_qualifier, source, root_node, child_count, file_path, properties, property_list, property_defaults); - TSNode pbody = ts_node_child_by_field_name(parent_node, "body", 4); - for (uint32_t j = 0; j < ts_node_child_count(pbody); j++) { - TSNode field = ts_node_child(pbody, j); - if (strcmp(ts_node_type(field), "public_field_definition") != 0) { - continue; - } - TSNode deco = ts_node_child_by_field_name(field, "decorator", 9); - if (ts_node_is_null(deco)) { - continue; - } - uint32_t ds = ts_node_start_byte(deco); - uint32_t de = ts_node_end_byte(deco); - if (source.substr(ds, de - ds).find("@Export") == std::string::npos) { - continue; - } - TSNode fname = ts_node_child_by_field_name(field, "name", 4); - if (ts_node_is_null(fname)) { - continue; - } - uint32_t ns = ts_node_start_byte(fname); - uint32_t ne = ts_node_end_byte(fname); - StringName prop_name(source.substr(ns, ne - ns).c_str()); - if (properties.has(prop_name)) { - continue; - } - PropertyInfo pi; - pi.name = prop_name; - pi.usage = PROPERTY_USAGE_DEFAULT; - TSNode ftype = ts_node_child_by_field_name(field, "type", 4); - if (!ts_node_is_null(ftype)) { - std::string type_str = type_text_from_annotation(ftype, source); - configure_property_type(pi, type_str, file_path, source, root_node, child_count); - } - properties[prop_name] = pi; - property_list.push_back(pi); - TSNode fvalue = ts_node_child_by_field_name(field, "value", 5); - if (!ts_node_is_null(fvalue)) { - Variant default_value; - if (parse_default_value(fvalue, source, pi.type, default_value)) { - property_defaults[prop_name] = default_value; - } - } - } - return; - } - } } + if (include) { + property_list.push_back(property); + } + } + // Initializers override inherited defaults even without a repeated @Export. + const HashSet initialized_fields = parse_exported_field_defaults(class_node, source, properties, property_defaults); + for (const KeyValue &entry : parent_defaults) { + const StringName field_name(String(entry.key).get_slice("::", 0)); + if (!property_defaults.has(entry.key) && !initialized_fields.has(field_name)) { + property_defaults[entry.key] = entry.value; + } + } + }; + + TSNode parent_node = parent_qualifier.is_empty() ? find_class_declaration_by_name(root_node, child_count, source, parent_name) : TSNode{}; + if (!ts_node_is_null(parent_node)) { + if (visited_classes.has(parent_name)) { + return; } + visited_classes.insert(parent_name); + HashMap parent_properties; + Vector parent_property_list; + HashMap parent_schemas; + HashMap parent_defaults; + HashMap parent_methods, parent_static_methods, parent_signals; + HashMap parent_rpc_configs; + HashMap parent_member_lines; + // Use the same parser as the default class, including hints and interface schemas. + parse_class_members(parent_node, source, file_path, root_node, child_count, parent_properties, parent_property_list, parent_schemas, parent_defaults, parent_methods, parent_static_methods, parent_signals, parent_rpc_configs, parent_member_lines, interfaces, true); + parse_static_exports(parent_node, source, file_path, root_node, child_count, parent_properties, parent_property_list, parent_defaults); + parse_exported_field_defaults(parent_node, source, parent_properties, parent_defaults); + collect_parent_properties(parent_node, source, root_node, child_count, file_path, parent_properties, parent_property_list, parent_schemas, parent_defaults, interfaces, visited_classes); + inherit_properties(parent_properties, parent_property_list, parent_schemas, parent_defaults); + return; } String ts_path = resolve_imported_class_path(file_path, source, root_node, child_count, parent_name, parent_qualifier); if (ts_path.is_empty()) { return; } - Ref