UNPKG

codn_ts

Version:

智能代码分析工具 - 支持语义搜索、调用链分析和代码结构可视化,对大模型/AI agent 友好

448 lines 18.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.showFileRange = showFileRange; exports.showClassDefinition = showClassDefinition; const path = __importStar(require("path")); const fs = __importStar(require("fs")); const glob = __importStar(require("glob")); const lsp_core_1 = require("./utils/lsp_core"); const symbol_utils_1 = require("./utils/symbol_utils"); const file_utils_1 = require("./utils/file_utils"); // 读取文件内容 function readFileLines(filePath) { const absPath = path.resolve(filePath); if (!fs.existsSync(absPath)) throw new Error(`文件不存在: ${filePath}`); return fs.readFileSync(absPath, "utf-8").split("\n"); } // 展示文件区间 function showFileRange(options) { const { file, startLine = 1, endLine = -1, contextLines = 0 } = options; const lines = readFileLines(file); const totalLines = lines.length; const actualStart = Math.max(1, startLine - contextLines); const actualEnd = endLine === -1 ? totalLines : Math.min(totalLines, endLine + contextLines); const content = lines.slice(actualStart - 1, actualEnd).join("\n"); return { file, startLine: actualStart, endLine: actualEnd, content, totalLines, }; } function findClassSymbol(symbols, className) { for (const s of symbols) { if (s.name === className && s.kind === lsp_core_1.SymbolKind.Class) return s; if (s.children && s.children.length > 0) { const found = findClassSymbol(s.children, className); if (found) return found; } } return undefined; } function formatMethodSignature(method) { // 优先输出 detail 字段(如 foo(a: number): string),否则输出 name if (method.detail && typeof method.detail === "string") { return `${method.name}${method.detail.startsWith("(") ? "" : " "}${method.detail}`; } return method.name; } function collectMethods(symbol) { let methods = []; if (symbol.kind === lsp_core_1.SymbolKind.Method || symbol.kind === lsp_core_1.SymbolKind.Constructor) { methods.push(formatMethodSignature(symbol)); } if (symbol.children && symbol.children.length > 0) { for (const child of symbol.children) { methods = methods.concat(collectMethods(child)); } } return methods; } function collectInit(symbol) { let items = []; if (symbol.kind === lsp_core_1.SymbolKind.Property) { items.push(`${symbol.name}${symbol.detail ? ": " + symbol.detail : ""}`); } if (symbol.kind === lsp_core_1.SymbolKind.Constructor) { items.push(formatMethodSignature(symbol)); } if (symbol.children && symbol.children.length > 0) { for (const child of symbol.children) { items = items.concat(collectInit(child)); } } return items; } function collectMethodsFlat(symbols, className) { return symbols .filter((s) => s.containerName === className && (s.kind === lsp_core_1.SymbolKind.Method || s.kind === lsp_core_1.SymbolKind.Constructor)) .map(formatMethodSignature); } function collectInitFlat(symbols, className) { return symbols .filter((s) => s.containerName === className && (s.kind === lsp_core_1.SymbolKind.Property || s.kind === lsp_core_1.SymbolKind.Constructor)) .map((s) => s.kind === lsp_core_1.SymbolKind.Constructor ? formatMethodSignature(s) : `${s.name}${s.detail ? ": " + s.detail : ""}`); } async function getMethodSignature(lsp, file, symbol) { const absPath = path.resolve(file); const uri = (0, file_utils_1.toFileUri)(absPath); const content = fs.readFileSync(absPath, "utf-8"); let pos = { line: symbol.location.range.start.line, character: symbol.location.range.start.character, }; // 用 checkRealFuncChar 精确定位方法名 try { const lines = content.split("\n"); // 获取 symbol 的完整代码块(多行,适配 Python 装饰器) let blockLines = lines .slice(symbol.location.range.start.line, symbol.location.range.end.line + 1) .join("\n"); const lineText = blockLines; const realPos = (0, symbol_utils_1.checkRealFuncChar)(lineText, pos.line, pos.character, symbol.name, symbol.kind, uri); if (realPos && realPos.line !== undefined && realPos.character !== undefined) { pos = { line: realPos.line, character: realPos.character }; } } catch (e) { console.error("[checkRealFuncChar error]", e); } let lineNum = pos.line; let charNum = pos.character; try { const hover = await lsp.sendHover(uri, pos); if (hover === undefined || hover === null) { // console.log('[DEBUG] hover result: null or undefined'); } else { // console.log('[DEBUG] hover result:', JSON.stringify(hover, null, 2)); } if (hover && hover.contents) { // 适配 TS/JS: 提取 markdown 代码块中的方法签名 const extractTSSignature = (text) => { // 匹配 ```typescript ... ``` 或 (method) ... const mdMatch = text.match(/```[a-zA-Z]*\n([\s\S]*?)```/); if (mdMatch && mdMatch[1]) { return (mdMatch[1] .split("\n") .map((l) => l.trim()) .filter(Boolean)[0] || ""); } // fallback: 匹配 (method) ... const methodMatch = text.match(/\(method\)[^\n]+/); if (methodMatch) return methodMatch[0]; // fallback: 第一行 return text.split("\n")[0].trim(); }; const extractDefSignature = (text, symbol, filePath) => { // TS/JS: markdown 代码块优先 if (/```/.test(text)) { const sig = extractTSSignature(text); let loc = ""; if (filePath) { const relPath = path.relative(process.cwd(), filePath); loc = `[${relPath}:${lineNum + 1}:${charNum + 1}]`; } else { loc = `[line:${lineNum + 1}, char:${charNum + 1}]`; } return sig + " " + loc; } // Python: def ... 多行拼接 text = text.replace(/^\(method\)\s*/, ""); const defMatch = text.match(/def\s+\w+\s*\(/); if (!defMatch) { let loc = ""; if (filePath) { const relPath = path.relative(process.cwd(), filePath); loc = `[${relPath}:${lineNum + 1}:${charNum + 1}]`; } else { loc = `[line:${lineNum + 1}, char:${charNum + 1}]`; } return text.split("\n")[0].trim() + " " + loc; } const defStart = text.indexOf(defMatch[0]); let lines = text.slice(defStart).split(/\r?\n/); let sig = ""; let parenCount = 0; let started = false; for (let line of lines) { line = line.replace(/#.*/, "").replace(/:[^,\)]+/g, ""); if (!started && /def\s+\w+\s*\(/.test(line)) started = true; if (started) { sig += line.trim() + " "; for (const ch of line) { if (ch === "(") parenCount++; if (ch === ")") parenCount--; } if (parenCount <= 0 && line.includes(")")) break; } } sig = sig .replace(/\s+/g, " ") .replace(/\s*\)\s*/, ")") .trim(); if (!sig.endsWith(":")) sig += ":"; let loc = ""; if (filePath) { const relPath = path.relative(process.cwd(), filePath); loc = `[${relPath}:${lineNum + 1}:${charNum + 1}]`; } else { loc = `[line:${lineNum + 1}, char:${charNum + 1}]`; } sig += " " + loc; return sig; }; if (typeof hover.contents === "string") { return extractDefSignature(hover.contents, symbol, absPath); } if (Array.isArray(hover.contents)) { for (const c of hover.contents) { if (typeof c === "string") { const res = extractDefSignature(c, symbol, absPath); if (res) return res; } else if (c.value) { const res = extractDefSignature(c.value, symbol, absPath); if (res) return res; } } const relPath = path.relative(process.cwd(), absPath); return (hover.contents .map((c) => typeof c === "string" ? c : c.value || c.language + ": " + c.value) .join("\n") + ` [${relPath}:${lineNum + 1}:${charNum + 1}]`); } if (hover.contents.value) { return extractDefSignature(hover.contents.value, symbol, absPath); } if (hover.contents.language && hover.contents.value) { return extractDefSignature(hover.contents.value, symbol, absPath); } } // 即使没有内容也要输出 console.log("[DEBUG] hover result (no contents):", JSON.stringify(hover, null, 2)); } catch (e) { console.error("[sendHover error]", e); } // hover 失败时也要返回 name + 行列号 return symbol.name + ` [line:${lineNum}, char:${charNum}]`; } // 展示类定义(语义化) async function showClassDefinition(options) { let { file, symbol, mode = "full" } = options; if (!symbol) throw new Error("必须指定 symbol 名称"); let absPath = path.resolve(file); let filesToSearch = []; let firstClassSymbol = null; let firstFile = ""; if (fs.existsSync(absPath) && fs.statSync(absPath).isDirectory()) { filesToSearch = glob.sync("**/*.{ts,js,tsx,jsx,py}", { cwd: absPath, absolute: true, }); filesToSearch = filesToSearch.filter((f) => fs.readFileSync(f, "utf-8").includes(symbol)); if (filesToSearch.length === 0) { throw new Error(`目录 ${file} 下未找到包含符号 ${symbol} 的文件`); } } else { filesToSearch = [absPath]; } if (mode === "methods") { // 新实现:每个文件单独分组展示方法或函数 let fileMethodMap = {}; let total = 0; for (const f of filesToSearch) { const absPathF = f; const projectRoot = process.cwd(); let lspLang = "typescript"; const ext = path.extname(absPathF).toLowerCase(); if ([".py"].includes(ext)) { lspLang = "python"; } else if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) { lspLang = "typescript"; } else { lspLang = "typescript"; } const lsp = new lsp_core_1.LSPClient((0, file_utils_1.toFileUri)(projectRoot)); await lsp.start(lspLang); try { const content = fs.readFileSync(absPathF, "utf-8"); await lsp.sendDidOpen((0, file_utils_1.toFileUri)(absPathF), content, lspLang); const symbols = await lsp.sendDocumentSymbol((0, file_utils_1.toFileUri)(absPathF)); if (!symbols) continue; const classSymbol = findClassSymbol(symbols, symbol); if (classSymbol) { // 只取一层,containerName 等于类名且 kind 为 Method/Constructor 的方法 const methodList = symbols.filter((s) => s.containerName === symbol && (s.kind === lsp_core_1.SymbolKind.Method || s.kind === lsp_core_1.SymbolKind.Constructor)); const signatures = await Promise.all(methodList.map((m) => getMethodSignature(lsp, absPathF, m))); const relFile = path.relative(process.cwd(), absPathF); fileMethodMap[relFile] = signatures; total += signatures.length; if (!firstClassSymbol) { firstClassSymbol = classSymbol; firstFile = absPathF; } } else { // 新增:查找同名顶层函数 const funcSymbols = symbols.filter((s) => s.kind === lsp_core_1.SymbolKind.Function && s.name === symbol); if (funcSymbols.length > 0) { const signatures = await Promise.all(funcSymbols.map((m) => getMethodSignature(lsp, absPathF, m))); const relFile = path.relative(process.cwd(), absPathF); fileMethodMap[relFile] = signatures; total += signatures.length; } } } finally { await lsp.shutdown(); } } // 拼接输出 let content = Object.entries(fileMethodMap) .filter(([_, methods]) => methods.length > 0) .map(([file, methods]) => { return `文件: ${file}\n` + methods.join("\n"); }) .join("\n\n"); return { file: filesToSearch .map((f) => path.relative(process.cwd(), f)) .join(", "), symbol, mode, startLine: 0, endLine: 0, content, totalLines: total, }; } // 修正:遍历所有文件,找到第一个classSymbol就赋值并break for (const f of filesToSearch) { const absPathF = f; const projectRoot = process.cwd(); let lspLang = "typescript"; const ext = path.extname(absPathF).toLowerCase(); if ([".py"].includes(ext)) { lspLang = "python"; } else if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) { lspLang = "typescript"; } else { lspLang = "typescript"; } const lsp = new lsp_core_1.LSPClient((0, file_utils_1.toFileUri)(projectRoot)); await lsp.start(lspLang); try { const content = fs.readFileSync(absPathF, "utf-8"); await lsp.sendDidOpen((0, file_utils_1.toFileUri)(absPathF), content, lspLang); const symbols = await lsp.sendDocumentSymbol((0, file_utils_1.toFileUri)(absPathF)); if (!symbols) continue; const classSymbol = findClassSymbol(symbols, symbol); if (classSymbol) { firstClassSymbol = classSymbol; firstFile = absPathF; await lsp.sendDidClose((0, file_utils_1.toFileUri)(absPathF)); break; } await lsp.sendDidClose((0, file_utils_1.toFileUri)(absPathF)); } finally { await lsp.shutdown(); } } if (!firstClassSymbol) throw new Error(`未找到符号 ${symbol} 的定义`); const content = fs.readFileSync(firstFile, "utf-8"); const lines = content.split("\n"); let startLine = 1; let endLine = lines.length; if (firstClassSymbol && firstClassSymbol.location) { startLine = firstClassSymbol.location.range.start.line + 1; endLine = firstClassSymbol.location.range.end.line + 1; } let resultContent = ""; if (mode === "full") { resultContent = lines.slice(startLine - 1, endLine).join("\n"); } else if (mode === "init") { const initList = collectInitFlat(firstClassSymbol.children || [], symbol); resultContent = initList.join("\n"); } return { file: path.relative(process.cwd(), firstFile), symbol, mode, startLine, endLine, content: resultContent, totalLines: lines.length, }; } //# sourceMappingURL=show.js.map