UNPKG

@felixgeelhaar/cclint

Version:

Catch CLAUDE.md drift before Claude misbehaves. Lints CLAUDE.md, skills, subagents, and hooks for Claude Code projects.

127 lines 4.12 kB
import { DiagnosticSeverity, CodeActionKind, } from 'vscode-languageserver'; import { Severity } from '../domain/Severity.js'; /** * The `source` field attached to every diagnostic, so editors label cclint * findings distinctly from other language servers active on the same document. */ export const CCLINT_SOURCE = 'cclint'; /** * Map a cclint {@link Severity} to the corresponding LSP `DiagnosticSeverity`. */ export function severityToDiagnosticSeverity(severity) { if (severity === Severity.ERROR) { return DiagnosticSeverity.Error; } if (severity === Severity.WARNING) { return DiagnosticSeverity.Warning; } return DiagnosticSeverity.Information; } /** * Convert a cclint {@link Location} (1-based line and column) to an LSP * {@link Position} (0-based line and character). * * @remarks * cclint columns are 1-based; a defensive `Math.max` guards the rare case of a * column reported as `0` so the character never becomes negative. */ export function locationToPosition(location) { return { line: location.line - 1, character: Math.max(0, location.column - 1), }; } /** * Convert a cclint {@link Fix} range (1-based start/end {@link Location}s) to an * LSP {@link Range} (0-based positions). */ export function fixRangeToRange(range) { return { start: locationToPosition(range.start), end: locationToPosition(range.end), }; } /** * Derive the LSP {@link Range} a diagnostic should highlight. * * @remarks * When the violation carries a structured fix, its range pinpoints the exact * span to replace, which is the most precise thing to underline. Otherwise we * fall back to a single-character range at the reported location — the pure * mapping has no document text, so it cannot extend the span to the token or * line end. */ function diagnosticRange(violation) { if (violation.fix) { return fixRangeToRange(violation.fix.range); } const start = locationToPosition(violation.location); return { start, end: { line: start.line, character: start.character + 1 }, }; } /** * Map a single cclint {@link Violation} to an LSP {@link Diagnostic}. */ export function violationToDiagnostic(violation) { const data = { ruleId: violation.ruleId, fixable: violation.fix !== undefined, }; return { range: diagnosticRange(violation), severity: severityToDiagnosticSeverity(violation.severity), code: violation.ruleId, source: CCLINT_SOURCE, message: violation.message, data, }; } /** * Map every violation in a {@link LintingResult} to LSP diagnostics, preserving * order. Returns an empty array for a clean result. */ export function violationsToDiagnostics(result) { return result.violations.map(violationToDiagnostic); } /** * Convert a cclint {@link Fix} to an LSP {@link TextEdit}. */ export function fixToTextEdit(fix) { return { range: fixRangeToRange(fix.range), newText: fix.text, }; } /** * Build a quick-fix {@link CodeAction} for a violation that carries a structured * {@link Fix}, or `undefined` when the violation has no fix. * * @param violation - the violation to offer a fix for * @param uri - the document URI the edit applies to * * @remarks * The fix comes straight from the rule that detected the problem (via * {@link Violation.fix}), so the edit is authoritative rather than re-derived * by pattern-matching the message. The originating diagnostic is attached to * the action so editors can associate the fix with the squiggle it resolves. */ export function violationToCodeAction(violation, uri) { const fix = violation.fix; if (!fix) { return undefined; } return { title: `cclint: ${fix.description}`, kind: CodeActionKind.QuickFix, diagnostics: [violationToDiagnostic(violation)], isPreferred: true, edit: { changes: { [uri]: [fixToTextEdit(fix)], }, }, }; } //# sourceMappingURL=diagnostics.js.map