@ctxo/lang-go 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/dist/index.d.ts +68 -1
- package/dist/index.js +483 -11
- package/dist/index.js.map +1 -1
- package/package.json +13 -6
- package/tools/ctxo-go-analyzer/go.mod +10 -0
- package/tools/ctxo-go-analyzer/go.sum +8 -0
- package/tools/ctxo-go-analyzer/internal/edges/edges.go +303 -0
- package/tools/ctxo-go-analyzer/internal/edges/edges_test.go +180 -0
- package/tools/ctxo-go-analyzer/internal/emit/emit.go +189 -0
- package/tools/ctxo-go-analyzer/internal/emit/emit_test.go +84 -0
- package/tools/ctxo-go-analyzer/internal/extends/extends.go +138 -0
- package/tools/ctxo-go-analyzer/internal/extends/extends_test.go +141 -0
- package/tools/ctxo-go-analyzer/internal/implements/implements.go +115 -0
- package/tools/ctxo-go-analyzer/internal/implements/implements_test.go +141 -0
- package/tools/ctxo-go-analyzer/internal/load/load.go +162 -0
- package/tools/ctxo-go-analyzer/internal/load/load_test.go +96 -0
- package/tools/ctxo-go-analyzer/internal/reach/reach.go +332 -0
- package/tools/ctxo-go-analyzer/internal/reach/reach_test.go +142 -0
- package/tools/ctxo-go-analyzer/internal/symbols/symbols.go +172 -0
- package/tools/ctxo-go-analyzer/internal/symbols/symbols_test.go +159 -0
- package/tools/ctxo-go-analyzer/main.go +183 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/go-adapter.ts","../src/tree-sitter-adapter.ts","../src/index.ts"],"sourcesContent":["import GoLanguage from 'tree-sitter-go';\nimport type { SyntaxNode } from 'tree-sitter';\nimport { TreeSitterAdapter } from './tree-sitter-adapter.js';\nimport type { SymbolNode, GraphEdge, ComplexityMetrics } from '@ctxo/plugin-api';\n\nconst GO_BRANCH_TYPES = [\n 'if_statement', 'for_statement',\n 'expression_switch_statement', 'type_switch_statement',\n 'expression_case', 'type_case',\n 'select_statement', 'communication_case',\n];\n\nexport class GoAdapter extends TreeSitterAdapter {\n readonly extensions = ['.go'] as const;\n\n constructor() {\n super(GoLanguage);\n }\n\n async extractSymbols(filePath: string, source: string): Promise<SymbolNode[]> {\n try {\n const tree = this.parse(source);\n const symbols: SymbolNode[] = [];\n\n for (let i = 0; i < tree.rootNode.childCount; i++) {\n const node = tree.rootNode.child(i)!;\n\n if (node.type === 'function_declaration') {\n const name = node.childForFieldName('name')?.text;\n if (!name || !this.isExported(name)) continue;\n const range = this.nodeToLineRange(node);\n symbols.push({\n symbolId: this.buildSymbolId(filePath, name, 'function'),\n name,\n kind: 'function',\n ...range,\n });\n }\n\n if (node.type === 'method_declaration') {\n const methodName = node.childForFieldName('name')?.text;\n if (!methodName || !this.isExported(methodName)) continue;\n const receiverType = this.extractReceiverType(node);\n const qualifiedName = receiverType ? `${receiverType}.${methodName}` : methodName;\n const range = this.nodeToLineRange(node);\n symbols.push({\n symbolId: this.buildSymbolId(filePath, qualifiedName, 'method'),\n name: qualifiedName,\n kind: 'method',\n ...range,\n });\n }\n\n if (node.type === 'type_declaration') {\n this.extractTypeSymbols(node, filePath, symbols);\n }\n }\n\n return symbols;\n } catch (err) {\n console.error(`[ctxo:go] Symbol extraction failed for ${filePath}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async extractEdges(filePath: string, source: string): Promise<GraphEdge[]> {\n try {\n const tree = this.parse(source);\n const edges: GraphEdge[] = [];\n const firstExportedSymbol = this.findFirstExportedSymbolId(tree.rootNode, filePath);\n if (!firstExportedSymbol) return edges;\n\n for (let i = 0; i < tree.rootNode.childCount; i++) {\n const node = tree.rootNode.child(i)!;\n\n if (node.type === 'import_declaration') {\n this.extractImportEdges(node, filePath, firstExportedSymbol, edges);\n }\n }\n\n return edges;\n } catch (err) {\n console.error(`[ctxo:go] Edge extraction failed for ${filePath}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async extractComplexity(filePath: string, source: string): Promise<ComplexityMetrics[]> {\n try {\n const tree = this.parse(source);\n const metrics: ComplexityMetrics[] = [];\n\n for (let i = 0; i < tree.rootNode.childCount; i++) {\n const node = tree.rootNode.child(i)!;\n\n if (node.type === 'function_declaration') {\n const name = node.childForFieldName('name')?.text;\n if (!name || !this.isExported(name)) continue;\n metrics.push({\n symbolId: this.buildSymbolId(filePath, name, 'function'),\n cyclomatic: this.countCyclomaticComplexity(node, GO_BRANCH_TYPES),\n });\n }\n\n if (node.type === 'method_declaration') {\n const methodName = node.childForFieldName('name')?.text;\n if (!methodName || !this.isExported(methodName)) continue;\n const receiverType = this.extractReceiverType(node);\n const qualifiedName = receiverType ? `${receiverType}.${methodName}` : methodName;\n metrics.push({\n symbolId: this.buildSymbolId(filePath, qualifiedName, 'method'),\n cyclomatic: this.countCyclomaticComplexity(node, GO_BRANCH_TYPES),\n });\n }\n }\n\n return metrics;\n } catch (err) {\n console.error(`[ctxo:go] Complexity extraction failed for ${filePath}: ${(err as Error).message}`);\n return [];\n }\n }\n\n // ── Private helpers ─────────────────────────────────────────\n\n private isExported(name: string): boolean {\n return name.length > 0 && name[0]! === name[0]!.toUpperCase() && name[0]! !== name[0]!.toLowerCase();\n }\n\n private extractReceiverType(methodNode: SyntaxNode): string | undefined {\n // method_declaration has parameter_list as first child (receiver)\n const params = methodNode.child(1);\n if (params?.type !== 'parameter_list') return undefined;\n\n for (let i = 0; i < params.childCount; i++) {\n const param = params.child(i)!;\n if (param.type === 'parameter_declaration') {\n // Find type identifier — may be pointer (*Type) or plain (Type)\n const typeNode = param.childForFieldName('type');\n if (typeNode) {\n const text = typeNode.text;\n return text.replace(/^\\*/, '');\n }\n }\n }\n return undefined;\n }\n\n private extractTypeSymbols(typeDecl: SyntaxNode, filePath: string, symbols: SymbolNode[]): void {\n for (let i = 0; i < typeDecl.childCount; i++) {\n const spec = typeDecl.child(i)!;\n if (spec.type !== 'type_spec') continue;\n\n const name = spec.childForFieldName('name')?.text;\n if (!name || !this.isExported(name)) continue;\n\n // Determine kind from the type body\n const typeBody = spec.childForFieldName('type');\n let kind: 'class' | 'interface' | 'type' = 'type';\n if (typeBody?.type === 'struct_type') kind = 'class';\n else if (typeBody?.type === 'interface_type') kind = 'interface';\n\n const range = this.nodeToLineRange(spec);\n symbols.push({\n symbolId: this.buildSymbolId(filePath, name, kind),\n name,\n kind,\n ...range,\n });\n }\n }\n\n private extractImportEdges(\n importDecl: SyntaxNode,\n _filePath: string,\n fromSymbol: string,\n edges: GraphEdge[],\n ): void {\n const visit = (node: SyntaxNode) => {\n if (node.type === 'import_spec') {\n const pathNode = node.childForFieldName('path') ?? node.child(0);\n if (pathNode) {\n const importPath = pathNode.text.replace(/\"/g, '');\n edges.push({\n from: fromSymbol,\n to: `${importPath}::${importPath.split('/').pop()}::variable`,\n kind: 'imports',\n });\n }\n }\n for (let i = 0; i < node.childCount; i++) {\n visit(node.child(i)!);\n }\n };\n visit(importDecl);\n }\n\n private findFirstExportedSymbolId(rootNode: SyntaxNode, filePath: string): string | undefined {\n for (let i = 0; i < rootNode.childCount; i++) {\n const node = rootNode.child(i)!;\n\n if (node.type === 'function_declaration') {\n const name = node.childForFieldName('name')?.text;\n if (name && this.isExported(name)) return this.buildSymbolId(filePath, name, 'function');\n }\n if (node.type === 'type_declaration') {\n for (let j = 0; j < node.childCount; j++) {\n const spec = node.child(j)!;\n if (spec.type === 'type_spec') {\n const name = spec.childForFieldName('name')?.text;\n if (name && this.isExported(name)) {\n const typeBody = spec.childForFieldName('type');\n const kind = typeBody?.type === 'struct_type' ? 'class' : typeBody?.type === 'interface_type' ? 'interface' : 'type';\n return this.buildSymbolId(filePath, name, kind);\n }\n }\n }\n }\n }\n return undefined;\n }\n}\n","import Parser from 'tree-sitter';\nimport type { Tree, SyntaxNode } from 'tree-sitter';\n// Grammars (tree-sitter-go etc.) ship their own Language type which can drift\n// against tree-sitter's parameter type across major versions. Accept any\n// structurally compatible value and hand it to the parser as the runtime API\n// expects (the parser does its own validation).\ntype Language = Parameters<InstanceType<typeof Parser>['setLanguage']>[0] | object;\nimport { extname } from 'node:path';\nimport type { SymbolNode, GraphEdge, ComplexityMetrics, SymbolKind, ILanguageAdapter } from '@ctxo/plugin-api';\n\nexport abstract class TreeSitterAdapter implements ILanguageAdapter {\n abstract readonly extensions: readonly string[];\n readonly tier = 'syntax' as const;\n\n protected parser: Parser;\n protected symbolRegistry = new Map<string, SymbolKind>();\n\n constructor(language: Language) {\n this.parser = new Parser();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.parser.setLanguage(language as any);\n }\n\n isSupported(filePath: string): boolean {\n const ext = extname(filePath).toLowerCase();\n return (this.extensions as readonly string[]).includes(ext);\n }\n\n setSymbolRegistry(registry: Map<string, SymbolKind>): void {\n this.symbolRegistry = registry;\n }\n\n protected parse(source: string): Tree {\n return this.parser.parse(source);\n }\n\n protected buildSymbolId(filePath: string, name: string, kind: SymbolKind): string {\n return `${filePath}::${name}::${kind}`;\n }\n\n protected nodeToLineRange(node: SyntaxNode): {\n startLine: number;\n endLine: number;\n startOffset: number;\n endOffset: number;\n } {\n return {\n startLine: node.startPosition.row,\n endLine: node.endPosition.row,\n startOffset: node.startIndex,\n endOffset: node.endIndex,\n };\n }\n\n protected countCyclomaticComplexity(node: SyntaxNode, branchTypes: string[]): number {\n let complexity = 1;\n const visit = (n: SyntaxNode) => {\n if (branchTypes.includes(n.type)) {\n complexity++;\n }\n for (let i = 0; i < n.childCount; i++) {\n visit(n.child(i)!);\n }\n };\n visit(node);\n return complexity;\n }\n\n abstract extractSymbols(filePath: string, source: string): Promise<SymbolNode[]>;\n abstract extractEdges(filePath: string, source: string): Promise<GraphEdge[]>;\n abstract extractComplexity(filePath: string, source: string): Promise<ComplexityMetrics[]>;\n}\n","import type { CtxoLanguagePlugin, PluginContext, ILanguageAdapter } from '@ctxo/plugin-api';\nimport { GoAdapter } from './go-adapter.js';\n\nexport { GoAdapter } from './go-adapter.js';\nexport { TreeSitterAdapter } from './tree-sitter-adapter.js';\n\nconst VERSION = '0.7.0-alpha.0';\n\nexport const plugin: CtxoLanguagePlugin = {\n apiVersion: '1',\n id: 'go',\n name: 'Go (tree-sitter)',\n version: VERSION,\n extensions: ['.go'],\n tier: 'syntax',\n createAdapter(_ctx: PluginContext): ILanguageAdapter {\n return new GoAdapter();\n },\n};\n\nexport default plugin;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/analyzer/analyzer-adapter.ts","../src/logger.ts","../src/analyzer/toolchain-detect.ts","../src/analyzer/module-discovery.ts","../src/analyzer/binary-build.ts","../src/analyzer/analyzer-process.ts","../src/go-adapter.ts","../src/tree-sitter-adapter.ts","../src/composite-adapter.ts","../src/index.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport type { SymbolNode, GraphEdge, ComplexityMetrics, SymbolKind, ILanguageAdapter } from '@ctxo/plugin-api';\nimport { createLogger } from '../logger.js';\nimport { detectGoToolchain } from './toolchain-detect.js';\nimport { discoverGoModule, findCtxoGoAnalyzerSource } from './module-discovery.js';\nimport { ensureAnalyzerBinary } from './binary-build.js';\nimport { runBatchAnalyze, type AnalyzerFileResult, type AnalyzerBatchResult } from './analyzer-process.js';\n\nconst log = createLogger('ctxo:lang-go');\nconst VALID_SYMBOL_ID = /^.+::.+::.+$/;\n\n/**\n * Full-tier Go adapter. Owns the analyzer binary lifecycle and an\n * in-memory cache of batch results keyed by project-relative file path.\n *\n * Caching strategy: the first extractSymbols/extractEdges call after\n * initialize() triggers a single batch analysis run. Subsequent calls\n * read from cache. This keeps the ILanguageAdapter contract intact\n * (per-file methods) while the binary runs once per index.\n */\nexport class GoAnalyzerAdapter implements ILanguageAdapter {\n readonly extensions = ['.go'] as const;\n readonly tier = 'full' as const;\n\n private moduleRoot: string | null = null;\n private binaryPath: string | null = null;\n private modulePathPrefix = '';\n private cache = new Map<string, AnalyzerFileResult>();\n private deadSymbolIds = new Set<string>();\n private hasMain = false;\n private timedOut = false;\n private batchPromise: Promise<void> | null = null;\n private initialized = false;\n\n isSupported(filePath: string): boolean {\n return filePath.endsWith('.go');\n }\n\n isReady(): boolean {\n return this.initialized && this.binaryPath !== null && this.moduleRoot !== null;\n }\n\n async initialize(rootDir: string): Promise<void> {\n const toolchain = detectGoToolchain();\n if (!toolchain.available) {\n log.info(`Go analyzer unavailable: go ${toolchain.version ?? 'not found'} (>= 1.22 required)`);\n return;\n }\n\n const moduleRoot = discoverGoModule(rootDir);\n if (!moduleRoot) {\n log.info('Go analyzer unavailable: no go.mod or go.work found');\n return;\n }\n this.moduleRoot = moduleRoot;\n const prefix = relative(rootDir, moduleRoot).replace(/\\\\/g, '/');\n this.modulePathPrefix = prefix && prefix !== '.' ? prefix : '';\n\n const sourceDir = findCtxoGoAnalyzerSource();\n if (!sourceDir) {\n log.info('Go analyzer unavailable: ctxo-go-analyzer source not located');\n return;\n }\n\n try {\n this.binaryPath = ensureAnalyzerBinary(sourceDir, toolchain.version!);\n } catch (err) {\n log.warn(`Go analyzer binary build failed: ${(err as Error).message}`);\n return;\n }\n\n log.info(`Go analyzer ready: go ${toolchain.version}, module ${moduleRoot}`);\n this.initialized = true;\n }\n\n /**\n * Returns the dead-symbol set emitted by the last batch run. Empty until\n * extractSymbols/Edges has been called at least once. Consumed by cli's\n * find_dead_code MCP tool via the composite delegate.\n */\n getDeadSymbolIds(): Set<string> {\n return this.deadSymbolIds;\n }\n\n /** True when the analyzer ran in binary mode (precise dead-code). */\n reachabilityHasMain(): boolean {\n return this.hasMain;\n }\n\n /** True when reach analysis exceeded the deadline (degraded precision). */\n reachabilityTimedOut(): boolean {\n return this.timedOut;\n }\n\n async extractSymbols(filePath: string, _source: string): Promise<SymbolNode[]> {\n if (!this.isReady()) return [];\n await this.ensureBatch();\n const file = this.cache.get(this.normalizePath(filePath));\n if (!file) return [];\n return file.symbols\n .filter((s) => VALID_SYMBOL_ID.test(s.symbolId))\n .map((s) => ({\n symbolId: s.symbolId,\n name: s.name,\n kind: s.kind as SymbolKind,\n startLine: s.startLine,\n endLine: s.endLine,\n ...(s.startOffset != null ? { startOffset: s.startOffset } : {}),\n ...(s.endOffset != null ? { endOffset: s.endOffset } : {}),\n }));\n }\n\n async extractEdges(filePath: string, _source: string): Promise<GraphEdge[]> {\n if (!this.isReady()) return [];\n await this.ensureBatch();\n const file = this.cache.get(this.normalizePath(filePath));\n if (!file) return [];\n return file.edges\n .filter((e) => VALID_SYMBOL_ID.test(e.from) && VALID_SYMBOL_ID.test(e.to))\n .map((e) => ({\n from: e.from,\n to: e.to,\n kind: e.kind as GraphEdge['kind'],\n }));\n }\n\n async extractComplexity(_filePath: string, _source: string): Promise<ComplexityMetrics[]> {\n // Tree-sitter layer fills complexity — the binary intentionally emits []\n // so the composite adapter merges the two sources without contention.\n return [];\n }\n\n async dispose(): Promise<void> {\n this.cache.clear();\n this.deadSymbolIds.clear();\n this.batchPromise = null;\n this.initialized = false;\n }\n\n private async ensureBatch(): Promise<void> {\n if (this.batchPromise) return this.batchPromise;\n this.batchPromise = this.runBatch();\n return this.batchPromise;\n }\n\n private async runBatch(): Promise<void> {\n if (!this.binaryPath || !this.moduleRoot) return;\n const result = await runBatchAnalyze(this.binaryPath, this.moduleRoot);\n this.absorbBatch(result);\n log.info(`Go analyzer batch: ${result.totalFiles} files in ${result.elapsed}, ${result.dead.length} dead`);\n }\n\n private absorbBatch(result: AnalyzerBatchResult): void {\n this.cache.clear();\n this.deadSymbolIds.clear();\n this.hasMain = result.hasMain;\n this.timedOut = result.timeout;\n\n const rewrite = this.buildPathRewriter();\n for (const file of result.files) {\n const projectRel = rewrite(file.file);\n this.cache.set(projectRel, {\n ...file,\n file: projectRel,\n symbols: file.symbols.map((s) => ({ ...s, symbolId: rewriteId(s.symbolId, file.file, projectRel) })),\n edges: file.edges.map((e) => ({\n ...e,\n from: rewriteId(e.from, file.file, projectRel),\n to: rewriteIdAcrossModule(e.to, this.modulePathPrefix),\n })),\n });\n }\n for (const id of result.dead) {\n this.deadSymbolIds.add(rewriteIdAcrossModule(id, this.modulePathPrefix));\n }\n }\n\n private normalizePath(filePath: string): string {\n return filePath.replace(/\\\\/g, '/');\n }\n\n private buildPathRewriter(): (analyzerRel: string) => string {\n if (!this.modulePathPrefix) return (p) => p;\n const prefix = this.modulePathPrefix;\n return (p) => `${prefix}/${p}`;\n }\n}\n\n/** Replace the analyzer's module-relative file prefix in a symbol id with the project-relative one. */\nfunction rewriteId(id: string, analyzerFile: string, projectFile: string): string {\n if (analyzerFile === projectFile) return id;\n return id.startsWith(`${analyzerFile}::`) ? `${projectFile}${id.slice(analyzerFile.length)}` : id;\n}\n\n/** Edge targets may live in any other file of the module — apply the global prefix shift. */\nfunction rewriteIdAcrossModule(id: string, prefix: string): string {\n if (!prefix) return id;\n const sepIdx = id.indexOf('::');\n if (sepIdx <= 0) return id;\n return `${prefix}/${id}`;\n}\n","/**\n * Minimal namespaced logger. Mirrors the shape of cli's `createLogger` so\n * adapter code extracted from the monorepo continues to work unchanged.\n * Emits to stderr only (stdio transport requires stdout kept clean).\n */\ntype LogFn = (message: string, ...args: unknown[]) => void;\n\nexport interface Logger {\n debug: LogFn;\n info: LogFn;\n warn: LogFn;\n error: LogFn;\n}\n\nfunction enabledFor(namespace: string): boolean {\n const env = process.env['DEBUG'];\n if (!env) return false;\n const patterns = env.split(',').map((p) => p.trim()).filter(Boolean);\n for (const pattern of patterns) {\n if (pattern === '*' || pattern === namespace) return true;\n if (pattern.endsWith('*') && namespace.startsWith(pattern.slice(0, -1))) return true;\n }\n return false;\n}\n\nfunction emit(namespace: string, level: string, message: string, args: unknown[]): void {\n const line = `[${namespace}] ${message}${args.length ? ' ' + args.map(String).join(' ') : ''}`;\n if (level === 'error') process.stderr.write(line + '\\n');\n else if (level === 'warn') process.stderr.write(line + '\\n');\n else if (enabledFor(namespace)) process.stderr.write(line + '\\n');\n}\n\nexport function createLogger(namespace: string): Logger {\n return {\n debug: (msg, ...args) => emit(namespace, 'debug', msg, args),\n info: (msg, ...args) => emit(namespace, 'info', msg, args),\n warn: (msg, ...args) => emit(namespace, 'warn', msg, args),\n error: (msg, ...args) => emit(namespace, 'error', msg, args),\n };\n}\n","import { execFileSync } from 'node:child_process';\nimport { createLogger } from '../logger.js';\n\nconst log = createLogger('ctxo:lang-go');\n\nexport interface GoToolchainInfo {\n available: boolean;\n version?: string;\n}\n\nconst MIN_MAJOR = 1;\nconst MIN_MINOR = 22;\n\n/**\n * Probe `go version` and gate full-tier on a minimum version. The minimum\n * matches the toolchain expected by golang.org/x/tools/go/ssa + callgraph\n * libraries used inside the binary.\n */\nexport function detectGoToolchain(): GoToolchainInfo {\n try {\n const out = execFileSync('go', ['version'], { encoding: 'utf-8', timeout: 10_000 }).trim();\n const match = out.match(/go(\\d+)\\.(\\d+)(?:\\.(\\d+))?/);\n if (!match) {\n log.info(`Could not parse go version output: ${out}`);\n return { available: false };\n }\n const major = parseInt(match[1]!, 10);\n const minor = parseInt(match[2]!, 10);\n const version = match[3] ? `${major}.${minor}.${match[3]}` : `${major}.${minor}`;\n if (major < MIN_MAJOR || (major === MIN_MAJOR && minor < MIN_MINOR)) {\n log.info(`go ${version} found but >= ${MIN_MAJOR}.${MIN_MINOR} required`);\n return { available: false, version };\n }\n return { available: true, version };\n } catch {\n return { available: false };\n }\n}\n","import { existsSync, readdirSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { createLogger } from '../logger.js';\n\nconst log = createLogger('ctxo:lang-go');\nconst IGNORE_DIRS = new Set(['node_modules', '.git', '.ctxo', 'vendor', 'dist', 'build']);\n\n/**\n * Locate the Go module (or go.work) root for a given project directory.\n * Walks up from rootDir first; if nothing matches, does a shallow recursive\n * search to support consumers whose Go code lives in a subdirectory.\n */\nexport function discoverGoModule(rootDir: string): string | null {\n let dir = resolve(rootDir);\n for (let i = 0; i < 10; i++) {\n if (existsSync(join(dir, 'go.work'))) return dir;\n if (existsSync(join(dir, 'go.mod'))) return dir;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return findShallow(rootDir, 3);\n}\n\nfunction findShallow(dir: string, maxDepth: number, depth = 0): string | null {\n if (depth > maxDepth) return null;\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const e of entries) {\n if (e.isFile() && (e.name === 'go.work' || e.name === 'go.mod')) {\n return dir;\n }\n }\n for (const e of entries) {\n if (e.isDirectory() && !IGNORE_DIRS.has(e.name) && !e.name.startsWith('.')) {\n const found = findShallow(join(dir, e.name), maxDepth, depth + 1);\n if (found) return found;\n }\n }\n } catch {\n // permission denied — ignore\n }\n return null;\n}\n\nfunction findPackageRoot(startDir: string): string | null {\n let dir = startDir;\n for (let i = 0; i < 10; i++) {\n if (existsSync(join(dir, 'package.json'))) return dir;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\nfunction findMonorepoRoot(startDir: string): string | null {\n let dir = startDir;\n for (let i = 0; i < 12; i++) {\n if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return dir;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\n/**\n * Locate the ctxo-go-analyzer source tree shipped inside the @ctxo/lang-go\n * package. Mirrors the candidate-list strategy from lang-csharp so it works\n * across monorepo dev runs, bundled dist, and consumer node_modules installs.\n */\nexport function findCtxoGoAnalyzerSource(): string | null {\n const pkgRoot = findPackageRoot(import.meta.dirname);\n const monorepoRoot = findMonorepoRoot(import.meta.dirname);\n const candidates = [\n ...(pkgRoot ? [join(pkgRoot, 'tools/ctxo-go-analyzer')] : []),\n ...(monorepoRoot ? [join(monorepoRoot, 'packages/lang-go/tools/ctxo-go-analyzer')] : []),\n join(import.meta.dirname, '../tools/ctxo-go-analyzer'),\n join(import.meta.dirname, '../../tools/ctxo-go-analyzer'),\n join(import.meta.dirname, '../../../tools/ctxo-go-analyzer'),\n join(process.cwd(), 'node_modules/@ctxo/lang-go/tools/ctxo-go-analyzer'),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(join(candidate, 'go.mod')) && existsSync(join(candidate, 'main.go'))) {\n return candidate;\n }\n }\n log.info('ctxo-go-analyzer source not found in any candidate location');\n return null;\n}\n","import { execFileSync } from 'node:child_process';\nimport { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { createLogger } from '../logger.js';\n\nconst log = createLogger('ctxo:lang-go');\nconst BINARY_NAME = platform() === 'win32' ? 'ctxo-go-analyzer.exe' : 'ctxo-go-analyzer';\nconst BUILD_TIMEOUT_MS = 180_000;\n\n/**\n * Hash the analyzer source tree + go version into a cache key, then build\n * the binary into ~/.cache/ctxo/lang-go-analyzer/<key>/ on first use. ADR-013\n * §4 Q2 — postinstall rejected (breaks sandboxed npm); `go run` rejected\n * (3-5s overhead per invocation).\n */\nexport function ensureAnalyzerBinary(sourceDir: string, goVersion: string): string {\n const key = `${hashSourceTree(sourceDir)}-go${goVersion}`;\n const cacheDir = join(homedir(), '.cache', 'ctxo', 'lang-go-analyzer', key);\n const binaryPath = join(cacheDir, BINARY_NAME);\n\n if (existsSync(binaryPath)) return binaryPath;\n\n mkdirSync(cacheDir, { recursive: true });\n log.info(`Building ctxo-go-analyzer (first run): ${binaryPath}`);\n try {\n execFileSync('go', ['build', '-trimpath', '-o', binaryPath, '.'], {\n cwd: sourceDir,\n timeout: BUILD_TIMEOUT_MS,\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n } catch (err) {\n throw new Error(`go build failed: ${(err as Error).message}`);\n }\n if (!existsSync(binaryPath)) {\n throw new Error('go build completed but expected binary is missing');\n }\n return binaryPath;\n}\n\nfunction hashSourceTree(dir: string): string {\n const hash = createHash('sha1');\n const files: string[] = [];\n walk(dir, (path) => {\n if (path.endsWith('.go') || path.endsWith('go.mod') || path.endsWith('go.sum')) {\n files.push(path);\n }\n });\n files.sort();\n for (const f of files) {\n hash.update(f);\n hash.update(readFileSync(f));\n }\n return hash.digest('hex').slice(0, 12);\n}\n\nfunction walk(dir: string, cb: (path: string) => void): void {\n let entries: import('node:fs').Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true }) as import('node:fs').Dirent[];\n } catch {\n return;\n }\n for (const entry of entries) {\n const path = join(dir, String(entry.name));\n if (entry.isDirectory()) walk(path, cb);\n else if (entry.isFile()) cb(path);\n }\n}\n","import { spawn } from 'node:child_process';\nimport { createLogger } from '../logger.js';\n\nconst log = createLogger('ctxo:lang-go');\n\nexport interface AnalyzerSymbol {\n symbolId: string;\n name: string;\n kind: string;\n startLine: number;\n endLine: number;\n startOffset?: number;\n endOffset?: number;\n}\n\nexport interface AnalyzerEdge {\n from: string;\n to: string;\n kind: string;\n typeArgs?: string[];\n}\n\nexport interface AnalyzerFileResult {\n type: 'file';\n file: string;\n symbols: AnalyzerSymbol[];\n edges: AnalyzerEdge[];\n complexity: unknown[];\n}\n\nexport interface AnalyzerBatchResult {\n files: AnalyzerFileResult[];\n dead: string[];\n hasMain: boolean;\n timeout: boolean;\n totalFiles: number;\n elapsed: string;\n}\n\n/**\n * Spawn the analyzer binary in batch mode and parse its JSONL stream.\n * Failures resolve to an empty result so the composite adapter can fall\n * back to tree-sitter without throwing.\n */\nexport async function runBatchAnalyze(\n binaryPath: string,\n moduleRoot: string,\n timeoutMs = 120_000,\n): Promise<AnalyzerBatchResult> {\n return new Promise((resolve) => {\n const empty: AnalyzerBatchResult = {\n files: [], dead: [], hasMain: false, timeout: false, totalFiles: 0, elapsed: '',\n };\n const proc = spawn(binaryPath, ['--root', moduleRoot], {\n stdio: ['ignore', 'pipe', 'pipe'],\n timeout: timeoutMs,\n });\n\n const files: AnalyzerFileResult[] = [];\n let dead: string[] = [];\n let hasMain = false;\n let timeout = false;\n let totalFiles = 0;\n let elapsed = '';\n let stderr = '';\n let buffer = '';\n\n proc.stdout.on('data', (chunk: Buffer) => {\n buffer += chunk.toString();\n let nl: number;\n while ((nl = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, nl).trim();\n buffer = buffer.slice(nl + 1);\n if (!line) continue;\n try {\n const obj = JSON.parse(line);\n switch (obj.type) {\n case 'file':\n files.push(obj as AnalyzerFileResult);\n break;\n case 'dead':\n dead = Array.isArray(obj.symbolIds) ? obj.symbolIds : [];\n hasMain = Boolean(obj.hasMain);\n timeout = Boolean(obj.timeout);\n break;\n case 'progress':\n log.info(String(obj.message ?? ''));\n break;\n case 'summary':\n totalFiles = Number(obj.totalFiles ?? 0);\n elapsed = String(obj.elapsed ?? '');\n break;\n }\n } catch {\n log.error(`Failed to parse JSONL line: ${line.slice(0, 120)}`);\n }\n }\n });\n\n proc.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n\n proc.on('close', (code) => {\n if (code !== 0) {\n log.error(`ctxo-go-analyzer exited with code ${code}: ${stderr.trim().slice(0, 500)}`);\n resolve(empty);\n return;\n }\n resolve({ files, dead, hasMain, timeout, totalFiles, elapsed });\n });\n\n proc.on('error', (err) => {\n log.error(`ctxo-go-analyzer spawn error: ${err.message}`);\n resolve(empty);\n });\n });\n}\n","import GoLanguage from 'tree-sitter-go';\nimport type { SyntaxNode } from 'tree-sitter';\nimport { TreeSitterAdapter } from './tree-sitter-adapter.js';\nimport { createLogger } from './logger.js';\nimport type { SymbolNode, GraphEdge, ComplexityMetrics } from '@ctxo/plugin-api';\n\nconst log = createLogger('ctxo:lang-go');\n\nconst GO_BRANCH_TYPES = [\n 'if_statement', 'for_statement',\n 'expression_switch_statement', 'type_switch_statement',\n 'expression_case', 'type_case',\n 'select_statement', 'communication_case',\n];\n\nexport class GoAdapter extends TreeSitterAdapter {\n readonly extensions = ['.go'] as const;\n\n constructor() {\n super(GoLanguage);\n }\n\n async extractSymbols(filePath: string, source: string): Promise<SymbolNode[]> {\n try {\n const tree = this.parse(source);\n const symbols: SymbolNode[] = [];\n\n for (let i = 0; i < tree.rootNode.childCount; i++) {\n const node = tree.rootNode.child(i)!;\n\n if (node.type === 'function_declaration') {\n const name = node.childForFieldName('name')?.text;\n if (!name) continue;\n const range = this.nodeToLineRange(node);\n symbols.push({\n symbolId: this.buildSymbolId(filePath, name, 'function'),\n name,\n kind: 'function',\n ...range,\n });\n }\n\n if (node.type === 'method_declaration') {\n const methodName = node.childForFieldName('name')?.text;\n if (!methodName) continue;\n const receiverType = this.extractReceiverType(node);\n const qualifiedName = receiverType ? `${receiverType}.${methodName}` : methodName;\n const range = this.nodeToLineRange(node);\n symbols.push({\n symbolId: this.buildSymbolId(filePath, qualifiedName, 'method'),\n name: qualifiedName,\n kind: 'method',\n ...range,\n });\n }\n\n if (node.type === 'type_declaration') {\n this.extractTypeSymbols(node, filePath, symbols);\n }\n }\n\n return symbols;\n } catch (err) {\n log.error(`Symbol extraction failed for ${filePath}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async extractEdges(filePath: string, source: string): Promise<GraphEdge[]> {\n try {\n const tree = this.parse(source);\n const edges: GraphEdge[] = [];\n const firstExportedSymbol = this.findFirstExportedSymbolId(tree.rootNode, filePath);\n if (!firstExportedSymbol) return edges;\n\n for (let i = 0; i < tree.rootNode.childCount; i++) {\n const node = tree.rootNode.child(i)!;\n\n if (node.type === 'import_declaration') {\n this.extractImportEdges(node, filePath, firstExportedSymbol, edges);\n }\n }\n\n return edges;\n } catch (err) {\n log.error(`Edge extraction failed for ${filePath}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async extractComplexity(filePath: string, source: string): Promise<ComplexityMetrics[]> {\n try {\n const tree = this.parse(source);\n const metrics: ComplexityMetrics[] = [];\n\n for (let i = 0; i < tree.rootNode.childCount; i++) {\n const node = tree.rootNode.child(i)!;\n\n if (node.type === 'function_declaration') {\n const name = node.childForFieldName('name')?.text;\n if (!name) continue;\n metrics.push({\n symbolId: this.buildSymbolId(filePath, name, 'function'),\n cyclomatic: this.countCyclomaticComplexity(node, GO_BRANCH_TYPES),\n });\n }\n\n if (node.type === 'method_declaration') {\n const methodName = node.childForFieldName('name')?.text;\n if (!methodName) continue;\n const receiverType = this.extractReceiverType(node);\n const qualifiedName = receiverType ? `${receiverType}.${methodName}` : methodName;\n metrics.push({\n symbolId: this.buildSymbolId(filePath, qualifiedName, 'method'),\n cyclomatic: this.countCyclomaticComplexity(node, GO_BRANCH_TYPES),\n });\n }\n }\n\n return metrics;\n } catch (err) {\n log.error(`Complexity extraction failed for ${filePath}: ${(err as Error).message}`);\n return [];\n }\n }\n\n // ── Private helpers ─────────────────────────────────────────\n\n private isExported(name: string): boolean {\n return name.length > 0 && name[0]! === name[0]!.toUpperCase() && name[0]! !== name[0]!.toLowerCase();\n }\n\n private extractReceiverType(methodNode: SyntaxNode): string | undefined {\n // method_declaration has parameter_list as first child (receiver)\n const params = methodNode.child(1);\n if (params?.type !== 'parameter_list') return undefined;\n\n for (let i = 0; i < params.childCount; i++) {\n const param = params.child(i)!;\n if (param.type === 'parameter_declaration') {\n // Find type identifier — may be pointer (*Type) or plain (Type)\n const typeNode = param.childForFieldName('type');\n if (typeNode) {\n const text = typeNode.text;\n return text.replace(/^\\*/, '');\n }\n }\n }\n return undefined;\n }\n\n private extractTypeSymbols(typeDecl: SyntaxNode, filePath: string, symbols: SymbolNode[]): void {\n for (let i = 0; i < typeDecl.childCount; i++) {\n const spec = typeDecl.child(i)!;\n if (spec.type !== 'type_spec') continue;\n\n const name = spec.childForFieldName('name')?.text;\n if (!name || !this.isExported(name)) continue;\n\n // Determine kind from the type body\n const typeBody = spec.childForFieldName('type');\n let kind: 'class' | 'interface' | 'type' = 'type';\n if (typeBody?.type === 'struct_type') kind = 'class';\n else if (typeBody?.type === 'interface_type') kind = 'interface';\n\n const range = this.nodeToLineRange(spec);\n symbols.push({\n symbolId: this.buildSymbolId(filePath, name, kind),\n name,\n kind,\n ...range,\n });\n }\n }\n\n private extractImportEdges(\n importDecl: SyntaxNode,\n _filePath: string,\n fromSymbol: string,\n edges: GraphEdge[],\n ): void {\n const visit = (node: SyntaxNode) => {\n if (node.type === 'import_spec') {\n const pathNode = node.childForFieldName('path') ?? node.child(0);\n if (pathNode) {\n const importPath = pathNode.text.replace(/\"/g, '');\n edges.push({\n from: fromSymbol,\n to: `${importPath}::${importPath.split('/').pop()}::variable`,\n kind: 'imports',\n });\n }\n }\n for (let i = 0; i < node.childCount; i++) {\n visit(node.child(i)!);\n }\n };\n visit(importDecl);\n }\n\n private findFirstExportedSymbolId(rootNode: SyntaxNode, filePath: string): string | undefined {\n for (let i = 0; i < rootNode.childCount; i++) {\n const node = rootNode.child(i)!;\n\n if (node.type === 'function_declaration') {\n const name = node.childForFieldName('name')?.text;\n if (name && this.isExported(name)) return this.buildSymbolId(filePath, name, 'function');\n }\n if (node.type === 'type_declaration') {\n for (let j = 0; j < node.childCount; j++) {\n const spec = node.child(j)!;\n if (spec.type === 'type_spec') {\n const name = spec.childForFieldName('name')?.text;\n if (name && this.isExported(name)) {\n const typeBody = spec.childForFieldName('type');\n const kind = typeBody?.type === 'struct_type' ? 'class' : typeBody?.type === 'interface_type' ? 'interface' : 'type';\n return this.buildSymbolId(filePath, name, kind);\n }\n }\n }\n }\n }\n return undefined;\n }\n}\n","import Parser from 'tree-sitter';\nimport type { Tree, SyntaxNode } from 'tree-sitter';\n// Grammars (tree-sitter-go etc.) ship their own Language type which can drift\n// against tree-sitter's parameter type across major versions. Accept any\n// structurally compatible value and hand it to the parser as the runtime API\n// expects (the parser does its own validation).\ntype Language = Parameters<InstanceType<typeof Parser>['setLanguage']>[0] | object;\nimport { extname } from 'node:path';\nimport type { SymbolNode, GraphEdge, ComplexityMetrics, SymbolKind, ILanguageAdapter } from '@ctxo/plugin-api';\n\nexport abstract class TreeSitterAdapter implements ILanguageAdapter {\n abstract readonly extensions: readonly string[];\n readonly tier = 'syntax' as const;\n\n protected parser: Parser;\n protected symbolRegistry = new Map<string, SymbolKind>();\n\n constructor(language: Language) {\n this.parser = new Parser();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.parser.setLanguage(language as any);\n }\n\n isSupported(filePath: string): boolean {\n const ext = extname(filePath).toLowerCase();\n return (this.extensions as readonly string[]).includes(ext);\n }\n\n setSymbolRegistry(registry: Map<string, SymbolKind>): void {\n this.symbolRegistry = registry;\n }\n\n protected parse(source: string): Tree {\n return this.parser.parse(source);\n }\n\n protected buildSymbolId(filePath: string, name: string, kind: SymbolKind): string {\n return `${filePath}::${name}::${kind}`;\n }\n\n protected nodeToLineRange(node: SyntaxNode): {\n startLine: number;\n endLine: number;\n startOffset: number;\n endOffset: number;\n } {\n return {\n startLine: node.startPosition.row,\n endLine: node.endPosition.row,\n startOffset: node.startIndex,\n endOffset: node.endIndex,\n };\n }\n\n protected countCyclomaticComplexity(node: SyntaxNode, branchTypes: string[]): number {\n let complexity = 1;\n const visit = (n: SyntaxNode) => {\n if (branchTypes.includes(n.type)) {\n complexity++;\n }\n for (let i = 0; i < n.childCount; i++) {\n visit(n.child(i)!);\n }\n };\n visit(node);\n return complexity;\n }\n\n abstract extractSymbols(filePath: string, source: string): Promise<SymbolNode[]>;\n abstract extractEdges(filePath: string, source: string): Promise<GraphEdge[]>;\n abstract extractComplexity(filePath: string, source: string): Promise<ComplexityMetrics[]>;\n}\n","import type { SymbolNode, GraphEdge, ComplexityMetrics, SymbolKind, ILanguageAdapter } from '@ctxo/plugin-api';\nimport { GoAnalyzerAdapter } from './analyzer/analyzer-adapter.js';\nimport { GoAdapter } from './go-adapter.js';\nimport { createLogger } from './logger.js';\n\nconst log = createLogger('ctxo:lang-go');\n\n/**\n * Picks between the full-tier ctxo-go-analyzer and the syntax-tier\n * tree-sitter adapter at initialize() time. Symbols and edges come from\n * whichever delegate is active; complexity is ALWAYS sourced from\n * tree-sitter because the binary intentionally emits empty complexity.\n */\nexport class GoCompositeAdapter implements ILanguageAdapter {\n private analyzer: GoAnalyzerAdapter | null = null;\n private treeSitter: GoAdapter;\n\n constructor() {\n this.treeSitter = new GoAdapter();\n }\n\n async initialize(rootDir: string): Promise<void> {\n try {\n const analyzer = new GoAnalyzerAdapter();\n await analyzer.initialize(rootDir);\n if (analyzer.isReady()) {\n this.analyzer = analyzer;\n log.info('Go plugin: ctxo-go-analyzer full-tier active');\n return;\n }\n await analyzer.dispose();\n } catch (err) {\n log.warn(`Go analyzer unavailable: ${(err as Error).message}`);\n }\n log.info('Go plugin: tree-sitter syntax-tier active (install Go 1.22+ for full tier)');\n }\n\n async dispose(): Promise<void> {\n if (this.analyzer) await this.analyzer.dispose();\n }\n\n extractSymbols(filePath: string, source: string): Promise<SymbolNode[]> {\n return (this.analyzer ?? this.treeSitter).extractSymbols(filePath, source);\n }\n\n extractEdges(filePath: string, source: string): Promise<GraphEdge[]> {\n return (this.analyzer ?? this.treeSitter).extractEdges(filePath, source);\n }\n\n extractComplexity(filePath: string, source: string): Promise<ComplexityMetrics[]> {\n // Always tree-sitter — analyzer emits empty complexity by design.\n return this.treeSitter.extractComplexity(filePath, source);\n }\n\n isSupported(filePath: string): boolean {\n return filePath.toLowerCase().endsWith('.go');\n }\n\n setSymbolRegistry(registry: Map<string, SymbolKind>): void {\n this.treeSitter.setSymbolRegistry?.(registry);\n }\n\n /** Exposed for cli optimizations. Null when running in syntax tier. */\n getAnalyzerDelegate(): GoAnalyzerAdapter | null {\n return this.analyzer;\n }\n\n getTier(): 'full' | 'syntax' | 'unavailable' {\n if (this.analyzer) return 'full';\n if (this.treeSitter) return 'syntax';\n return 'unavailable';\n }\n}\n","import type { CtxoLanguagePlugin, PluginContext, ILanguageAdapter } from '@ctxo/plugin-api';\nimport { GoCompositeAdapter } from './composite-adapter.js';\n\nexport { GoAdapter } from './go-adapter.js';\nexport { GoAnalyzerAdapter } from './analyzer/analyzer-adapter.js';\nexport { GoCompositeAdapter } from './composite-adapter.js';\nexport { TreeSitterAdapter } from './tree-sitter-adapter.js';\n\nconst VERSION = '0.8.0-alpha.0';\n\nexport const plugin: CtxoLanguagePlugin = {\n apiVersion: '1',\n id: 'go',\n name: 'Go (ctxo-go-analyzer + tree-sitter)',\n version: VERSION,\n extensions: ['.go'],\n tier: 'full',\n createAdapter(_ctx: PluginContext): ILanguageAdapter {\n return new GoCompositeAdapter();\n },\n};\n\nexport default plugin;\n"],"mappings":"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","names":["log","execFileSync","existsSync","readdirSync","join","log","join","existsSync","execFileSync","readdirSync","log","resolve","log","log","log"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ctxo/lang-go",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Ctxo Go language plugin (tree-sitter,
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Ctxo Go language plugin (ctxo-go-analyzer + tree-sitter, full tier)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20"
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist/",
|
|
20
|
+
"tools/ctxo-go-analyzer/**/*.go",
|
|
21
|
+
"tools/ctxo-go-analyzer/go.mod",
|
|
22
|
+
"tools/ctxo-go-analyzer/go.sum",
|
|
20
23
|
"README.md"
|
|
21
24
|
],
|
|
22
25
|
"keywords": [
|
|
@@ -30,18 +33,18 @@
|
|
|
30
33
|
"author": "Alper Hankendi",
|
|
31
34
|
"license": "MIT",
|
|
32
35
|
"dependencies": {
|
|
33
|
-
"tree-sitter": "^0.
|
|
34
|
-
"tree-sitter-go": "^0.
|
|
36
|
+
"tree-sitter": "^0.22.4",
|
|
37
|
+
"tree-sitter-go": "^0.23.4"
|
|
35
38
|
},
|
|
36
39
|
"peerDependencies": {
|
|
37
|
-
"@ctxo/plugin-api": "^0.7.
|
|
40
|
+
"@ctxo/plugin-api": "^0.7.1"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
40
43
|
"@types/node": "^22.15.3",
|
|
41
44
|
"tsup": "^8.4.0",
|
|
42
45
|
"typescript": "^5.8.3",
|
|
43
46
|
"vitest": "^3.1.3",
|
|
44
|
-
"@ctxo/plugin-api": "0.7.
|
|
47
|
+
"@ctxo/plugin-api": "0.7.1"
|
|
45
48
|
},
|
|
46
49
|
"repository": {
|
|
47
50
|
"type": "git",
|
|
@@ -52,6 +55,10 @@
|
|
|
52
55
|
"url": "https://github.com/alperhankendi/Ctxo/issues"
|
|
53
56
|
},
|
|
54
57
|
"homepage": "https://github.com/alperhankendi/Ctxo#readme",
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public",
|
|
60
|
+
"provenance": true
|
|
61
|
+
},
|
|
55
62
|
"scripts": {
|
|
56
63
|
"build": "tsup",
|
|
57
64
|
"typecheck": "tsc --noEmit",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
2
|
+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
3
|
+
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
|
4
|
+
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
|
5
|
+
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
|
6
|
+
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
|
7
|
+
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
|
8
|
+
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// Package edges extracts type-resolved dependency edges from loaded Go
|
|
2
|
+
// packages. This pass covers `calls` and `uses` — the edges whose targets
|
|
3
|
+
// can be resolved straight from go/types without SSA or reachability info.
|
|
4
|
+
// `implements` (Step 4), `extends` (Step 5), and refinements driven by RTA
|
|
5
|
+
// live in sibling packages.
|
|
6
|
+
package edges
|
|
7
|
+
|
|
8
|
+
import (
|
|
9
|
+
"go/ast"
|
|
10
|
+
"go/token"
|
|
11
|
+
"go/types"
|
|
12
|
+
"path/filepath"
|
|
13
|
+
"strings"
|
|
14
|
+
|
|
15
|
+
"golang.org/x/tools/go/packages"
|
|
16
|
+
|
|
17
|
+
"github.com/alperhankendi/ctxo/tools/ctxo-go-analyzer/internal/emit"
|
|
18
|
+
"github.com/alperhankendi/ctxo/tools/ctxo-go-analyzer/internal/symbols"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
// Extractor turns AST identifiers into Ctxo symbol-id edges by looking up the
|
|
22
|
+
// resolved *types.Object and mapping it back to the loaded package set. It is
|
|
23
|
+
// safe to reuse across files within a single analyzer invocation.
|
|
24
|
+
type Extractor struct {
|
|
25
|
+
rootDir string
|
|
26
|
+
pkgByPath map[string]*packages.Package
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// NewExtractor builds a package-path index across the loaded dependency
|
|
30
|
+
// closure so edge targets in imported internal packages can be resolved.
|
|
31
|
+
func NewExtractor(rootDir string, roots []*packages.Package) *Extractor {
|
|
32
|
+
idx := make(map[string]*packages.Package)
|
|
33
|
+
packages.Visit(roots, nil, func(p *packages.Package) {
|
|
34
|
+
if p.PkgPath != "" {
|
|
35
|
+
idx[p.PkgPath] = p
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
return &Extractor{rootDir: rootDir, pkgByPath: idx}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Extract walks one parsed file and returns the edges whose source is a
|
|
42
|
+
// declaration in that file. Duplicate edges (same from/to/kind/typeArgs) are
|
|
43
|
+
// deduplicated so repeated call sites count once.
|
|
44
|
+
func (e *Extractor) Extract(pkg *packages.Package, file *ast.File) []emit.Edge {
|
|
45
|
+
if pkg == nil || pkg.Fset == nil || pkg.TypesInfo == nil {
|
|
46
|
+
return nil
|
|
47
|
+
}
|
|
48
|
+
relFile := relativeFile(pkg.Fset, file, e.rootDir)
|
|
49
|
+
if relFile == "" {
|
|
50
|
+
return nil
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
seen := make(map[edgeKey]struct{})
|
|
54
|
+
var out []emit.Edge
|
|
55
|
+
addEdge := func(from, to, kind string, typeArgs []string) {
|
|
56
|
+
if from == "" || to == "" || from == to {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
key := edgeKey{from: from, to: to, kind: kind, typeArgs: strings.Join(typeArgs, ",")}
|
|
60
|
+
if _, dup := seen[key]; dup {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
seen[key] = struct{}{}
|
|
64
|
+
out = append(out, emit.Edge{From: from, To: to, Kind: kind, TypeArgs: typeArgs})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for _, decl := range file.Decls {
|
|
68
|
+
fd, ok := decl.(*ast.FuncDecl)
|
|
69
|
+
if !ok || fd.Name == nil || fd.Name.Name == "_" {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
fromID := funcDeclSymbolID(relFile, fd)
|
|
73
|
+
if fromID == "" {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
inspect := func(node ast.Node) {
|
|
78
|
+
if node == nil {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
82
|
+
switch x := n.(type) {
|
|
83
|
+
case *ast.CallExpr:
|
|
84
|
+
e.emitCallEdge(pkg, x, fromID, addEdge)
|
|
85
|
+
case *ast.Ident:
|
|
86
|
+
e.emitUsesFromIdent(pkg, x, fromID, addEdge)
|
|
87
|
+
}
|
|
88
|
+
return true
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if fd.Recv != nil {
|
|
93
|
+
for _, f := range fd.Recv.List {
|
|
94
|
+
inspect(f.Type)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if fd.Type != nil {
|
|
98
|
+
if fd.Type.Params != nil {
|
|
99
|
+
for _, f := range fd.Type.Params.List {
|
|
100
|
+
inspect(f.Type)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if fd.Type.Results != nil {
|
|
104
|
+
for _, f := range fd.Type.Results.List {
|
|
105
|
+
inspect(f.Type)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
inspect(fd.Body)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return out
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type edgeKey struct {
|
|
116
|
+
from, to, kind, typeArgs string
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func (e *Extractor) emitCallEdge(pkg *packages.Package, call *ast.CallExpr, fromID string, add func(string, string, string, []string)) {
|
|
120
|
+
ident := calleeIdent(call.Fun)
|
|
121
|
+
if ident == nil {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
obj := pkg.TypesInfo.Uses[ident]
|
|
125
|
+
if obj == nil {
|
|
126
|
+
obj = pkg.TypesInfo.Defs[ident]
|
|
127
|
+
}
|
|
128
|
+
if _, ok := obj.(*types.Func); !ok {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
toID, ok := e.symbolIDForObject(obj)
|
|
132
|
+
if !ok {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
add(fromID, toID, "calls", typeArgsFor(pkg, ident))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
func (e *Extractor) emitUsesFromIdent(pkg *packages.Package, ident *ast.Ident, fromID string, add func(string, string, string, []string)) {
|
|
139
|
+
obj := pkg.TypesInfo.Uses[ident]
|
|
140
|
+
if obj == nil {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
switch obj.(type) {
|
|
144
|
+
case *types.TypeName:
|
|
145
|
+
// type reference — always package-level, always a uses edge candidate
|
|
146
|
+
case *types.Var, *types.Const:
|
|
147
|
+
if obj.Pkg() == nil || obj.Pkg().Scope().Lookup(obj.Name()) != obj {
|
|
148
|
+
// field access, local var, or unexported import alias
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
default:
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
toID, ok := e.symbolIDForObject(obj)
|
|
155
|
+
if !ok {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
add(fromID, toID, "uses", typeArgsFor(pkg, ident))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// symbolIDForObject maps a resolved types.Object to a Ctxo symbol id, scoped
|
|
162
|
+
// to the module root. Returns (id, true) only when the declaration lives
|
|
163
|
+
// inside the analyzed module; stdlib and external deps are intentionally
|
|
164
|
+
// skipped to keep the graph focused on intra-module coupling.
|
|
165
|
+
func (e *Extractor) symbolIDForObject(obj types.Object) (string, bool) {
|
|
166
|
+
if obj == nil || obj.Pkg() == nil {
|
|
167
|
+
return "", false
|
|
168
|
+
}
|
|
169
|
+
target := e.pkgByPath[obj.Pkg().Path()]
|
|
170
|
+
if target == nil || target.Fset == nil {
|
|
171
|
+
return "", false
|
|
172
|
+
}
|
|
173
|
+
pos := target.Fset.Position(obj.Pos())
|
|
174
|
+
if !pos.IsValid() {
|
|
175
|
+
return "", false
|
|
176
|
+
}
|
|
177
|
+
rel, err := filepath.Rel(e.rootDir, pos.Filename)
|
|
178
|
+
if err != nil || strings.HasPrefix(rel, "..") {
|
|
179
|
+
return "", false
|
|
180
|
+
}
|
|
181
|
+
name := objectName(obj)
|
|
182
|
+
kind := objectKind(obj)
|
|
183
|
+
if name == "" || kind == "" {
|
|
184
|
+
return "", false
|
|
185
|
+
}
|
|
186
|
+
return symbols.SymbolID(filepath.ToSlash(rel), name, kind), true
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func objectName(obj types.Object) string {
|
|
190
|
+
if fn, ok := obj.(*types.Func); ok {
|
|
191
|
+
if sig, ok := fn.Type().(*types.Signature); ok && sig.Recv() != nil {
|
|
192
|
+
if recv := receiverTypeName(sig.Recv().Type()); recv != "" {
|
|
193
|
+
return recv + "." + fn.Name()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return obj.Name()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
func objectKind(obj types.Object) string {
|
|
201
|
+
switch o := obj.(type) {
|
|
202
|
+
case *types.Func:
|
|
203
|
+
if sig, ok := o.Type().(*types.Signature); ok && sig.Recv() != nil {
|
|
204
|
+
return "method"
|
|
205
|
+
}
|
|
206
|
+
return "function"
|
|
207
|
+
case *types.TypeName:
|
|
208
|
+
switch o.Type().Underlying().(type) {
|
|
209
|
+
case *types.Struct:
|
|
210
|
+
return "class"
|
|
211
|
+
case *types.Interface:
|
|
212
|
+
return "interface"
|
|
213
|
+
default:
|
|
214
|
+
return "type"
|
|
215
|
+
}
|
|
216
|
+
case *types.Var, *types.Const:
|
|
217
|
+
return "variable"
|
|
218
|
+
}
|
|
219
|
+
return ""
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
func receiverTypeName(t types.Type) string {
|
|
223
|
+
if p, ok := t.(*types.Pointer); ok {
|
|
224
|
+
t = p.Elem()
|
|
225
|
+
}
|
|
226
|
+
if n, ok := t.(*types.Named); ok {
|
|
227
|
+
return n.Obj().Name()
|
|
228
|
+
}
|
|
229
|
+
return ""
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func calleeIdent(expr ast.Expr) *ast.Ident {
|
|
233
|
+
switch x := expr.(type) {
|
|
234
|
+
case *ast.Ident:
|
|
235
|
+
return x
|
|
236
|
+
case *ast.SelectorExpr:
|
|
237
|
+
return x.Sel
|
|
238
|
+
case *ast.IndexExpr:
|
|
239
|
+
return calleeIdent(x.X)
|
|
240
|
+
case *ast.IndexListExpr:
|
|
241
|
+
return calleeIdent(x.X)
|
|
242
|
+
case *ast.ParenExpr:
|
|
243
|
+
return calleeIdent(x.X)
|
|
244
|
+
}
|
|
245
|
+
return nil
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
func typeArgsFor(pkg *packages.Package, ident *ast.Ident) []string {
|
|
249
|
+
if pkg.TypesInfo == nil || pkg.TypesInfo.Instances == nil {
|
|
250
|
+
return nil
|
|
251
|
+
}
|
|
252
|
+
inst, ok := pkg.TypesInfo.Instances[ident]
|
|
253
|
+
if !ok || inst.TypeArgs == nil {
|
|
254
|
+
return nil
|
|
255
|
+
}
|
|
256
|
+
n := inst.TypeArgs.Len()
|
|
257
|
+
if n == 0 {
|
|
258
|
+
return nil
|
|
259
|
+
}
|
|
260
|
+
out := make([]string, n)
|
|
261
|
+
for i := 0; i < n; i++ {
|
|
262
|
+
out[i] = inst.TypeArgs.At(i).String()
|
|
263
|
+
}
|
|
264
|
+
return out
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
func funcDeclSymbolID(relFile string, fd *ast.FuncDecl) string {
|
|
268
|
+
if fd.Recv != nil && len(fd.Recv.List) > 0 {
|
|
269
|
+
if recv := receiverASTName(fd.Recv.List[0].Type); recv != "" {
|
|
270
|
+
return symbols.SymbolID(relFile, recv+"."+fd.Name.Name, "method")
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return symbols.SymbolID(relFile, fd.Name.Name, "function")
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
func receiverASTName(expr ast.Expr) string {
|
|
277
|
+
for {
|
|
278
|
+
switch x := expr.(type) {
|
|
279
|
+
case *ast.StarExpr:
|
|
280
|
+
expr = x.X
|
|
281
|
+
case *ast.IndexExpr:
|
|
282
|
+
expr = x.X
|
|
283
|
+
case *ast.IndexListExpr:
|
|
284
|
+
expr = x.X
|
|
285
|
+
case *ast.Ident:
|
|
286
|
+
return x.Name
|
|
287
|
+
default:
|
|
288
|
+
return ""
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
func relativeFile(fset *token.FileSet, file *ast.File, rootDir string) string {
|
|
294
|
+
tf := fset.File(file.Pos())
|
|
295
|
+
if tf == nil {
|
|
296
|
+
return ""
|
|
297
|
+
}
|
|
298
|
+
rel, err := filepath.Rel(rootDir, tf.Name())
|
|
299
|
+
if err != nil || strings.HasPrefix(rel, "..") {
|
|
300
|
+
return ""
|
|
301
|
+
}
|
|
302
|
+
return filepath.ToSlash(rel)
|
|
303
|
+
}
|