UNPKG

@ton-ai-core/vibecode-linter

Version:

Advanced TypeScript linter with Git integration, dependency analysis, and comprehensive error reporting

170 lines 8.12 kB
// CHANGE: Extracted dependency analysis helpers // WHY: Reduce file size and parameter count in main dependency module // QUOTE(LINT): "File has too many lines (317). Maximum allowed is 300" // REF: ESLint max-lines // SOURCE: n/a import * as path from "node:path"; import { match } from "ts-pattern"; import ts from "typescript"; /** * Создает уникальный идентификатор для сообщения линтера. * * CHANGE: Use switch with proper type narrowing for discriminated unions * WHY: TypeScript requires explicit narrowing for union types to avoid unsafe access * QUOTE(ERROR): "Unsafe member access on error typed value" * REF: ESLint @typescript-eslint/no-unsafe-member-access * * @param filePath Путь к файлу * @param message Сообщение линтера * @returns Уникальный идентификатор */ export function createMessageId(filePath, message) { // CHANGE: Replace switch with ts-pattern exhaustive match // WHY: In functional paradigm, switch is forbidden; ts-pattern ensures total, type-safe branching over discriminant // QUOTE(TЗ): "Исправить все ошибки линтера" — запрет switch, использовать ts-pattern match() // REF: REQ-LINT-FIX, ESLint no-restricted-syntax, ts-pattern recommendation // SOURCE: "Switch statements are forbidden in functional programming paradigm. Use ts-pattern match() instead." // FORMAT THEOREME: Пусть S — множество источников {typescript, eslint, biome} ∪ {прочие}. // Определим f: S → string как: // f(typescript) = message.code // f(eslint) = message.ruleId ?? "no-rule" // f(biome) = message.ruleId ?? "no-rule" // f(other) = "no-rule" // Матч ts-pattern реализует тот же морфизм f с исчерпывающей проверкой: для всех s ∈ S, f(s) определена. // Следовательно, замена switch на match сохраняет поведение и инвариант тотальности. const ruleId = match(message) // CHANGE: Match on the whole discriminated union object, not just the source string // WHY: Ensures type-safe narrowing so variant-specific fields are accessible // QUOTE(TЗ): "Давать проверяемые решения через формализацию и строгую типизацию" // REF: REQ-LINT-FIX, @typescript-eslint/no-unsafe-member-access .with({ source: "typescript" }, (m) => m.code) .with({ source: "eslint" }, { source: "biome" }, (m) => m.ruleId ?? "no-rule") .otherwise(() => "no-rule"); return `${path.resolve(filePath)}:${message.line}:${message.column}:${message.source}:${ruleId}`; } /** * Вычисляет позицию в исходном файле. * * @param sourceFile Исходный файл TypeScript * @param message Сообщение с информацией о позиции * @returns Начальная и конечная позиция */ export function getPosition(sourceFile, message) { const start = ts.getPositionOfLineAndCharacter(sourceFile, Math.max(0, message.line - 1), Math.max(0, message.column - 1)); // CHANGE: Use explicit numeric guard for end position instead of truthiness // WHY: strict-boolean-expressions — must handle nullish/zero/NaN explicitly; end positions are positive integers // QUOTE(TЗ): "Исправить все ошибки линтера" // REF: REQ-LINT-FIX, @typescript-eslint/strict-boolean-expressions // CHANGE: Use helper for end position validation to lower function complexity // WHY: reduce cyclomatic complexity; keep strict checks centralized // QUOTE(TЗ): "Исправить все ошибки линтера" // REF: REQ-LINT-FIX, @typescript-eslint/strict-boolean-expressions const hasEndPosition = isValidEndPosition(message); // CHANGE: Avoid referencing possibly undefined fields directly; compute via locals under guard // WHY: TS flagged 'message.endLine'/'message.endColumn' possibly undefined even after boolean guard // QUOTE(TЗ): "Исправить все ошибки линтера" // REF: REQ-LINT-FIX, @typescript-eslint/strict-boolean-expressions let end; if (hasEndPosition) { const endLine = message.endLine ?? 0; const endColumn = message.endColumn ?? 0; end = ts.getPositionOfLineAndCharacter(sourceFile, endLine - 1, Math.max(0, endColumn - 1)); } else { end = start; } return { start, end }; } /** * Получает символы определения для узла. * * @param checker Type checker * @param node Узел AST * @returns Массив символов */ export function getDefinitionSymbols(checker, node) { const locus = ts.isIdentifier(node) ? node : ts.isPropertyAccessExpression(node) ? node.name : ts.isElementAccessExpression(node) ? node.argumentExpression : node; const symbol0 = checker.getSymbolAtLocation(locus); if (!symbol0) { return []; } const symbol = symbol0.getFlags() & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol0) : symbol0; // CHANGE: Avoid object truthiness check; symbol is non-nullable here // WHY: strict-boolean-expressions — object in conditional is always truthy; after the early return, symbol is a ts.Symbol // QUOTE(TЗ): "Исправить все ошибки линтера" // REF: REQ-LINT-FIX, @typescript-eslint/strict-boolean-expressions return [symbol]; } /** * Группирует сообщения по файлам. * * @param messages Массив сообщений * @returns Карта файл -> сообщения */ export function groupMessagesByFile(messages) { const byFile = new Map(); for (const message of messages) { const file = path.resolve(message.filePath); if (!byFile.has(file)) { byFile.set(file, []); } const fileMessages = byFile.get(file); if (fileMessages) fileMessages.push(message); } return byFile; } /** * Находит сообщение для декларации. * * @param declaration Декларация TypeScript * @param context Контекст обработки зависимостей * @returns Файл и сообщение или null */ export function findDeclarationMessage(declaration, context) { const declFile = path.resolve(declaration.getSourceFile().fileName); const declMessages = context.byFile.get(declFile); if (!declMessages || declMessages.length === 0) { return null; } const declStart = declaration.getStart(); const declEnd = declaration.getEnd(); const declSourceFile = context.program.getSourceFile(declFile); if (!declSourceFile) return null; const found = declMessages.find((dm) => { const pos = getPosition(declSourceFile, dm); return pos.start >= declStart && pos.end <= declEnd; }); if (!found) return null; return { file: declFile, message: found }; } /** * Validates that endLine and endColumn are present and positive finite integers. * * @param msg Object possibly containing endLine/endColumn * @returns Type predicate ensuring both fields are valid numbers * @invariant endLine > 0 and endColumn > 0 */ // CHANGE: Centralize end position validation // WHY: Maintain single source of truth and reduce getPosition complexity // QUOTE(TЗ): "Исправить все ошибки линтера" // REF: REQ-LINT-FIX export function isValidEndPosition(msg) { return (typeof msg.endLine === "number" && typeof msg.endColumn === "number" && Number.isFinite(msg.endLine) && Number.isFinite(msg.endColumn) && msg.endLine > 0 && msg.endColumn > 0); } //# sourceMappingURL=dependency-helpers.js.map