UNPKG

cspell

Version:

A Spelling Checker for Code!

209 lines 7.58 kB
import assert from 'node:assert'; import { IssueType, unknownWordsChoices, } from '@cspell/cspell-types'; import { dynamicImport } from '@cspell/dynamic-import'; import { pkgDir } from '../pkgInfo.js'; import { ApplicationError, toError } from './errors.js'; import { clean } from './util.js'; function filterFeatureIssues(features, issue, reportOptions) { if (issue.issueType === IssueType.directive) { return (features?.issueType && reportOptions?.validateDirectives) || false; } if (features?.unknownWords) { return true; } if (!reportOptions) { // If no options are provided, report the issue. return true; } // The reporter doesn't support the `unknownWords` feature. Handle the logic here. let reportIssue = issue.isFlagged; reportIssue ||= !reportOptions.unknownWords || reportOptions.unknownWords === unknownWordsChoices.ReportAll; reportIssue ||= reportOptions.unknownWords === unknownWordsChoices.ReportSimple && issue.hasSimpleSuggestions; reportIssue ||= reportOptions.unknownWords === unknownWordsChoices.ReportCommonTypos && issue.hasPreferredSuggestions; return reportIssue || false; } function handleIssue(reporter, issue, reportOptions) { if (!reporter.issue) return; if (!filterFeatureIssues(reporter.features, issue, reportOptions)) { // The reporter does not want to handle this issue. return; } if (!reporter.features?.contextGeneration && !issue.context) { issue = { ...issue }; issue.context = issue.line; // Ensure context is always set if the reporter does not support context generation. } return reporter.issue(issue, reportOptions); } /** * Loads reporter modules configured in cspell config file */ export async function loadReporters(reporters, defaultReporter, config) { async function loadReporter(reporterSettings) { if (reporterSettings === 'default') return defaultReporter; if (!Array.isArray(reporterSettings)) { reporterSettings = [reporterSettings]; } const [moduleName, settings] = reporterSettings; try { const { getReporter } = await dynamicImport(moduleName, [process.cwd(), pkgDir]); return getReporter(settings, config); } catch (e) { throw new ApplicationError(`Failed to load reporter ${moduleName}: ${toError(e).message}`); } } reporters = !reporters || !reporters.length ? ['default'] : [...reporters]; const loadedReporters = await Promise.all(reporters.map(loadReporter)); return loadedReporters.filter((v) => v !== undefined); } export function finalizeReporter(reporter) { if (!reporter) return undefined; if (reporterIsFinalized(reporter)) { return reporter; } const final = { issue: (...params) => reporter.issue?.(...params), info: (...params) => reporter.info?.(...params), debug: (...params) => reporter.debug?.(...params), progress: (...params) => reporter.progress?.(...params), error: (...params) => reporter.error?.(...params), result: (...params) => reporter.result?.(...params), features: reporter.features, }; return final; } function reporterIsFinalized(reporter) { return ((!!reporter && reporter.features && typeof reporter.issue === 'function' && typeof reporter.info === 'function' && typeof reporter.debug === 'function' && typeof reporter.error === 'function' && typeof reporter.progress === 'function' && typeof reporter.result === 'function') || false); } const reportIssueOptionsKeyMap = { unknownWords: 'unknownWords', validateDirectives: 'validateDirectives', showContext: 'showContext', }; function setValue(options, key, value) { if (value !== undefined) { options[key] = value; } } export function extractReporterIssueOptions(settings) { const src = settings; const options = {}; for (const key in reportIssueOptionsKeyMap) { const k = key; setValue(options, k, src[k]); } return options; } export function mergeReportIssueOptions(a, b) { const options = extractReporterIssueOptions(a); if (!b) return options; for (const key in reportIssueOptionsKeyMap) { const k = key; setValue(options, k, b[k]); } return options; } export class LintReporter { defaultReporter; #reporters = []; #config; #finalized = false; constructor(defaultReporter, config) { this.defaultReporter = defaultReporter; this.#config = config; if (defaultReporter) { this.#reporters.push(finalizeReporter(defaultReporter)); } } get config() { return this.#config; } set config(config) { assert(!this.#finalized, 'Cannot change the configuration of a finalized reporter'); this.#config = config; } issue(issue, reportOptions) { for (const reporter of this.#reporters) { handleIssue(reporter, issue, reportOptions); } } info(...params) { for (const reporter of this.#reporters) { reporter.info(...params); } } debug(...params) { for (const reporter of this.#reporters) { reporter.debug(...params); } } error(...params) { for (const reporter of this.#reporters) { reporter.error(...params); } } progress(...params) { for (const reporter of this.#reporters) { reporter.progress(...params); } } async result(result) { await Promise.all(this.#reporters.map((reporter) => reporter.result?.(result))); } get features() { return { unknownWords: true, issueType: true, }; } async loadReportersAndFinalize(reporters) { assert(!this.#finalized, 'Cannot change the configuration of a finalized reporter'); const loaded = await loadReporters(reporters, this.defaultReporter, this.config); this.#reporters = [...new Set(loaded)].map((reporter) => finalizeReporter(reporter)); } emitProgressBegin(filename, fileNum, fileCount) { this.progress({ type: 'ProgressFileBegin', fileNum, fileCount, filename, }); } emitProgressComplete(filename, fileNum, fileCount, result) { const filteredIssues = result.issues.filter((issue) => filterFeatureIssues({}, issue, result.reportIssueOptions)); const numIssues = filteredIssues.length; for (const reporter of this.#reporters) { const progress = clean({ type: 'ProgressFileComplete', fileNum, fileCount, filename, elapsedTimeMs: result.elapsedTimeMs, processed: result.processed, numErrors: numIssues || result.errors, cached: result.cached, perf: result.perf, issues: reporter.features && result.issues, reportIssueOptions: reporter.features && result.reportIssueOptions, }); reporter.progress(progress); } // Show the spelling errors after emitting the progress. result.issues.forEach((issue) => this.issue(issue, result.reportIssueOptions)); return numIssues; } } //# sourceMappingURL=reporters.js.map