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