cspell 9.0.1 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +78 -59
  2. package/dist/esm/app.mjs +4 -15
  3. package/dist/esm/application.d.mts +3 -1
  4. package/dist/esm/application.mjs +14 -6
  5. package/dist/esm/cli-reporter.js +2 -1
  6. package/dist/esm/commandConfig.d.ts +3 -0
  7. package/dist/esm/commandConfig.js +18 -0
  8. package/dist/esm/commandDictionaries.d.ts +3 -0
  9. package/dist/esm/commandDictionaries.js +40 -0
  10. package/dist/esm/commandHelpers.d.ts +18 -0
  11. package/dist/esm/commandHelpers.js +30 -0
  12. package/dist/esm/commandInit.d.ts +3 -0
  13. package/dist/esm/commandInit.js +25 -0
  14. package/dist/esm/commandLint.js +13 -26
  15. package/dist/esm/commandTrace.js +2 -0
  16. package/dist/esm/config/adjustConfig.d.ts +7 -0
  17. package/dist/esm/config/adjustConfig.js +137 -0
  18. package/dist/esm/config/config.d.ts +5 -0
  19. package/dist/esm/config/config.js +18 -0
  20. package/dist/esm/config/configInit.d.ts +3 -0
  21. package/dist/esm/config/configInit.js +104 -0
  22. package/dist/esm/config/constants.d.ts +17 -0
  23. package/dist/esm/config/constants.js +23 -0
  24. package/dist/esm/config/index.d.ts +3 -0
  25. package/dist/esm/config/index.js +2 -0
  26. package/dist/esm/config/options.d.ts +62 -0
  27. package/dist/esm/config/options.js +2 -0
  28. package/dist/esm/config/updateConfig.d.ts +3 -0
  29. package/dist/esm/config/updateConfig.js +2 -0
  30. package/dist/esm/dictionaries/index.d.ts +3 -0
  31. package/dist/esm/dictionaries/index.js +2 -0
  32. package/dist/esm/dictionaries/listDictionaries.d.ts +33 -0
  33. package/dist/esm/dictionaries/listDictionaries.js +131 -0
  34. package/dist/esm/emitters/dictionaryListEmitter.d.ts +19 -0
  35. package/dist/esm/emitters/dictionaryListEmitter.js +82 -0
  36. package/dist/esm/emitters/helpers.d.ts +14 -0
  37. package/dist/esm/emitters/helpers.js +67 -0
  38. package/dist/esm/emitters/traceEmitter.d.ts +1 -10
  39. package/dist/esm/emitters/traceEmitter.js +1 -69
  40. package/dist/esm/lint/LintRequest.d.ts +3 -2
  41. package/dist/esm/lint/LintRequest.js +41 -5
  42. package/dist/esm/lint/index.d.ts +1 -1
  43. package/dist/esm/lint/index.js +1 -1
  44. package/dist/esm/lint/lint.js +28 -66
  45. package/dist/esm/options.d.ts +101 -4
  46. package/dist/esm/options.js +1 -0
  47. package/dist/esm/pkgInfo.d.ts +1 -1
  48. package/dist/esm/pkgInfo.js +1 -1
  49. package/dist/esm/util/InMemoryReporter.d.ts +10 -7
  50. package/dist/esm/util/InMemoryReporter.js +20 -13
  51. package/dist/esm/util/LintFileResult.d.ts +14 -0
  52. package/dist/esm/util/LintFileResult.js +2 -0
  53. package/dist/esm/util/cache/CSpellLintResultCache.d.ts +3 -3
  54. package/dist/esm/util/cache/DiskCache.d.ts +4 -4
  55. package/dist/esm/util/configFileHelper.d.ts +1 -1
  56. package/dist/esm/util/configFileHelper.js +2 -2
  57. package/dist/esm/util/extractContext.d.ts +5 -0
  58. package/dist/esm/util/extractContext.js +75 -0
  59. package/dist/esm/util/fileHelper.d.ts +2 -11
  60. package/dist/esm/util/fileHelper.js +9 -1
  61. package/dist/esm/util/pad.d.ts +16 -0
  62. package/dist/esm/util/pad.js +61 -1
  63. package/dist/esm/util/reporters.d.ts +21 -5
  64. package/dist/esm/util/reporters.js +178 -31
  65. package/dist/esm/util/table.d.ts +31 -4
  66. package/dist/esm/util/table.js +76 -16
  67. package/dist/esm/util/util.d.ts +5 -0
  68. package/dist/esm/util/util.js +5 -0
  69. package/package.json +16 -15
