UNPKG

codn_ts

Version:

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

401 lines 14.6 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.searchProject = searchProject; const lsp_core_1 = require("./utils/lsp_core"); const os_utils_1 = require("./utils/os_utils"); const path = __importStar(require("path")); const fs = __importStar(require("fs")); const glob_1 = require("glob"); const semantic_search_1 = require("./semantic_search"); const file_utils_1 = require("./utils/file_utils"); async function searchProject(query, projectRoot, options = {}) { const { type = "all", limit = 20, caseSensitive = false, regex = false, contextLines = 2, semantic = false, minRelevance = 0.1, file, } = options; const results = []; // 1. 语义搜索(优先级最高) if (semantic || type === "semantic") { const semanticResults = await (0, semantic_search_1.semanticSearch)(query, projectRoot, { limit: Math.floor(limit / 2), minRelevance, file, }); // 转换语义搜索结果格式 const convertedResults = semanticResults.map((result) => ({ type: "semantic", name: result.name, file: result.file, line: result.line, column: result.column, description: result.description, kind: result.kind, context: result.context, relevance: result.relevance, keywords: result.keywords, })); results.push(...convertedResults); } // 2. 文件搜索 if (type === "all" || type === "file") { const fileResults = await searchFiles(query, projectRoot, file); results.push(...fileResults); } // 3. 符号搜索(函数、类、接口等) if (type === "all" || (type !== "file" && type !== "content" && type !== "semantic")) { const symbolResults = await searchSymbols(query, projectRoot, type, file); results.push(...symbolResults); } // 4. 内容搜索(类似 grep) // 当查询为空且类型为 all 时,跳过内容搜索以提供更好的用户体验 if ((type === "all" && query.trim() !== "") || type === "content") { const contentResults = await searchContent(query, projectRoot, { caseSensitive, regex, contextLines, file, }); results.push(...contentResults); } // 5. 排序和限制结果 return results .sort((a, b) => { // 优先显示语义搜索结果(按相关性排序) if (a.type === "semantic" && b.type === "semantic") { return (b.relevance || 0) - (a.relevance || 0); } if (a.type === "semantic") return -1; if (b.type === "semantic") return 1; // 优先显示精确匹配 const aExact = a.name.toLowerCase() === query.toLowerCase(); const bExact = b.name.toLowerCase() === query.toLowerCase(); if (aExact && !bExact) return -1; if (!aExact && bExact) return 1; // 然后按类型排序 const typeOrder = { file: 0, function: 1, class: 2, interface: 3, method: 4, property: 5, content: 6, semantic: 7, }; return typeOrder[a.type] - typeOrder[b.type]; }) .slice(0, limit); } async function searchFiles(query, projectRoot, targetFile) { const results = []; try { let files; if (targetFile) { // 如果指定了文件,只搜索该文件 const targetPath = path.resolve(projectRoot, targetFile); if (fs.existsSync(targetPath)) { files = [targetPath]; } else { return results; } } else { // 搜索所有文件 files = (await (0, glob_1.glob)("**/*", { cwd: projectRoot, ignore: ["node_modules/**", "dist/**", ".git/**"], absolute: true, })) ?? []; } const queryLower = query.toLowerCase(); for (const file of files) { const relativePath = path.relative(projectRoot, file); const fileName = path.basename(file); // 检查文件名是否匹配 if (fileName.toLowerCase().includes(queryLower) || relativePath.toLowerCase().includes(queryLower)) { const stats = fs.statSync(file); if (stats && stats.isFile()) { results.push({ type: "file", name: fileName, file: relativePath, line: 1, column: 1, kind: lsp_core_1.SymbolKind.File, }); } } } } catch (error) { console.error("文件搜索错误:", error); } return results; } async function searchSymbols(query, projectRoot, type, targetFile) { const results = []; try { let lang; let file_ext; if (targetFile) { // 如果指定了文件,根据文件扩展名确定语言 const ext = path.extname(targetFile).toLowerCase(); const extToLang = { ".c": "c", ".h": "c", ".cpp": "cpp", ".hpp": "cpp", ".cc": "cpp", ".cxx": "cpp", ".py": "python", ".ts": "typescript", ".tsx": "typescript", ".js": "typescript", ".jsx": "typescript", }; lang = extToLang[ext] || "typescript"; file_ext = (0, os_utils_1.getLanguageFileExtensions)(lang); } else { // 如果没有指定文件,使用项目主导语言 const languages = (0, os_utils_1.detectDominantLanguages)(projectRoot, { ignoredDirs: new Set(["node_modules", "dist"]), topN: 1, }) ?? []; if (languages.length === 0) { return results; } lang = languages[0]; file_ext = (0, os_utils_1.getLanguageFileExtensions)(lang); } const client = new lsp_core_1.LSPClient((0, file_utils_1.toFileUri)(projectRoot), { ...lsp_core_1.DEFAULT_CONFIG, timeout: 30000, logLevel: "error", // 减少日志输出 }); await client.start(lang); try { let files; if (targetFile) { // 如果指定了文件,只搜索该文件 const targetPath = path.resolve(projectRoot, targetFile); if (fs.existsSync(targetPath)) { files = [targetPath]; } else { return results; } } else { files = (await (0, glob_1.glob)(`**/*.{${file_ext}}`, { cwd: projectRoot, ignore: "node_modules/**", absolute: true, })) ?? []; } const queryLower = query.toLowerCase(); for (const filePath of files) { const fileUri = (0, file_utils_1.toFileUri)(filePath); const relativePath = path.relative(projectRoot, filePath); try { const content = fs.readFileSync(filePath, "utf-8"); await client.sendDidOpen(fileUri, content, "typescript"); const symbols = await client.sendDocumentSymbol(fileUri); if (!symbols) continue; for (const symbol of symbols) { if (shouldIncludeSymbol(symbol, type, queryLower)) { results.push({ type: getSymbolType(symbol.kind), name: symbol.name, file: relativePath, line: symbol.location.range.start.line + 1, column: symbol.location.range.start.character + 1, description: symbol.detail || undefined, kind: symbol.kind, }); } } await client.sendDidClose(fileUri); } catch (err) { // 忽略单个文件的错误,继续处理其他文件 } } } finally { await client.shutdown(); } } catch (error) { console.error("符号搜索错误:", error); } return results; } function shouldIncludeSymbol(symbol, type, query) { // 检查类型过滤 if (type !== "all") { const symbolType = getSymbolType(symbol.kind); if (symbolType !== type) { return false; } } // 检查名称匹配 return symbol.name.toLowerCase().includes(query); } function getSymbolType(kind) { switch (kind) { case lsp_core_1.SymbolKind.Function: return "function"; case lsp_core_1.SymbolKind.Class: return "class"; case lsp_core_1.SymbolKind.Interface: return "interface"; case lsp_core_1.SymbolKind.Method: return "method"; case lsp_core_1.SymbolKind.Property: return "property"; default: return "function"; } } async function searchContent(query, projectRoot, options) { const results = []; const { caseSensitive, regex, contextLines, file: targetFile } = options; try { // 搜索所有文本文件 const textExtensions = [ "ts", "js", "tsx", "jsx", "json", "md", "txt", "yml", "yaml", "xml", "html", "css", "scss", "less", "py", "java", "cpp", "c", "h", "hpp", "go", "rs", "php", "rb", "sh", "bash", "zsh", "fish", ]; let files; if (targetFile) { // 如果指定了文件,只搜索该文件 const targetPath = path.resolve(projectRoot, targetFile); if (fs.existsSync(targetPath) && textExtensions.some((ext) => targetPath.endsWith(`.${ext}`))) { files = [targetPath]; } else { return results; } } else { files = (await (0, glob_1.glob)(`**/*.{${textExtensions.join(",")}}`, { cwd: projectRoot, ignore: ["node_modules/**", "dist/**", ".git/**"], absolute: true, })) ?? []; } const searchPattern = regex ? new RegExp(query, caseSensitive ? "g" : "gi") : new RegExp(escapeRegExp(query), caseSensitive ? "g" : "gi"); for (const filePath of files) { try { const content = fs.readFileSync(filePath, "utf-8"); const lines = content.split("\n"); const relativePath = path.relative(projectRoot, filePath); for (let i = 0; i < lines.length; i++) { const line = lines[i]; const matches = line.match(searchPattern); if (matches) { // 构建上下文 const startLine = Math.max(0, i - contextLines); const endLine = Math.min(lines.length - 1, i + contextLines); const context = lines.slice(startLine, endLine + 1).join("\n"); // 找到匹配的列位置 const matchIndex = line.search(searchPattern); results.push({ type: "content", name: `匹配: ${matches[0]}`, file: relativePath, line: i + 1, column: matchIndex + 1, content: matches[0], context: context, kind: lsp_core_1.SymbolKind.String, }); } } } catch (err) { // 忽略无法读取的文件 continue; } } } catch (error) { console.error("内容搜索错误:", error); } return results; } function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } //# sourceMappingURL=search.js.map