/** * Grammar-check core logic — framework-agnostic and pure. * * The network client lives in `languagetool.ts`. This module owns the shape of * a grammar error and the parser that turns a LanguageTool API response into a * plain list of errors with offsets relative to the checked text. Keeping the * parser pure makes the response-shaping logic fully unit-testable. */ /** A single grammar/punctuation error with its offset into the checked text. */ export interface GrammarError { /** Zero-based offset of the flagged span within the checked text. */ offset: number; /** Character length of the flagged span. */ length: number; /** Human-readable explanation of the problem. */ message: string; /** Suggested replacements, best first. */ replacements: string[]; /** LanguageTool rule id (e.g. `EN_A_VS_AN`), when available. */ ruleId?: string; /** LanguageTool category id (e.g. `TYPOSIS`), when available. */ category?: string; } /** Minimal shape of a LanguageTool `/v2/check` response that we consume. */ export interface LTResponse { issues?: Array<{ offset: number; length: number; message: string; replacements?: Array<{ value: string; length?: number; quality?: number }>; rule?: { id?: string; category?: { id?: string } }; }>; } /** Parse a LanguageTool response into a flat list of {@link GrammarError}. */ export function parseGrammarResponse(response: LTResponse): GrammarError[] { const issues = response.issues ?? []; const errors: GrammarError[] = []; for (const issue of issues) { const replacements = (issue.replacements ?? []).map((r) => r.value); const error: GrammarError = { offset: issue.offset, length: issue.length, message: issue.message, replacements, }; if (issue.rule?.id) error.ruleId = issue.rule.id; if (issue.rule?.category?.id) error.category = issue.rule.category.id; errors.push(error); } return errors; } /** * A grammar-check engine. Implementations accept a block of text and a language * tag and return grammar/punctuation errors. */ export interface GrammarEngine { /** Check a block of text and return grammar errors. */ check(text: string, language?: string): Promise; }