codn_ts
Version:
智能代码分析工具 - 支持语义搜索、调用链分析和代码结构可视化,对大模型/AI agent 友好
460 lines • 22.1 kB
JavaScript
"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 });
const vitest_1 = require("vitest");
const search_1 = require("./search");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const glob_1 = require("glob");
// Mock console.error,避免测试输出污染
vitest_1.vi.spyOn(console, "error").mockImplementation(() => { });
// Mock 依赖
vitest_1.vi.mock("fs", () => ({
readFileSync: vitest_1.vi.fn((file) => {
if (file.includes("testFile.ts"))
return "const testFile = 1;\n// test message";
if (file.includes("utils.ts"))
return "function utilFunc() { return true; }";
if (file.includes("content.ts"))
return "before\ntarget\nafter";
return "function testFunction() { return true; }\nclass TestClass {}\ntest message";
}),
statSync: vitest_1.vi
.fn()
.mockReturnValue({ isFile: () => true, isDirectory: () => false }),
}));
vitest_1.vi.mock("path", () => ({
relative: vitest_1.vi.fn((root, file) => file.replace(root + "/", "")),
basename: vitest_1.vi.fn((file) => file.split("/").pop()),
resolve: vitest_1.vi.fn((...args) => args.join("/")),
}));
vitest_1.vi.mock("glob", () => ({
glob: vitest_1.vi.fn().mockImplementation((pattern, opts) => {
// 针对符号搜索测试,始终返回 testFile.ts
return Promise.resolve(["testFile.ts"]);
}),
}));
vitest_1.vi.mock("./utils/os_utils", () => ({
detectDominantLanguages: vitest_1.vi.fn().mockReturnValue(["typescript"]),
getLanguageFileExtensions: vitest_1.vi.fn().mockReturnValue("ts"),
}));
vitest_1.vi.mock("./utils/lsp_core", () => {
class MockLSPClient {
constructor() { }
async start() { }
async shutdown() { }
async sendDidOpen() { }
async sendDidClose() { }
async sendDocumentSymbol() {
return [
{
kind: 12, // Function
name: "testFunction",
location: {
range: {
start: { line: 0, character: 0 },
end: { line: 1, character: 0 },
},
},
},
{
kind: 5, // Class
name: "TestClass",
location: {
range: {
start: { line: 2, character: 0 },
end: { line: 3, character: 0 },
},
},
},
];
}
}
return {
LSPClient: MockLSPClient,
SymbolKind: {
File: 1,
Module: 2,
Namespace: 3,
Package: 4,
Class: 5,
Method: 6,
Property: 7,
Field: 8,
Constructor: 9,
Enum: 10,
Interface: 11,
Function: 12,
Variable: 13,
Constant: 14,
String: 15,
Number: 16,
Boolean: 17,
Array: 18,
Object: 19,
Key: 20,
Null: 21,
EnumMember: 22,
Struct: 23,
Event: 24,
Operator: 25,
TypeParameter: 26,
},
DEFAULT_CONFIG: {},
};
});
vitest_1.vi.mock("./semantic_search", () => ({
semanticSearch: vitest_1.vi.fn(),
}));
(0, vitest_1.describe)("search", () => {
(0, vitest_1.beforeEach)(() => {
vitest_1.vi.clearAllMocks();
});
(0, vitest_1.afterEach)(() => {
vitest_1.vi.restoreAllMocks();
});
(0, vitest_1.describe)("searchProject 主函数", () => {
(0, vitest_1.it)("应该返回空数组当没有找到结果时", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
mockGlob.mockResolvedValue([]);
const result = await (0, search_1.searchProject)("nonexistent", "/project/root");
(0, vitest_1.expect)(result).toEqual([]);
});
(0, vitest_1.it)("应该处理语义搜索", async () => {
const mockSemanticSearch = vitest_1.vi.mocked(await Promise.resolve().then(() => __importStar(require("./semantic_search")))).semanticSearch;
mockSemanticSearch.mockResolvedValue([
{
type: "semantic",
name: "testFunction",
file: "src/test.ts",
line: 1,
column: 1,
description: "Test function",
kind: 12,
relevance: 0.9,
context: "function testFunction() {}",
keywords: ["test"],
parameters: [],
returnType: "void",
callers: [],
callees: [],
complexity: 1,
callCount: 0,
},
]);
const options = { semantic: true };
const result = await (0, search_1.searchProject)("test", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeGreaterThan(0);
(0, vitest_1.expect)(result[0].type).toBe("semantic");
(0, vitest_1.expect)(result[0].relevance).toBe(0.9);
});
(0, vitest_1.it)("应该应用限制参数", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
mockGlob.mockResolvedValue([
"/project/root/src/test1.ts",
"/project/root/src/test2.ts",
]);
mockFs.statSync.mockReturnValue({ isFile: () => true });
mockPath.relative.mockReturnValue("src/test.ts");
mockPath.basename.mockReturnValue("test.ts");
const options = { limit: 1 };
const result = await (0, search_1.searchProject)("test", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeLessThanOrEqual(1);
});
(0, vitest_1.it)("应该按相关性排序结果", async () => {
const mockSemanticSearch = vitest_1.vi.mocked(await Promise.resolve().then(() => __importStar(require("./semantic_search")))).semanticSearch;
mockSemanticSearch.mockResolvedValue([
{
type: "semantic",
name: "lowRelevance",
file: "src/low.ts",
line: 1,
column: 1,
description: "Low relevance",
kind: 12,
relevance: 0.3,
context: "function lowRelevance() {}",
keywords: ["low"],
parameters: [],
returnType: "void",
callers: [],
callees: [],
complexity: 1,
callCount: 0,
},
{
type: "semantic",
name: "highRelevance",
file: "src/high.ts",
line: 1,
column: 1,
description: "High relevance",
kind: 12,
relevance: 0.9,
context: "function highRelevance() {}",
keywords: ["high"],
parameters: [],
returnType: "void",
callers: [],
callees: [],
complexity: 1,
callCount: 0,
},
]);
const options = { semantic: true };
const result = await (0, search_1.searchProject)("test", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeGreaterThan(1);
const rel0 = typeof result[0].relevance === 'number' ? result[0].relevance : 0;
const rel1 = typeof result[1].relevance === 'number' ? result[1].relevance : 0;
(0, vitest_1.expect)(rel0).toBeGreaterThan(rel1);
});
});
(0, vitest_1.describe)("文件搜索测试", () => {
(0, vitest_1.it)("应该搜索文件名", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
mockGlob.mockResolvedValue(["/project/root/src/testFile.ts"]);
mockFs.statSync.mockReturnValue({ isFile: () => true });
mockPath.relative.mockReturnValue("src/testFile.ts");
mockPath.basename.mockReturnValue("testFile.ts");
const options = { type: "file" };
const result = await (0, search_1.searchProject)("testFile", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeGreaterThan(0);
(0, vitest_1.expect)(result[0].type).toBe("file");
(0, vitest_1.expect)(result[0].name).toBe("testFile.ts");
});
(0, vitest_1.it)("应该搜索文件路径", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
mockGlob.mockResolvedValue(["/project/root/src/utils/helper.ts"]);
mockFs.statSync.mockReturnValue({ isFile: () => true });
mockPath.relative.mockReturnValue("src/utils/helper.ts");
mockPath.basename.mockReturnValue("helper.ts");
const options = { type: "file" };
const result = await (0, search_1.searchProject)("utils", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeGreaterThan(0);
(0, vitest_1.expect)(result[0].file).toContain("utils");
});
(0, vitest_1.it)("应该忽略目录", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
mockGlob.mockResolvedValue(["/project/root/src/testDir"]);
mockFs.statSync.mockReturnValue({ isFile: () => false });
mockPath.relative.mockReturnValue("src/testDir");
mockPath.basename.mockReturnValue("testDir");
const options = { type: "file" };
const result = await (0, search_1.searchProject)("testDir", "/project/root", options);
(0, vitest_1.expect)(result.length).toBe(0);
});
});
(0, vitest_1.describe)("符号搜索测试", () => {
(0, vitest_1.it)("应该搜索函数", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
const mockOsUtils = vitest_1.vi.mocked(await Promise.resolve().then(() => __importStar(require("./utils/os_utils"))));
mockGlob.mockResolvedValue(["/project/root/src/test.ts"]);
mockFs.readFileSync.mockReturnValue("function testFunction() {}");
mockPath.relative.mockReturnValue("src/test.ts");
mockOsUtils.detectDominantLanguages.mockReturnValue(["typescript"]);
mockOsUtils.getLanguageFileExtensions.mockReturnValue("ts");
const options = { type: "function" };
const result = await (0, search_1.searchProject)("testFunction", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeGreaterThan(0);
(0, vitest_1.expect)(result[0].type).toBe("function");
});
(0, vitest_1.it)("应该搜索类", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
const mockOsUtils = vitest_1.vi.mocked(await Promise.resolve().then(() => __importStar(require("./utils/os_utils"))));
mockGlob.mockResolvedValue(["/project/root/src/test.ts"]);
mockFs.readFileSync.mockReturnValue("class TestClass {}");
mockPath.relative.mockReturnValue("src/test.ts");
mockOsUtils.detectDominantLanguages.mockReturnValue(["typescript"]);
mockOsUtils.getLanguageFileExtensions.mockReturnValue("ts");
const options = { type: "class" };
const result = await (0, search_1.searchProject)("TestClass", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeGreaterThan(0);
(0, vitest_1.expect)(result[0].type).toBe("class");
});
(0, vitest_1.it)("应该处理 LSP 错误", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
const mockOsUtils = vitest_1.vi.mocked(await Promise.resolve().then(() => __importStar(require("./utils/os_utils"))));
mockGlob.mockResolvedValue(["/project/root/src/test.ts"]);
mockFs.readFileSync.mockReturnValue("function test() {}");
mockPath.relative.mockReturnValue("src/test.ts");
mockOsUtils.detectDominantLanguages.mockReturnValue(["typescript"]);
mockOsUtils.getLanguageFileExtensions.mockReturnValue("ts");
const options = { type: "function" };
const result = await (0, search_1.searchProject)("test", "/project/root", options);
(0, vitest_1.expect)(Array.isArray(result)).toBe(true);
});
});
(0, vitest_1.describe)("内容搜索测试", () => {
(0, vitest_1.it)("应该搜索文件内容", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
const fileContent = `
function testFunction() {
console.log("test message");
return true;
}
`;
mockGlob.mockResolvedValue(["/project/root/src/test.ts"]);
mockFs.readFileSync.mockReturnValue(fileContent);
mockPath.relative.mockReturnValue("src/test.ts");
const options = { type: "content" };
const result = await (0, search_1.searchProject)("test message", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeGreaterThan(0);
(0, vitest_1.expect)(result[0].type).toBe("content");
(0, vitest_1.expect)(result[0].content).toContain("test message");
});
(0, vitest_1.it)("应该处理大小写敏感搜索", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
const fileContent = "function TestFunction() { return true; }";
mockGlob.mockResolvedValue(["/project/root/src/test.ts"]);
mockFs.readFileSync.mockReturnValue(fileContent);
mockPath.relative.mockReturnValue("src/test.ts");
const options = { type: "content", caseSensitive: true };
const result = await (0, search_1.searchProject)("testfunction", "/project/root", options);
(0, vitest_1.expect)(result.length).toBe(0);
});
(0, vitest_1.it)("应该处理正则表达式搜索", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
const fileContent = "function test123() { return true; }";
mockGlob.mockResolvedValue(["/project/root/src/test.ts"]);
mockFs.readFileSync.mockReturnValue(fileContent);
mockPath.relative.mockReturnValue("src/test.ts");
const options = { type: "content", regex: true };
const result = await (0, search_1.searchProject)("test\\d+", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeGreaterThan(0);
});
(0, vitest_1.it)("应该包含上下文行", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
const fileContent = `
function before() {}
function target() {
console.log("target");
}
function after() {}
`;
mockGlob.mockResolvedValue(["/project/root/src/test.ts"]);
mockFs.readFileSync.mockReturnValue(fileContent);
mockPath.relative.mockReturnValue("src/test.ts");
const options = { type: "content", contextLines: 1 };
const result = await (0, search_1.searchProject)("target", "/project/root", options);
(0, vitest_1.expect)(result.length).toBeGreaterThan(0);
(0, vitest_1.expect)(result[0].context).toContain("before");
(0, vitest_1.expect)(result[0].context).toContain("target");
});
});
(0, vitest_1.describe)("搜索类型过滤测试", () => {
(0, vitest_1.it)("应该只搜索指定类型", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
const mockOsUtils = vitest_1.vi.mocked(await Promise.resolve().then(() => __importStar(require("./utils/os_utils"))));
mockGlob.mockResolvedValue(["/project/root/src/test.ts"]);
mockFs.readFileSync.mockReturnValue("function test() {}");
mockPath.relative.mockReturnValue("src/test.ts");
mockOsUtils.detectDominantLanguages.mockReturnValue(["typescript"]);
mockOsUtils.getLanguageFileExtensions.mockReturnValue("ts");
const options = { type: "function" };
const result = await (0, search_1.searchProject)("test", "/project/root", options);
// 所有结果都应该是函数类型
result.forEach((r) => {
(0, vitest_1.expect)(r.type).toBe("function");
});
});
(0, vitest_1.it)("应该搜索所有类型", async () => {
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
const mockFs = vitest_1.vi.mocked(fs);
const mockPath = vitest_1.vi.mocked(path);
const mockOsUtils = vitest_1.vi.mocked(await Promise.resolve().then(() => __importStar(require("./utils/os_utils"))));
mockGlob.mockResolvedValue(["/project/root/src/test.ts"]);
mockFs.readFileSync.mockReturnValue("function test() {}");
mockPath.relative.mockReturnValue("src/test.ts");
mockOsUtils.detectDominantLanguages.mockReturnValue(["typescript"]);
mockOsUtils.getLanguageFileExtensions.mockReturnValue("ts");
const options = { type: "all" };
const result = await (0, search_1.searchProject)("test", "/project/root", options);
// 应该包含多种类型的结果
const types = result.map((r) => r.type);
(0, vitest_1.expect)(types.length).toBeGreaterThan(0);
});
});
(0, vitest_1.describe)("错误处理测试", () => {
(0, vitest_1.beforeAll)(() => {
vitest_1.vi.spyOn(console, "error").mockImplementation(() => { });
});
(0, vitest_1.afterAll)(() => {
vitest_1.vi.restoreAllMocks();
});
(0, vitest_1.it)("应该处理 glob 错误", async () => {
const options = { type: "file" };
// mock glob 抛出错误
const mockGlob = vitest_1.vi.mocked(glob_1.glob);
mockGlob.mockRejectedValueOnce(new Error("Glob error"));
const result = await (0, search_1.searchProject)("error", "/project/root", options);
(0, vitest_1.expect)(result).toEqual([]);
});
(0, vitest_1.it)("应该处理文件读取错误", async () => {
const options = { type: "file" };
// mock fs.readFileSync 抛出错误
const mockFs = vitest_1.vi.mocked(fs);
mockFs.readFileSync.mockImplementationOnce(() => {
throw new Error("Read error");
});
const result = await (0, search_1.searchProject)("error", "/project/root", options);
(0, vitest_1.expect)(result).toEqual([]);
});
});
});
//# sourceMappingURL=search.test.js.map