@@ -0,0 +1,14 @@
1
+ import type { DictionaryPathFormat } from './DictionaryPathFormat.js';
2
+ export interface PathInterface {
3
+ relative(from: string, to: string): string;
4
+ basename(path: string): string;
5
+ sep: string;
6
+ }
7
+ export declare function trimMidPath(s: string, w: number, sep: string): string;
8
+ export declare function trimMid(s: string, w: number): string;
9
+ export declare function formatDictionaryLocation(dictSource: string, maxWidth: number, { cwd, dictionaryPathFormat: format, iPath, }: {
10
+ cwd: string;
11
+ dictionaryPathFormat: DictionaryPathFormat;
12
+ iPath: PathInterface;
13
+ }): string;
14
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1,67 @@
1
+ export function trimMidPath(s, w, sep) {
2
+ if (s.length <= w)
3
+ return s;
4
+ const parts = s.split(sep);
5
+ if (parts[parts.length - 1].length > w)
6
+ return trimMid(s, w);
7
+ function join(left, right) {
8
+ // if (left === right) return parts.join(sep);
9
+ return [...parts.slice(0, left), '…', ...parts.slice(right)].join(sep);
10
+ }
11
+ let left = 0, right = parts.length, last = '';
12
+ for (let i = 0; i < parts.length; ++i) {
13
+ const incLeft = i & 1 ? 1 : 0;
14
+ const incRight = incLeft ? 0 : -1;
15
+ const next = join(left + incLeft, right + incRight);
16
+ if (next.length > w)
17
+ break;
18
+ left += incLeft;
19
+ right += incRight;
20
+ last = next;
21
+ }
22
+ for (let i = left + 1; i < right; ++i) {
23
+ const next = join(i, right);
24
+ if (next.length > w)
25
+ break;
26
+ last = next;
27
+ }
28
+ for (let i = right - 1; i > left; --i) {
29
+ const next = join(left, i);
30
+ if (next.length > w)
31
+ break;
32
+ last = next;
33
+ }
34
+ return last || trimMid(s, w);
35
+ }
36
+ export function trimMid(s, w) {
37
+ s = s.trim();
38
+ if (s.length <= w) {
39
+ return s;
40
+ }
41
+ const l = Math.floor((w - 1) / 2);
42
+ const r = Math.ceil((w - 1) / 2);
43
+ return s.slice(0, l) + '…' + s.slice(-r);
44
+ }
45
+ export function formatDictionaryLocation(dictSource, maxWidth, { cwd, dictionaryPathFormat: format, iPath, }) {
46
+ let relPath = cwd ? iPath.relative(cwd, dictSource) : dictSource;
47
+ const idxNodeModule = relPath.lastIndexOf('node_modules');
48
+ const isNodeModule = idxNodeModule >= 0;
49
+ if (format === 'hide')
50
+ return '';
51
+ if (format === 'short') {
52
+ const prefix = isNodeModule
53
+ ? '[node_modules]/'
54
+ : relPath.startsWith('..' + iPath.sep + '..')
55
+ ? '…/'
56
+ : relPath.startsWith('..' + iPath.sep)
57
+ ? '../'
58
+ : '';
59
+ return prefix + iPath.basename(dictSource);
60
+ }
61
+ if (format === 'full')
62
+ return dictSource;
63
+ relPath = isNodeModule ? relPath.slice(idxNodeModule) : relPath;
64
+ const usePath = relPath.length < dictSource.length ? relPath : dictSource;
65
+ return trimMidPath(usePath, maxWidth, iPath.sep);
66
+ }
67
+ //# sourceMappingURL=helpers.js.map
@@ -1,10 +1,6 @@
1
1
  import type { TraceResult } from '../application.mjs';
