@ton-ai-core/vibecode-linter
Version:
Advanced TypeScript linter with Git integration, dependency analysis, and comprehensive error reporting
159 lines • 7.23 kB
JavaScript
// CHANGE: Extracted git utility functions from lint.ts
// WHY: Git helper functions should be in a separate module
// QUOTE(ТЗ): "Разбить lint.ts на подфайлы, каждый файл желательно должен быть не больше 300 строчек кода"
// REF: REQ-20250210-MODULAR-ARCH
// SOURCE: n/a
import { extractStdoutFromError } from "../../core/types/index.js";
// CHANGE: Use node: protocol for Node.js built-in modules
// WHY: Biome lint rule requires explicit node: prefix for clarity
// REF: lint/style/useNodejsImportProtocol
// SOURCE: https://biomejs.dev/linter/rules/lint/style/useNodejsImportProtocol
import { exec, fs, path, promisify } from "../utils/node-mods.js";
const execAsync = promisify(exec);
/**
* Получает фрагмент кода из рабочей директории вокруг указанной строки.
*
* @param filePath Путь к файлу
* @param centerLine Центральная строка для контекста (1-based)
* @param context Количество строк контекста с каждой стороны
* @returns Массив отформатированных строк или null при ошибке
*/
export function getWorkspaceSnippet(filePath, centerLine, context = 2) {
try {
const fileContent = fs.readFileSync(filePath, "utf8").split(/\r?\n/u);
const start = Math.max(0, centerLine - context - 1);
const end = Math.min(fileContent.length, centerLine + context);
if (start >= end) {
return null;
}
const snippet = [];
for (let i = start; i < end; i += 1) {
snippet.push(`${String(i + 1).padStart(4)} | ${fileContent[i] ?? ""}`);
}
return snippet;
}
catch {
return null;
}
}
/**
* Получает фрагмент кода из указанного коммита вокруг указанной строки.
*
* @param commitHash Хэш коммита
* @param filePath Путь к файлу
* @param lineNumber Номер строки (1-based)
* @param context Количество строк контекста с каждой стороны
* @returns Массив отформатированных строк или null при ошибке
*/
export async function getCommitSnippetForLine(commitHash, filePath, lineNumber, context = 3) {
const relativePath = path
.relative(process.cwd(), filePath)
.replace(/\\/g, "/");
try {
const { stdout } = await execAsync(`git show ${commitHash}:${relativePath}`);
const lines = stdout.split(/\r?\n/u);
if (lineNumber <= 0 || lineNumber > lines.length) {
return null;
}
const start = Math.max(0, lineNumber - context - 1);
const end = Math.min(lines.length, lineNumber + context);
const snippet = [];
for (let i = start; i < end; i += 1) {
snippet.push(`${String(i + 1).padStart(4)} | ${lines[i] ?? ""}`);
}
return snippet;
}
catch {
return null;
}
}
/**
* Определяет диапазон для git diff (upstream...HEAD или HEAD).
*
* Проверяет наличие upstream ветки и возвращает соответствующую конфигурацию.
*
* @returns Конфигурация диапазона для git diff
*/
export async function detectDiffRange() {
try {
const { stdout } = await execAsync("git rev-parse --abbrev-ref --symbolic-full-name HEAD@{upstream}");
const upstream = stdout.trim();
if (upstream.length > 0) {
return {
diffArg: `${upstream}...HEAD`,
label: `${upstream}...HEAD`,
};
}
}
catch (error) {
const execError = error;
// CHANGE: Avoid truthiness check on nullable string stderr
// WHY: strict-boolean-expressions — handle nullish/empty explicitly
// QUOTE(ТЗ): "Исправить все ошибки линтера"
// REF: REQ-LINT-FIX, @typescript-eslint/strict-boolean-expressions
if (typeof execError.stderr === "string" && execError.stderr.length > 0) {
// Upstream is missing — fall back to local comparison
}
}
return {
diffArg: "HEAD",
label: "HEAD",
};
}
/**
* Выполняет git-команду и возвращает stdout либо null.
*
* Инварианты:
* - Не бросает исключение; при ошибке пытается извлечь stdout с помощью extractStdoutFromError.
* - Возвращает null, если stdout отсутствует/пуст.
*
* @param command Команда git для выполнения
* @param maxBuffer Максимальный размер буфера для stdout
* @returns Строка stdout или null
*/
// CHANGE: Унифицированная обертка для устранения дублирования try/catch-паттерна
// WHY: jscpd указывал на повторяющиеся блоки с разбором stdout в нескольких модулях
// REF: REQ-LINT-FIX
export async function execGitStdoutOrNull(command, maxBuffer = 10 * 1024 * 1024) {
try {
const { stdout } = await execGitCommand(command, maxBuffer);
return stdout;
}
catch (error) {
const out = extractStdoutFromError(error);
return typeof out === "string" && out.length > 0 ? out : null;
}
}
/**
* Выполняет git-команду и возвращает непустой stdout или null.
*
* Инварианты:
* - Использует execGitStdoutOrNull для безопасного извлечения stdout.
* - Возвращает null, если stdout отсутствует или после trim() пуст.
*
* @param command Команда git для выполнения
* @param maxBuffer Максимальный размер буфера для stdout
* @returns Строка stdout (непустая) или null
*/
// CHANGE: Централизация проверки "непустого stdout" для устранения дублей
// WHY: jscpd фиксировал повторяющийся паттерн проверки длины/trim()
// QUOTE(ТЗ): "Убрать дубли кода"
// REF: REQ-LINT-FIX
export async function execGitNonEmptyOrNull(command, maxBuffer = 10 * 1024 * 1024) {
const out = await execGitStdoutOrNull(command, maxBuffer);
if (typeof out !== "string")
return null;
const trimmed = out.trim();
return trimmed.length > 0 ? out : null;
}
/**
* Выполняет команду git и возвращает результат.
*
* @param command Команда git для выполнения
* @param maxBuffer Максимальный размер буфера для stdout
* @returns Результат выполнения команды
*/
export async function execGitCommand(command, maxBuffer = 10 * 1024 * 1024) {
return await execAsync(command, { maxBuffer });
}
//# sourceMappingURL=utils.js.map