• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

streetsidesoftware / cspell / 20903101383

11 Jan 2026 10:42PM UTC coverage: 92.777%. First build
20903101383

Pull #8344

github

web-flow
Merge b7bd9030e into 0214cdb0b
Pull Request #8344: refactor: extract processFile from lint.ts

8800 of 11495 branches covered (76.56%)

80 of 84 new or added lines in 3 files covered. (95.24%)

17136 of 18470 relevant lines covered (92.78%)

32513.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.81
/packages/cspell/src/lint/processFile.ts
1
import { formatWithOptions } from 'node:util';
2

3
import type { ChalkInstance } from 'chalk';
4
import type {
5
    CSpellSettings,
6
    CSpellSettingsWithSourceTrace,
7
    Document,
8
    Issue,
9
    SpellCheckFileResult,
10
    TextDocumentOffset,
11
    ValidationIssue,
12
} from 'cspell-lib';
13
import {
14
    extractDependencies,
15
    extractImportErrors,
16
    getDictionary,
17
    MessageTypes,
18
    spellCheckDocument,
19
    Text as cspellText,
20
} from 'cspell-lib';
21

22
import type { CSpellLintResultCache } from '../util/cache/CSpellLintResultCache.js';
23
import type { ConfigInfo } from '../util/configFileHelper.js';
24
import { toError } from '../util/errors.js';
25
import { extractContext } from '../util/extractContext.js';
26
import { fileInfoToDocument, readFileInfo, relativeToCwd } from '../util/fileHelper.js';
27
import type { LintFileResult } from '../util/LintFileResult.js';
28
import { type LintReporter, mergeReportIssueOptions } from '../util/reporters.js';
29
import { getTimeMeasurer } from '../util/timer.js';
30
import { indent, unindent } from '../util/unindent.js';
31
import * as util from '../util/util.js';
32
import { wordWrapAnsiText } from '../util/wrap.js';
33
import { LinterError } from './LinterError.js';
34
import type { LintRequest } from './LintRequest.js';
35
import type { PrefetchResult } from './types.js';
36

37
export interface ProcessFileOptions {
38
    reporter: LintReporter;
39
    configInfo: ConfigInfo;
40
    verboseLevel: number;
41
    useColor: boolean;
42
    cfg: LintRequest;
43
    configErrors: Set<string>;
44
    chalk: ChalkInstance;
45
}
46

47
export async function processFile(
48
    filename: string,
49
    cache: CSpellLintResultCache,
50
    prefetch: PrefetchResult | undefined,
51
    processFileOptions: ProcessFileOptions,
52
): Promise<LintFileResult> {
53
    if (prefetch?.fileResult) return prefetch.fileResult;
256✔
54

55
    const { reporter, cfg, configInfo } = processFileOptions;
236✔
56

57
    const getElapsedTimeMs = getTimeMeasurer();
236✔
58
    const reportIssueOptions = prefetch?.reportIssueOptions;
236✔
59
    const cachedResult = await cache.getCachedLintResults(filename);
236✔
60
    if (cachedResult) {
236!
NEW
61
        reporter.debug(`Filename: ${filename}, using cache`);
×
NEW
62
        return {
×
63
            ...cachedResult,
64
            elapsedTimeMs: getElapsedTimeMs(),
65
            reportIssueOptions: { ...cachedResult.reportIssueOptions, ...reportIssueOptions },
66
        };
67
    }
68

69
    const result: LintFileResult = {
236✔
70
        fileInfo: {
71
            filename,
72
        },
73
        issues: [],
74
        processed: false,
75
        errors: 0,
76
        configErrors: 0,
77
        elapsedTimeMs: 0,
78
        reportIssueOptions,
79
    };
80

81
    const fileInfo = prefetch?.fileInfo || (await readFileInfo(filename, undefined, true));
236!
82
    if (fileInfo.errorCode) {
236✔
83
        if (fileInfo.errorCode !== 'EISDIR' && cfg.options.mustFindFiles) {
6✔
84
            const err = new LinterError(`File not found: "${filename}"`);
3✔
85
            reporter.error('Linter:', err);
3✔
86
            result.errors += 1;
3✔
87
        }
88
        return result;
6✔
89
    }
90

91
    const doc = fileInfoToDocument(fileInfo, cfg.options.languageId, cfg.locale);
230✔
92
    const { text } = fileInfo;
230✔
93
    result.fileInfo = fileInfo;
230✔
94

95
    let spellResult: Partial<SpellCheckFileResult> = {};
230✔
96
    try {
230✔
97
        const { showSuggestions: generateSuggestions, validateDirectives, skipValidation } = cfg.options;
230✔
98
        const numSuggestions = configInfo.config.numSuggestions ?? 5;
230✔
99
        const validateOptions = util.clean({
230✔
100
            generateSuggestions,
101
            numSuggestions,
102
            validateDirectives,
103
            skipValidation,
104
        });
105
        const r = await spellCheckDocument(doc, validateOptions, configInfo.config);
230✔
106
        // console.warn('filename: %o %o', path.relative(process.cwd(), filename), r.perf);
107
        spellResult = r;
230✔
108
        result.processed = r.checked;
230✔
109
        result.perf = r.perf ? { ...r.perf } : undefined;
230!
110
        result.issues = cspellText.calculateTextDocumentOffsets(doc.uri, text, r.issues).map(mapIssue);
230✔
111
    } catch (e) {
NEW
112
        reporter.error(`Failed to process "${filename}"`, toError(e));
×
NEW
113
        result.errors += 1;
×
114
    }
115
    result.elapsedTimeMs = getElapsedTimeMs();
230✔
116

117
    const config = spellResult.settingsUsed ?? {};
230!
118
    result.reportIssueOptions = mergeReportIssueOptions(
230✔
119
        spellResult.settingsUsed || configInfo.config,
230!
120
        reportIssueOptions,
121
    );
122
    result.configErrors += await reportConfigurationErrors(config, processFileOptions);
230✔
123

124
    reportCheckResult(result, doc, spellResult, config, processFileOptions);
230✔
125

126
    const dep = calcDependencies(config);
230✔
127

128
    await cache.setCachedLintResults(result, dep.files);
230✔
129
    return result;
230✔
130

131
    function mapIssue({ doc: _, ...tdo }: TextDocumentOffset & ValidationIssue): Issue {
132
        const context = cfg.showContext ? extractContext(tdo, cfg.showContext) : undefined;
1,364✔
133
        return util.clean({ ...tdo, context });
1,364✔
134
    }
135
}
136

