(node: SyntaxNode, source: string, extractor: LanguageExtractor)
| 96 | } |
| 97 | |
| 98 | function extractNameRaw(node: SyntaxNode, source: string, extractor: LanguageExtractor): string { |
| 99 | const hookName = extractor.resolveName?.(node, source); |
| 100 | if (hookName) return hookName; |
| 101 | |
| 102 | // Try field name first |
| 103 | const nameNode = getChildByField(node, extractor.nameField); |
| 104 | if (nameNode) { |
| 105 | // Unwrap pointer_declarator / reference_declarator for C/C++ pointer and |
| 106 | // reference return types (`int* f()`, `int& f()`, `int&& f()`). Without |
| 107 | // unwrapping the reference wrapper an inline reference-returning method is |
| 108 | // named "& f() const" instead of "f" — common in Unreal Engine gameplay |
| 109 | // headers (`const FGameplayTagContainer& GetActiveTags() const`). Out-of-line |
| 110 | // defs (`T& C::f()`) already resolve via the qualified-name hook. A |
| 111 | // pointer_declarator exposes its inner through a `declarator` field; a |
| 112 | // reference_declarator has none, so it's reached via namedChild(0). |
| 113 | let resolved = nameNode; |
| 114 | while (resolved.type === 'pointer_declarator' || resolved.type === 'reference_declarator') { |
| 115 | const inner = getChildByField(resolved, 'declarator') || resolved.namedChild(0); |
| 116 | if (!inner) break; |
| 117 | resolved = inner; |
| 118 | } |
| 119 | // C++ user-defined conversion operator: the declarator is an `operator_cast` |
| 120 | // whose first child is the target type and second is the `() const` tail. Name |
| 121 | // it `operator <type>` (the conventional spelling) rather than the whole |
| 122 | // `operator EALSMovementState() const` declarator, so it matches symbolic |
| 123 | // overloads (`operator+`) and is findable by the type name. |
| 124 | if (resolved.type === 'operator_cast') { |
| 125 | const typeNode = resolved.namedChild(0); |
| 126 | return typeNode ? `operator ${getNodeText(typeNode, source).trim()}` : getNodeText(resolved, source); |
| 127 | } |
| 128 | // Handle complex declarators (C/C++) |
| 129 | if (resolved.type === 'function_declarator' || resolved.type === 'declarator') { |
| 130 | const innerName = getChildByField(resolved, 'declarator') || resolved.namedChild(0); |
| 131 | return innerName ? getNodeText(innerName, source) : getNodeText(resolved, source); |
| 132 | } |
| 133 | // Lua: `function t.f()` / `function t:m()` — the name node is a dot/method |
| 134 | // index expression; the simple name is the trailing field/method (the table |
| 135 | // receiver is captured separately via getReceiverType). |
| 136 | if (resolved.type === 'dot_index_expression') { |
| 137 | const field = getChildByField(resolved, 'field'); |
| 138 | if (field) return getNodeText(field, source); |
| 139 | } |
| 140 | if (resolved.type === 'method_index_expression') { |
| 141 | const method = getChildByField(resolved, 'method'); |
| 142 | if (method) return getNodeText(method, source); |
| 143 | } |
| 144 | return getNodeText(resolved, source); |
| 145 | } |
| 146 | |
| 147 | // For Dart method_signature, look inside inner signature types |
| 148 | if (node.type === 'method_signature') { |
| 149 | for (let i = 0; i < node.namedChildCount; i++) { |
| 150 | const child = node.namedChild(i); |
| 151 | if (child && ( |
| 152 | child.type === 'function_signature' || |
| 153 | child.type === 'getter_signature' || |
| 154 | child.type === 'setter_signature' || |
| 155 | child.type === 'constructor_signature' || |
no test coverage detected