2
2
  import type { DictionaryPathFormat } from './DictionaryPathFormat.js';
3
- interface PathInterface {
4
- relative(from: string, to: string): string;
5
- basename(path: string): string;
6
- sep: string;
7
- }
3
+ import { type PathInterface } from './helpers.js';
8
4
  export interface EmitTraceOptions {
9
5
  /** current working directory */
10
6
  cwd: string;
@@ -20,9 +16,4 @@ export declare function calcTraceResultsReport(word: string, found: boolean, res
20
16
  table: string;
21
17
  errors: string;
22
18
  };
23
- declare function trimMidPath(s: string, w: number, sep: string): string;
24
- export declare const __testing__: {
25
- trimMidPath: typeof trimMidPath;
26
- };
27
- export {};
28
19
  //# sourceMappingURL=traceEmitter.d.ts.map
@@ -2,6 +2,7 @@ import * as iPath from 'node:path';
2
2
  import chalk from 'chalk';
3
3
  import { console } from '../console.js';
4
4
  import { tableToLines } from '../util/table.js';
5
+ import { formatDictionaryLocation } from './helpers.js';
5
6
  const maxWidth = 120;
6
7
  const colWidthDictionaryName = 20;
7
8
  export function emitTraceResults(word, found, results, options) {
@@ -66,15 +67,6 @@ function emitErrors(results) {
66
67
  return chalk.bold(r.dictName) + '\n\t' + chalk.red(errors);
67
68
  });
68
69
  }