137
export function reportCheckResult(
138
    result: LintFileResult,
139
    _doc: Document,
140
    spellResult: Partial<SpellCheckFileResult>,
141
    config: CSpellSettingsWithSourceTrace,
142
    processFileOptions: ProcessFileOptions,
143
): void {
144
    const { configInfo, reporter, verboseLevel, useColor, cfg, chalk } = processFileOptions;
230✔
145
    const elapsed = result.elapsedTimeMs || 0;
230!
146
    const dictionaries = config.dictionaries || [];
230✔
147

148
    if (verboseLevel > 1) {
230✔
149
        const dictsUsed = [...dictionaries]
19✔
150
            .sort()
151
            .map((name) => chalk.green(name))
261✔
152
            .join(', ');
153
        const msg = unindent`
19✔
154
                    File type: ${config.languageId}, Language: ${config.language}, Issues: ${
155
                        result.issues.length
156
                    } ${elapsed.toFixed(2)}ms
157
                    Config file Used: ${relativeToCwd(spellResult.localConfigFilepath || configInfo.source, cfg.root)}
23✔
158
                    Dictionaries Used:
159
                      ${wordWrapAnsiText(dictsUsed, 70)}`;
160
        reporter.info(indent(msg, '  '), MessageTypes.Info);
19✔
161
    }
162

163
    if (cfg.options.debug) {
230✔
164
        const { enabled, language, languageId, dictionaries } = config;
2✔
165
        const useConfig = { languageId, enabled, language, dictionaries };
2✔
166
        const msg = unindent`\
2✔
167
                Debug Config: ${formatWithOptions({ depth: 2, colors: useColor }, useConfig)}`;
168
        reporter.debug(msg);
2✔
169
    }
170
}
171

172
interface ConfigDependencies {
173
    files: string[];
174
}
175

176
function calcDependencies(config: CSpellSettings): ConfigDependencies {
177
    const { configFiles, dictionaryFiles } = extractDependencies(config);
230✔
178

179
    return { files: [...configFiles, ...dictionaryFiles] };
230✔
180
}
181

182
export async function reportConfigurationErrors(
183
    config: CSpellSettings,
184
    processFileOptions: ProcessFileOptions,
185
): Promise<number> {
186
    const { reporter, configErrors } = processFileOptions;
385✔
187
    const errors = extractImportErrors(config);
385✔
188
    let count = 0;
385✔
189
    errors.forEach((ref) => {
385✔
190
        const key = ref.error.toString();
14✔
191
        if (configErrors.has(key)) return;
14✔
192
        configErrors.add(key);
10✔
193
        count += 1;
10✔
194
        reporter.error('Configuration', ref.error);
10✔
195
    });
196

197
    const dictCollection = await getDictionary(config);
385✔
198
    dictCollection.dictionaries.forEach((dict) => {
385✔
199
        const dictErrors = dict.getErrors?.() || [];
5,237!
200
        const msg = `Dictionary Error with (${dict.name})`;
5,237✔
201
        dictErrors.forEach((error) => {
5,237✔
202
            const key = msg + error.toString();
2✔
203
            if (configErrors.has(key)) return;
2✔
204
            configErrors.add(key);
1✔
205
            count += 1;
1✔
206
            reporter.error(msg, error);
1✔
207
        });
208
    });
209

210
    return count;
385✔
211
}
212

213
export function countConfigErrors(configInfo: ConfigInfo, processFileOptions: ProcessFileOptions): Promise<number> {
214
    return reportConfigurationErrors(configInfo.config, processFileOptions);
155✔
215
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc