cspell
Version:
A Spelling Checker for Code!
219 lines • 8.64 kB
JavaScript
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