69
- function trimMid(s, w) {
70
- s = s.trim();
71
- if (s.length <= w) {
72
- return s;
73
- }
74
- const l = Math.floor((w - 3) / 2);
75
- const r = Math.ceil((w - 3) / 2);
76
- return s.slice(0, l) + '...' + s.slice(-r);
77
- }
78
70
  function calcFoundChar(r) {
79
71
  const errors = r.errors?.map((e) => e.message)?.join('\n\t') || '';
80
72
  let color = chalk.dim;
@@ -89,67 +81,7 @@ function calcFoundChar(r) {
89
81
  char = errors ? 'X' : char;
90
82
  return color(char);
91
83
  }
92
- function formatDictionaryLocation(dictSource, maxWidth, { cwd, dictionaryPathFormat: format, iPath, }) {
93
- let relPath = cwd ? iPath.relative(cwd, dictSource) : dictSource;
94
- const idxNodeModule = relPath.lastIndexOf('node_modules');
95
- const isNodeModule = idxNodeModule >= 0;
96
- if (format === 'hide')
97
- return '';
98
- if (format === 'short') {
99
- const prefix = isNodeModule
100
- ? '[node_modules]/'
101
- : relPath.startsWith('..' + iPath.sep + '..')
102
- ? '.../'
103
- : relPath.startsWith('..' + iPath.sep)
104
- ? '../'
105
- : '';
106
- return prefix + iPath.basename(dictSource);
107
- }
108
- if (format === 'full')
109
- return dictSource;
110
- relPath = isNodeModule ? relPath.slice(idxNodeModule) : relPath;
111
- const usePath = relPath.length < dictSource.length ? relPath : dictSource;
112
- return trimMidPath(usePath, maxWidth, iPath.sep);
113
- }
114
84
  function colorize(fn) {
115
85
  return (s) => (s ? fn(s) : '');
116
86
  }
117
- function trimMidPath(s, w, sep) {
118
- if (s.length <= w)
119
- return s;
120
- const parts = s.split(sep);
121
- if (parts[parts.length - 1].length > w)
122
- return trimMid(s, w);
123
- function join(left, right) {
124
- // if (left === right) return parts.join(sep);
125
- return [...parts.slice(0, left), '...', ...parts.slice(right)].join(sep);
126
- }
127
- let left = 0, right = parts.length, last = '';
128
- for (let i = 0; i < parts.length; ++i) {
129
- const incLeft = i & 1 ? 1 : 0;
130
- const incRight = incLeft ? 0 : -1;
131
- const next = join(left + incLeft, right + incRight);
132
- if (next.length > w)
133
- break;
134
- left += incLeft;
135
- right += incRight;
136
- last = next;
137
- }
138
- for (let i = left + 1; i < right; ++i) {
139
- const next = join(i, right);
140
- if (next.length > w)
141
- break;
142
- last = next;
143
- }
144
- for (let i = right - 1; i > left; --i) {
145
- const next = join(left, i);
146
- if (next.length > w)
147
- break;
148
- last = next;
149
- }
150
- return last || trimMid(s, w);
151
- }
152
- export const __testing__ = {
153
- trimMidPath,
154
- };
155
87
  //# sourceMappingURL=traceEmitter.js.map
@@ -1,4 +1,4 @@
1
- import type { Issue } from '@cspell/cspell-types';
1
+ import { type CSpellUserSettings, type UnknownWordsConfiguration } from '@cspell/cspell-types';
2
2
  import type { CSpellConfigFile, LinterCliOptions, LinterOptions } from '../options.js';
3
3
  import type { GlobSrcInfo } from '../util/glob.js';
4
4
  import type { FinalizedReporter } from '../util/reporters.js';
@@ -10,7 +10,6 @@ export declare class LintRequest {
10
10
  readonly fileGlobs: string[];
11
11
  readonly options: LinterCliOptions & Deprecated;
12
12
  readonly reporter: FinalizedReporter;
13
- readonly uniqueFilter: (issue: Issue) => boolean;
14
13
  readonly locale: string;
15
14
  readonly configFile: string | CSpellConfigFile | undefined;
16
15
  readonly excludes: GlobSrcInfo[];
@@ -19,7 +18,9 @@ export declare class LintRequest {
19
18
  readonly enableGlobDot: boolean | undefined;
20
19
  readonly fileLists: string[];
21
20
  readonly files: string[] | undefined;
21
+ readonly cspellSettingsFromCliOptions: CSpellUserSettings;
22
22
  constructor(fileGlobs: string[], options: LinterCliOptions & Deprecated, reporter: FinalizedReporter);
23
23
  }
24
+ export declare function extractUnknownWordsConfig(options: LinterCliOptions): UnknownWordsConfiguration;
24
25
  export {};
25
26
  //# sourceMappingURL=LintRequest.d.ts.map
@@ -1,11 +1,11 @@
1
1
  import * as path from 'node:path';
2
+ import { unknownWordsChoices } from '@cspell/cspell-types';
2
3
  import { calcExcludeGlobInfo } from '../util/glob.js';
3
4
  const defaultContextRange = 20;
4
5
  export class LintRequest {
5
6
  fileGlobs;
6
7
  options;
7
8
  reporter;
8
- uniqueFilter;
9
9
  locale;
10
10
  configFile;
11
11
  excludes;
@@ -14,6 +14,7 @@ export class LintRequest {
14
14
  enableGlobDot;
15
15
  fileLists;
16
16
  files;
17
+ cspellSettingsFromCliOptions;
17
18
  constructor(fileGlobs, options, reporter) {
18
19
  this.fileGlobs = fileGlobs;
19
20
  this.options = options;
@@ -23,12 +24,23 @@ export class LintRequest {
23
24
  this.excludes = calcExcludeGlobInfo(this.root, options.exclude);
24
25
  this.locale = options.locale ?? options.local ?? '';
25
26
  this.enableGlobDot = options.dot;
26
- // this.uniqueFilter = options.unique ? util.uniqueFilterFnGenerator((issue: Issue) => issue.text) : () => true;
27
- this.uniqueFilter = () => true;
28
- this.showContext =
29
- options.showContext === true ? defaultContextRange : options.showContext ? options.showContext : 0;
27
+ this.showContext = Math.max(options.showContext === true ? defaultContextRange : options.showContext ? options.showContext : 0, 0);
30
28
  this.fileLists = (options.fileList ?? options.fileLists) || [];
31
29
  this.files = mergeFiles(options.file, options.files);
30
+ const noConfigSearch = options.configSearch === false ? true : options.configSearch === true ? false : undefined;
31
+ const dictionaries = [
32
+ ...(options.disableDictionary ?? []).map((d) => `!${d}`), // first disable dictionaries
33
+ ...(options.dictionary ?? []).map((d) => `!!${d}`), // Use `!!` to ensure dictionaries are enabled
34
+ ];
35
+ const languageSettings = [
36
+ // Use `*` to match all languages and locales
37
+ { languageId: '*', locale: '*', dictionaries },
38
+ ];
39
+ this.cspellSettingsFromCliOptions = {
40
+ ...(noConfigSearch !== undefined ? { noConfigSearch } : {}),
41
+ ...extractUnknownWordsConfig(options),
42
+ languageSettings,
43
+ };
32
44
  }
33
45
  }
34
46
  function mergeFiles(a, b) {
@@ -44,4 +56,28 @@ function merge(a, b) {
44
56
  return a;
45
57
  return [...a, ...b];
46
58
  }
59
+ export function extractUnknownWordsConfig(options) {
60
+ const config = {};
61
+ if (!options.report)
62
+ return config;
63
+ switch (options.report) {
64
+ case 'all': {
65
+ config.unknownWords = unknownWordsChoices.ReportAll;
66
+ break;
67
+ }
68
+ case 'simple': {
69
+ config.unknownWords = unknownWordsChoices.ReportSimple;
70
+ break;
71
+ }
72
+ case 'typos': {
73
+ config.unknownWords = unknownWordsChoices.ReportCommonTypos;
74
+ break;
75
+ }
76
+ case 'flagged': {
77
+ config.unknownWords = unknownWordsChoices.ReportFlagged;
78
+ break;
79
+ }
80
+ }
81
+ return config;
82
+ }
47
83
  //# sourceMappingURL=LintRequest.js.map
@@ -1,3 +1,3 @@
1
1
  export { runLint } from './lint.js';
2
- export { LintRequest } from './LintRequest.js';
2
+ export { extractUnknownWordsConfig, LintRequest } from './LintRequest.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,3 @@
1
1
  export { runLint } from './lint.js';
2
- export { LintRequest } from './LintRequest.js';
2
+ export { extractUnknownWordsConfig, LintRequest } from './LintRequest.js';
3
3
  //# sourceMappingURL=index.js.map
@@ -8,7 +8,7 @@ import chalk from 'chalk';
8
8
  import { _debug as cspellDictionaryDebug } from 'cspell-dictionary';
9
9
  import { findRepoRoot, GitIgnore } from 'cspell-gitignore';
10
10
  import { GlobMatcher } from 'cspell-glob';
11
- import { ENV_CSPELL_GLOB_ROOT, extractDependencies, extractImportErrors, getDefaultConfigLoader, getDictionary, isBinaryFile as cspellIsBinaryFile, setLogger, shouldCheckDocument, spellCheckDocument, Text as cspellText, } from 'cspell-lib';
11
+ import { ENV_CSPELL_GLOB_ROOT, extractDependencies, extractImportErrors, getDefaultConfigLoader, getDictionary, isBinaryFile as cspellIsBinaryFile, mergeSettings, setLogger, shouldCheckDocument, spellCheckDocument, Text as cspellText, } from 'cspell-lib';
12
12
  import { console } from '../console.js';
13
13
  import { getEnvironmentVariable, setEnvironmentVariable, truthy } from '../environment.js';
14
14
  import { getFeatureFlags } from '../featureFlags/index.js';
@@ -16,10 +16,11 @@ import { npmPackage } from '../pkgInfo.js';
16
16
  import { calcCacheSettings, createCache } from '../util/cache/index.js';
17
17
  import { readConfig } from '../util/configFileHelper.js';
18
18
  import { CheckFailed, toApplicationError, toError } from '../util/errors.js';
19
- import { fileInfoToDocument, filenameToUri, findFiles, isBinaryFile, isFile, isNotDir, readFileInfo, readFileListFiles, resolveFilename, } from '../util/fileHelper.js';
19
+ import { extractContext } from '../util/extractContext.js';
20
+ import { fileInfoToDocument, filenameToUri, findFiles, isBinaryFile, isFile, isNotDir, readFileInfo, readFileListFiles, relativeToCwd, resolveFilename, } from '../util/fileHelper.js';
20
21
  import { buildGlobMatcher, extractGlobsFromMatcher, extractPatterns, normalizeFileOrGlobsToRoot, normalizeGlobsToRoot, } from '../util/glob.js';
21
22
  import { prefetchIterable } from '../util/prefetch.js';
22
- import { loadReporters, mergeReporters } from '../util/reporters.js';
23
+ import { extractReporterIssueOptions, LintReporter, mergeReportIssueOptions } from '../util/reporters.js';
23
24
  import { getTimeMeasurer } from '../util/timer.js';
24
25
  import * as util from '../util/util.js';
25
26
  import { writeFileOrStream } from '../util/writeFile.js';
@@ -28,8 +29,7 @@ const BATCH_SIZE = 8;
28
29
  const debugStats = false;
29
30
  const { opFilterAsync } = operators;
30
31
  export async function runLint(cfg) {
31
- let { reporter } = cfg;
32
- setLogger(getLoggerFromReporter(reporter));
32
+ const reporter = new LintReporter(cfg.reporter, cfg.options);
33
33
  const configErrors = new Set();
34
34
  const timer = getTimeMeasurer();
35
35
  const logDictRequests = truthy(getEnvironmentVariable('CSPELL_ENABLE_DICTIONARY_LOGGING'));
@@ -49,6 +49,7 @@ export async function runLint(cfg) {
49
49
  function prefetch(filename, configInfo, cache) {
50
50
  if (isBinaryFile(filename, cfg.root))
51
51
  return { filename, result: Promise.resolve({ skip: true }) };
52
+ const reportIssueOptions = extractReporterIssueOptions(configInfo.config);
52
53
  async function fetch() {
53
54
  const getElapsedTimeMs = getTimeMeasurer();
54
55
  const cachedResult = await cache.getCachedLintResults(filename);
@@ -62,7 +63,7 @@ export async function runLint(cfg) {
62
63
  if (!checkResult.shouldCheck)
63
64
  return { skip: true };
64
65
  const fileInfo = await readFileInfo(filename, undefined, true);
65
- return { fileInfo };
66
+ return { fileInfo, reportIssueOptions };
66
67
  }
67
68
  const result = fetch();
68
69
  return { filename, result };
@@ -71,10 +72,11 @@ export async function runLint(cfg) {
71
72
  if (prefetch?.fileResult)
72
73
  return prefetch.fileResult;
73
74
  const getElapsedTimeMs = getTimeMeasurer();
75
+ const reportIssueOptions = prefetch?.reportIssueOptions;
74
76
  const cachedResult = await cache.getCachedLintResults(filename);
75
77
  if (cachedResult) {
76
78
  reporter.debug(`Filename: ${filename}, using cache`);
77
- return { ...cachedResult, elapsedTimeMs: getElapsedTimeMs() };
79
+ return { ...cachedResult, elapsedTimeMs: getElapsedTimeMs(), reportIssueOptions };
78
80
  }
79
81
  const result = {
80
82
  fileInfo: {
@@ -85,6 +87,7 @@ export async function runLint(cfg) {
85
87
  errors: 0,
86
88
  configErrors: 0,
87
89
  elapsedTimeMs: 0,
90
+ reportIssueOptions,
88
91
  };
89
92
  const fileInfo = prefetch?.fileInfo || (await readFileInfo(filename, undefined, true));
90
93
  if (fileInfo.errorCode) {
@@ -122,6 +125,7 @@ export async function runLint(cfg) {
122
125
  }
123
126
  result.elapsedTimeMs = getElapsedTimeMs();
124
127
  const config = spellResult.settingsUsed ?? {};
128
+ result.reportIssueOptions = mergeReportIssueOptions(spellResult.settingsUsed || configInfo.config, reportIssueOptions);
125
129
  result.configErrors += await reportConfigurationErrors(config);
126
130
  const elapsed = result.elapsedTimeMs;
127
131
  const dictionaries = config.dictionaries || [];
@@ -144,9 +148,7 @@ export async function runLint(cfg) {
144
148
  return result;
145
149
  }
146
150
  function mapIssue({ doc: _, ...tdo }) {
147
- const context = cfg.showContext
148
- ? extractContext(tdo, cfg.showContext)
149
- : { text: tdo.line.text.trimEnd(), offset: tdo.line.offset };
151
+ const context = cfg.showContext ? extractContext(tdo, cfg.showContext) : undefined;
150
152
  return util.clean({ ...tdo, context });
151
153
  }
152
154
  async function processFiles(files, configInfo, cacheSettings) {
@@ -154,23 +156,6 @@ export async function runLint(cfg) {
154
156
  const status = runResult();
155
157
  const cache = createCache(cacheSettings);
156
158
  const failFast = cfg.options.failFast ?? configInfo.config.failFast ?? false;
157
- const emitProgressBegin = (filename, fileNum, fileCount) => reporter.progress({
158
- type: 'ProgressFileBegin',
159
- fileNum,
160
- fileCount,
161
- filename,
162
- });
163
- const emitProgressComplete = (filename, fileNum, fileCount, result) => reporter.progress(util.clean({
164
- type: 'ProgressFileComplete',
165
- fileNum,
166
- fileCount,
167
- filename,
168
- elapsedTimeMs: result?.elapsedTimeMs,
169
- processed: result?.processed,
170
- numErrors: result?.issues.length || result?.errors,
171
- cached: result?.cached,
172
- perf: result?.perf,
173
- }));
174
159
  function* prefetchFiles(files) {
175
160
  const iter = prefetchIterable(pipe(files, opMap((filename) => prefetch(filename, configInfo, cache))), BATCH_SIZE);
176
161
  for (const v of iter) {
@@ -189,12 +174,13 @@ export async function runLint(cfg) {
189
174
  errors: 0,
190
175
  configErrors: 0,
191
176
  elapsedTimeMs: 1,
177
+ reportIssueOptions: undefined,
192
178
  };
193
179
  async function processPrefetchFileResult(pf, index) {
194
180
  const { filename, result: pFetchResult } = pf;
195
181
  const getElapsedTimeMs = getTimeMeasurer();
196
182
  const fetchResult = await pFetchResult;
197
- emitProgressBegin(filename, index, fileCount ?? index);
183
+ reporter.emitProgressBegin(filename, index, fileCount ?? index);
198
184
  if (fetchResult?.skip) {
199
185
  return {
200
186
  filename,
@@ -230,15 +216,13 @@ export async function runLint(cfg) {
230
216
  }
231
217
  }
232
218
  for await (const fileP of loadAndProcessFiles()) {
233
- const { filename, fileNum, result } = await fileP;
219
+ const { filename, fileNum, result } = fileP;
234
220
  status.files += 1;
235
221
  status.cachedFiles = (status.cachedFiles || 0) + (result.cached ? 1 : 0);
236
- emitProgressComplete(filename, fileNum, fileCount ?? fileNum, result);
237
- // Show the spelling errors after emitting the progress.
238
- result.issues.filter(cfg.uniqueFilter).forEach((issue) => reporter.issue(issue));
239
- if (result.issues.length || result.errors) {
240
- status.filesWithIssues.add(filename);
241
- status.issues += result.issues.length;
222
+ const numIssues = reporter.emitProgressComplete(filename, fileNum, fileCount ?? fileNum, result);
223
+ if (numIssues || result.errors) {
224
+ status.filesWithIssues.add(relativeToCwd(filename, cfg.root));
225
+ status.issues += numIssues;
242
226
  status.errors += result.errors;
243
227
  if (failFast) {
244
228
  return status;
@@ -286,10 +270,11 @@ export async function runLint(cfg) {
286
270
  if (cfg.options.root) {
287
271
  setEnvironmentVariable(ENV_CSPELL_GLOB_ROOT, cfg.root);
288
272
  }
289
- const configInfo = await readConfig(cfg.configFile, cfg.root);
273
+ const configInfo = await readConfig(cfg.configFile, cfg.root, cfg.options.stopConfigSearchAt);
290
274
  if (cfg.options.defaultConfiguration !== undefined) {
291
275
  configInfo.config.loadDefaultConfiguration = cfg.options.defaultConfiguration;
292
276
  }
277
+ configInfo.config = mergeSettings(configInfo.config, cfg.cspellSettingsFromCliOptions);
293
278
  const reporterConfig = util.clean({
294
279
  maxNumberOfProblems: configInfo.config.maxNumberOfProblems,
295
280
  maxDuplicateProblems: configInfo.config.maxDuplicateProblems,
@@ -298,7 +283,8 @@ export async function runLint(cfg) {
298
283
  console,
299
284
  });
300
285
  const reporters = cfg.options.reporter ?? configInfo.config.reporters;
301
- reporter = mergeReporters(...(await loadReporters(reporters, cfg.reporter, reporterConfig)));
286
+ reporter.config = reporterConfig;
287
+ await reporter.loadReportersAndFinalize(reporters);
302
288
  setLogger(getLoggerFromReporter(reporter));
303
289
  const globInfo = await determineGlobs(configInfo, cfg);
304
290
  const { fileGlobs, excludeGlobs } = globInfo;
@@ -311,14 +297,18 @@ export async function runLint(cfg) {
311
297
  checkGlobs(fileGlobs, reporter);
312
298
  reporter.info(`Config Files Found:\n ${configInfo.source}\n`, MessageTypes.Info);
313
299
  const configErrors = await countConfigErrors(configInfo);
314
- if (configErrors && cfg.options.exitCode !== false)
300
+ if (configErrors && cfg.options.exitCode !== false && !cfg.options.continueOnError) {
315
301
  return runResult({ errors: configErrors });
302
+ }
316
303
  // Get Exclusions from the config files.
317
304
  const { root } = cfg;
318
305
  try {
319
306
  const cacheSettings = await calcCacheSettings(configInfo.config, { ...cfg.options, version }, root);
320
307
  const files = await determineFilesToCheck(configInfo, cfg, reporter, globInfo);
321
308
  const result = await processFiles(files, configInfo, cacheSettings);
309
+ if (configErrors && cfg.options.exitCode !== false) {
310
+ result.errors ||= configErrors;
311
+ }
322
312
  debugStats && console.error('stats: %o', getDefaultConfigLoader().getStats());
323
313
  return result;
324
314
  }
@@ -433,34 +423,6 @@ async function determineFilesToCheck(configInfo, cfg, reporter, globInfo) {
433
423
  }
434
424
  return _determineFilesToCheck();
435
425
  }
436
- function extractContext(tdo, contextRange) {
437
- const { line, offset } = tdo;
438
- const textOffsetInLine = offset - line.offset;
439
- let left = Math.max(textOffsetInLine - contextRange, 0);
440
- let right = Math.min(line.text.length, textOffsetInLine + contextRange + tdo.text.length);
441
- const lineText = line.text;
442
- const isLetter = /^[a-z]$/i;
443
- const isSpace = /^\s$/;
444
- for (let n = contextRange / 2; n > 0 && left > 0; n--, left--) {
445
- if (!isLetter.test(lineText[left - 1])) {
446
- break;
447
- }
448
- }
449
- for (let n = contextRange / 2; n > 0 && right < lineText.length; n--, right++) {
450
- if (!isLetter.test(lineText[right])) {
451
- break;
452
- }
453
- }
454
- // remove leading space
455
- for (; left < textOffsetInLine && isSpace.test(lineText[left]); left++) {
456
- /* do nothing */
457
- }
458
- const context = {
459
- text: line.text.slice(left, right).trimEnd(),
460
- offset: left + line.offset,
461
- };
462
- return context;
463
- }
464
426
  function extractGlobSource(g) {
465
427
  const { glob, rawGlob, source } = g;
466
428
  return {