UNPKG

codn_ts

Version:

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

460 lines 22.1 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 }); 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