|
|
@@ -2,6 +2,44 @@ import type { Node as SyntaxNode } from 'web-tree-sitter';
|
|
|
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
|
|
import type { LanguageExtractor } from '../tree-sitter-types';
|
|
|
|
|
|
+/**
|
|
|
+ * A Swift function's declared return type, normalized to the bare class name a
|
|
|
+ * chained `Foo.make().draw()` could be called on (the #645/#608 mechanism).
|
|
|
+ * tree-sitter-swift labels BOTH the function name (`simple_identifier`) and the
|
|
|
+ * return type (a `user_type`) with the field `name`, so `childForFieldName`
|
|
|
+ * returns the name; the return type is found positionally — the first type node
|
|
|
+ * after the `simple_identifier` name, before the body. Optionals (`Foo?`) are
|
|
|
+ * unwrapped; arrays/tuples/function types and `Void` yield undefined.
|
|
|
+ */
|
|
|
+function extractSwiftReturnType(node: SyntaxNode, source: string): string | undefined {
|
|
|
+ let seenName = false;
|
|
|
+ for (let i = 0; i < node.namedChildCount; i++) {
|
|
|
+ const child = node.namedChild(i);
|
|
|
+ if (!child) continue;
|
|
|
+ if (child.type === 'simple_identifier' && !seenName) {
|
|
|
+ seenName = true;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!seenName) continue;
|
|
|
+ if (child.type === 'function_body') return undefined; // body reached: no return type
|
|
|
+ let typeNode: SyntaxNode | null = null;
|
|
|
+ if (child.type === 'user_type') typeNode = child;
|
|
|
+ else if (child.type === 'optional_type') {
|
|
|
+ typeNode = child.namedChildren.find((c: SyntaxNode) => c.type === 'user_type') ?? null;
|
|
|
+ }
|
|
|
+ if (typeNode) {
|
|
|
+ // Use the whole type node's text, strip generics, then take the LAST
|
|
|
+ // dotted segment — a member type `KF.Builder` resolves to `Builder` (its
|
|
|
+ // first type_identifier is the OUTER `KF`, which would be wrong).
|
|
|
+ const name = getNodeText(typeNode, source).trim().replace(/<[^>]*>/g, '');
|
|
|
+ const last = name.split('.').pop()?.trim();
|
|
|
+ if (!last || !/^[A-Za-z_]\w*$/.test(last) || last === 'Void') return undefined;
|
|
|
+ return last;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return undefined;
|
|
|
+}
|
|
|
+
|
|
|
export const swiftExtractor: LanguageExtractor = {
|
|
|
functionTypes: ['function_declaration'],
|
|
|
classTypes: ['class_declaration'],
|
|
|
@@ -18,6 +56,23 @@ export const swiftExtractor: LanguageExtractor = {
|
|
|
bodyField: 'body',
|
|
|
paramsField: 'parameter',
|
|
|
returnField: 'return_type',
|
|
|
+ getReturnType: extractSwiftReturnType,
|
|
|
+ resolveName: (node, source) => {
|
|
|
+ // A nested-type extension `extension KF.Builder { … }` parses as a
|
|
|
+ // class_declaration whose `name` is a multi-segment `user_type` (`KF.Builder`
|
|
|
+ // = type_identifiers `KF`, `Builder`). Name the node by the LAST segment
|
|
|
+ // (`Builder`) so it shares the simple name of the extended type's own
|
|
|
+ // declaration (`struct Builder` → `KF::Builder`) instead of becoming a
|
|
|
+ // distinct `KF.Builder` node. Without this, the extension's conformances and
|
|
|
+ // members are invisible to a chained call on the type — supertype lookup and
|
|
|
+ // method matching both key off the simple name (#750). Simple names (regular
|
|
|
+ // class/struct/enum, or `extension Plain`) fall through to default extraction.
|
|
|
+ if (node.type !== 'class_declaration') return undefined;
|
|
|
+ const nameNode = getChildByField(node, 'name');
|
|
|
+ if (!nameNode || nameNode.type !== 'user_type') return undefined;
|
|
|
+ const ids = nameNode.namedChildren.filter((c: SyntaxNode) => c.type === 'type_identifier');
|
|
|
+ return ids.length > 1 ? getNodeText(ids[ids.length - 1]!, source) : undefined;
|
|
|
+ },
|
|
|
getSignature: (node, source) => {
|
|
|
// Swift function signature: func name(params) -> ReturnType
|
|
|
const params = getChildByField(node, 'parameter');
|