codn_ts
Version:
智能代码分析工具 - 支持语义搜索、调用链分析和代码结构可视化,对大模型/AI agent 友好
401 lines • 14.6 kB
JavaScript
;
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