UNPKG

cspell

Version:
219 lines 8.64 kB
import chalk from 'chalk'; import chalkTemplate from 'chalk-template'; import { isSpellingDictionaryLoadError } from 'cspell-lib'; import * as path from 'path'; import { URI } from 'vscode-uri'; const templateIssue = `{green $filename}:{yellow $row:$col} - $message ({red $text}) $quickFix`; const templateIssueNoFix = `{green $filename}:{yellow $row:$col} - $message ({red $text})`; const templateIssueWithSuggestions = `{green $filename}:{yellow $row:$col} - $message ({red $text}) Suggestions: {yellow [$suggestions]}`; const templateIssueWithContext = `{green $filename}:{yellow $row:$col} $padRowCol- $message ({red $text})$padContext -- {gray $contextLeft}{red {underline $text}}{gray $contextRight}`; const templateIssueWithContextWithSuggestions = `{green $filename}:{yellow $row:$col} $padRowCol- $message ({red $text})$padContext -- {gray $contextLeft}{red {underline $text}}{gray $contextRight}\n\t Suggestions: {yellow [$suggestions]}`; const templateIssueLegacy = `${chalk.green('$filename')}[$row, $col]: $message: ${chalk.red('$text')}`; const templateIssueWordsOnly = '$text'; function genIssueEmitter(template) { const defaultWidth = 10; let maxWidth = defaultWidth; let uri; return function issueEmitter(issue) { if (uri !== issue.uri) { maxWidth = defaultWidth; uri = issue.uri; } maxWidth = Math.max(maxWidth * 0.999, issue.text.length, 10); console.log(formatIssue(template, issue, Math.ceil(maxWidth))); }; } function errorEmitter(message, error) { if (isSpellingDictionaryLoadError(error)) { error = error.cause; } console.error(chalk.red(message), error.toString()); } function nullEmitter() { /* empty */ } function relativeFilename(filename, cwd = process.cwd()) { const rel = path.relative(cwd, filename); if (rel.startsWith('..')) return filename; return '.' + path.sep + rel; } function relativeUriFilename(uri, fsPathRoot) { const fsPath = URI.parse(uri).fsPath; const rel = path.relative(fsPathRoot, fsPath); if (rel.startsWith('..')) return fsPath; return '.' + path.sep + rel; } function reportProgress(p) { if (p.type === 'ProgressFileComplete') { return reportProgressFileComplete(p); } if (p.type === 'ProgressFileBegin') { return reportProgressFileBegin(p); } } function reportProgressFileBegin(p) { const fc = '' + p.fileCount; const fn = (' '.repeat(fc.length) + p.fileNum).slice(-fc.length); const idx = fn + '/' + fc; const filename = chalk.gray(relativeFilename(p.filename)); process.stderr.write(`\r${idx} ${filename}`); } function reportProgressFileComplete(p) { const time = reportTime(p.elapsedTimeMs, !!p.cached); const skipped = p.processed === false ? ' skipped' : ''; const hasErrors = p.numErrors ? chalk.red ` X` : ''; console.error(` ${time}${skipped}${hasErrors}`); } function reportTime(elapsedTimeMs, cached) { if (cached) return chalk.green('cached'); if (elapsedTimeMs === undefined) return '-'; const color = elapsedTimeMs < 1000 ? chalk.white : elapsedTimeMs < 2000 ? chalk.yellow : chalk.redBright; return color(elapsedTimeMs.toFixed(2) + 'ms'); } export function getReporter(options) { const issueTemplate = options.wordsOnly ? templateIssueWordsOnly : options.legacy ? templateIssueLegacy : options.showContext ? options.showSuggestions ? templateIssueWithContextWithSuggestions : templateIssueWithContext : options.showSuggestions ? templateIssueWithSuggestions : options.showSuggestions === false ? templateIssueNoFix : templateIssue; const { fileGlobs, silent, summary, issues, progress, verbose, debug } = options; const emitters = { Debug: !silent && debug ? (s) => console.info(chalk.cyan(s)) : nullEmitter, Info: !silent && verbose ? (s) => console.info(chalk.yellow(s)) : nullEmitter, Warning: (s) => console.info(chalk.yellow(s)), }; function infoEmitter(message, msgType) { emitters[msgType]?.(message); } const root = URI.file(options.root || process.cwd()); const fsPathRoot = root.fsPath; function relativeIssue(fn) { const fnFilename = options.relative ? (uri) => relativeUriFilename(uri, fsPathRoot) : (uri) => URI.parse(uri).fsPath; return (i) => { const filename = i.uri ? fnFilename(i.uri) : ''; const r = { ...i, filename }; fn(r); }; } const resultEmitter = (result) => { if (!fileGlobs.length && !result.files) { return; } if (result.cachedFiles) { console.error('CSpell\x3a Files checked: %d (%d from cache), Issues found: %d in %d files', result.files, result.cachedFiles, result.issues, result.filesWithIssues.size); return; } console.error('CSpell\x3a Files checked: %d, Issues found: %d in %d files', result.files, result.issues, result.filesWithIssues.size); }; return { issue: relativeIssue(silent || !issues ? nullEmitter : genIssueEmitter(issueTemplate)), error: silent ? nullEmitter : errorEmitter, info: infoEmitter, debug: emitters.Debug, progress: !silent && progress ? reportProgress : nullEmitter, result: !silent && summary ? resultEmitter : nullEmitter, }; } function formatIssue(templateStr, issue, maxIssueTextWidth) { function clean(t) { return t.replace(/\s+/, ' '); } const { uri = '', filename, row, col, text, context, offset } = issue; const contextLeft = clean(context.text.slice(0, offset - context.offset)); const contextRight = clean(context.text.slice(offset + text.length - context.offset)); const contextFull = clean(context.text); const padContext = ' '.repeat(Math.max(maxIssueTextWidth - text.length, 0)); const rowText = row.toString(); const colText = col.toString(); const padRowCol = ' '.repeat(Math.max(1, 8 - (rowText.length + colText.length))); const suggestions = formatSuggestions(issue); const msg = issue.message || (issue.isFlagged ? 'Forbidden word' : 'Unknown word'); const message = issue.isFlagged ? `{yellow ${msg}}` : msg; const substitutions = { $col: colText, $contextFull: contextFull, $contextLeft: contextLeft, $contextRight: contextRight, $filename: filename, $padContext: padContext, $padRowCol: padRowCol, $row: rowText, $suggestions: suggestions, $text: text, $uri: uri, $quickFix: formatQuickFix(issue), }; const t = template(templateStr.replace(/\$message/g, message)); return substitute(chalkTemplate(t), substitutions).trimEnd(); } function formatSuggestions(issue) { if (issue.suggestionsEx) { return issue.suggestionsEx .map((sug) => sug.isPreferred ? chalk.italic(chalk.bold(sug.wordAdjustedToMatchCase || sug.word)) + '*' : sug.wordAdjustedToMatchCase || sug.word) .join(', '); } if (issue.suggestions) { return issue.suggestions.join(', '); } return ''; } function formatQuickFix(issue) { if (!issue.suggestionsEx?.length) return ''; const preferred = issue.suggestionsEx .filter((sug) => sug.isPreferred) .map((sug) => sug.wordAdjustedToMatchCase || sug.word); if (!preferred.length) return ''; const fixes = preferred.map((w) => chalk.italic(chalk.yellow(w))); return `fix: (${fixes.join(', ')})`; } class TS extends Array { raw; constructor(s) { super(s); this.raw = [s]; } } function template(s) { return new TS(s); } function substitute(text, substitutions) { const subs = []; for (const [match, replaceWith] of Object.entries(substitutions)) { const len = match.length; for (let i = text.indexOf(match); i >= 0; i = text.indexOf(match, i + 1)) { subs.push([i, i + len, replaceWith]); } } subs.sort((a, b) => a[0] - b[0]); let i = 0; function sub(r) { const [a, b, t] = r; const prefix = text.slice(i, a); i = b; return prefix + t; } const parts = subs.map(sub); return parts.join('') + text.slice(i); } export const __testing__ = { formatIssue, }; //# sourceMappingURL=cli-reporter.js.map