Locate a Swift symbol's source declaration
swiftGetSymbolDefinitionFinds the file:line where a Swift symbol is declared, enabling direct navigation from cycle analysis to source code.
Instructions
[mg.code] Find the file:line where a Swift symbol (class, struct, enum, protocol, func, var, etc.) is declared. Pre-scans candidatePaths (or hint.filePath) with a fast regex first, then asks SourceKit-LSP for jump-to-definition. Returns the position even when LSP can't follow through. Use after findRetainers / classifyCycle surface a class name from a memgraph cycle to land in the actual source file.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| symbolName | Yes | Name of the Swift symbol to locate (class, struct, enum, protocol, func, var, etc.). | |
| hint | No | Optional hint to speed up the search. `filePath` skips the project scan; `module` is reserved for future multi-module work. | |
| projectRoot | No | Override the project root. Default discovers the nearest Package.swift / .xcodeproj / .xcworkspace from the cwd. | |
| candidatePaths | No | If provided, search these files for the symbol declaration before asking SourceKit-LSP. Speeds up location when the agent already has a guess (e.g. from `findSymbolReferences` or `swift_search_pattern`). |
Implementation Reference
- Main handler function for the swiftGetSymbolDefinition tool. Resolves the project root, iterates candidate files to find a symbol declaration via regex pre-scan, then queries SourceKit-LSP for jump-to-definition. Falls back to the pre-scan position if LSP returns nothing.
export async function swiftGetSymbolDefinition( input: SwiftGetSymbolDefinitionInput, ): Promise<SwiftGetSymbolDefinitionResult> { const root = input.projectRoot ? resolvePath(input.projectRoot) : projectRootFor( input.candidatePaths?.[0] ?? input.hint?.filePath ?? process.cwd(), ); if (!existsSync(root)) { throw new Error(`Project root not found: ${root}`); } const fileCandidates = [ ...(input.hint?.filePath ? [input.hint.filePath] : []), ...(input.candidatePaths ?? []), ]; for (const fp of fileCandidates) { const abs = resolvePath(fp); if (!existsSync(abs)) continue; const pos = findSymbolDeclaration(abs, input.symbolName); if (pos) { const client = await acquireClient(root); const defs = await lspDefinition(client, abs, pos.line, pos.character); return { ok: true, symbolName: input.symbolName, definitions: defs.length > 0 ? defs : [{ filePath: abs, line: pos.line, character: pos.character }], preScanHit: { filePath: abs, matchedText: pos.matchedText.trim() }, }; } } // No candidate found locally. SourceKit-LSP doesn't expose a workspace-wide // "find me a symbol named X" endpoint over plain LSP, so we punt and let // the agent feed back candidate paths from a prior `searchPattern` or // `findCycles` call. return { ok: true, symbolName: input.symbolName, definitions: [], preScanHit: undefined, }; } - Zod schema defining the input parameters: symbolName (required), hint (optional filePath/module), projectRoot (optional), candidatePaths (optional array of file paths).
export const swiftGetSymbolDefinitionSchema = z.object({ symbolName: z .string() .min(1) .describe( "Name of the Swift symbol to locate (class, struct, enum, protocol, func, var, etc.).", ), hint: z .object({ filePath: z.string().optional(), module: z.string().optional(), }) .optional() .describe( "Optional hint to speed up the search. `filePath` skips the project scan; `module` is reserved for future multi-module work.", ), projectRoot: z .string() .optional() .describe( "Override the project root. Default discovers the nearest Package.swift / .xcodeproj / .xcworkspace from the cwd.", ), candidatePaths: z .array(z.string()) .optional() .describe( "If provided, search these files for the symbol declaration before asking SourceKit-LSP. Speeds up location when the agent already has a guess (e.g. from `findSymbolReferences` or `swift_search_pattern`).", ), }); - Result type: ok status, symbol name, array of definition locations, and optional pre-scan hit info.
export interface SwiftGetSymbolDefinitionResult { ok: boolean; symbolName: string; /** Definition locations returned by SourceKit-LSP (or pre-scan when LSP returns nothing). */ definitions: SourceLocation[]; /** When set, indicates we located the symbol via filename pre-scan rather than a true LSP query. */ preScanHit?: { filePath: string; matchedText: string }; } - src/index.ts:427-439 (registration)Registration of the tool via server.registerTool with the name, title/description metadata, input schema, and handler callback.
server.registerTool( "swiftGetSymbolDefinition", { title: "Locate a Swift symbol's source declaration", description: "[mg.code] Find the file:line where a Swift symbol (class, struct, enum, protocol, func, var, etc.) is declared. Pre-scans `candidatePaths` (or `hint.filePath`) with a fast regex first, then asks SourceKit-LSP for jump-to-definition. Returns the position even when LSP can't follow through. Use after `findRetainers` / `classifyCycle` surface a class name from a memgraph cycle to land in the actual source file.", inputSchema: swiftGetSymbolDefinitionSchema.shape, }, async (input) => { const result = await swiftGetSymbolDefinition(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, ); - src/tools/swift/_helpers.ts:30-62 (helper)Pre-scan helper that finds a symbol's declaration in a file using regex, first matching declaration keywords (class/struct/enum/etc.), then falling back to any standalone word occurrence. Returns 0-based LSP-compatible positions.
export function findSymbolDeclaration( filePath: string, symbolName: string, ): SymbolPosition | null { const text = readFileSync(filePath, "utf8"); const lines = text.split(/\r?\n/); const declRe = new RegExp( `\\b(?:class|struct|enum|protocol|func|var|let|actor|extension)\\s+${escapeRegex(symbolName)}\\b`, ); for (let i = 0; i < lines.length; i++) { const m = lines[i].match(declRe); if (m) { const ch = lines[i].indexOf(symbolName, m.index ?? 0); if (ch >= 0) { return { line: i, character: ch, matchedText: lines[i] }; } } } const wordRe = new RegExp(`\\b${escapeRegex(symbolName)}\\b`); for (let i = 0; i < lines.length; i++) { const m = lines[i].match(wordRe); if (m) { return { line: i, character: m.index ?? 0, matchedText: lines[i], }; } } return null; }