Skip to content

Commit f598991

Browse files
authored
refactor: extract processFile from lint.ts (#8344)
1 parent 0214cdb commit f598991

4 files changed

Lines changed: 289 additions & 232 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export class LinterError extends Error {
2+
constructor(message: string) {
3+
super(message);
4+
}
5+
6+
toString(): string {
7+
return this.message;
8+
}
9+
}

packages/cspell/src/lint/lint.ts

Lines changed: 25 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,21 @@ import { formatWithOptions } from 'node:util';
33

44
import { isAsyncIterable, operators, opFilter, pipeAsync } from '@cspell/cspell-pipe';
55
import { opMap, pipe } from '@cspell/cspell-pipe/sync';
6-
import type {
7-
CSpellSettings,
8-
CSpellSettingsWithSourceTrace,
9-
Glob,
10-
Issue,
11-
ReportIssueOptions,
12-
RunResult,
13-
TextDocumentOffset,
14-
} from '@cspell/cspell-types';
6+
import type { Glob, RunResult } from '@cspell/cspell-types';
157
import { MessageTypes } from '@cspell/cspell-types';
168
import { toFileURL } from '@cspell/url';
179
import chalk from 'chalk';
1810
import { dictionaryCacheEnableLogging, dictionaryCacheGetLog } from 'cspell-dictionary';
1911
import { findRepoRoot, GitIgnore } from 'cspell-gitignore';
2012
import { GlobMatcher, type GlobMatchOptions, type GlobPatternNormalized, type GlobPatternWithRoot } from 'cspell-glob';
21-
import type { Document, Logger, SpellCheckFileResult, ValidationIssue } from 'cspell-lib';
13+
import type { Logger } from 'cspell-lib';
2214
import {
2315
ENV_CSPELL_GLOB_ROOT,
24-
extractDependencies,
25-
extractImportErrors,
2616
getDefaultConfigLoader,
27-
getDictionary,
2817
isBinaryFile as cspellIsBinaryFile,
2918
mergeSettings,
3019
setLogger,
3120
shouldCheckDocument,
32-
spellCheckDocument,
33-
Text as cspellText,
3421
} from 'cspell-lib';
3522

3623
import { console } from '../console.js';
@@ -41,11 +28,8 @@ import { npmPackage } from '../pkgInfo.js';
4128
import type { CreateCacheSettings, CSpellLintResultCache } from '../util/cache/index.js';
4229
import { calcCacheSettings, createCache } from '../util/cache/index.js';
4330
import { type ConfigInfo, readConfig } from '../util/configFileHelper.js';
44-
import { ApplicationError, CheckFailed, toApplicationError, toError } from '../util/errors.js';
45-
import { extractContext } from '../util/extractContext.js';
46-
import type { ReadFileInfoResult } from '../util/fileHelper.js';
31+
import { ApplicationError, CheckFailed, toApplicationError } from '../util/errors.js';
4732
import {
48-
fileInfoToDocument,
4933
filenameToUri,
5034
findFiles,
5135
getFileSize,
@@ -68,14 +52,16 @@ import {
6852
import type { LintFileResult } from '../util/LintFileResult.js';
6953
import { prefetchIterable } from '../util/prefetch.js';
7054
import type { FinalizedReporter } from '../util/reporters.js';
71-
import { extractReporterIssueOptions, LintReporter, mergeReportIssueOptions } from '../util/reporters.js';
55+
import { extractReporterIssueOptions, LintReporter } from '../util/reporters.js';
7256
import { getTimeMeasurer } from '../util/timer.js';
73-
import { indent, unindent } from '../util/unindent.js';
57+
import { unindent } from '../util/unindent.js';
7458
import { sizeToNumber } from '../util/unitNumbers.js';
7559
import * as util from '../util/util.js';
7660
import { wordWrapAnsiText } from '../util/wrap.js';
7761
import { writeFileOrStream } from '../util/writeFile.js';
7862
import type { LintRequest } from './LintRequest.js';
63+
import { countConfigErrors, processFile, type ProcessFileOptions } from './processFile.js';
64+
import type { PFCached, PFFile, PFSkipped, PrefetchFileResult } from './types.js';
7965

8066
const version = npmPackage.version;
8167

@@ -111,42 +97,6 @@ export async function runLint(cfg: LintRequest): Promise<RunResult> {
11197
}
11298
return lintResult;
11399

114-
interface PrefetchResult {
115-
fileResult?: LintFileResult | undefined;
116-
fileInfo?: ReadFileInfoResult | undefined;
117-
skip?: boolean | undefined;
118-
skipReason?: string | undefined;
119-
reportIssueOptions?: ReportIssueOptions | undefined;
120-
}
121-
122-
interface PFCached extends PrefetchResult {
123-
fileResult: LintFileResult;
124-
fileInfo?: undefined;
125-
skipReason?: undefined;
126-
skip?: undefined;
127-
}
128-
129-
interface PFFile extends PrefetchResult {
130-
fileResult?: undefined;
131-
fileInfo: ReadFileInfoResult;
132-
skip?: undefined;
133-
skipReason?: undefined;
134-
reportIssueOptions: ReportIssueOptions | undefined;
135-
}
136-
137-
interface PFSkipped extends PrefetchResult {
138-
fileResult?: undefined;
139-
fileInfo?: undefined;
140-
skip: true;
141-
skipReason?: string | undefined;
142-
reportIssueOptions?: undefined;
143-
}
144-
145-
interface PrefetchFileResult {
146-
filename: string;
147-
result?: Promise<PFCached | PFFile | PFSkipped | Error>;
148-
}
149-
150100
function prefetch(filename: string, configInfo: ConfigInfo, cache: CSpellLintResultCache): PrefetchFileResult {
151101
if (isBinaryFile(filename, cfg.root)) {
152102
return { filename, result: Promise.resolve({ skip: true, skipReason: 'Binary file.' }) };
@@ -184,128 +134,6 @@ export async function runLint(cfg: LintRequest): Promise<RunResult> {
184134
return { filename, result };
185135
}
186136

187-
async function processFile(
188-
filename: string,
189-
configInfo: ConfigInfo,
190-
cache: CSpellLintResultCache,
191-
prefetch: PrefetchResult | undefined,
192-
): Promise<LintFileResult> {
193-
if (prefetch?.fileResult) return prefetch.fileResult;
194-
195-
const getElapsedTimeMs = getTimeMeasurer();
196-
const reportIssueOptions = prefetch?.reportIssueOptions;
197-
const cachedResult = await cache.getCachedLintResults(filename);
198-
if (cachedResult) {
199-
reporter.debug(`Filename: ${filename}, using cache`);
200-
return {
201-
...cachedResult,
202-
elapsedTimeMs: getElapsedTimeMs(),
203-
reportIssueOptions: { ...cachedResult.reportIssueOptions, ...reportIssueOptions },
204-
};
205-
}
206-
207-
const result: LintFileResult = {
208-
fileInfo: {
209-
filename,
210-
},
211-
issues: [],
212-
processed: false,
213-
errors: 0,
214-
configErrors: 0,
215-
elapsedTimeMs: 0,
216-
reportIssueOptions,
217-
};
218-
219-
const fileInfo = prefetch?.fileInfo || (await readFileInfo(filename, undefined, true));
220-
if (fileInfo.errorCode) {
221-
if (fileInfo.errorCode !== 'EISDIR' && cfg.options.mustFindFiles) {
222-
const err = new LinterError(`File not found: "${filename}"`);
223-
reporter.error('Linter:', err);
224-
result.errors += 1;
225-
}
226-
return result;
227-
}
228-
229-
const doc = fileInfoToDocument(fileInfo, cfg.options.languageId, cfg.locale);
230-
const { text } = fileInfo;
231-
result.fileInfo = fileInfo;
232-
233-
let spellResult: Partial<SpellCheckFileResult> = {};
234-
try {
235-
const { showSuggestions: generateSuggestions, validateDirectives, skipValidation } = cfg.options;
236-
const numSuggestions = configInfo.config.numSuggestions ?? 5;
237-
const validateOptions = util.clean({
238-
generateSuggestions,
239-
numSuggestions,
240-
validateDirectives,
241-
skipValidation,
242-
});
243-
const r = await spellCheckDocument(doc, validateOptions, configInfo.config);
244-
// console.warn('filename: %o %o', path.relative(process.cwd(), filename), r.perf);
245-
spellResult = r;
246-
result.processed = r.checked;
247-
result.perf = r.perf ? { ...r.perf } : undefined;
248-
result.issues = cspellText.calculateTextDocumentOffsets(doc.uri, text, r.issues).map(mapIssue);
249-
} catch (e) {
250-
reporter.error(`Failed to process "${filename}"`, toError(e));
251-
result.errors += 1;
252-
}
253-
result.elapsedTimeMs = getElapsedTimeMs();
254-
255-
const config = spellResult.settingsUsed ?? {};
256-
result.reportIssueOptions = mergeReportIssueOptions(
257-
spellResult.settingsUsed || configInfo.config,
258-
reportIssueOptions,
259-
);
260-
result.configErrors += await reportConfigurationErrors(config);
261-
262-
reportCheckResult(result, doc, spellResult, configInfo, config);
263-
264-
const dep = calcDependencies(config);
265-
266-
await cache.setCachedLintResults(result, dep.files);
267-
return result;
268-
}
269-
270-
function reportCheckResult(
271-
result: LintFileResult,
272-
_doc: Document,
273-
spellResult: Partial<SpellCheckFileResult>,
274-
configInfo: ConfigInfo,
275-
config: CSpellSettingsWithSourceTrace,
276-
) {
277-
const elapsed = result.elapsedTimeMs || 0;
278-
const dictionaries = config.dictionaries || [];
279-
280-
if (verboseLevel > 1) {
281-
const dictsUsed = [...dictionaries]
282-
.sort()
283-
.map((name) => chalk.green(name))
284-
.join(', ');
285-
const msg = unindent`
286-
File type: ${config.languageId}, Language: ${config.language}, Issues: ${
287-
result.issues.length
288-
} ${elapsed.toFixed(2)}ms
289-
Config file Used: ${relativeToCwd(spellResult.localConfigFilepath || configInfo.source, cfg.root)}
290-
Dictionaries Used:
291-
${wordWrapAnsiText(dictsUsed, 70)}`;
292-
reporter.info(indent(msg, ' '), MessageTypes.Info);
293-
}
294-
295-
if (cfg.options.debug) {
296-
const { enabled, language, languageId, dictionaries } = config;
297-
const useConfig = { languageId, enabled, language, dictionaries };
298-
const msg = unindent`\
299-
Debug Config: ${formatWithOptions({ depth: 2, colors: useColor }, useConfig)}`;
300-
reporter.debug(msg);
301-
}
302-
}
303-
304-
function mapIssue({ doc: _, ...tdo }: TextDocumentOffset & ValidationIssue): Issue {
305-
const context = cfg.showContext ? extractContext(tdo, cfg.showContext) : undefined;
306-
return util.clean({ ...tdo, context });
307-
}
308-
309137
async function processFiles(
310138
files: string[] | AsyncIterable<string>,
311139
configInfo: ConfigInfo,
@@ -366,7 +194,7 @@ export async function runLint(cfg: LintRequest): Promise<RunResult> {
366194
result,
367195
};
368196
}
369-
const result = await processFile(filename, configInfo, cache, fetchResult);
197+
const result = await processFile(filename, cache, fetchResult, getProcessFileOptions(configInfo));
370198
return { filename, fileNum: index, result };
371199
}
372200

@@ -405,53 +233,15 @@ export async function runLint(cfg: LintRequest): Promise<RunResult> {
405233
return status;
406234
}
407235

408-
interface ConfigDependencies {
409-
files: string[];
410-
}
411-
412-
function calcDependencies(config: CSpellSettings): ConfigDependencies {
413-
const { configFiles, dictionaryFiles } = extractDependencies(config);
414-
415-
return { files: [...configFiles, ...dictionaryFiles] };
416-
}
417-
418-
async function reportConfigurationErrors(config: CSpellSettings): Promise<number> {
419-
const errors = extractImportErrors(config);
420-
let count = 0;
421-
errors.forEach((ref) => {
422-
const key = ref.error.toString();
423-
if (configErrors.has(key)) return;
424-
configErrors.add(key);
425-
count += 1;
426-
reporter.error('Configuration', ref.error);
427-
});
428-
429-
const dictCollection = await getDictionary(config);
430-
dictCollection.dictionaries.forEach((dict) => {
431-
const dictErrors = dict.getErrors?.() || [];
432-
const msg = `Dictionary Error with (${dict.name})`;
433-
dictErrors.forEach((error) => {
434-
const key = msg + error.toString();
435-
if (configErrors.has(key)) return;
436-
configErrors.add(key);
437-
count += 1;
438-
reporter.error(msg, error);
439-
});
440-
});
441-
442-
return count;
443-
}
444-
445-
function countConfigErrors(configInfo: ConfigInfo): Promise<number> {
446-
return reportConfigurationErrors(configInfo.config);
447-
}
448-
449236
async function run(): Promise<RunResult> {
450237
if (cfg.options.root) {
451238
setEnvironmentVariable(ENV_CSPELL_GLOB_ROOT, cfg.root);
452239
}
453240

454241
const configInfo: ConfigInfo = await readConfig(cfg.configFile, cfg.root, cfg.options.stopConfigSearchAt);
242+
243+
const processFileOptions = getProcessFileOptions(configInfo);
244+
455245
if (cfg.options.defaultConfiguration !== undefined) {
456246
configInfo.config.loadDefaultConfiguration = cfg.options.defaultConfiguration;
457247
}
@@ -484,7 +274,7 @@ export async function runLint(cfg: LintRequest): Promise<RunResult> {
484274
reporter.info(`Config Files Found:\n ${relativeToCwd(configInfo.source)}\n`, MessageTypes.Info);
485275
}
486276

487-
const configErrors = await countConfigErrors(configInfo);
277+
const configErrors = await countConfigErrors(configInfo, processFileOptions);
488278
if (configErrors && cfg.options.exitCode !== false && !cfg.options.continueOnError) {
489279
return runResult({ errors: configErrors });
490280
}
@@ -528,6 +318,19 @@ export async function runLint(cfg: LintRequest): Promise<RunResult> {
528318
MessageTypes.Info,
529319
);
530320
}
321+
322+
function getProcessFileOptions(configInfo: ConfigInfo): ProcessFileOptions {
323+
const processFileOptionsGeneral: ProcessFileOptions = {
324+
reporter,
325+
chalk,
326+
configInfo,
327+
cfg,
328+
verboseLevel,
329+
useColor,
330+
configErrors,
331+
};
332+
return processFileOptionsGeneral;
333+
}
531334
}
532335

533336
interface AppGlobInfo {
@@ -765,16 +568,6 @@ function globPattern(g: Glob) {
765568
return typeof g === 'string' ? g : g.glob;
766569
}
767570

768-
export class LinterError extends Error {
769-
constructor(message: string) {
770-
super(message);
771-
}
772-
773-
toString(): string {
774-
return this.message;
775-
}
776-
}
777-
778571
function calcVerboseLevel(options: LintRequest['options']): number {
779572
return options.verboseLevel ?? (options.verbose ? 1 : 0);
780573
}

0 commit comments

Comments
 (0)