cspell 9.1.2 → 9.1.5
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.
- package/README.md +29 -21
- package/bin.mjs +1 -1
- package/dist/esm/app.d.ts +19 -0
- package/dist/esm/app.js +1033 -0
- package/dist/esm/application-D-NwS6qb.js +2573 -0
- package/dist/esm/application-DbOQYm56.d.ts +116 -0
- package/dist/esm/application.d.ts +3 -0
- package/dist/esm/application.js +3 -0
- package/dist/esm/index.d.ts +55 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/options-ChaXtdFn.d.ts +387 -0
- package/package.json +27 -25
- package/dist/esm/app.d.mts +0 -5
- package/dist/esm/app.mjs +0 -34
- package/dist/esm/application.d.mts +0 -17
- package/dist/esm/application.mjs +0 -99
- package/dist/esm/cli-reporter.d.ts +0 -38
- package/dist/esm/cli-reporter.js +0 -386
- package/dist/esm/commandCheck.d.ts +0 -3
- package/dist/esm/commandCheck.js +0 -52
- package/dist/esm/commandConfig.d.ts +0 -3
- package/dist/esm/commandConfig.js +0 -18
- package/dist/esm/commandDictionaries.d.ts +0 -3
- package/dist/esm/commandDictionaries.js +0 -40
- package/dist/esm/commandHelpers.d.ts +0 -18
- package/dist/esm/commandHelpers.js +0 -30
- package/dist/esm/commandInit.d.ts +0 -3
- package/dist/esm/commandInit.js +0 -25
- package/dist/esm/commandLink.d.ts +0 -3
- package/dist/esm/commandLink.js +0 -48
- package/dist/esm/commandLint.d.ts +0 -3
- package/dist/esm/commandLint.js +0 -226
- package/dist/esm/commandSuggestion.d.ts +0 -3
- package/dist/esm/commandSuggestion.js +0 -61
- package/dist/esm/commandTrace.d.ts +0 -3
- package/dist/esm/commandTrace.js +0 -91
- package/dist/esm/config/adjustConfig.d.ts +0 -7
- package/dist/esm/config/adjustConfig.js +0 -137
- package/dist/esm/config/config.d.ts +0 -5
- package/dist/esm/config/config.js +0 -18
- package/dist/esm/config/configInit.d.ts +0 -3
- package/dist/esm/config/configInit.js +0 -104
- package/dist/esm/config/constants.d.ts +0 -17
- package/dist/esm/config/constants.js +0 -23
- package/dist/esm/config/index.d.ts +0 -3
- package/dist/esm/config/index.js +0 -2
- package/dist/esm/config/options.d.ts +0 -62
- package/dist/esm/config/options.js +0 -2
- package/dist/esm/config/updateConfig.d.ts +0 -3
- package/dist/esm/config/updateConfig.js +0 -2
- package/dist/esm/console.d.ts +0 -25
- package/dist/esm/console.js +0 -53
- package/dist/esm/dictionaries/index.d.ts +0 -3
- package/dist/esm/dictionaries/index.js +0 -2
- package/dist/esm/dictionaries/listDictionaries.d.ts +0 -33
- package/dist/esm/dictionaries/listDictionaries.js +0 -131
- package/dist/esm/dirname.d.ts +0 -2
- package/dist/esm/dirname.js +0 -13
- package/dist/esm/emitters/DictionaryPathFormat.d.ts +0 -3
- package/dist/esm/emitters/DictionaryPathFormat.js +0 -12
- package/dist/esm/emitters/dictionaryListEmitter.d.ts +0 -19
- package/dist/esm/emitters/dictionaryListEmitter.js +0 -82
- package/dist/esm/emitters/helpers.d.ts +0 -14
- package/dist/esm/emitters/helpers.js +0 -67
- package/dist/esm/emitters/suggestionsEmitter.d.ts +0 -13
- package/dist/esm/emitters/suggestionsEmitter.js +0 -79
- package/dist/esm/emitters/traceEmitter.d.ts +0 -19
- package/dist/esm/emitters/traceEmitter.js +0 -87
- package/dist/esm/environment.d.ts +0 -39
- package/dist/esm/environment.js +0 -30
- package/dist/esm/featureFlags/featureFlags.d.ts +0 -4
- package/dist/esm/featureFlags/featureFlags.js +0 -21
- package/dist/esm/featureFlags/index.d.ts +0 -2
- package/dist/esm/featureFlags/index.js +0 -2
- package/dist/esm/index.d.mts +0 -6
- package/dist/esm/index.mjs +0 -4
- package/dist/esm/link.d.ts +0 -8
- package/dist/esm/link.js +0 -39
- package/dist/esm/lint/LintRequest.d.ts +0 -26
- package/dist/esm/lint/LintRequest.js +0 -83
- package/dist/esm/lint/index.d.ts +0 -3
- package/dist/esm/lint/index.js +0 -3
- package/dist/esm/lint/lint.d.ts +0 -8
- package/dist/esm/lint/lint.js +0 -515
- package/dist/esm/models.d.ts +0 -15
- package/dist/esm/models.js +0 -2
- package/dist/esm/options.d.ts +0 -353
- package/dist/esm/options.js +0 -9
- package/dist/esm/pkgInfo.d.ts +0 -14
- package/dist/esm/pkgInfo.js +0 -7
- package/dist/esm/repl/index.d.ts +0 -18
- package/dist/esm/repl/index.js +0 -52
- package/dist/esm/util/InMemoryReporter.d.ts +0 -31
- package/dist/esm/util/InMemoryReporter.js +0 -49
- package/dist/esm/util/LintFileResult.d.ts +0 -14
- package/dist/esm/util/LintFileResult.js +0 -2
- package/dist/esm/util/async.d.ts +0 -3
- package/dist/esm/util/async.js +0 -4
- package/dist/esm/util/cache/CSpellLintResultCache.d.ts +0 -20
- package/dist/esm/util/cache/CSpellLintResultCache.js +0 -2
- package/dist/esm/util/cache/CacheOptions.d.ts +0 -34
- package/dist/esm/util/cache/CacheOptions.js +0 -2
- package/dist/esm/util/cache/DiskCache.d.ts +0 -63
- package/dist/esm/util/cache/DiskCache.js +0 -214
- package/dist/esm/util/cache/DummyCache.d.ts +0 -11
- package/dist/esm/util/cache/DummyCache.js +0 -18
- package/dist/esm/util/cache/ObjectCollection.d.ts +0 -17
- package/dist/esm/util/cache/ObjectCollection.js +0 -127
- package/dist/esm/util/cache/createCache.d.ts +0 -31
- package/dist/esm/util/cache/createCache.js +0 -69
- package/dist/esm/util/cache/file-entry-cache.d.mts +0 -4
- package/dist/esm/util/cache/file-entry-cache.mjs +0 -5
- package/dist/esm/util/cache/fileEntryCache.d.ts +0 -9
- package/dist/esm/util/cache/fileEntryCache.js +0 -79
- package/dist/esm/util/cache/index.d.ts +0 -4
- package/dist/esm/util/cache/index.js +0 -2
- package/dist/esm/util/canUseColor.d.ts +0 -2
- package/dist/esm/util/canUseColor.js +0 -10
- package/dist/esm/util/configFileHelper.d.ts +0 -15
- package/dist/esm/util/configFileHelper.js +0 -43
- package/dist/esm/util/constants.d.ts +0 -6
- package/dist/esm/util/constants.js +0 -6
- package/dist/esm/util/errors.d.ts +0 -24
- package/dist/esm/util/errors.js +0 -60
- package/dist/esm/util/extractContext.d.ts +0 -5
- package/dist/esm/util/extractContext.js +0 -75
- package/dist/esm/util/fileHelper.d.ts +0 -44
- package/dist/esm/util/fileHelper.js +0 -165
- package/dist/esm/util/glob.d.ts +0 -45
- package/dist/esm/util/glob.js +0 -147
- package/dist/esm/util/pad.d.ts +0 -45
- package/dist/esm/util/pad.js +0 -191
- package/dist/esm/util/prefetch.d.ts +0 -2
- package/dist/esm/util/prefetch.js +0 -15
- package/dist/esm/util/reporters.d.ts +0 -30
- package/dist/esm/util/reporters.js +0 -209
- package/dist/esm/util/stdin.d.ts +0 -2
- package/dist/esm/util/stdin.js +0 -5
- package/dist/esm/util/stdinUrl.d.ts +0 -9
- package/dist/esm/util/stdinUrl.js +0 -26
- package/dist/esm/util/table.d.ts +0 -41
- package/dist/esm/util/table.js +0 -115
- package/dist/esm/util/timer.d.ts +0 -4
- package/dist/esm/util/timer.js +0 -9
- package/dist/esm/util/types.d.ts +0 -7
- package/dist/esm/util/types.js +0 -5
- package/dist/esm/util/unindent.d.ts +0 -14
- package/dist/esm/util/unindent.js +0 -55
- package/dist/esm/util/util.d.ts +0 -14
- package/dist/esm/util/util.js +0 -30
- package/dist/esm/util/writeFile.d.ts +0 -3
- package/dist/esm/util/writeFile.js +0 -30
|
@@ -0,0 +1,2573 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import chalk, { Chalk } from "chalk";
|
|
3
|
+
import { isAsyncIterable, opFilter, opMap, opTap, operators, pipeAsync, pipeAsync as asyncPipe, toAsyncIterable, toAsyncIterable as mergeAsyncIterables } from "@cspell/cspell-pipe";
|
|
4
|
+
import * as cspell from "cspell-lib";
|
|
5
|
+
import { ENV_CSPELL_GLOB_ROOT, IncludeExcludeFlag, SuggestionError, Text, checkTextDocument, combineTextAndLanguageSettings, createPerfTimer, extractDependencies, extractImportErrors, fileToDocument, getDefaultSettings, getDictionary, getGlobalSettingsAsync, getSystemFeatureFlags, isBinaryFile, isSpellingDictionaryLoadError, mergeSettings, setLogger, shouldCheckDocument, spellCheckDocument, suggestionsForWords, traceWordsAsync } from "cspell-lib";
|
|
6
|
+
import assert from "node:assert";
|
|
7
|
+
import { format, formatWithOptions } from "node:util";
|
|
8
|
+
import { toFileDirURL, toFilePathOrHref, toFileURL, urlRelative } from "@cspell/url";
|
|
9
|
+
import { makeTemplate } from "chalk-template";
|
|
10
|
+
import fs, { stat } from "node:fs/promises";
|
|
11
|
+
import { MutableCSpellConfigFile, createReaderWriter, cspellConfigFileSchema, isCfgArrayNode } from "cspell-config-lib";
|
|
12
|
+
import * as fs$1 from "node:fs";
|
|
13
|
+
import { mkdirSync, promises } from "node:fs";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import * as path$1 from "node:path";
|
|
16
|
+
import path, { dirname, isAbsolute, posix, relative, resolve } from "node:path";
|
|
17
|
+
import { opMap as opMap$1, pipe } from "@cspell/cspell-pipe/sync";
|
|
18
|
+
import { IssueType, MessageTypes, unknownWordsChoices } from "@cspell/cspell-types";
|
|
19
|
+
import { _debug } from "cspell-dictionary";
|
|
20
|
+
import { GitIgnore, findRepoRoot } from "cspell-gitignore";
|
|
21
|
+
import { GlobMatcher, fileOrGlobToGlob, workaroundPicomatchBug } from "cspell-glob";
|
|
22
|
+
import * as crypto from "node:crypto";
|
|
23
|
+
import streamConsumers from "node:stream/consumers";
|
|
24
|
+
import { readFileText, toURL } from "cspell-io";
|
|
25
|
+
import { glob } from "tinyglobby";
|
|
26
|
+
import * as readline from "node:readline";
|
|
27
|
+
import { isMainThread } from "node:worker_threads";
|
|
28
|
+
import fileEntryCache from "file-entry-cache";
|
|
29
|
+
import { dynamicImport } from "@cspell/dynamic-import";
|
|
30
|
+
|
|
31
|
+
//#region src/console.ts
|
|
32
|
+
var ImplChannel = class {
|
|
33
|
+
constructor(stream) {
|
|
34
|
+
this.stream = stream;
|
|
35
|
+
}
|
|
36
|
+
write = (msg) => this.stream.write(msg);
|
|
37
|
+
writeLine = (msg) => this.write(msg + "\n");
|
|
38
|
+
clearLine = (dir, callback) => this.stream.clearLine?.(dir, callback) ?? false;
|
|
39
|
+
printLine = (...params) => this.writeLine(params.length && formatWithOptions({ colors: this.stream.hasColors?.() }, ...params) || "");
|
|
40
|
+
getColorLevel = () => getColorLevel(this.stream);
|
|
41
|
+
};
|
|
42
|
+
var Console = class {
|
|
43
|
+
stderrChannel;
|
|
44
|
+
stdoutChannel;
|
|
45
|
+
constructor(stdout = process.stdout, stderr = process.stderr) {
|
|
46
|
+
this.stdout = stdout;
|
|
47
|
+
this.stderr = stderr;
|
|
48
|
+
this.stderrChannel = new ImplChannel(this.stderr);
|
|
49
|
+
this.stdoutChannel = new ImplChannel(this.stdout);
|
|
50
|
+
}
|
|
51
|
+
log = (...p) => this.stdoutChannel.printLine(...p);
|
|
52
|
+
error = (...p) => this.stderrChannel.printLine(...p);
|
|
53
|
+
info = this.log;
|
|
54
|
+
warn = this.error;
|
|
55
|
+
};
|
|
56
|
+
const console = new Console();
|
|
57
|
+
function getColorLevel(stream) {
|
|
58
|
+
const depth = stream.getColorDepth?.() || 0;
|
|
59
|
+
switch (depth) {
|
|
60
|
+
case 1: return 1;
|
|
61
|
+
case 4: return 2;
|
|
62
|
+
case 24: return 3;
|
|
63
|
+
default: return 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/util/errors.ts
|
|
69
|
+
var CheckFailed = class extends Error {
|
|
70
|
+
constructor(message, exitCode = 1) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.exitCode = exitCode;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var ApplicationError = class extends Error {
|
|
76
|
+
constructor(message, exitCode = 1, cause) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.exitCode = exitCode;
|
|
79
|
+
this.cause = cause;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var IOError = class extends ApplicationError {
|
|
83
|
+
constructor(message, cause) {
|
|
84
|
+
super(message, void 0, cause);
|
|
85
|
+
this.cause = cause;
|
|
86
|
+
}
|
|
87
|
+
get code() {
|
|
88
|
+
return this.cause.code;
|
|
89
|
+
}
|
|
90
|
+
isNotFound() {
|
|
91
|
+
return this.cause.code === "ENOENT";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
function toError(e) {
|
|
95
|
+
if (isError(e)) return e;
|
|
96
|
+
if (isErrorLike(e)) {
|
|
97
|
+
const ex = new Error(e.message, { cause: e });
|
|
98
|
+
if (e.code !== void 0) ex.code = e.code;
|
|
99
|
+
return ex;
|
|
100
|
+
}
|
|
101
|
+
const message = format(e);
|
|
102
|
+
return new Error(message);
|
|
103
|
+
}
|
|
104
|
+
function isError(e) {
|
|
105
|
+
return e instanceof Error;
|
|
106
|
+
}
|
|
107
|
+
function isErrorLike(e) {
|
|
108
|
+
if (e instanceof Error) return true;
|
|
109
|
+
if (!e || typeof e !== "object") return false;
|
|
110
|
+
const ex = e;
|
|
111
|
+
return typeof ex.message === "string";
|
|
112
|
+
}
|
|
113
|
+
function toApplicationError(e, message) {
|
|
114
|
+
if (e instanceof ApplicationError && !message) return e;
|
|
115
|
+
const err = toError(e);
|
|
116
|
+
return new ApplicationError(message ?? err.message, void 0, err);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/util/util.ts
|
|
121
|
+
const uniqueFn = uniqueFilterFnGenerator;
|
|
122
|
+
function uniqueFilterFnGenerator(extractFn) {
|
|
123
|
+
const values = /* @__PURE__ */ new Set();
|
|
124
|
+
const extractor = extractFn || ((a) => a);
|
|
125
|
+
return (v) => {
|
|
126
|
+
const vv = extractor(v);
|
|
127
|
+
const ret = !values.has(vv);
|
|
128
|
+
values.add(vv);
|
|
129
|
+
return ret;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Removed all properties with a value of `undefined` from the object.
|
|
134
|
+
* @param src - the object to clean.
|
|
135
|
+
* @returns the same object with all properties with a value of `undefined` removed.
|
|
136
|
+
*/
|
|
137
|
+
function clean(src) {
|
|
138
|
+
const r = src;
|
|
139
|
+
for (const key of Object.keys(r)) if (r[key] === void 0) delete r[key];
|
|
140
|
+
return r;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/cli-reporter.ts
|
|
145
|
+
const templateIssue = `{green $filename}:{yellow $row:$col} - $message ({red $text}) $quickFix`;
|
|
146
|
+
const templateIssueNoFix = `{green $filename}:{yellow $row:$col} - $message ({red $text})`;
|
|
147
|
+
const templateIssueWithSuggestions = `{green $filename}:{yellow $row:$col} - $message ({red $text}) Suggestions: {yellow [$suggestions]}`;
|
|
148
|
+
const templateIssueWithContext = `{green $filename}:{yellow $row:$col} $padRowCol- $message ({red $text})$padContext -- {gray $contextLeft}{red {underline $text}}{gray $contextRight}`;
|
|
149
|
+
const templateIssueWithContextWithSuggestions = `{green $filename}:{yellow $row:$col} $padRowCol- $message ({red $text})$padContext -- {gray $contextLeft}{red {underline $text}}{gray $contextRight}\n\t Suggestions: {yellow [$suggestions]}`;
|
|
150
|
+
const templateIssueLegacy = `{green $filename}[$row, $col]: $message: {red $text}`;
|
|
151
|
+
const templateIssueWordsOnly = "$text";
|
|
152
|
+
assert(true);
|
|
153
|
+
/**
|
|
154
|
+
*
|
|
155
|
+
* @param template - The template to use for the issue.
|
|
156
|
+
* @param uniqueIssues - If true, only unique issues will be reported.
|
|
157
|
+
* @param reportedIssuesCollection - optional collection to store reported issues.
|
|
158
|
+
* @returns issueEmitter function
|
|
159
|
+
*/
|
|
160
|
+
function genIssueEmitter(stdIO, errIO, template, uniqueIssues, reportedIssuesCollection) {
|
|
161
|
+
const uniqueFilter = uniqueIssues ? uniqueFilterFnGenerator((issue) => issue.text) : () => true;
|
|
162
|
+
const defaultWidth = 10;
|
|
163
|
+
let maxWidth = defaultWidth;
|
|
164
|
+
let uri;
|
|
165
|
+
return function issueEmitter(issue) {
|
|
166
|
+
if (!uniqueFilter(issue)) return;
|
|
167
|
+
if (uri !== issue.uri) {
|
|
168
|
+
maxWidth = defaultWidth;
|
|
169
|
+
uri = issue.uri;
|
|
170
|
+
}
|
|
171
|
+
maxWidth = Math.max(maxWidth * .999, issue.text.length, 10);
|
|
172
|
+
const issueText = formatIssue(stdIO, template, issue, Math.ceil(maxWidth));
|
|
173
|
+
reportedIssuesCollection?.push(formatIssue(errIO, template, issue, Math.ceil(maxWidth)));
|
|
174
|
+
stdIO.writeLine(issueText);
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function nullEmitter() {}
|
|
178
|
+
function relativeUriFilename(uri, rootURL) {
|
|
179
|
+
const url = toFileURL(uri);
|
|
180
|
+
const rel = urlRelative(rootURL, url);
|
|
181
|
+
if (rel.startsWith("..")) return toFilePathOrHref(url);
|
|
182
|
+
return rel;
|
|
183
|
+
}
|
|
184
|
+
function reportProgress(io, p, cwdURL, options) {
|
|
185
|
+
if (p.type === "ProgressFileComplete") return reportProgressFileComplete(io, p, cwdURL, options);
|
|
186
|
+
if (p.type === "ProgressFileBegin") return reportProgressFileBegin(io, p, cwdURL);
|
|
187
|
+
}
|
|
188
|
+
function determineFilename(io, p, cwd) {
|
|
189
|
+
const fc = "" + p.fileCount;
|
|
190
|
+
const fn = (" ".repeat(fc.length) + p.fileNum).slice(-fc.length);
|
|
191
|
+
const idx = fn + "/" + fc;
|
|
192
|
+
const filename = io.chalk.gray(relativeUriFilename(p.filename, cwd));
|
|
193
|
+
return {
|
|
194
|
+
idx,
|
|
195
|
+
filename
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function reportProgressFileBegin(io, p, cwdURL) {
|
|
199
|
+
const { idx, filename } = determineFilename(io, p, cwdURL);
|
|
200
|
+
if (io.getColorLevel() > 0) {
|
|
201
|
+
io.clearLine?.(0);
|
|
202
|
+
io.write(`${idx} ${filename}\r`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function reportProgressFileComplete(io, p, cwd, options) {
|
|
206
|
+
const { idx, filename } = determineFilename(io, p, cwd);
|
|
207
|
+
const { verbose, debug } = options;
|
|
208
|
+
const time = reportTime(io, p.elapsedTimeMs, !!p.cached);
|
|
209
|
+
const skipped = p.processed === false ? " skipped" : "";
|
|
210
|
+
const hasErrors = p.numErrors ? io.chalk.red` X` : "";
|
|
211
|
+
const newLine = skipped && (verbose || debug) || hasErrors || isSlow(p.elapsedTimeMs) || io.getColorLevel() < 1 ? "\n" : "";
|
|
212
|
+
const msg = `${idx} ${filename} ${time}${skipped}${hasErrors}${newLine || "\r"}`;
|
|
213
|
+
io.write(msg);
|
|
214
|
+
}
|
|
215
|
+
function reportTime(io, elapsedTimeMs, cached) {
|
|
216
|
+
if (cached) return io.chalk.green("cached");
|
|
217
|
+
if (elapsedTimeMs === void 0) return "-";
|
|
218
|
+
const slow = isSlow(elapsedTimeMs);
|
|
219
|
+
const color = !slow ? io.chalk.white : slow === 1 ? io.chalk.yellow : io.chalk.redBright;
|
|
220
|
+
return color(elapsedTimeMs.toFixed(2) + "ms");
|
|
221
|
+
}
|
|
222
|
+
function isSlow(elapsedTmeMs) {
|
|
223
|
+
if (!elapsedTmeMs || elapsedTmeMs < 1e3) return 0;
|
|
224
|
+
if (elapsedTmeMs < 2e3) return 1;
|
|
225
|
+
return 2;
|
|
226
|
+
}
|
|
227
|
+
function getReporter(options, config) {
|
|
228
|
+
const perfStats = {
|
|
229
|
+
filesProcessed: 0,
|
|
230
|
+
filesSkipped: 0,
|
|
231
|
+
filesCached: 0,
|
|
232
|
+
elapsedTimeMs: 0,
|
|
233
|
+
perf: Object.create(null)
|
|
234
|
+
};
|
|
235
|
+
const noColor = options.color === false;
|
|
236
|
+
const forceColor = options.color === true;
|
|
237
|
+
const uniqueIssues = config?.unique || false;
|
|
238
|
+
const defaultIssueTemplate = options.wordsOnly ? templateIssueWordsOnly : options.legacy ? templateIssueLegacy : options.showContext ? options.showSuggestions ? templateIssueWithContextWithSuggestions : templateIssueWithContext : options.showSuggestions ? templateIssueWithSuggestions : options.showSuggestions === false ? templateIssueNoFix : templateIssue;
|
|
239
|
+
const { fileGlobs, silent, summary, issues, progress: showProgress, verbose, debug } = options;
|
|
240
|
+
const issueTemplate = config?.issueTemplate || defaultIssueTemplate;
|
|
241
|
+
assertCheckTemplate(issueTemplate);
|
|
242
|
+
const console$1 = config?.console || console;
|
|
243
|
+
const colorLevel = noColor ? 0 : forceColor ? 2 : console$1.stdoutChannel.getColorLevel();
|
|
244
|
+
const stdio = {
|
|
245
|
+
...console$1.stdoutChannel,
|
|
246
|
+
chalk: new Chalk({ level: colorLevel })
|
|
247
|
+
};
|
|
248
|
+
const stderr = {
|
|
249
|
+
...console$1.stderrChannel,
|
|
250
|
+
chalk: new Chalk({ level: colorLevel })
|
|
251
|
+
};
|
|
252
|
+
const consoleError = (msg) => stderr.writeLine(msg);
|
|
253
|
+
function createInfoLog(wrap) {
|
|
254
|
+
return (msg) => console$1.info(wrap(msg));
|
|
255
|
+
}
|
|
256
|
+
const emitters = {
|
|
257
|
+
Debug: !silent && debug ? createInfoLog(stdio.chalk.cyan) : nullEmitter,
|
|
258
|
+
Info: !silent && verbose ? createInfoLog(stdio.chalk.yellow) : nullEmitter,
|
|
259
|
+
Warning: createInfoLog(stdio.chalk.yellow)
|
|
260
|
+
};
|
|
261
|
+
function infoEmitter(message, msgType) {
|
|
262
|
+
emitters[msgType]?.(message);
|
|
263
|
+
}
|
|
264
|
+
const rootURL = toFileDirURL(options.root || process.cwd());
|
|
265
|
+
function relativeIssue(fn) {
|
|
266
|
+
const fnFilename = options.relative ? (uri) => relativeUriFilename(uri, rootURL) : (uri) => toFilePathOrHref(toFileURL(uri, rootURL));
|
|
267
|
+
return (i) => {
|
|
268
|
+
const fullFilename = i.uri ? toFilePathOrHref(toFileURL(i.uri, rootURL)) : "";
|
|
269
|
+
const filename = i.uri ? fnFilename(i.uri) : "";
|
|
270
|
+
const r = {
|
|
271
|
+
...i,
|
|
272
|
+
filename,
|
|
273
|
+
fullFilename
|
|
274
|
+
};
|
|
275
|
+
fn(r);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const repeatIssues = false;
|
|
279
|
+
const issuesCollection = void 0;
|
|
280
|
+
const errorCollection = [];
|
|
281
|
+
function errorEmitter(message, error) {
|
|
282
|
+
if (isSpellingDictionaryLoadError(error)) error = error.cause;
|
|
283
|
+
const errorText = formatWithOptions({ colors: stderr.stream.hasColors?.() }, stderr.chalk.red(message), debug ? error : error.toString());
|
|
284
|
+
errorCollection?.push(errorText);
|
|
285
|
+
consoleError(errorText);
|
|
286
|
+
}
|
|
287
|
+
const resultEmitter = (result) => {
|
|
288
|
+
if (!fileGlobs.length && !result.files) return;
|
|
289
|
+
const { files, issues: issues$1, cachedFiles, filesWithIssues, errors } = result;
|
|
290
|
+
const numFilesWithIssues = filesWithIssues.size;
|
|
291
|
+
if (stderr.getColorLevel() > 0) {
|
|
292
|
+
stderr.write("\r");
|
|
293
|
+
stderr.clearLine(0);
|
|
294
|
+
}
|
|
295
|
+
if (issuesCollection?.length || errorCollection?.length) consoleError("-------------------------------------------");
|
|
296
|
+
if (issuesCollection?.length) {
|
|
297
|
+
consoleError("Issues found:");
|
|
298
|
+
issuesCollection.forEach((issue) => consoleError(issue));
|
|
299
|
+
}
|
|
300
|
+
const cachedFilesText = cachedFiles ? ` (${cachedFiles} from cache)` : "";
|
|
301
|
+
const withErrorsText = errors ? ` with ${errors} error${errors === 1 ? "" : "s"}` : "";
|
|
302
|
+
const numFilesWidthIssuesText = numFilesWithIssues === 1 ? "1 file" : `${numFilesWithIssues} files`;
|
|
303
|
+
const summaryMessage = `CSpell\u003A Files checked: ${files}${cachedFilesText}, Issues found: ${issues$1} in ${numFilesWidthIssuesText}${withErrorsText}.`;
|
|
304
|
+
consoleError(summaryMessage);
|
|
305
|
+
if (errorCollection?.length && issues$1 > 5) {
|
|
306
|
+
consoleError("-------------------------------------------");
|
|
307
|
+
consoleError("Errors:");
|
|
308
|
+
errorCollection.forEach((error) => consoleError(error));
|
|
309
|
+
}
|
|
310
|
+
if (options.showPerfSummary) {
|
|
311
|
+
consoleError("-------------------------------------------");
|
|
312
|
+
consoleError("Performance Summary:");
|
|
313
|
+
consoleError(` Files Processed: ${perfStats.filesProcessed.toString().padStart(6)}`);
|
|
314
|
+
consoleError(` Files Skipped : ${perfStats.filesSkipped.toString().padStart(6)}`);
|
|
315
|
+
consoleError(` Files Cached : ${perfStats.filesCached.toString().padStart(6)}`);
|
|
316
|
+
consoleError(` Processing Time: ${perfStats.elapsedTimeMs.toFixed(2).padStart(9)}ms`);
|
|
317
|
+
consoleError("Stats:");
|
|
318
|
+
const stats = Object.entries(perfStats.perf).filter((p) => !!p[1]).map(([key, value]) => [key, value.toFixed(2)]);
|
|
319
|
+
const padName = Math.max(...stats.map((s) => s[0].length));
|
|
320
|
+
const padValue = Math.max(...stats.map((s) => s[1].length));
|
|
321
|
+
stats.sort((a, b) => a[0].localeCompare(b[0]));
|
|
322
|
+
for (const [key, value] of stats) value && consoleError(` ${key.padEnd(padName)}: ${value.padStart(padValue)}ms`);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
function collectPerfStats(p) {
|
|
326
|
+
if (p.cached) {
|
|
327
|
+
perfStats.filesCached++;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
perfStats.filesProcessed += p.processed ? 1 : 0;
|
|
331
|
+
perfStats.filesSkipped += !p.processed ? 1 : 0;
|
|
332
|
+
perfStats.elapsedTimeMs += p.elapsedTimeMs || 0;
|
|
333
|
+
if (!p.perf) return;
|
|
334
|
+
for (const [key, value] of Object.entries(p.perf)) if (typeof value === "number") perfStats.perf[key] = (perfStats.perf[key] || 0) + value;
|
|
335
|
+
}
|
|
336
|
+
function progress(p) {
|
|
337
|
+
if (!silent && showProgress) reportProgress(stderr, p, rootURL, options);
|
|
338
|
+
if (p.type === "ProgressFileComplete") collectPerfStats(p);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
issue: relativeIssue(silent || !issues ? nullEmitter : genIssueEmitter(stdio, stderr, issueTemplate, uniqueIssues, issuesCollection)),
|
|
342
|
+
error: silent ? nullEmitter : errorEmitter,
|
|
343
|
+
info: infoEmitter,
|
|
344
|
+
debug: emitters.Debug,
|
|
345
|
+
progress,
|
|
346
|
+
result: !silent && summary ? resultEmitter : nullEmitter,
|
|
347
|
+
features: void 0
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function formatIssue(io, templateStr, issue, maxIssueTextWidth) {
|
|
351
|
+
function clean$1(t$1) {
|
|
352
|
+
return t$1.replace(/\s+/, " ");
|
|
353
|
+
}
|
|
354
|
+
const { uri = "", filename, row, col, text, context = issue.line, offset } = issue;
|
|
355
|
+
const contextLeft = clean$1(context.text.slice(0, offset - context.offset));
|
|
356
|
+
const contextRight = clean$1(context.text.slice(offset + text.length - context.offset));
|
|
357
|
+
const contextFull = clean$1(context.text);
|
|
358
|
+
const padContext = " ".repeat(Math.max(maxIssueTextWidth - text.length, 0));
|
|
359
|
+
const rowText = row.toString();
|
|
360
|
+
const colText = col.toString();
|
|
361
|
+
const padRowCol = " ".repeat(Math.max(1, 8 - (rowText.length + colText.length)));
|
|
362
|
+
const suggestions$1 = formatSuggestions(io, issue);
|
|
363
|
+
const msg = issue.message || (issue.isFlagged ? "Forbidden word" : "Unknown word");
|
|
364
|
+
const messageColored = issue.isFlagged ? `{yellow ${msg}}` : msg;
|
|
365
|
+
const substitutions = {
|
|
366
|
+
$col: colText,
|
|
367
|
+
$contextFull: contextFull,
|
|
368
|
+
$contextLeft: contextLeft,
|
|
369
|
+
$contextRight: contextRight,
|
|
370
|
+
$filename: filename,
|
|
371
|
+
$padContext: padContext,
|
|
372
|
+
$padRowCol: padRowCol,
|
|
373
|
+
$row: rowText,
|
|
374
|
+
$suggestions: suggestions$1,
|
|
375
|
+
$text: text,
|
|
376
|
+
$uri: uri,
|
|
377
|
+
$quickFix: formatQuickFix(io, issue),
|
|
378
|
+
$message: msg,
|
|
379
|
+
$messageColored: messageColored
|
|
380
|
+
};
|
|
381
|
+
const t = templateStr.replaceAll("$messageColored", messageColored);
|
|
382
|
+
const chalkTemplate = makeTemplate(io.chalk);
|
|
383
|
+
return substitute(chalkTemplate(t), substitutions).trimEnd();
|
|
384
|
+
}
|
|
385
|
+
function formatSuggestions(io, issue) {
|
|
386
|
+
if (issue.suggestionsEx) return issue.suggestionsEx.map((sug) => sug.isPreferred ? io.chalk.italic(io.chalk.bold(sug.wordAdjustedToMatchCase || sug.word)) + "*" : sug.wordAdjustedToMatchCase || sug.word).join(", ");
|
|
387
|
+
if (issue.suggestions) return issue.suggestions.join(", ");
|
|
388
|
+
return "";
|
|
389
|
+
}
|
|
390
|
+
function formatQuickFix(io, issue) {
|
|
391
|
+
if (!issue.suggestionsEx?.length) return "";
|
|
392
|
+
const preferred = issue.suggestionsEx.filter((sug) => sug.isPreferred).map((sug) => sug.wordAdjustedToMatchCase || sug.word);
|
|
393
|
+
if (!preferred.length) return "";
|
|
394
|
+
const fixes = preferred.map((w) => io.chalk.italic(io.chalk.yellow(w)));
|
|
395
|
+
return `fix: (${fixes.join(", ")})`;
|
|
396
|
+
}
|
|
397
|
+
function substitute(text, substitutions) {
|
|
398
|
+
const subs = [];
|
|
399
|
+
for (const [match, replaceWith] of Object.entries(substitutions)) {
|
|
400
|
+
const len = match.length;
|
|
401
|
+
for (let i$1 = text.indexOf(match); i$1 >= 0; i$1 = text.indexOf(match, i$1)) {
|
|
402
|
+
const end = i$1 + len;
|
|
403
|
+
const reg = /\b/y;
|
|
404
|
+
reg.lastIndex = end;
|
|
405
|
+
if (reg.test(text)) subs.push([
|
|
406
|
+
i$1,
|
|
407
|
+
end,
|
|
408
|
+
replaceWith
|
|
409
|
+
]);
|
|
410
|
+
i$1 = end;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
subs.sort((a, b) => a[0] - b[0]);
|
|
414
|
+
let i = 0;
|
|
415
|
+
function sub(r) {
|
|
416
|
+
const [a, b, t] = r;
|
|
417
|
+
const prefix = text.slice(i, a);
|
|
418
|
+
i = b;
|
|
419
|
+
return prefix + t;
|
|
420
|
+
}
|
|
421
|
+
const parts = subs.map(sub);
|
|
422
|
+
return parts.join("") + text.slice(i);
|
|
423
|
+
}
|
|
424
|
+
function assertCheckTemplate(template) {
|
|
425
|
+
const r = checkTemplate(template);
|
|
426
|
+
if (r instanceof Error) throw r;
|
|
427
|
+
}
|
|
428
|
+
function checkTemplate(template) {
|
|
429
|
+
const chalk$1 = new Chalk();
|
|
430
|
+
const chalkTemplate = makeTemplate(chalk$1);
|
|
431
|
+
const substitutions = {
|
|
432
|
+
$col: "<col>",
|
|
433
|
+
$contextFull: "<contextFull>",
|
|
434
|
+
$contextLeft: "<contextLeft>",
|
|
435
|
+
$contextRight: "<contextRight>",
|
|
436
|
+
$filename: "<filename>",
|
|
437
|
+
$padContext: "<padContext>",
|
|
438
|
+
$padRowCol: "<padRowCol>",
|
|
439
|
+
$row: "<row>",
|
|
440
|
+
$suggestions: "<suggestions>",
|
|
441
|
+
$text: "<text>",
|
|
442
|
+
$uri: "<uri>",
|
|
443
|
+
$quickFix: "<quickFix>",
|
|
444
|
+
$message: "<message>",
|
|
445
|
+
$messageColored: "<messageColored>"
|
|
446
|
+
};
|
|
447
|
+
try {
|
|
448
|
+
const t = chalkTemplate(template);
|
|
449
|
+
const result = substitute(t, substitutions);
|
|
450
|
+
const problems = [...result.matchAll(/\$[a-z]+/gi)].map((m) => m[0]);
|
|
451
|
+
if (problems.length) throw new Error(`Unresolved template variable${problems.length > 1 ? "s" : ""}: ${problems.map((v) => `'${v}'`).join(", ")}`);
|
|
452
|
+
return true;
|
|
453
|
+
} catch (e) {
|
|
454
|
+
const msg = e instanceof Error ? e.message : `${e}`;
|
|
455
|
+
return new ApplicationError(msg);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/config/adjustConfig.ts
|
|
461
|
+
async function fileExists(url) {
|
|
462
|
+
if (url.protocol !== "file:") return false;
|
|
463
|
+
try {
|
|
464
|
+
const stats = await promises.stat(url);
|
|
465
|
+
return stats.isFile();
|
|
466
|
+
} catch (e) {
|
|
467
|
+
const err = toError(e);
|
|
468
|
+
if (err.code === "ENOENT") return false;
|
|
469
|
+
throw e;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function resolveImports(configFile, imports) {
|
|
473
|
+
const fromConfigDir = new URL("./", configFile.url);
|
|
474
|
+
const fromCurrentDir = toFileDirURL("./");
|
|
475
|
+
const require = createRequire(fromConfigDir);
|
|
476
|
+
function isPackageName(name$1) {
|
|
477
|
+
try {
|
|
478
|
+
require.resolve(name$1, { paths: [fileURLToPath(fromConfigDir)] });
|
|
479
|
+
return true;
|
|
480
|
+
} catch {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const _imports = [];
|
|
485
|
+
for (const imp of imports) {
|
|
486
|
+
const url = new URL(imp, fromCurrentDir);
|
|
487
|
+
if (url.protocol !== "file:") {
|
|
488
|
+
_imports.push(imp);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (await fileExists(url)) {
|
|
492
|
+
let rel = urlRelative(fromConfigDir, url);
|
|
493
|
+
if (!(rel.startsWith("./") || rel.startsWith("../"))) rel = "./" + rel;
|
|
494
|
+
_imports.push(rel);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (url.protocol !== "file:") {
|
|
498
|
+
_imports.push(url.href);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (isPackageName(imp)) {
|
|
502
|
+
_imports.push(imp);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
throw new Error(`Cannot resolve import: ${imp}`);
|
|
506
|
+
}
|
|
507
|
+
return _imports;
|
|
508
|
+
}
|
|
509
|
+
function addImportsToMutableConfigFile(configFile, resolvedImports, comment) {
|
|
510
|
+
let importNode = configFile.getNode("import", []);
|
|
511
|
+
if (importNode.type === "scalar") {
|
|
512
|
+
configFile.setValue("import", [importNode.value]);
|
|
513
|
+
importNode = configFile.getNode("import", []);
|
|
514
|
+
}
|
|
515
|
+
assert(isCfgArrayNode(importNode));
|
|
516
|
+
const knownImports = new Set(importNode.value);
|
|
517
|
+
for (const imp of resolvedImports) {
|
|
518
|
+
if (knownImports.has(imp)) continue;
|
|
519
|
+
importNode.push(imp);
|
|
520
|
+
}
|
|
521
|
+
if (comment) configFile.setComment("import", comment);
|
|
522
|
+
}
|
|
523
|
+
async function addImportsToConfigFile(configFile, imports, comment) {
|
|
524
|
+
const resolvedImports = await resolveImports(configFile, imports);
|
|
525
|
+
if (configFile instanceof MutableCSpellConfigFile) return addImportsToMutableConfigFile(configFile, resolvedImports, comment);
|
|
526
|
+
const settings = configFile.settings;
|
|
527
|
+
let importNode = settings.import;
|
|
528
|
+
if (!Array.isArray(importNode)) {
|
|
529
|
+
importNode = typeof importNode === "string" ? [importNode] : [];
|
|
530
|
+
settings.import = importNode;
|
|
531
|
+
if (comment) configFile.setComment("import", comment);
|
|
532
|
+
}
|
|
533
|
+
assert(Array.isArray(importNode));
|
|
534
|
+
const knownImports = new Set(importNode);
|
|
535
|
+
for (const imp of resolvedImports) {
|
|
536
|
+
if (knownImports.has(imp)) continue;
|
|
537
|
+
importNode.push(imp);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function setConfigFieldValue(configFile, key, value, comment) {
|
|
541
|
+
configFile.setValue(key, value);
|
|
542
|
+
if (comment !== void 0) configFile.setComment(key, comment);
|
|
543
|
+
}
|
|
544
|
+
function addDictionariesToConfigFile(configFile, dictionaries, comment) {
|
|
545
|
+
if (configFile instanceof MutableCSpellConfigFile) {
|
|
546
|
+
const found = configFile.getValue("dictionaries");
|
|
547
|
+
const dicts$1 = configFile.getNode("dictionaries", []);
|
|
548
|
+
assert(isCfgArrayNode(dicts$1));
|
|
549
|
+
const knownDicts$1 = new Set(dicts$1.value);
|
|
550
|
+
for (const dict of dictionaries) if (!knownDicts$1.has(dict)) {
|
|
551
|
+
dicts$1.push(dict);
|
|
552
|
+
knownDicts$1.add(dict);
|
|
553
|
+
}
|
|
554
|
+
if (!found && comment) configFile.setComment("dictionaries", comment);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const settings = configFile.settings;
|
|
558
|
+
const dicts = settings.dictionaries || [];
|
|
559
|
+
const knownDicts = new Set(dicts);
|
|
560
|
+
for (const dict of dictionaries) if (!knownDicts.has(dict)) {
|
|
561
|
+
dicts.push(dict);
|
|
562
|
+
knownDicts.add(dict);
|
|
563
|
+
}
|
|
564
|
+
setConfigFieldValue(configFile, "dictionaries", dicts, comment);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
//#endregion
|
|
568
|
+
//#region src/config/config.ts
|
|
569
|
+
function applyValuesToConfigFile(config, settings, defaultValues, addComments) {
|
|
570
|
+
const currentSettings = config.settings || {};
|
|
571
|
+
for (const [k, entry] of Object.entries(defaultValues)) {
|
|
572
|
+
const { value: defaultValue, comment } = entry;
|
|
573
|
+
const key = k;
|
|
574
|
+
const newValue = settings[key];
|
|
575
|
+
const oldValue = currentSettings[key];
|
|
576
|
+
const value = newValue ?? oldValue ?? defaultValue;
|
|
577
|
+
if (newValue === void 0 && oldValue !== void 0 || value === void 0) continue;
|
|
578
|
+
const useComment = addComments && oldValue === void 0 && comment || void 0;
|
|
579
|
+
setConfigFieldValue(config, key, value, useComment);
|
|
580
|
+
}
|
|
581
|
+
return config;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region src/config/constants.ts
|
|
586
|
+
const defaultConfig = {
|
|
587
|
+
$schema: {
|
|
588
|
+
value: void 0,
|
|
589
|
+
comment: " The schema for the configuration file."
|
|
590
|
+
},
|
|
591
|
+
version: {
|
|
592
|
+
value: "0.2",
|
|
593
|
+
comment: " The version of the configuration file format."
|
|
594
|
+
},
|
|
595
|
+
name: {
|
|
596
|
+
value: void 0,
|
|
597
|
+
comment: " The name of the configuration. Use for display purposes only."
|
|
598
|
+
},
|
|
599
|
+
description: {
|
|
600
|
+
value: void 0,
|
|
601
|
+
comment: " A description of the configuration."
|
|
602
|
+
},
|
|
603
|
+
language: {
|
|
604
|
+
value: "en",
|
|
605
|
+
comment: " The locale to use when spell checking. (e.g., en, en-GB, de-DE"
|
|
606
|
+
},
|
|
607
|
+
import: {
|
|
608
|
+
value: void 0,
|
|
609
|
+
comment: " Configuration or packages to import."
|
|
610
|
+
},
|
|
611
|
+
dictionaryDefinitions: {
|
|
612
|
+
value: void 0,
|
|
613
|
+
comment: " Define user dictionaries."
|
|
614
|
+
},
|
|
615
|
+
dictionaries: {
|
|
616
|
+
value: void 0,
|
|
617
|
+
comment: " Enable the dictionaries."
|
|
618
|
+
},
|
|
619
|
+
ignorePaths: {
|
|
620
|
+
value: void 0,
|
|
621
|
+
comment: " Glob patterns of files to be skipped."
|
|
622
|
+
},
|
|
623
|
+
files: {
|
|
624
|
+
value: void 0,
|
|
625
|
+
comment: " Glob patterns of files to be included."
|
|
626
|
+
},
|
|
627
|
+
words: {
|
|
628
|
+
value: void 0,
|
|
629
|
+
comment: " Words to be considered correct."
|
|
630
|
+
},
|
|
631
|
+
ignoreWords: {
|
|
632
|
+
value: void 0,
|
|
633
|
+
comment: " Words to be ignored."
|
|
634
|
+
},
|
|
635
|
+
flagWords: {
|
|
636
|
+
value: void 0,
|
|
637
|
+
comment: " Words to be flagged as incorrect."
|
|
638
|
+
},
|
|
639
|
+
overrides: {
|
|
640
|
+
value: void 0,
|
|
641
|
+
comment: " Set configuration based upon file globs."
|
|
642
|
+
},
|
|
643
|
+
languageSettings: {
|
|
644
|
+
value: void 0,
|
|
645
|
+
comment: " Define language specific settings."
|
|
646
|
+
},
|
|
647
|
+
enabledFileTypes: {
|
|
648
|
+
value: void 0,
|
|
649
|
+
comment: " Enable for specific file types."
|
|
650
|
+
},
|
|
651
|
+
caseSensitive: {
|
|
652
|
+
value: void 0,
|
|
653
|
+
comment: " Enable case sensitive spell checking."
|
|
654
|
+
},
|
|
655
|
+
patterns: {
|
|
656
|
+
value: void 0,
|
|
657
|
+
comment: " Regular expression patterns."
|
|
658
|
+
},
|
|
659
|
+
ignoreRegExpList: {
|
|
660
|
+
value: void 0,
|
|
661
|
+
comment: " Regular expressions / patterns of text to be ignored."
|
|
662
|
+
},
|
|
663
|
+
includeRegExpList: {
|
|
664
|
+
value: void 0,
|
|
665
|
+
comment: " Regular expressions / patterns of text to be included."
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/config/configInit.ts
|
|
671
|
+
const schemaRef = cspellConfigFileSchema;
|
|
672
|
+
const defaultConfigJson = `\
|
|
673
|
+
{
|
|
674
|
+
}
|
|
675
|
+
`;
|
|
676
|
+
const defaultConfigYaml = `
|
|
677
|
+
`;
|
|
678
|
+
async function configInit(options) {
|
|
679
|
+
const rw = createReaderWriter();
|
|
680
|
+
const url = determineFileNameURL(options);
|
|
681
|
+
const configFile = await createConfigFile(rw, url, options);
|
|
682
|
+
await applyOptionsToConfigFile(configFile, options);
|
|
683
|
+
await fs.mkdir(new URL("./", configFile.url), { recursive: true });
|
|
684
|
+
if (options.stdout) console.stdoutChannel.write(rw.serialize(configFile));
|
|
685
|
+
else await rw.writeConfig(configFile);
|
|
686
|
+
}
|
|
687
|
+
async function applyOptionsToConfigFile(configFile, options) {
|
|
688
|
+
const settings = {};
|
|
689
|
+
const addComments = options.comments || options.comments === void 0 && !options.removeComments && !configFile.url.pathname.endsWith(".json");
|
|
690
|
+
if (options.comments === false) configFile.removeAllComments();
|
|
691
|
+
if (options.schema ?? true) configFile.setSchema(schemaRef);
|
|
692
|
+
if (options.locale) settings.language = options.locale;
|
|
693
|
+
applyValuesToConfigFile(configFile, settings, defaultConfig, addComments);
|
|
694
|
+
if (options.import) await addImportsToConfigFile(configFile, options.import, addComments && defaultConfig.import?.comment || void 0);
|
|
695
|
+
if (options.dictionary) addDictionariesToConfigFile(configFile, options.dictionary, addComments && defaultConfig.dictionaries?.comment || void 0);
|
|
696
|
+
return configFile;
|
|
697
|
+
}
|
|
698
|
+
function determineFileNameURL(options) {
|
|
699
|
+
if (options.config) return toFileURL(options.config);
|
|
700
|
+
const defaultFileName = determineDefaultFileName(options);
|
|
701
|
+
const outputUrl = toFileURL(options.output || defaultFileName);
|
|
702
|
+
const path$2 = outputUrl.pathname;
|
|
703
|
+
if (path$2.endsWith(".json") || path$2.endsWith(".jsonc") || path$2.endsWith(".yaml") || path$2.endsWith(".yml")) return outputUrl;
|
|
704
|
+
if (/\.{m,c}?{j,t}s$/.test(path$2)) throw new Error(`Unsupported file extension: ${path$2}`);
|
|
705
|
+
return new URL(defaultFileName, toFileDirURL(outputUrl));
|
|
706
|
+
}
|
|
707
|
+
function determineDefaultFileName(options) {
|
|
708
|
+
switch (options.format || "yaml") {
|
|
709
|
+
case "json": return "cspell.json";
|
|
710
|
+
case "jsonc": return "cspell.jsonc";
|
|
711
|
+
case "yaml": return "cspell.config.yaml";
|
|
712
|
+
case "yml": return "cspell.config.yml";
|
|
713
|
+
}
|
|
714
|
+
throw new Error(`Unsupported format: ${options.format}`);
|
|
715
|
+
}
|
|
716
|
+
function getDefaultContent(options) {
|
|
717
|
+
switch (options.format) {
|
|
718
|
+
case void 0:
|
|
719
|
+
case "yaml": return defaultConfigYaml;
|
|
720
|
+
case "json":
|
|
721
|
+
case "jsonc": return defaultConfigJson;
|
|
722
|
+
default: throw new Error(`Unsupported format: ${options.format}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async function createConfigFile(rw, url, options) {
|
|
726
|
+
if (url.pathname.endsWith("package.json")) return rw.readConfig(url);
|
|
727
|
+
const content = await fs.readFile(url, "utf8").catch(() => getDefaultContent(options));
|
|
728
|
+
return rw.parse({
|
|
729
|
+
url,
|
|
730
|
+
content
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
//#endregion
|
|
735
|
+
//#region src/featureFlags/featureFlags.ts
|
|
736
|
+
function getFeatureFlags() {
|
|
737
|
+
return getSystemFeatureFlags();
|
|
738
|
+
}
|
|
739
|
+
function parseFeatureFlags(flags, featureFlags = getFeatureFlags()) {
|
|
740
|
+
if (!flags) return featureFlags;
|
|
741
|
+
const flagsKvP = flags.map((f) => f.split(":", 2));
|
|
742
|
+
for (const flag of flagsKvP) {
|
|
743
|
+
const [name$1, value] = flag;
|
|
744
|
+
try {
|
|
745
|
+
featureFlags.setFlag(name$1, value);
|
|
746
|
+
} catch {
|
|
747
|
+
console.warn(`Unknown flag: "${name$1}"`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return featureFlags;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
//#endregion
|
|
754
|
+
//#region src/environment.ts
|
|
755
|
+
const environmentKeys = {
|
|
756
|
+
CSPELL_ENABLE_DICTIONARY_LOGGING: "CSPELL_ENABLE_DICTIONARY_LOGGING",
|
|
757
|
+
CSPELL_ENABLE_DICTIONARY_LOG_FILE: "CSPELL_ENABLE_DICTIONARY_LOG_FILE",
|
|
758
|
+
CSPELL_ENABLE_DICTIONARY_LOG_FIELDS: "CSPELL_ENABLE_DICTIONARY_LOG_FIELDS",
|
|
759
|
+
CSPELL_GLOB_ROOT: "CSPELL_GLOB_ROOT",
|
|
760
|
+
CSPELL_CONFIG_PATH: "CSPELL_CONFIG_PATH",
|
|
761
|
+
CSPELL_DEFAULT_CONFIG_PATH: "CSPELL_DEFAULT_CONFIG_PATH"
|
|
762
|
+
};
|
|
763
|
+
function setEnvironmentVariable(key, value) {
|
|
764
|
+
process.env[key] = value;
|
|
765
|
+
}
|
|
766
|
+
function getEnvironmentVariable(key) {
|
|
767
|
+
return process.env[key];
|
|
768
|
+
}
|
|
769
|
+
function truthy(value) {
|
|
770
|
+
switch (value?.toLowerCase().trim()) {
|
|
771
|
+
case "t":
|
|
772
|
+
case "true":
|
|
773
|
+
case "on":
|
|
774
|
+
case "yes":
|
|
775
|
+
case "1": return true;
|
|
776
|
+
}
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
//#endregion
|
|
781
|
+
//#region src/dirname.ts
|
|
782
|
+
let _dirname;
|
|
783
|
+
try {
|
|
784
|
+
if (typeof import.meta.url !== "string") throw new Error("assert");
|
|
785
|
+
_dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
786
|
+
} catch {
|
|
787
|
+
_dirname = __dirname;
|
|
788
|
+
}
|
|
789
|
+
const pkgDir = _dirname;
|
|
790
|
+
|
|
791
|
+
//#endregion
|
|
792
|
+
//#region src/pkgInfo.ts
|
|
793
|
+
const name = "cspell";
|
|
794
|
+
const version$1 = "9.1.5";
|
|
795
|
+
const engines = { node: ">=20" };
|
|
796
|
+
const npmPackage = {
|
|
797
|
+
name,
|
|
798
|
+
version: version$1,
|
|
799
|
+
engines
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region src/util/async.ts
|
|
804
|
+
const asyncMap = operators.opMapAsync;
|
|
805
|
+
const asyncFilter = operators.opFilterAsync;
|
|
806
|
+
const asyncAwait = operators.opAwaitAsync;
|
|
807
|
+
const asyncFlatten = operators.opFlattenAsync;
|
|
808
|
+
|
|
809
|
+
//#endregion
|
|
810
|
+
//#region src/util/constants.ts
|
|
811
|
+
const UTF8 = "utf8";
|
|
812
|
+
const STDIN = "stdin";
|
|
813
|
+
const STDINProtocol = "stdin:";
|
|
814
|
+
const STDINUrlPrefix = "stdin://";
|
|
815
|
+
const FileUrlPrefix = "file://";
|
|
816
|
+
|
|
817
|
+
//#endregion
|
|
818
|
+
//#region src/util/glob.ts
|
|
819
|
+
const defaultExcludeGlobs = ["node_modules/**"];
|
|
820
|
+
/**
|
|
821
|
+
*
|
|
822
|
+
* @param pattern - glob patterns and NOT file paths. It can be a file path turned into a glob.
|
|
823
|
+
* @param options - search options.
|
|
824
|
+
*/
|
|
825
|
+
async function globP(pattern, options) {
|
|
826
|
+
const cwd = options?.root || options?.cwd || process.cwd();
|
|
827
|
+
const ignoreRaw = typeof options?.ignore === "string" ? [options.ignore] : options?.ignore;
|
|
828
|
+
const ignore = ignoreRaw?.filter((g) => !g.startsWith("../"));
|
|
829
|
+
const onlyFiles = options?.nodir;
|
|
830
|
+
const dot = options?.dot;
|
|
831
|
+
const patterns = typeof pattern === "string" ? [pattern] : pattern;
|
|
832
|
+
const useOptions = clean({
|
|
833
|
+
cwd,
|
|
834
|
+
onlyFiles,
|
|
835
|
+
dot,
|
|
836
|
+
ignore,
|
|
837
|
+
absolute: true,
|
|
838
|
+
followSymbolicLinks: false,
|
|
839
|
+
expandDirectories: false
|
|
840
|
+
});
|
|
841
|
+
const compare$1 = new Intl.Collator("en").compare;
|
|
842
|
+
const absolutePaths = (await glob$1(patterns, useOptions)).sort(compare$1);
|
|
843
|
+
const relativePaths = absolutePaths.map((absFilename) => path$1.relative(cwd, absFilename));
|
|
844
|
+
return relativePaths;
|
|
845
|
+
}
|
|
846
|
+
function calcGlobs(commandLineExclude) {
|
|
847
|
+
const globs = new Set((commandLineExclude || []).flatMap((glob$2) => glob$2.split(/(?<!\\)\s+/g)).map((g) => g.replaceAll("\\ ", " ")));
|
|
848
|
+
const commandLineExcludes = {
|
|
849
|
+
globs: [...globs],
|
|
850
|
+
source: "arguments"
|
|
851
|
+
};
|
|
852
|
+
const defaultExcludes = {
|
|
853
|
+
globs: defaultExcludeGlobs,
|
|
854
|
+
source: "default"
|
|
855
|
+
};
|
|
856
|
+
return commandLineExcludes.globs.length ? commandLineExcludes : defaultExcludes;
|
|
857
|
+
}
|
|
858
|
+
function extractPatterns(globs) {
|
|
859
|
+
const r = globs.reduce((info, g) => {
|
|
860
|
+
const source = g.source;
|
|
861
|
+
const patterns = g.matcher.patternsNormalizedToRoot;
|
|
862
|
+
return [...info, ...patterns.map((glob$2) => ({
|
|
863
|
+
glob: glob$2,
|
|
864
|
+
source
|
|
865
|
+
}))];
|
|
866
|
+
}, []);
|
|
867
|
+
return r;
|
|
868
|
+
}
|
|
869
|
+
function calcExcludeGlobInfo(root, commandLineExclude) {
|
|
870
|
+
commandLineExclude = typeof commandLineExclude === "string" ? [commandLineExclude] : commandLineExclude;
|
|
871
|
+
const choice = calcGlobs(commandLineExclude);
|
|
872
|
+
const matcher = new GlobMatcher(choice.globs, {
|
|
873
|
+
root,
|
|
874
|
+
dot: true
|
|
875
|
+
});
|
|
876
|
+
return [{
|
|
877
|
+
matcher,
|
|
878
|
+
source: choice.source
|
|
879
|
+
}];
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Build GlobMatcher from command line or config file globs.
|
|
883
|
+
* @param globs Glob patterns or file paths
|
|
884
|
+
* @param root - directory to use as the root
|
|
885
|
+
*/
|
|
886
|
+
function buildGlobMatcher(globs, root, isExclude) {
|
|
887
|
+
const withRoots = globs.map((g) => {
|
|
888
|
+
const source = typeof g === "string" ? "command line" : void 0;
|
|
889
|
+
return {
|
|
890
|
+
source,
|
|
891
|
+
...fileOrGlobToGlob(g, root)
|
|
892
|
+
};
|
|
893
|
+
});
|
|
894
|
+
return new GlobMatcher(withRoots, {
|
|
895
|
+
root,
|
|
896
|
+
mode: isExclude ? "exclude" : "include"
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
function extractGlobsFromMatcher(globMatcher) {
|
|
900
|
+
return globMatcher.patternsNormalizedToRoot.map((g) => g.glob);
|
|
901
|
+
}
|
|
902
|
+
function normalizeGlobsToRoot(globs, root, isExclude) {
|
|
903
|
+
const urls = globs.filter((g) => typeof g === "string" && isPossibleUrlRegExp.test(g));
|
|
904
|
+
const onlyGlobs = globs.filter((g) => typeof g !== "string" || !isPossibleUrlRegExp.test(g));
|
|
905
|
+
return [urls, extractGlobsFromMatcher(buildGlobMatcher(onlyGlobs, root, isExclude))].flat();
|
|
906
|
+
}
|
|
907
|
+
const isPossibleGlobRegExp = /[()*?[{}]/;
|
|
908
|
+
const isPossibleUrlRegExp = /^[\d_a-z-]{3,}:\/\//;
|
|
909
|
+
/**
|
|
910
|
+
* If a 'glob' is a path to a directory, then append `**` so that
|
|
911
|
+
* directory searches work.
|
|
912
|
+
* @param glob - a glob, file, or directory
|
|
913
|
+
* @param root - root to use.
|
|
914
|
+
* @returns `**` is appended directories.
|
|
915
|
+
*/
|
|
916
|
+
async function adjustPossibleDirectory(glob$2, root) {
|
|
917
|
+
const g = typeof glob$2 === "string" ? {
|
|
918
|
+
glob: glob$2,
|
|
919
|
+
root
|
|
920
|
+
} : {
|
|
921
|
+
glob: glob$2.glob,
|
|
922
|
+
root: glob$2.root ?? root
|
|
923
|
+
};
|
|
924
|
+
if (isPossibleGlobRegExp.test(g.glob)) return glob$2;
|
|
925
|
+
if (isPossibleUrlRegExp.test(g.glob)) return glob$2;
|
|
926
|
+
const dirPath = path$1.resolve(g.root, g.glob);
|
|
927
|
+
try {
|
|
928
|
+
const stat$1 = await promises.stat(dirPath);
|
|
929
|
+
if (stat$1.isDirectory()) {
|
|
930
|
+
const useGlob = posix.join(posixPath(g.glob), "**");
|
|
931
|
+
return typeof glob$2 === "string" ? useGlob : {
|
|
932
|
+
...glob$2,
|
|
933
|
+
glob: useGlob
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
} catch {
|
|
937
|
+
return glob$2;
|
|
938
|
+
}
|
|
939
|
+
return glob$2;
|
|
940
|
+
}
|
|
941
|
+
function posixPath(p) {
|
|
942
|
+
return path$1.sep === "\\" ? p.replaceAll("\\", "/") : p;
|
|
943
|
+
}
|
|
944
|
+
async function normalizeFileOrGlobsToRoot(globs, root) {
|
|
945
|
+
const adjustedGlobs = await Promise.all(globs.map((g) => adjustPossibleDirectory(g, root)));
|
|
946
|
+
return normalizeGlobsToRoot(adjustedGlobs, root, false);
|
|
947
|
+
}
|
|
948
|
+
function glob$1(patterns, options) {
|
|
949
|
+
patterns = typeof patterns === "string" ? workaroundPicomatchBug(patterns) : patterns.map((g) => workaroundPicomatchBug(g));
|
|
950
|
+
return glob(patterns, options);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
//#endregion
|
|
954
|
+
//#region src/util/stdin.ts
|
|
955
|
+
function readStdin() {
|
|
956
|
+
return readline.createInterface(process.stdin);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
//#endregion
|
|
960
|
+
//#region src/util/stdinUrl.ts
|
|
961
|
+
function isStdinUrl(url) {
|
|
962
|
+
if (url instanceof URL) return url.protocol === STDINProtocol;
|
|
963
|
+
return url.startsWith(STDINProtocol);
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Normalize and resolve a stdin url.
|
|
967
|
+
* @param url - stdin url to resolve.
|
|
968
|
+
* @param cwd - file path to resolve relative paths against.
|
|
969
|
+
* @returns
|
|
970
|
+
*/
|
|
971
|
+
function resolveStdinUrl(url, cwd) {
|
|
972
|
+
assert(url.startsWith(STDINProtocol), `Expected url to start with ${STDINProtocol}`);
|
|
973
|
+
const path$2 = decodeURIComponent(url).slice(STDINProtocol.length).replace(/^\/\//, "").replace(/^\/([a-z]:)/i, "$1");
|
|
974
|
+
const fileUrl = toFileURL(path$2, cwd);
|
|
975
|
+
return fileUrl.toString().replace(/^file:/, STDINProtocol) + (path$2 ? "" : "/");
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
//#endregion
|
|
979
|
+
//#region src/util/fileHelper.ts
|
|
980
|
+
function fileInfoToDocument(fileInfo, languageId, locale) {
|
|
981
|
+
const { filename, text } = fileInfo;
|
|
982
|
+
languageId = languageId || void 0;
|
|
983
|
+
locale = locale || void 0;
|
|
984
|
+
const uri = filenameToUrl(filename);
|
|
985
|
+
if (uri.href.startsWith(STDINProtocol)) return clean({
|
|
986
|
+
uri: uri.href,
|
|
987
|
+
text,
|
|
988
|
+
languageId,
|
|
989
|
+
locale
|
|
990
|
+
});
|
|
991
|
+
return fileToDocument(uri.href, text, languageId, locale);
|
|
992
|
+
}
|
|
993
|
+
function filenameToUrl(filename, cwd = ".") {
|
|
994
|
+
if (filename instanceof URL) return filename;
|
|
995
|
+
const cwdURL = toFileDirURL(cwd);
|
|
996
|
+
if (filename === STDIN) return new URL("stdin:///");
|
|
997
|
+
if (isStdinUrl(filename)) return new URL(resolveStdinUrl(filename, cwd));
|
|
998
|
+
return toFileURL(filename, cwdURL);
|
|
999
|
+
}
|
|
1000
|
+
function filenameToUri(filename, cwd) {
|
|
1001
|
+
return toURL(filenameToUrl(filename, cwd));
|
|
1002
|
+
}
|
|
1003
|
+
function isBinaryFile$1(filename, cwd) {
|
|
1004
|
+
const uri = filenameToUri(filename, cwd);
|
|
1005
|
+
if (uri.protocol.startsWith("stdin")) return false;
|
|
1006
|
+
return isBinaryFile(uri);
|
|
1007
|
+
}
|
|
1008
|
+
function resolveFilename(filename, cwd) {
|
|
1009
|
+
cwd = cwd || process.cwd();
|
|
1010
|
+
if (filename === STDIN) return STDINUrlPrefix;
|
|
1011
|
+
if (filename.startsWith(FileUrlPrefix)) {
|
|
1012
|
+
const url = new URL(filename.slice(FileUrlPrefix.length), toFileDirURL(cwd));
|
|
1013
|
+
return fileURLToPath(url);
|
|
1014
|
+
}
|
|
1015
|
+
if (isStdinUrl(filename)) return resolveStdinUrl(filename, cwd);
|
|
1016
|
+
return path$1.resolve(cwd, filename);
|
|
1017
|
+
}
|
|
1018
|
+
function readFileInfo(filename, encoding = UTF8, handleNotFound = false) {
|
|
1019
|
+
filename = resolveFilename(filename);
|
|
1020
|
+
const pText = filename.startsWith(STDINProtocol) ? streamConsumers.text(process.stdin) : readFileText(filename, encoding);
|
|
1021
|
+
return pText.then((text) => ({
|
|
1022
|
+
text,
|
|
1023
|
+
filename
|
|
1024
|
+
}), (e) => {
|
|
1025
|
+
const error = toError(e);
|
|
1026
|
+
return handleNotFound && error.code === "EISDIR" ? Promise.resolve({
|
|
1027
|
+
text: "",
|
|
1028
|
+
filename,
|
|
1029
|
+
errorCode: error.code
|
|
1030
|
+
}) : handleNotFound && error.code === "ENOENT" ? Promise.resolve({
|
|
1031
|
+
text: "",
|
|
1032
|
+
filename,
|
|
1033
|
+
errorCode: error.code
|
|
1034
|
+
}) : Promise.reject(new IOError(`Error reading file: "${filename}"`, error));
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
function readFile(filename, encoding = UTF8) {
|
|
1038
|
+
return readFileInfo(filename, encoding).then((info) => info.text);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Looks for matching glob patterns or stdin
|
|
1042
|
+
* @param globPatterns patterns or stdin
|
|
1043
|
+
*/
|
|
1044
|
+
async function findFiles(globPatterns, options) {
|
|
1045
|
+
const stdin = [];
|
|
1046
|
+
const globPats = globPatterns.filter((filename) => !isStdin(filename) && !filename.startsWith(FileUrlPrefix) ? true : (stdin.push(filename), false));
|
|
1047
|
+
const globResults = globPats.length ? await globP(globPats, options) : [];
|
|
1048
|
+
const cwd = options.cwd || process.cwd();
|
|
1049
|
+
return [...stdin, ...globResults].map((filename) => resolveFilename(filename, cwd));
|
|
1050
|
+
}
|
|
1051
|
+
const resolveFilenames = asyncMap(resolveFilename);
|
|
1052
|
+
/**
|
|
1053
|
+
* Read
|
|
1054
|
+
* @param listFiles - array of file paths to read that will contain a list of files. Paths contained in each
|
|
1055
|
+
* file will be resolved relative to the containing file.
|
|
1056
|
+
* @returns - a list of files to be processed.
|
|
1057
|
+
*/
|
|
1058
|
+
function readFileListFiles(listFiles) {
|
|
1059
|
+
let useStdin = false;
|
|
1060
|
+
const files = listFiles.filter((file) => {
|
|
1061
|
+
const isStdin$1 = file === "stdin";
|
|
1062
|
+
useStdin = useStdin || isStdin$1;
|
|
1063
|
+
return !isStdin$1;
|
|
1064
|
+
});
|
|
1065
|
+
const found = asyncPipe(files, asyncMap((file) => readFileListFile(file)), asyncAwait(), asyncFlatten());
|
|
1066
|
+
const stdin = useStdin ? readStdin() : [];
|
|
1067
|
+
return asyncPipe(mergeAsyncIterables(found, stdin), resolveFilenames);
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Read a `listFile` and return the containing file paths resolved relative to the `listFile`.
|
|
1071
|
+
* @param listFiles - array of file paths to read that will contain a list of files. Paths contained in each
|
|
1072
|
+
* file will be resolved relative to the containing file.
|
|
1073
|
+
* @returns - a list of files to be processed.
|
|
1074
|
+
*/
|
|
1075
|
+
async function readFileListFile(listFile) {
|
|
1076
|
+
try {
|
|
1077
|
+
const relTo = path$1.resolve(path$1.dirname(listFile));
|
|
1078
|
+
const content = await readFile(listFile);
|
|
1079
|
+
const lines = content.split("\n").map((a) => a.trim()).filter((a) => !!a).map((file) => path$1.resolve(relTo, file));
|
|
1080
|
+
return lines;
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
throw toApplicationError(err, `Error reading file list from: "${listFile}"`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function isStdin(filename) {
|
|
1086
|
+
return filename === STDIN || isStdinUrl(filename);
|
|
1087
|
+
}
|
|
1088
|
+
async function isFile(filename) {
|
|
1089
|
+
if (isStdin(filename)) return true;
|
|
1090
|
+
try {
|
|
1091
|
+
const stat$1 = await promises.stat(filename);
|
|
1092
|
+
return stat$1.isFile();
|
|
1093
|
+
} catch {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
async function isDir(filename) {
|
|
1098
|
+
try {
|
|
1099
|
+
const stat$1 = await promises.stat(filename);
|
|
1100
|
+
return stat$1.isDirectory();
|
|
1101
|
+
} catch {
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function isNotDir(filename) {
|
|
1106
|
+
return isDir(filename).then((a) => !a);
|
|
1107
|
+
}
|
|
1108
|
+
function relativeToCwd(filename, cwd = process.cwd()) {
|
|
1109
|
+
const urlCwd = toFileDirURL(cwd);
|
|
1110
|
+
const url = toFileURL(filename, urlCwd);
|
|
1111
|
+
const rel = urlRelative(urlCwd, url);
|
|
1112
|
+
if (rel.startsWith("..")) return toFilePathOrHref(url);
|
|
1113
|
+
return rel;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region src/util/cache/file-entry-cache.mts
|
|
1118
|
+
function createFromFile$1(pathToCache, useChecksum) {
|
|
1119
|
+
return fileEntryCache.createFromFile(pathToCache, useChecksum);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
//#endregion
|
|
1123
|
+
//#region src/util/cache/fileEntryCache.ts
|
|
1124
|
+
function createFromFile(pathToCache, useCheckSum, useRelative) {
|
|
1125
|
+
const absPathToCache = path$1.resolve(pathToCache);
|
|
1126
|
+
const relDir = path$1.dirname(absPathToCache);
|
|
1127
|
+
mkdirSync(relDir, { recursive: true });
|
|
1128
|
+
const create = wrap(() => createFromFile$1(absPathToCache, useCheckSum));
|
|
1129
|
+
const feCache = create();
|
|
1130
|
+
const cacheWrapper = {
|
|
1131
|
+
get cache() {
|
|
1132
|
+
return feCache.cache;
|
|
1133
|
+
},
|
|
1134
|
+
getHash(buffer) {
|
|
1135
|
+
return feCache.getHash(buffer);
|
|
1136
|
+
},
|
|
1137
|
+
hasFileChanged: wrap((cwd, file) => {
|
|
1138
|
+
return feCache.hasFileChanged(resolveFile(cwd, file));
|
|
1139
|
+
}),
|
|
1140
|
+
analyzeFiles: wrap((cwd, files) => {
|
|
1141
|
+
return feCache.analyzeFiles(resolveFiles(cwd, files));
|
|
1142
|
+
}),
|
|
1143
|
+
getFileDescriptor: wrap((cwd, file) => {
|
|
1144
|
+
return feCache.getFileDescriptor(resolveFile(cwd, file));
|
|
1145
|
+
}),
|
|
1146
|
+
getUpdatedFiles: wrap((cwd, files) => {
|
|
1147
|
+
return feCache.getUpdatedFiles(resolveFiles(cwd, files));
|
|
1148
|
+
}),
|
|
1149
|
+
normalizeEntries: wrap((cwd, files) => {
|
|
1150
|
+
return feCache.normalizeEntries(resolveFiles(cwd, files));
|
|
1151
|
+
}),
|
|
1152
|
+
removeEntry: wrap((cwd, file) => {
|
|
1153
|
+
return feCache.removeEntry(resolveFile(cwd, file));
|
|
1154
|
+
}),
|
|
1155
|
+
deleteCacheFile() {
|
|
1156
|
+
feCache.deleteCacheFile();
|
|
1157
|
+
},
|
|
1158
|
+
destroy() {
|
|
1159
|
+
feCache.destroy();
|
|
1160
|
+
},
|
|
1161
|
+
reconcile: wrap((_cwd, noPrune) => {
|
|
1162
|
+
feCache.reconcile(noPrune);
|
|
1163
|
+
})
|
|
1164
|
+
};
|
|
1165
|
+
return cacheWrapper;
|
|
1166
|
+
function resolveFile(cwd, file) {
|
|
1167
|
+
if (!useRelative) return normalizePath(file);
|
|
1168
|
+
const r = path$1.relative(relDir, path$1.resolve(cwd, file));
|
|
1169
|
+
return normalizePath(r);
|
|
1170
|
+
}
|
|
1171
|
+
function resolveFiles(cwd, files) {
|
|
1172
|
+
return files?.map((file) => resolveFile(cwd, file));
|
|
1173
|
+
}
|
|
1174
|
+
function wrap(fn) {
|
|
1175
|
+
return (...params) => {
|
|
1176
|
+
const cwd = process.cwd();
|
|
1177
|
+
try {
|
|
1178
|
+
isMainThread && process.chdir(relDir);
|
|
1179
|
+
return fn(cwd, ...params);
|
|
1180
|
+
} finally {
|
|
1181
|
+
isMainThread && process.chdir(cwd);
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
function normalizePath(filePath) {
|
|
1187
|
+
if (path$1.sep === "/") return filePath;
|
|
1188
|
+
return filePath.split(path$1.sep).join("/");
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
//#endregion
|
|
1192
|
+
//#region src/util/cache/ObjectCollection.ts
|
|
1193
|
+
const compare = Intl.Collator().compare;
|
|
1194
|
+
var ShallowObjectCollection = class {
|
|
1195
|
+
tree = {};
|
|
1196
|
+
get(v) {
|
|
1197
|
+
if (typeof v !== "object" || v === null) return v;
|
|
1198
|
+
const keys = Object.entries(v).filter((entry) => entry[1] !== void 0).sort((a, b) => compare(a[0], b[0]));
|
|
1199
|
+
let t = this.tree;
|
|
1200
|
+
for (const [key, obj] of keys) {
|
|
1201
|
+
if (!t.c) t.c = /* @__PURE__ */ new Map();
|
|
1202
|
+
const c0 = t.c.get(key);
|
|
1203
|
+
const cc = c0 || /* @__PURE__ */ new Map();
|
|
1204
|
+
if (!c0) t.c.set(key, cc);
|
|
1205
|
+
const c1 = cc.get(obj);
|
|
1206
|
+
const ccc = c1 || {};
|
|
1207
|
+
if (!c1) cc.set(obj, ccc);
|
|
1208
|
+
t = ccc;
|
|
1209
|
+
}
|
|
1210
|
+
if (t.v) return t.v;
|
|
1211
|
+
t.v = v;
|
|
1212
|
+
return v;
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
//#endregion
|
|
1217
|
+
//#region src/util/cache/DiskCache.ts
|
|
1218
|
+
const cacheDataKeys = {
|
|
1219
|
+
v: "v",
|
|
1220
|
+
r: "r",
|
|
1221
|
+
d: "d"
|
|
1222
|
+
};
|
|
1223
|
+
/**
|
|
1224
|
+
* Meta Data Version is used to detect if the structure of the meta data has changed.
|
|
1225
|
+
* This is used in combination with the Suffix and the version of CSpell.
|
|
1226
|
+
*/
|
|
1227
|
+
const META_DATA_BASE_VERSION = "1";
|
|
1228
|
+
const META_DATA_VERSION_SUFFIX = "-" + META_DATA_BASE_VERSION + "-" + Object.keys(cacheDataKeys).join("|");
|
|
1229
|
+
/**
|
|
1230
|
+
* Caches cspell results on disk
|
|
1231
|
+
*/
|
|
1232
|
+
var DiskCache = class {
|
|
1233
|
+
cacheFileLocation;
|
|
1234
|
+
cacheDir;
|
|
1235
|
+
fileEntryCache;
|
|
1236
|
+
dependencyCache = /* @__PURE__ */ new Map();
|
|
1237
|
+
dependencyCacheTree = {};
|
|
1238
|
+
objectCollection = new ShallowObjectCollection();
|
|
1239
|
+
ocCacheFileResult = new ShallowObjectCollection();
|
|
1240
|
+
version;
|
|
1241
|
+
constructor(cacheFileLocation, useCheckSum, cspellVersion, useUniversalCache) {
|
|
1242
|
+
this.useCheckSum = useCheckSum;
|
|
1243
|
+
this.cspellVersion = cspellVersion;
|
|
1244
|
+
this.useUniversalCache = useUniversalCache;
|
|
1245
|
+
this.cacheFileLocation = resolve(cacheFileLocation);
|
|
1246
|
+
this.cacheDir = dirname(this.cacheFileLocation);
|
|
1247
|
+
this.fileEntryCache = createFromFile(this.cacheFileLocation, useCheckSum, useUniversalCache);
|
|
1248
|
+
this.version = calcVersion(cspellVersion);
|
|
1249
|
+
}
|
|
1250
|
+
async getCachedLintResults(filename) {
|
|
1251
|
+
filename = normalizePath(filename);
|
|
1252
|
+
const fileDescriptor = this.fileEntryCache.getFileDescriptor(filename);
|
|
1253
|
+
const meta = fileDescriptor.meta;
|
|
1254
|
+
const data = meta?.data;
|
|
1255
|
+
const result = data?.r;
|
|
1256
|
+
const versionMatches = this.version === data?.v;
|
|
1257
|
+
if (fileDescriptor.notFound || fileDescriptor.changed || !meta || !result || !versionMatches || !this.checkDependencies(data.d)) return void 0;
|
|
1258
|
+
const dd = { ...data };
|
|
1259
|
+
if (dd.d) dd.d = setTreeEntry(this.dependencyCacheTree, dd.d);
|
|
1260
|
+
dd.r = dd.r && this.normalizeResult(dd.r);
|
|
1261
|
+
meta.data = this.objectCollection.get(dd);
|
|
1262
|
+
const hasErrors = !!result && (result.errors > 0 || result.configErrors > 0 || result.issues.length > 0);
|
|
1263
|
+
const cached = true;
|
|
1264
|
+
const shouldReadFile = hasErrors;
|
|
1265
|
+
return {
|
|
1266
|
+
...result,
|
|
1267
|
+
elapsedTimeMs: void 0,
|
|
1268
|
+
fileInfo: shouldReadFile ? await readFileInfo(filename) : { filename },
|
|
1269
|
+
cached
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
setCachedLintResults({ fileInfo, elapsedTimeMs: _, cached: __,...result }, dependsUponFiles) {
|
|
1273
|
+
const fileDescriptor = this.fileEntryCache.getFileDescriptor(fileInfo.filename);
|
|
1274
|
+
const meta = fileDescriptor.meta;
|
|
1275
|
+
if (fileDescriptor.notFound || !meta) return;
|
|
1276
|
+
const data = this.objectCollection.get({
|
|
1277
|
+
v: this.version,
|
|
1278
|
+
r: this.normalizeResult(result),
|
|
1279
|
+
d: this.calcDependencyHashes(dependsUponFiles)
|
|
1280
|
+
});
|
|
1281
|
+
meta.data = data;
|
|
1282
|
+
}
|
|
1283
|
+
reconcile() {
|
|
1284
|
+
this.fileEntryCache.reconcile();
|
|
1285
|
+
}
|
|
1286
|
+
reset() {
|
|
1287
|
+
this.fileEntryCache.destroy();
|
|
1288
|
+
this.dependencyCache.clear();
|
|
1289
|
+
this.dependencyCacheTree = {};
|
|
1290
|
+
this.objectCollection = new ShallowObjectCollection();
|
|
1291
|
+
this.ocCacheFileResult = new ShallowObjectCollection();
|
|
1292
|
+
}
|
|
1293
|
+
normalizeResult(result) {
|
|
1294
|
+
const { issues, processed, errors, configErrors, reportIssueOptions,...rest } = result;
|
|
1295
|
+
if (!Object.keys(rest).length) return this.ocCacheFileResult.get(result);
|
|
1296
|
+
return this.ocCacheFileResult.get({
|
|
1297
|
+
issues,
|
|
1298
|
+
processed,
|
|
1299
|
+
errors,
|
|
1300
|
+
configErrors,
|
|
1301
|
+
reportIssueOptions
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
calcDependencyHashes(dependsUponFiles) {
|
|
1305
|
+
dependsUponFiles.sort();
|
|
1306
|
+
const c = getTreeEntry(this.dependencyCacheTree, dependsUponFiles);
|
|
1307
|
+
if (c?.d) return c.d;
|
|
1308
|
+
const dependencies = dependsUponFiles.map((f) => this.getDependency(f));
|
|
1309
|
+
return setTreeEntry(this.dependencyCacheTree, dependencies);
|
|
1310
|
+
}
|
|
1311
|
+
checkDependency(dep) {
|
|
1312
|
+
const depFile = this.resolveFile(dep.f);
|
|
1313
|
+
const cDep = this.dependencyCache.get(depFile);
|
|
1314
|
+
if (cDep && compDep(dep, cDep)) return true;
|
|
1315
|
+
if (cDep) return false;
|
|
1316
|
+
const d = this.getFileDep(depFile);
|
|
1317
|
+
if (compDep(dep, d)) {
|
|
1318
|
+
this.dependencyCache.set(depFile, dep);
|
|
1319
|
+
return true;
|
|
1320
|
+
}
|
|
1321
|
+
this.dependencyCache.set(depFile, d);
|
|
1322
|
+
return false;
|
|
1323
|
+
}
|
|
1324
|
+
getDependency(file) {
|
|
1325
|
+
const dep = this.dependencyCache.get(file);
|
|
1326
|
+
if (dep) return dep;
|
|
1327
|
+
const d = this.getFileDep(file);
|
|
1328
|
+
this.dependencyCache.set(file, d);
|
|
1329
|
+
return d;
|
|
1330
|
+
}
|
|
1331
|
+
getFileDep(file) {
|
|
1332
|
+
assert(isAbsolute(file), `Dependency must be absolute "${file}"`);
|
|
1333
|
+
const f = this.toRelFile(file);
|
|
1334
|
+
let h;
|
|
1335
|
+
try {
|
|
1336
|
+
const buffer = fs$1.readFileSync(file);
|
|
1337
|
+
h = this.getHash(buffer);
|
|
1338
|
+
} catch {
|
|
1339
|
+
return { f };
|
|
1340
|
+
}
|
|
1341
|
+
return {
|
|
1342
|
+
f,
|
|
1343
|
+
h
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
checkDependencies(dependencies) {
|
|
1347
|
+
if (!dependencies) return false;
|
|
1348
|
+
for (const dep of dependencies) if (!this.checkDependency(dep)) return false;
|
|
1349
|
+
return true;
|
|
1350
|
+
}
|
|
1351
|
+
getHash(buffer) {
|
|
1352
|
+
return crypto.createHash("md5").update(buffer).digest("hex");
|
|
1353
|
+
}
|
|
1354
|
+
resolveFile(file) {
|
|
1355
|
+
return normalizePath(resolve(this.cacheDir, file));
|
|
1356
|
+
}
|
|
1357
|
+
toRelFile(file) {
|
|
1358
|
+
return normalizePath(this.useUniversalCache ? relative(this.cacheDir, file) : file);
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
function getTreeEntry(tree, keys) {
|
|
1362
|
+
let r = tree;
|
|
1363
|
+
for (const k of keys) {
|
|
1364
|
+
r = r.c?.get(k);
|
|
1365
|
+
if (!r) return r;
|
|
1366
|
+
}
|
|
1367
|
+
return r;
|
|
1368
|
+
}
|
|
1369
|
+
function setTreeEntry(tree, deps, update = false) {
|
|
1370
|
+
let r = tree;
|
|
1371
|
+
for (const d$1 of deps) {
|
|
1372
|
+
const k = d$1.f;
|
|
1373
|
+
if (!r.c) r.c = /* @__PURE__ */ new Map();
|
|
1374
|
+
const cn = r.c.get(k);
|
|
1375
|
+
const n = cn ?? {};
|
|
1376
|
+
if (!cn) r.c.set(k, n);
|
|
1377
|
+
r = n;
|
|
1378
|
+
}
|
|
1379
|
+
let d = r.d;
|
|
1380
|
+
if (!d || r.d && update) {
|
|
1381
|
+
r.d = deps;
|
|
1382
|
+
d = deps;
|
|
1383
|
+
}
|
|
1384
|
+
return d;
|
|
1385
|
+
}
|
|
1386
|
+
function compDep(a, b) {
|
|
1387
|
+
return a.f === b.f && a.h === b.h;
|
|
1388
|
+
}
|
|
1389
|
+
function calcVersion(version$2) {
|
|
1390
|
+
return version$2 + META_DATA_VERSION_SUFFIX;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
//#endregion
|
|
1394
|
+
//#region src/util/cache/DummyCache.ts
|
|
1395
|
+
/**
|
|
1396
|
+
* Dummy cache implementation that should be usd if caching option is disabled.
|
|
1397
|
+
*/
|
|
1398
|
+
var DummyCache = class {
|
|
1399
|
+
getCachedLintResults() {
|
|
1400
|
+
return Promise.resolve(void 0);
|
|
1401
|
+
}
|
|
1402
|
+
setCachedLintResults() {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
reconcile() {
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
reset() {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
//#endregion
|
|
1414
|
+
//#region src/util/cache/createCache.ts
|
|
1415
|
+
const DEFAULT_CACHE_LOCATION = ".cspellcache";
|
|
1416
|
+
const versionSuffix = "";
|
|
1417
|
+
/**
|
|
1418
|
+
* Creates CSpellLintResultCache (disk cache if caching is enabled in config or dummy otherwise)
|
|
1419
|
+
*/
|
|
1420
|
+
function createCache(options) {
|
|
1421
|
+
const { useCache, cacheLocation, cacheStrategy, reset } = options;
|
|
1422
|
+
const location = path.resolve(cacheLocation);
|
|
1423
|
+
const useChecksum = cacheStrategy === "content";
|
|
1424
|
+
const version$2 = normalizeVersion(options.version);
|
|
1425
|
+
const useUniversal = options.cacheFormat === "universal";
|
|
1426
|
+
const cache = useCache ? new DiskCache(location, useChecksum, version$2, useUniversal) : new DummyCache();
|
|
1427
|
+
reset && cache.reset();
|
|
1428
|
+
return cache;
|
|
1429
|
+
}
|
|
1430
|
+
async function calcCacheSettings(config, cacheOptions, root) {
|
|
1431
|
+
const cs = config.cache ?? {};
|
|
1432
|
+
const useCache = cacheOptions.cache ?? cs.useCache ?? false;
|
|
1433
|
+
const cacheLocation = await resolveCacheLocation(path.resolve(root, cacheOptions.cacheLocation ?? cs.cacheLocation ?? DEFAULT_CACHE_LOCATION));
|
|
1434
|
+
const cacheStrategy = cacheOptions.cacheStrategy ?? cs.cacheStrategy ?? "content";
|
|
1435
|
+
const cacheFormat = cacheOptions.cacheFormat ?? cs.cacheFormat ?? "universal";
|
|
1436
|
+
const optionals = {};
|
|
1437
|
+
if (cacheOptions.cacheReset) optionals.reset = true;
|
|
1438
|
+
return {
|
|
1439
|
+
...optionals,
|
|
1440
|
+
useCache,
|
|
1441
|
+
cacheLocation,
|
|
1442
|
+
cacheStrategy,
|
|
1443
|
+
version: cacheOptions.version,
|
|
1444
|
+
cacheFormat
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
async function resolveCacheLocation(cacheLocation) {
|
|
1448
|
+
try {
|
|
1449
|
+
const s = await stat(cacheLocation);
|
|
1450
|
+
if (s.isFile()) return cacheLocation;
|
|
1451
|
+
return path.join(cacheLocation, DEFAULT_CACHE_LOCATION);
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
if (isErrorLike(err) && err.code === "ENOENT") return cacheLocation;
|
|
1454
|
+
throw err;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Normalizes the version and return only `major.minor + versionSuffix`
|
|
1459
|
+
* @param version The cspell semantic version.
|
|
1460
|
+
*/
|
|
1461
|
+
function normalizeVersion(version$2) {
|
|
1462
|
+
const parts = version$2.split(".").slice(0, 2);
|
|
1463
|
+
assert(parts.length === 2);
|
|
1464
|
+
return parts.join(".") + versionSuffix;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/util/configFileHelper.ts
|
|
1469
|
+
async function readConfig(configFile, root, stopConfigSearchAt) {
|
|
1470
|
+
configFile ??= getEnvironmentVariable(environmentKeys.CSPELL_CONFIG_PATH);
|
|
1471
|
+
if (configFile) {
|
|
1472
|
+
const cfgFile = typeof configFile === "string" ? await readConfigHandleError(configFile) : configFile;
|
|
1473
|
+
return configFileToConfigInfo(cfgFile);
|
|
1474
|
+
}
|
|
1475
|
+
const config = await cspell.searchForConfig(root, { stopSearchAt: stopConfigSearchAt });
|
|
1476
|
+
const defaultConfigFile = getEnvironmentVariable(environmentKeys.CSPELL_DEFAULT_CONFIG_PATH);
|
|
1477
|
+
if (!config && defaultConfigFile) {
|
|
1478
|
+
const cfgFile = await readConfigFile(defaultConfigFile).catch(() => void 0);
|
|
1479
|
+
if (cfgFile) return configFileToConfigInfo(cfgFile);
|
|
1480
|
+
}
|
|
1481
|
+
return {
|
|
1482
|
+
source: config?.__importRef?.filename || "None found",
|
|
1483
|
+
config: config || {}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
async function configFileToConfigInfo(cfgFile) {
|
|
1487
|
+
const config = await cspell.resolveConfigFileImports(cfgFile);
|
|
1488
|
+
const source = toFilePathOrHref(cfgFile.url);
|
|
1489
|
+
return {
|
|
1490
|
+
source,
|
|
1491
|
+
config
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
function readConfigFile(filename) {
|
|
1495
|
+
return cspell.readConfigFile(filename);
|
|
1496
|
+
}
|
|
1497
|
+
async function readConfigHandleError(filename) {
|
|
1498
|
+
try {
|
|
1499
|
+
return await readConfigFile(filename);
|
|
1500
|
+
} catch (e) {
|
|
1501
|
+
const settings = { __importRef: {
|
|
1502
|
+
filename: filename.toString(),
|
|
1503
|
+
error: e
|
|
1504
|
+
} };
|
|
1505
|
+
return {
|
|
1506
|
+
url: filenameToUrl(filename),
|
|
1507
|
+
settings
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region src/util/extractContext.ts
|
|
1514
|
+
function prefCharIndex(text, offset, count = 1) {
|
|
1515
|
+
if (offset - count < 0) return 0;
|
|
1516
|
+
for (; count > 0 && offset > 0; count--) {
|
|
1517
|
+
let code = text.charCodeAt(--offset) || 0;
|
|
1518
|
+
if (code === 65039) code = text.charCodeAt(--offset) || 0;
|
|
1519
|
+
offset -= (code & 64512) === 56320 ? 1 : 0;
|
|
1520
|
+
}
|
|
1521
|
+
return offset < 0 ? 0 : offset;
|
|
1522
|
+
}
|
|
1523
|
+
function nextCharIndex(text, offset, count = 1) {
|
|
1524
|
+
if (offset + count >= text.length) return text.length;
|
|
1525
|
+
for (; count > 0 && offset < text.length; count--) {
|
|
1526
|
+
const code = text.charCodeAt(offset++) || 0;
|
|
1527
|
+
offset += (code & 64512) === 55296 ? 1 : 0;
|
|
1528
|
+
if (text.charCodeAt(offset) === 65039) offset++;
|
|
1529
|
+
}
|
|
1530
|
+
return offset > text.length ? text.length : offset;
|
|
1531
|
+
}
|
|
1532
|
+
function lineContext(lineText, start, end, contextRange) {
|
|
1533
|
+
let left = prefCharIndex(lineText, start, contextRange);
|
|
1534
|
+
let right = nextCharIndex(lineText, end, contextRange);
|
|
1535
|
+
const isLetter = /^\p{L}$/u;
|
|
1536
|
+
const isMark = /^\p{M}$/u;
|
|
1537
|
+
for (let n = contextRange / 2; n > 0 && left > 0; n--, left--) {
|
|
1538
|
+
const c = lineText[left - 1];
|
|
1539
|
+
if (isMark.test(c)) {
|
|
1540
|
+
if (!isLetter.test(lineText[left - 2])) break;
|
|
1541
|
+
left--;
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
if (!isLetter.test(lineText[left - 1])) break;
|
|
1545
|
+
}
|
|
1546
|
+
for (let n = contextRange / 2; n > 0 && right < lineText.length; n--, right++) {
|
|
1547
|
+
if (!isLetter.test(lineText[right])) break;
|
|
1548
|
+
if (isMark.test(lineText[right + 1])) right++;
|
|
1549
|
+
}
|
|
1550
|
+
left = left < 0 ? 0 : left;
|
|
1551
|
+
const t0 = lineText.slice(left, right);
|
|
1552
|
+
const tLeft = t0.trimStart();
|
|
1553
|
+
left = Math.min(left + t0.length - tLeft.length, start);
|
|
1554
|
+
const text = tLeft.trimEnd();
|
|
1555
|
+
const context = {
|
|
1556
|
+
text,
|
|
1557
|
+
offset: left
|
|
1558
|
+
};
|
|
1559
|
+
return context;
|
|
1560
|
+
}
|
|
1561
|
+
function extractContext(tdo, contextRange) {
|
|
1562
|
+
const { line, offset, text } = tdo;
|
|
1563
|
+
const start = offset - line.offset;
|
|
1564
|
+
const context = lineContext(line.text, start, start + text.length, contextRange);
|
|
1565
|
+
context.offset += line.offset;
|
|
1566
|
+
return context;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
//#endregion
|
|
1570
|
+
//#region src/util/prefetch.ts
|
|
1571
|
+
function* prefetchIterable(iterable, size) {
|
|
1572
|
+
assert(size >= 0);
|
|
1573
|
+
const buffer = [];
|
|
1574
|
+
for (const value of iterable) {
|
|
1575
|
+
buffer.push(value);
|
|
1576
|
+
if (buffer.length >= size - 1) {
|
|
1577
|
+
const value$1 = buffer[0];
|
|
1578
|
+
buffer.shift();
|
|
1579
|
+
yield value$1;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
yield* buffer;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
//#endregion
|
|
1586
|
+
//#region src/util/reporters.ts
|
|
1587
|
+
function filterFeatureIssues(features, issue, reportOptions) {
|
|
1588
|
+
if (issue.issueType === IssueType.directive) return features?.issueType && reportOptions?.validateDirectives || false;
|
|
1589
|
+
if (features?.unknownWords) return true;
|
|
1590
|
+
if (!reportOptions) return true;
|
|
1591
|
+
if (issue.isFlagged || !reportOptions.unknownWords || reportOptions.unknownWords === unknownWordsChoices.ReportAll) return true;
|
|
1592
|
+
if (issue.hasPreferredSuggestions && reportOptions.unknownWords !== unknownWordsChoices.ReportFlagged) return true;
|
|
1593
|
+
if (issue.hasSimpleSuggestions && reportOptions.unknownWords === unknownWordsChoices.ReportSimple) return true;
|
|
1594
|
+
return false;
|
|
1595
|
+
}
|
|
1596
|
+
function handleIssue(reporter, issue, reportOptions) {
|
|
1597
|
+
if (!reporter.issue) return;
|
|
1598
|
+
if (!filterFeatureIssues(reporter.features, issue, reportOptions)) return;
|
|
1599
|
+
if (!reporter.features?.contextGeneration && !issue.context) {
|
|
1600
|
+
issue = { ...issue };
|
|
1601
|
+
issue.context = issue.line;
|
|
1602
|
+
}
|
|
1603
|
+
return reporter.issue(issue, reportOptions);
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Loads reporter modules configured in cspell config file
|
|
1607
|
+
*/
|
|
1608
|
+
async function loadReporters(reporters, defaultReporter, config) {
|
|
1609
|
+
async function loadReporter(reporterSettings) {
|
|
1610
|
+
if (reporterSettings === "default") return defaultReporter;
|
|
1611
|
+
if (!Array.isArray(reporterSettings)) reporterSettings = [reporterSettings];
|
|
1612
|
+
const [moduleName, settings] = reporterSettings;
|
|
1613
|
+
try {
|
|
1614
|
+
const { getReporter: getReporter$1 } = await dynamicImport(moduleName, [process.cwd(), pkgDir]);
|
|
1615
|
+
return getReporter$1(settings, config);
|
|
1616
|
+
} catch (e) {
|
|
1617
|
+
throw new ApplicationError(`Failed to load reporter ${moduleName}: ${toError(e).message}`);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
reporters = !reporters || !reporters.length ? ["default"] : [...reporters];
|
|
1621
|
+
const loadedReporters = await Promise.all(reporters.map(loadReporter));
|
|
1622
|
+
return loadedReporters.filter((v) => v !== void 0);
|
|
1623
|
+
}
|
|
1624
|
+
function finalizeReporter(reporter) {
|
|
1625
|
+
if (!reporter) return void 0;
|
|
1626
|
+
if (reporterIsFinalized(reporter)) return reporter;
|
|
1627
|
+
const final = {
|
|
1628
|
+
issue: (...params) => reporter.issue?.(...params),
|
|
1629
|
+
info: (...params) => reporter.info?.(...params),
|
|
1630
|
+
debug: (...params) => reporter.debug?.(...params),
|
|
1631
|
+
progress: (...params) => reporter.progress?.(...params),
|
|
1632
|
+
error: (...params) => reporter.error?.(...params),
|
|
1633
|
+
result: (...params) => reporter.result?.(...params),
|
|
1634
|
+
features: reporter.features
|
|
1635
|
+
};
|
|
1636
|
+
return final;
|
|
1637
|
+
}
|
|
1638
|
+
function reporterIsFinalized(reporter) {
|
|
1639
|
+
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;
|
|
1640
|
+
}
|
|
1641
|
+
const reportIssueOptionsKeyMap = {
|
|
1642
|
+
unknownWords: "unknownWords",
|
|
1643
|
+
validateDirectives: "validateDirectives",
|
|
1644
|
+
showContext: "showContext"
|
|
1645
|
+
};
|
|
1646
|
+
function setValue(options, key, value) {
|
|
1647
|
+
if (value !== void 0) options[key] = value;
|
|
1648
|
+
}
|
|
1649
|
+
function extractReporterIssueOptions(settings) {
|
|
1650
|
+
const src = settings;
|
|
1651
|
+
const options = {};
|
|
1652
|
+
for (const key in reportIssueOptionsKeyMap) {
|
|
1653
|
+
const k = key;
|
|
1654
|
+
setValue(options, k, src[k]);
|
|
1655
|
+
}
|
|
1656
|
+
return options;
|
|
1657
|
+
}
|
|
1658
|
+
function mergeReportIssueOptions(a, b) {
|
|
1659
|
+
const options = extractReporterIssueOptions(a);
|
|
1660
|
+
if (!b) return options;
|
|
1661
|
+
for (const key in reportIssueOptionsKeyMap) {
|
|
1662
|
+
const k = key;
|
|
1663
|
+
setValue(options, k, b[k]);
|
|
1664
|
+
}
|
|
1665
|
+
return options;
|
|
1666
|
+
}
|
|
1667
|
+
var LintReporter = class {
|
|
1668
|
+
#reporters = [];
|
|
1669
|
+
#config;
|
|
1670
|
+
#finalized = false;
|
|
1671
|
+
constructor(defaultReporter, config) {
|
|
1672
|
+
this.defaultReporter = defaultReporter;
|
|
1673
|
+
this.#config = config;
|
|
1674
|
+
if (defaultReporter) this.#reporters.push(finalizeReporter(defaultReporter));
|
|
1675
|
+
}
|
|
1676
|
+
get config() {
|
|
1677
|
+
return this.#config;
|
|
1678
|
+
}
|
|
1679
|
+
set config(config) {
|
|
1680
|
+
assert(!this.#finalized, "Cannot change the configuration of a finalized reporter");
|
|
1681
|
+
this.#config = config;
|
|
1682
|
+
}
|
|
1683
|
+
issue(issue, reportOptions) {
|
|
1684
|
+
for (const reporter of this.#reporters) handleIssue(reporter, issue, reportOptions);
|
|
1685
|
+
}
|
|
1686
|
+
info(...params) {
|
|
1687
|
+
for (const reporter of this.#reporters) reporter.info(...params);
|
|
1688
|
+
}
|
|
1689
|
+
debug(...params) {
|
|
1690
|
+
for (const reporter of this.#reporters) reporter.debug(...params);
|
|
1691
|
+
}
|
|
1692
|
+
error(...params) {
|
|
1693
|
+
for (const reporter of this.#reporters) reporter.error(...params);
|
|
1694
|
+
}
|
|
1695
|
+
progress(...params) {
|
|
1696
|
+
for (const reporter of this.#reporters) reporter.progress(...params);
|
|
1697
|
+
}
|
|
1698
|
+
async result(result) {
|
|
1699
|
+
await Promise.all(this.#reporters.map((reporter) => reporter.result?.(result)));
|
|
1700
|
+
}
|
|
1701
|
+
get features() {
|
|
1702
|
+
return {
|
|
1703
|
+
unknownWords: true,
|
|
1704
|
+
issueType: true
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
async loadReportersAndFinalize(reporters) {
|
|
1708
|
+
assert(!this.#finalized, "Cannot change the configuration of a finalized reporter");
|
|
1709
|
+
const loaded = await loadReporters(reporters, this.defaultReporter, this.config);
|
|
1710
|
+
this.#reporters = [...new Set(loaded)].map((reporter) => finalizeReporter(reporter));
|
|
1711
|
+
}
|
|
1712
|
+
emitProgressBegin(filename, fileNum, fileCount) {
|
|
1713
|
+
this.progress({
|
|
1714
|
+
type: "ProgressFileBegin",
|
|
1715
|
+
fileNum,
|
|
1716
|
+
fileCount,
|
|
1717
|
+
filename
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
emitProgressComplete(filename, fileNum, fileCount, result) {
|
|
1721
|
+
const filteredIssues = result.issues.filter((issue) => filterFeatureIssues({}, issue, result.reportIssueOptions));
|
|
1722
|
+
const numIssues = filteredIssues.length;
|
|
1723
|
+
for (const reporter of this.#reporters) {
|
|
1724
|
+
const progress = clean({
|
|
1725
|
+
type: "ProgressFileComplete",
|
|
1726
|
+
fileNum,
|
|
1727
|
+
fileCount,
|
|
1728
|
+
filename,
|
|
1729
|
+
elapsedTimeMs: result.elapsedTimeMs,
|
|
1730
|
+
processed: result.processed,
|
|
1731
|
+
numErrors: numIssues || result.errors,
|
|
1732
|
+
cached: result.cached,
|
|
1733
|
+
perf: result.perf,
|
|
1734
|
+
issues: reporter.features && result.issues,
|
|
1735
|
+
reportIssueOptions: reporter.features && result.reportIssueOptions
|
|
1736
|
+
});
|
|
1737
|
+
reporter.progress(progress);
|
|
1738
|
+
}
|
|
1739
|
+
result.issues.forEach((issue) => this.issue(issue, result.reportIssueOptions));
|
|
1740
|
+
return numIssues;
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
//#endregion
|
|
1745
|
+
//#region src/util/timer.ts
|
|
1746
|
+
function getTimeMeasurer() {
|
|
1747
|
+
const timer = createPerfTimer("timer");
|
|
1748
|
+
return () => timer.elapsed;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
//#endregion
|
|
1752
|
+
//#region src/util/writeFile.ts
|
|
1753
|
+
async function writeFileOrStream(filename, data) {
|
|
1754
|
+
switch (filename) {
|
|
1755
|
+
case "stdout": {
|
|
1756
|
+
await writeStream(process.stdout, data);
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
case "stderr": {
|
|
1760
|
+
await writeStream(process.stderr, data);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
case "null": return;
|
|
1764
|
+
}
|
|
1765
|
+
return fs.writeFile(filename, data);
|
|
1766
|
+
}
|
|
1767
|
+
function writeStream(stream, data) {
|
|
1768
|
+
return new Promise((resolve$1, reject) => {
|
|
1769
|
+
stream.write(data, (err) => {
|
|
1770
|
+
if (err) reject(err);
|
|
1771
|
+
else resolve$1();
|
|
1772
|
+
});
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
//#endregion
|
|
1777
|
+
//#region src/lint/lint.ts
|
|
1778
|
+
const version = npmPackage.version;
|
|
1779
|
+
const BATCH_SIZE = 8;
|
|
1780
|
+
const { opFilterAsync } = operators;
|
|
1781
|
+
async function runLint(cfg) {
|
|
1782
|
+
const reporter = new LintReporter(cfg.reporter, cfg.options);
|
|
1783
|
+
const configErrors = /* @__PURE__ */ new Set();
|
|
1784
|
+
const timer = getTimeMeasurer();
|
|
1785
|
+
const logDictRequests = truthy(getEnvironmentVariable("CSPELL_ENABLE_DICTIONARY_LOGGING"));
|
|
1786
|
+
if (logDictRequests) _debug.cacheDictionaryEnableLogging(true);
|
|
1787
|
+
const lintResult = await run();
|
|
1788
|
+
if (logDictRequests) await writeDictionaryLog();
|
|
1789
|
+
await reporter.result(lintResult);
|
|
1790
|
+
const elapsed = timer();
|
|
1791
|
+
if (getFeatureFlags().getFlag("timer")) console.log(`Elapsed Time: ${elapsed.toFixed(2)}ms`);
|
|
1792
|
+
return lintResult;
|
|
1793
|
+
function prefetch(filename, configInfo, cache) {
|
|
1794
|
+
if (isBinaryFile$1(filename, cfg.root)) return {
|
|
1795
|
+
filename,
|
|
1796
|
+
result: Promise.resolve({ skip: true })
|
|
1797
|
+
};
|
|
1798
|
+
const reportIssueOptions = extractReporterIssueOptions(configInfo.config);
|
|
1799
|
+
async function fetch() {
|
|
1800
|
+
const getElapsedTimeMs = getTimeMeasurer();
|
|
1801
|
+
const cachedResult = await cache.getCachedLintResults(filename);
|
|
1802
|
+
if (cachedResult) {
|
|
1803
|
+
reporter.debug(`Filename: ${filename}, using cache`);
|
|
1804
|
+
const fileResult = {
|
|
1805
|
+
...cachedResult,
|
|
1806
|
+
elapsedTimeMs: getElapsedTimeMs()
|
|
1807
|
+
};
|
|
1808
|
+
return { fileResult };
|
|
1809
|
+
}
|
|
1810
|
+
const uri = filenameToUri(filename, cfg.root).href;
|
|
1811
|
+
const checkResult = await shouldCheckDocument({ uri }, {}, configInfo.config);
|
|
1812
|
+
if (!checkResult.shouldCheck) return { skip: true };
|
|
1813
|
+
const fileInfo = await readFileInfo(filename, void 0, true);
|
|
1814
|
+
return {
|
|
1815
|
+
fileInfo,
|
|
1816
|
+
reportIssueOptions
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
const result = fetch();
|
|
1820
|
+
return {
|
|
1821
|
+
filename,
|
|
1822
|
+
result
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
async function processFile(filename, configInfo, cache, prefetch$1) {
|
|
1826
|
+
if (prefetch$1?.fileResult) return prefetch$1.fileResult;
|
|
1827
|
+
const getElapsedTimeMs = getTimeMeasurer();
|
|
1828
|
+
const reportIssueOptions = prefetch$1?.reportIssueOptions;
|
|
1829
|
+
const cachedResult = await cache.getCachedLintResults(filename);
|
|
1830
|
+
if (cachedResult) {
|
|
1831
|
+
reporter.debug(`Filename: ${filename}, using cache`);
|
|
1832
|
+
return {
|
|
1833
|
+
...cachedResult,
|
|
1834
|
+
elapsedTimeMs: getElapsedTimeMs(),
|
|
1835
|
+
reportIssueOptions: {
|
|
1836
|
+
...cachedResult.reportIssueOptions,
|
|
1837
|
+
...reportIssueOptions
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
const result = {
|
|
1842
|
+
fileInfo: { filename },
|
|
1843
|
+
issues: [],
|
|
1844
|
+
processed: false,
|
|
1845
|
+
errors: 0,
|
|
1846
|
+
configErrors: 0,
|
|
1847
|
+
elapsedTimeMs: 0,
|
|
1848
|
+
reportIssueOptions
|
|
1849
|
+
};
|
|
1850
|
+
const fileInfo = prefetch$1?.fileInfo || await readFileInfo(filename, void 0, true);
|
|
1851
|
+
if (fileInfo.errorCode) {
|
|
1852
|
+
if (fileInfo.errorCode !== "EISDIR" && cfg.options.mustFindFiles) {
|
|
1853
|
+
const err = new LinterError(`File not found: "${filename}"`);
|
|
1854
|
+
reporter.error("Linter:", err);
|
|
1855
|
+
result.errors += 1;
|
|
1856
|
+
}
|
|
1857
|
+
return result;
|
|
1858
|
+
}
|
|
1859
|
+
const doc = fileInfoToDocument(fileInfo, cfg.options.languageId, cfg.locale);
|
|
1860
|
+
const { text } = fileInfo;
|
|
1861
|
+
result.fileInfo = fileInfo;
|
|
1862
|
+
let spellResult = {};
|
|
1863
|
+
reporter.info(`Checking: ${filename}, File type: ${doc.languageId ?? "auto"}, Language: ${doc.locale ?? "default"}`, MessageTypes.Info);
|
|
1864
|
+
try {
|
|
1865
|
+
const { showSuggestions: generateSuggestions, validateDirectives, skipValidation } = cfg.options;
|
|
1866
|
+
const numSuggestions = configInfo.config.numSuggestions ?? 5;
|
|
1867
|
+
const validateOptions = clean({
|
|
1868
|
+
generateSuggestions,
|
|
1869
|
+
numSuggestions,
|
|
1870
|
+
validateDirectives,
|
|
1871
|
+
skipValidation
|
|
1872
|
+
});
|
|
1873
|
+
const r = await spellCheckDocument(doc, validateOptions, configInfo.config);
|
|
1874
|
+
spellResult = r;
|
|
1875
|
+
result.processed = r.checked;
|
|
1876
|
+
result.perf = r.perf ? { ...r.perf } : void 0;
|
|
1877
|
+
result.issues = Text.calculateTextDocumentOffsets(doc.uri, text, r.issues).map(mapIssue);
|
|
1878
|
+
} catch (e) {
|
|
1879
|
+
reporter.error(`Failed to process "${filename}"`, toError(e));
|
|
1880
|
+
result.errors += 1;
|
|
1881
|
+
}
|
|
1882
|
+
result.elapsedTimeMs = getElapsedTimeMs();
|
|
1883
|
+
const config = spellResult.settingsUsed ?? {};
|
|
1884
|
+
result.reportIssueOptions = mergeReportIssueOptions(spellResult.settingsUsed || configInfo.config, reportIssueOptions);
|
|
1885
|
+
result.configErrors += await reportConfigurationErrors(config);
|
|
1886
|
+
const elapsed$1 = result.elapsedTimeMs;
|
|
1887
|
+
const dictionaries = config.dictionaries || [];
|
|
1888
|
+
reporter.info(`Checked: ${filename}, File type: ${config.languageId}, Language: ${config.language} ... Issues: ${result.issues.length} ${elapsed$1.toFixed(2)}ms`, MessageTypes.Info);
|
|
1889
|
+
reporter.info(`Config file Used: ${spellResult.localConfigFilepath || configInfo.source}`, MessageTypes.Info);
|
|
1890
|
+
reporter.info(`Dictionaries Used: ${dictionaries.join(", ")}`, MessageTypes.Info);
|
|
1891
|
+
if (cfg.options.debug) {
|
|
1892
|
+
const { id: _id, name: _name, __imports, __importRef,...cfg$1 } = config;
|
|
1893
|
+
const debugCfg = {
|
|
1894
|
+
filename,
|
|
1895
|
+
languageId: doc.languageId ?? cfg$1.languageId ?? "default",
|
|
1896
|
+
config: {
|
|
1897
|
+
...cfg$1,
|
|
1898
|
+
source: null
|
|
1899
|
+
},
|
|
1900
|
+
source: spellResult.localConfigFilepath
|
|
1901
|
+
};
|
|
1902
|
+
reporter.debug(JSON.stringify(debugCfg, void 0, 2));
|
|
1903
|
+
}
|
|
1904
|
+
const dep = calcDependencies(config);
|
|
1905
|
+
cache.setCachedLintResults(result, dep.files);
|
|
1906
|
+
return result;
|
|
1907
|
+
}
|
|
1908
|
+
function mapIssue({ doc: _,...tdo }) {
|
|
1909
|
+
const context = cfg.showContext ? extractContext(tdo, cfg.showContext) : void 0;
|
|
1910
|
+
return clean({
|
|
1911
|
+
...tdo,
|
|
1912
|
+
context
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
async function processFiles(files, configInfo, cacheSettings) {
|
|
1916
|
+
const fileCount = Array.isArray(files) ? files.length : void 0;
|
|
1917
|
+
const status = runResult();
|
|
1918
|
+
const cache = createCache(cacheSettings);
|
|
1919
|
+
const failFast = cfg.options.failFast ?? configInfo.config.failFast ?? false;
|
|
1920
|
+
function* prefetchFiles(files$1) {
|
|
1921
|
+
const iter = prefetchIterable(pipe(files$1, opMap$1((filename) => prefetch(filename, configInfo, cache))), BATCH_SIZE);
|
|
1922
|
+
for (const v of iter) yield v;
|
|
1923
|
+
}
|
|
1924
|
+
async function* prefetchFilesAsync(files$1) {
|
|
1925
|
+
for await (const filename of files$1) yield prefetch(filename, configInfo, cache);
|
|
1926
|
+
}
|
|
1927
|
+
const emptyResult = {
|
|
1928
|
+
fileInfo: { filename: "" },
|
|
1929
|
+
issues: [],
|
|
1930
|
+
processed: false,
|
|
1931
|
+
errors: 0,
|
|
1932
|
+
configErrors: 0,
|
|
1933
|
+
elapsedTimeMs: 1,
|
|
1934
|
+
reportIssueOptions: void 0
|
|
1935
|
+
};
|
|
1936
|
+
async function processPrefetchFileResult(pf, index) {
|
|
1937
|
+
const { filename, result: pFetchResult } = pf;
|
|
1938
|
+
const getElapsedTimeMs = getTimeMeasurer();
|
|
1939
|
+
const fetchResult = await pFetchResult;
|
|
1940
|
+
reporter.emitProgressBegin(filename, index, fileCount ?? index);
|
|
1941
|
+
if (fetchResult?.skip) return {
|
|
1942
|
+
filename,
|
|
1943
|
+
fileNum: index,
|
|
1944
|
+
result: {
|
|
1945
|
+
...emptyResult,
|
|
1946
|
+
fileInfo: { filename },
|
|
1947
|
+
elapsedTimeMs: getElapsedTimeMs()
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
const result = await processFile(filename, configInfo, cache, fetchResult);
|
|
1951
|
+
return {
|
|
1952
|
+
filename,
|
|
1953
|
+
fileNum: index,
|
|
1954
|
+
result
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
async function* loadAndProcessFiles() {
|
|
1958
|
+
let i = 0;
|
|
1959
|
+
if (isAsyncIterable(files)) for await (const pf of prefetchFilesAsync(files)) yield processPrefetchFileResult(pf, ++i);
|
|
1960
|
+
else for (const pf of prefetchFiles(files)) {
|
|
1961
|
+
await pf.result;
|
|
1962
|
+
yield processPrefetchFileResult(pf, ++i);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
for await (const fileP of loadAndProcessFiles()) {
|
|
1966
|
+
const { filename, fileNum, result } = fileP;
|
|
1967
|
+
status.files += 1;
|
|
1968
|
+
status.cachedFiles = (status.cachedFiles || 0) + (result.cached ? 1 : 0);
|
|
1969
|
+
const numIssues = reporter.emitProgressComplete(filename, fileNum, fileCount ?? fileNum, result);
|
|
1970
|
+
if (numIssues || result.errors) {
|
|
1971
|
+
status.filesWithIssues.add(relativeToCwd(filename, cfg.root));
|
|
1972
|
+
status.issues += numIssues;
|
|
1973
|
+
status.errors += result.errors;
|
|
1974
|
+
if (failFast) return status;
|
|
1975
|
+
}
|
|
1976
|
+
status.errors += result.configErrors;
|
|
1977
|
+
}
|
|
1978
|
+
cache.reconcile();
|
|
1979
|
+
return status;
|
|
1980
|
+
}
|
|
1981
|
+
function calcDependencies(config) {
|
|
1982
|
+
const { configFiles, dictionaryFiles } = extractDependencies(config);
|
|
1983
|
+
return { files: [...configFiles, ...dictionaryFiles] };
|
|
1984
|
+
}
|
|
1985
|
+
async function reportConfigurationErrors(config) {
|
|
1986
|
+
const errors = extractImportErrors(config);
|
|
1987
|
+
let count = 0;
|
|
1988
|
+
errors.forEach((ref) => {
|
|
1989
|
+
const key = ref.error.toString();
|
|
1990
|
+
if (configErrors.has(key)) return;
|
|
1991
|
+
configErrors.add(key);
|
|
1992
|
+
count += 1;
|
|
1993
|
+
reporter.error("Configuration", ref.error);
|
|
1994
|
+
});
|
|
1995
|
+
const dictCollection = await getDictionary(config);
|
|
1996
|
+
dictCollection.dictionaries.forEach((dict) => {
|
|
1997
|
+
const dictErrors = dict.getErrors?.() || [];
|
|
1998
|
+
const msg = `Dictionary Error with (${dict.name})`;
|
|
1999
|
+
dictErrors.forEach((error) => {
|
|
2000
|
+
const key = msg + error.toString();
|
|
2001
|
+
if (configErrors.has(key)) return;
|
|
2002
|
+
configErrors.add(key);
|
|
2003
|
+
count += 1;
|
|
2004
|
+
reporter.error(msg, error);
|
|
2005
|
+
});
|
|
2006
|
+
});
|
|
2007
|
+
return count;
|
|
2008
|
+
}
|
|
2009
|
+
function countConfigErrors(configInfo) {
|
|
2010
|
+
return reportConfigurationErrors(configInfo.config);
|
|
2011
|
+
}
|
|
2012
|
+
async function run() {
|
|
2013
|
+
if (cfg.options.root) setEnvironmentVariable(ENV_CSPELL_GLOB_ROOT, cfg.root);
|
|
2014
|
+
const configInfo = await readConfig(cfg.configFile, cfg.root, cfg.options.stopConfigSearchAt);
|
|
2015
|
+
if (cfg.options.defaultConfiguration !== void 0) configInfo.config.loadDefaultConfiguration = cfg.options.defaultConfiguration;
|
|
2016
|
+
configInfo.config = mergeSettings(configInfo.config, cfg.cspellSettingsFromCliOptions);
|
|
2017
|
+
const reporterConfig = clean({
|
|
2018
|
+
maxNumberOfProblems: configInfo.config.maxNumberOfProblems,
|
|
2019
|
+
maxDuplicateProblems: configInfo.config.maxDuplicateProblems,
|
|
2020
|
+
minWordLength: configInfo.config.minWordLength,
|
|
2021
|
+
...cfg.options,
|
|
2022
|
+
console
|
|
2023
|
+
});
|
|
2024
|
+
const reporters = cfg.options.reporter ?? configInfo.config.reporters;
|
|
2025
|
+
reporter.config = reporterConfig;
|
|
2026
|
+
await reporter.loadReportersAndFinalize(reporters);
|
|
2027
|
+
setLogger(getLoggerFromReporter(reporter));
|
|
2028
|
+
const globInfo = await determineGlobs(configInfo, cfg);
|
|
2029
|
+
const { fileGlobs, excludeGlobs } = globInfo;
|
|
2030
|
+
const hasFileLists = !!cfg.fileLists.length;
|
|
2031
|
+
if (!fileGlobs.length && !hasFileLists && !cfg.files?.length) return runResult();
|
|
2032
|
+
header(fileGlobs, excludeGlobs);
|
|
2033
|
+
checkGlobs(fileGlobs, reporter);
|
|
2034
|
+
reporter.info(`Config Files Found:\n ${configInfo.source}\n`, MessageTypes.Info);
|
|
2035
|
+
const configErrors$1 = await countConfigErrors(configInfo);
|
|
2036
|
+
if (configErrors$1 && cfg.options.exitCode !== false && !cfg.options.continueOnError) return runResult({ errors: configErrors$1 });
|
|
2037
|
+
const { root } = cfg;
|
|
2038
|
+
try {
|
|
2039
|
+
const cacheSettings = await calcCacheSettings(configInfo.config, {
|
|
2040
|
+
...cfg.options,
|
|
2041
|
+
version
|
|
2042
|
+
}, root);
|
|
2043
|
+
const files = await determineFilesToCheck(configInfo, cfg, reporter, globInfo);
|
|
2044
|
+
const result = await processFiles(files, configInfo, cacheSettings);
|
|
2045
|
+
if (configErrors$1 && cfg.options.exitCode !== false) result.errors ||= configErrors$1;
|
|
2046
|
+
return result;
|
|
2047
|
+
} catch (e) {
|
|
2048
|
+
const err = toApplicationError(e);
|
|
2049
|
+
reporter.error("Linter", err);
|
|
2050
|
+
return runResult({ errors: 1 });
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
function header(files, cliExcludes) {
|
|
2054
|
+
const formattedFiles = files.length > 100 ? [...files.slice(0, 100), "..."] : files;
|
|
2055
|
+
reporter.info(`
|
|
2056
|
+
cspell;
|
|
2057
|
+
Date: ${(/* @__PURE__ */ new Date()).toUTCString()}
|
|
2058
|
+
Options:
|
|
2059
|
+
verbose: ${yesNo(!!cfg.options.verbose)}
|
|
2060
|
+
config: ${cfg.configFile || "default"}
|
|
2061
|
+
exclude: ${cliExcludes.join("\n ")}
|
|
2062
|
+
files: ${formattedFiles}
|
|
2063
|
+
wordsOnly: ${yesNo(!!cfg.options.wordsOnly)}
|
|
2064
|
+
unique: ${yesNo(!!cfg.options.unique)}
|
|
2065
|
+
`, MessageTypes.Info);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
function checkGlobs(globs, reporter) {
|
|
2069
|
+
globs.filter((g) => g.startsWith("'") || g.endsWith("'")).map((glob$2) => chalk.yellow(glob$2)).forEach((glob$2) => reporter.error("Linter", new CheckFailed(`Glob starting or ending with ' (single quote) is not likely to match any files: ${glob$2}.`)));
|
|
2070
|
+
}
|
|
2071
|
+
async function determineGlobs(configInfo, cfg) {
|
|
2072
|
+
const useGitignore = cfg.options.gitignore ?? configInfo.config.useGitignore ?? false;
|
|
2073
|
+
const gitignoreRoots = cfg.options.gitignoreRoot ?? configInfo.config.gitignoreRoot;
|
|
2074
|
+
const gitIgnore = useGitignore ? await generateGitIgnore(gitignoreRoots) : void 0;
|
|
2075
|
+
const cliGlobs = cfg.fileGlobs;
|
|
2076
|
+
const allGlobs = cliGlobs.length && cliGlobs || cfg.options.filterFiles !== false && configInfo.config.files || [];
|
|
2077
|
+
const combinedGlobs = await normalizeFileOrGlobsToRoot(allGlobs, cfg.root);
|
|
2078
|
+
const cliExcludeGlobs = extractPatterns(cfg.excludes).map((p) => p.glob);
|
|
2079
|
+
const normalizedExcludes = normalizeGlobsToRoot(cliExcludeGlobs, cfg.root, true);
|
|
2080
|
+
const includeGlobs = combinedGlobs.filter((g) => !g.startsWith("!"));
|
|
2081
|
+
const excludeGlobs = [...combinedGlobs.filter((g) => g.startsWith("!")).map((g) => g.slice(1)), ...normalizedExcludes];
|
|
2082
|
+
const fileGlobs = includeGlobs;
|
|
2083
|
+
const appGlobs = {
|
|
2084
|
+
allGlobs,
|
|
2085
|
+
gitIgnore,
|
|
2086
|
+
fileGlobs,
|
|
2087
|
+
excludeGlobs,
|
|
2088
|
+
normalizedExcludes
|
|
2089
|
+
};
|
|
2090
|
+
return appGlobs;
|
|
2091
|
+
}
|
|
2092
|
+
async function determineFilesToCheck(configInfo, cfg, reporter, globInfo) {
|
|
2093
|
+
async function _determineFilesToCheck() {
|
|
2094
|
+
const { fileLists } = cfg;
|
|
2095
|
+
const hasFileLists = !!fileLists.length;
|
|
2096
|
+
const { allGlobs, gitIgnore, fileGlobs, excludeGlobs, normalizedExcludes } = globInfo;
|
|
2097
|
+
const { root } = cfg;
|
|
2098
|
+
const globsToExcludeRaw = [...configInfo.config.ignorePaths || [], ...excludeGlobs];
|
|
2099
|
+
const globsToExclude = globsToExcludeRaw.filter((g) => !globPattern(g).startsWith("!"));
|
|
2100
|
+
if (globsToExclude.length !== globsToExcludeRaw.length) {
|
|
2101
|
+
const globs = globsToExcludeRaw.map((g) => globPattern(g)).filter((g) => g.startsWith("!"));
|
|
2102
|
+
const msg = `Negative glob exclusions are not supported: ${globs.join(", ")}`;
|
|
2103
|
+
reporter.info(msg, MessageTypes.Warning);
|
|
2104
|
+
}
|
|
2105
|
+
const globMatcher = buildGlobMatcher(globsToExclude, root, true);
|
|
2106
|
+
const ignoreGlobs = extractGlobsFromMatcher(globMatcher);
|
|
2107
|
+
const globOptions = {
|
|
2108
|
+
root,
|
|
2109
|
+
cwd: root,
|
|
2110
|
+
ignore: [...ignoreGlobs, ...normalizedExcludes],
|
|
2111
|
+
nodir: true
|
|
2112
|
+
};
|
|
2113
|
+
const enableGlobDot = cfg.enableGlobDot ?? configInfo.config.enableGlobDot;
|
|
2114
|
+
if (enableGlobDot !== void 0) globOptions.dot = enableGlobDot;
|
|
2115
|
+
const opFilterExcludedFiles = opFilter(filterOutExcludedFilesFn(globMatcher));
|
|
2116
|
+
const includeFilter = createIncludeFileFilterFn(allGlobs, root, enableGlobDot);
|
|
2117
|
+
const rawCliFiles = cfg.files?.map((file) => resolveFilename(file, root)).filter(includeFilter);
|
|
2118
|
+
const cliFiles = cfg.options.mustFindFiles ? rawCliFiles : rawCliFiles && pipeAsync(rawCliFiles, opFilterAsync(isFile));
|
|
2119
|
+
const foundFiles = hasFileLists ? concatAsyncIterables(cliFiles, await useFileLists(fileLists, includeFilter)) : cliFiles || await findFiles(fileGlobs, globOptions);
|
|
2120
|
+
const filtered = gitIgnore ? await gitIgnore.filterOutIgnored(foundFiles) : foundFiles;
|
|
2121
|
+
const files = isAsyncIterable(filtered) ? pipeAsync(filtered, opFilterExcludedFiles) : [...pipe(filtered, opFilterExcludedFiles)];
|
|
2122
|
+
return files;
|
|
2123
|
+
}
|
|
2124
|
+
function isExcluded(filename, globMatcherExclude) {
|
|
2125
|
+
if (isBinaryFile(toFileURL(filename))) return true;
|
|
2126
|
+
const { root } = cfg;
|
|
2127
|
+
const absFilename = path$1.resolve(root, filename);
|
|
2128
|
+
const r = globMatcherExclude.matchEx(absFilename);
|
|
2129
|
+
if (r.matched) {
|
|
2130
|
+
const { glob: glob$2, source } = extractGlobSource(r.pattern);
|
|
2131
|
+
reporter.info(`Excluded File: ${path$1.relative(root, absFilename)}; Excluded by ${glob$2} from ${source}`, MessageTypes.Info);
|
|
2132
|
+
}
|
|
2133
|
+
return r.matched;
|
|
2134
|
+
}
|
|
2135
|
+
function filterOutExcludedFilesFn(globMatcherExclude) {
|
|
2136
|
+
const patterns = globMatcherExclude.patterns;
|
|
2137
|
+
const excludeInfo = patterns.map(extractGlobSource).map(({ glob: glob$2, source }) => `Glob: ${glob$2} from ${source}`).filter(uniqueFn());
|
|
2138
|
+
reporter.info(`Exclusion Globs: \n ${excludeInfo.join("\n ")}\n`, MessageTypes.Info);
|
|
2139
|
+
return (filename) => !isExcluded(filename, globMatcherExclude);
|
|
2140
|
+
}
|
|
2141
|
+
return _determineFilesToCheck();
|
|
2142
|
+
}
|
|
2143
|
+
function extractGlobSource(g) {
|
|
2144
|
+
const { glob: glob$2, rawGlob, source } = g;
|
|
2145
|
+
return {
|
|
2146
|
+
glob: rawGlob || glob$2,
|
|
2147
|
+
source
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
function runResult(init = {}) {
|
|
2151
|
+
const { files = 0, filesWithIssues = /* @__PURE__ */ new Set(), issues = 0, errors = 0, cachedFiles = 0 } = init;
|
|
2152
|
+
return {
|
|
2153
|
+
files,
|
|
2154
|
+
filesWithIssues,
|
|
2155
|
+
issues,
|
|
2156
|
+
errors,
|
|
2157
|
+
cachedFiles
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
function yesNo(value) {
|
|
2161
|
+
return value ? "Yes" : "No";
|
|
2162
|
+
}
|
|
2163
|
+
function getLoggerFromReporter(reporter) {
|
|
2164
|
+
const log = (...params) => {
|
|
2165
|
+
const msg = format(...params);
|
|
2166
|
+
reporter.info(msg, "Info");
|
|
2167
|
+
};
|
|
2168
|
+
const error = (...params) => {
|
|
2169
|
+
const msg = format(...params);
|
|
2170
|
+
const err = {
|
|
2171
|
+
message: "",
|
|
2172
|
+
name: "error",
|
|
2173
|
+
toString: () => ""
|
|
2174
|
+
};
|
|
2175
|
+
reporter.error(msg, err);
|
|
2176
|
+
};
|
|
2177
|
+
const warn = (...params) => {
|
|
2178
|
+
const msg = format(...params);
|
|
2179
|
+
reporter.info(msg, "Warning");
|
|
2180
|
+
};
|
|
2181
|
+
return {
|
|
2182
|
+
log,
|
|
2183
|
+
warn,
|
|
2184
|
+
error
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
async function generateGitIgnore(roots) {
|
|
2188
|
+
const root = (typeof roots === "string" ? [roots].filter((r) => !!r) : roots) || [];
|
|
2189
|
+
if (!root?.length) {
|
|
2190
|
+
const cwd = process.cwd();
|
|
2191
|
+
const repo = await findRepoRoot(cwd) || cwd;
|
|
2192
|
+
root.push(repo);
|
|
2193
|
+
}
|
|
2194
|
+
return new GitIgnore(root?.map((p) => path$1.resolve(p)));
|
|
2195
|
+
}
|
|
2196
|
+
async function useFileLists(fileListFiles, filterFiles) {
|
|
2197
|
+
const files = readFileListFiles(fileListFiles);
|
|
2198
|
+
return pipeAsync(files, opFilter(filterFiles), opFilterAsync(isNotDir));
|
|
2199
|
+
}
|
|
2200
|
+
function createIncludeFileFilterFn(includeGlobPatterns, root, dot) {
|
|
2201
|
+
if (!includeGlobPatterns?.length) return () => true;
|
|
2202
|
+
const patterns = includeGlobPatterns.map((g) => g === "." ? "/**" : g);
|
|
2203
|
+
const options = {
|
|
2204
|
+
root,
|
|
2205
|
+
mode: "include"
|
|
2206
|
+
};
|
|
2207
|
+
if (dot !== void 0) options.dot = dot;
|
|
2208
|
+
const globMatcher = new GlobMatcher(patterns, options);
|
|
2209
|
+
return (file) => globMatcher.match(file);
|
|
2210
|
+
}
|
|
2211
|
+
async function* concatAsyncIterables(...iterables) {
|
|
2212
|
+
for (const iter of iterables) {
|
|
2213
|
+
if (!iter) continue;
|
|
2214
|
+
yield* iter;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
async function writeDictionaryLog() {
|
|
2218
|
+
const fieldsCsv = getEnvironmentVariable("CSPELL_ENABLE_DICTIONARY_LOG_FIELDS") || "time, word, value";
|
|
2219
|
+
const fields = fieldsCsv.split(",").map((f) => f.trim());
|
|
2220
|
+
const header = fields.join(", ") + "\n";
|
|
2221
|
+
const lines = _debug.cacheDictionaryGetLog().filter((d) => d.method === "has").map((d) => fields.map((f) => f in d ? `${d[f]}` : "").join(", "));
|
|
2222
|
+
const data = header + lines.join("\n") + "\n";
|
|
2223
|
+
const filename = getEnvironmentVariable("CSPELL_ENABLE_DICTIONARY_LOG_FILE") || "cspell-dictionary-log.csv";
|
|
2224
|
+
await writeFileOrStream(filename, data);
|
|
2225
|
+
}
|
|
2226
|
+
function globPattern(g) {
|
|
2227
|
+
return typeof g === "string" ? g : g.glob;
|
|
2228
|
+
}
|
|
2229
|
+
var LinterError = class extends Error {
|
|
2230
|
+
constructor(message) {
|
|
2231
|
+
super(message);
|
|
2232
|
+
}
|
|
2233
|
+
toString() {
|
|
2234
|
+
return this.message;
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
|
|
2238
|
+
//#endregion
|
|
2239
|
+
//#region src/lint/LintRequest.ts
|
|
2240
|
+
const defaultContextRange = 20;
|
|
2241
|
+
var LintRequest = class {
|
|
2242
|
+
locale;
|
|
2243
|
+
configFile;
|
|
2244
|
+
excludes;
|
|
2245
|
+
root;
|
|
2246
|
+
showContext;
|
|
2247
|
+
enableGlobDot;
|
|
2248
|
+
fileLists;
|
|
2249
|
+
files;
|
|
2250
|
+
cspellSettingsFromCliOptions;
|
|
2251
|
+
constructor(fileGlobs, options, reporter) {
|
|
2252
|
+
this.fileGlobs = fileGlobs;
|
|
2253
|
+
this.options = options;
|
|
2254
|
+
this.reporter = reporter;
|
|
2255
|
+
this.root = path$1.resolve(options.root || process.cwd());
|
|
2256
|
+
this.configFile = options.config;
|
|
2257
|
+
this.excludes = calcExcludeGlobInfo(this.root, options.exclude);
|
|
2258
|
+
this.locale = options.locale ?? options.local ?? "";
|
|
2259
|
+
this.enableGlobDot = options.dot;
|
|
2260
|
+
this.showContext = Math.max(options.showContext === true ? defaultContextRange : options.showContext ? options.showContext : 0, 0);
|
|
2261
|
+
this.fileLists = (options.fileList ?? options.fileLists) || [];
|
|
2262
|
+
this.files = mergeFiles(options.file, options.files);
|
|
2263
|
+
const noConfigSearch = options.configSearch === false ? true : options.configSearch === true ? false : void 0;
|
|
2264
|
+
const dictionaries = [...(options.disableDictionary ?? []).map((d) => `!${d}`), ...(options.dictionary ?? []).map((d) => `!!${d}`)];
|
|
2265
|
+
const languageSettings = [{
|
|
2266
|
+
languageId: "*",
|
|
2267
|
+
locale: "*",
|
|
2268
|
+
dictionaries
|
|
2269
|
+
}];
|
|
2270
|
+
this.cspellSettingsFromCliOptions = {
|
|
2271
|
+
...noConfigSearch !== void 0 ? { noConfigSearch } : {},
|
|
2272
|
+
...extractUnknownWordsConfig(options),
|
|
2273
|
+
languageSettings
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
function mergeFiles(a, b) {
|
|
2278
|
+
const files = merge(a, b);
|
|
2279
|
+
if (!files) return void 0;
|
|
2280
|
+
return [...new Set(files.flatMap((a$1) => a$1.split("\n").map((a$2) => a$2.trim())).filter((a$1) => !!a$1))];
|
|
2281
|
+
}
|
|
2282
|
+
function merge(a, b) {
|
|
2283
|
+
if (!a) return b;
|
|
2284
|
+
if (!b) return a;
|
|
2285
|
+
return [...a, ...b];
|
|
2286
|
+
}
|
|
2287
|
+
function extractUnknownWordsConfig(options) {
|
|
2288
|
+
const config = {};
|
|
2289
|
+
if (!options.report) return config;
|
|
2290
|
+
switch (options.report) {
|
|
2291
|
+
case "all": {
|
|
2292
|
+
config.unknownWords = unknownWordsChoices.ReportAll;
|
|
2293
|
+
break;
|
|
2294
|
+
}
|
|
2295
|
+
case "simple": {
|
|
2296
|
+
config.unknownWords = unknownWordsChoices.ReportSimple;
|
|
2297
|
+
break;
|
|
2298
|
+
}
|
|
2299
|
+
case "typos": {
|
|
2300
|
+
config.unknownWords = unknownWordsChoices.ReportCommonTypos;
|
|
2301
|
+
break;
|
|
2302
|
+
}
|
|
2303
|
+
case "flagged": {
|
|
2304
|
+
config.unknownWords = unknownWordsChoices.ReportFlagged;
|
|
2305
|
+
break;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
return config;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
//#endregion
|
|
2312
|
+
//#region src/options.ts
|
|
2313
|
+
const ReportChoicesAll = [
|
|
2314
|
+
"all",
|
|
2315
|
+
"simple",
|
|
2316
|
+
"typos",
|
|
2317
|
+
"flagged"
|
|
2318
|
+
];
|
|
2319
|
+
function fixLegacy(opts) {
|
|
2320
|
+
const { local,...rest } = opts;
|
|
2321
|
+
if (local && !rest.locale) rest.locale = local;
|
|
2322
|
+
return rest;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
//#endregion
|
|
2326
|
+
//#region src/repl/index.ts
|
|
2327
|
+
function simpleRepl() {
|
|
2328
|
+
return new SimpleRepl();
|
|
2329
|
+
}
|
|
2330
|
+
var SimpleRepl = class {
|
|
2331
|
+
beforeEach;
|
|
2332
|
+
completer;
|
|
2333
|
+
_history;
|
|
2334
|
+
rl;
|
|
2335
|
+
constructor(prompt = "> ") {
|
|
2336
|
+
this.prompt = prompt;
|
|
2337
|
+
this._history = [];
|
|
2338
|
+
this.rl = readline.createInterface({
|
|
2339
|
+
input: process.stdin,
|
|
2340
|
+
output: process.stdout,
|
|
2341
|
+
prompt,
|
|
2342
|
+
history: this._history,
|
|
2343
|
+
historySize: 100,
|
|
2344
|
+
completer: (line) => this._completer(line)
|
|
2345
|
+
});
|
|
2346
|
+
this.rl.on("history", (h) => (this._history = h, void 0));
|
|
2347
|
+
}
|
|
2348
|
+
question(query) {
|
|
2349
|
+
return new Promise((resolve$1) => {
|
|
2350
|
+
this.rl.question(query, resolve$1);
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
_completer(line) {
|
|
2354
|
+
if (this.completer) return this.completer(line);
|
|
2355
|
+
const hist = this._history.filter((h) => h.startsWith(line));
|
|
2356
|
+
return [hist, line];
|
|
2357
|
+
}
|
|
2358
|
+
get history() {
|
|
2359
|
+
return this._history;
|
|
2360
|
+
}
|
|
2361
|
+
[Symbol.asyncIterator]() {
|
|
2362
|
+
const next = () => {
|
|
2363
|
+
if (this.beforeEach) this.beforeEach();
|
|
2364
|
+
return this.question(this.prompt).then((value) => ({ value })).catch(() => ({
|
|
2365
|
+
done: true,
|
|
2366
|
+
value: void 0
|
|
2367
|
+
}));
|
|
2368
|
+
};
|
|
2369
|
+
return { next };
|
|
2370
|
+
}
|
|
2371
|
+
};
|
|
2372
|
+
|
|
2373
|
+
//#endregion
|
|
2374
|
+
//#region src/dictionaries/listDictionaries.ts
|
|
2375
|
+
const inlineDictionaries = {
|
|
2376
|
+
"[words]": {
|
|
2377
|
+
name: "[words]",
|
|
2378
|
+
description: "List of words to be included in the spell check.",
|
|
2379
|
+
enabled: true
|
|
2380
|
+
},
|
|
2381
|
+
"[flagWords]": {
|
|
2382
|
+
name: "[flagWords]",
|
|
2383
|
+
description: "List of words to be flagged as incorrect.",
|
|
2384
|
+
enabled: true
|
|
2385
|
+
},
|
|
2386
|
+
"[ignoreWords]": {
|
|
2387
|
+
name: "[ignoreWords]",
|
|
2388
|
+
description: "List of words to be ignored in the spell check.",
|
|
2389
|
+
enabled: true
|
|
2390
|
+
},
|
|
2391
|
+
"[suggestWords]": {
|
|
2392
|
+
name: "[suggestWords]",
|
|
2393
|
+
description: "List of spelling suggestions for words.",
|
|
2394
|
+
enabled: true
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
function splitList(list) {
|
|
2398
|
+
if (!list) return [];
|
|
2399
|
+
if (typeof list === "string") return list.split(",").map((s) => s.trim()).filter((s) => !!s);
|
|
2400
|
+
return list.flatMap((s) => splitList(s));
|
|
2401
|
+
}
|
|
2402
|
+
function extractDictionaryLocalesAndFileTypes(config) {
|
|
2403
|
+
const map = /* @__PURE__ */ new Map();
|
|
2404
|
+
function getDict(name$1) {
|
|
2405
|
+
const found = map.get(name$1);
|
|
2406
|
+
if (found) return found;
|
|
2407
|
+
const dict = {
|
|
2408
|
+
locales: /* @__PURE__ */ new Set(),
|
|
2409
|
+
fileTypes: /* @__PURE__ */ new Set()
|
|
2410
|
+
};
|
|
2411
|
+
map.set(name$1, dict);
|
|
2412
|
+
return dict;
|
|
2413
|
+
}
|
|
2414
|
+
const languageSettings = config.languageSettings || [];
|
|
2415
|
+
for (const lang of languageSettings) {
|
|
2416
|
+
const locales = splitList(lang.locale);
|
|
2417
|
+
const fileTypes = splitList(lang.languageId);
|
|
2418
|
+
const dicts = lang.dictionaries || [];
|
|
2419
|
+
for (const dictName of dicts) {
|
|
2420
|
+
const dict = getDict(dictName);
|
|
2421
|
+
for (const locale of locales) if (locale) dict.locales.add(locale);
|
|
2422
|
+
for (const fileType of fileTypes) if (fileType) dict.fileTypes.add(fileType);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
return map;
|
|
2426
|
+
}
|
|
2427
|
+
function extractInlineDictionaries(dict) {
|
|
2428
|
+
const iDict = dict;
|
|
2429
|
+
const inline = [];
|
|
2430
|
+
if (iDict.words?.length) inline.push("[words]");
|
|
2431
|
+
if (iDict.flagWords?.length) inline.push("[flagWords]");
|
|
2432
|
+
if (iDict.ignoreWords?.length) inline.push("[ignoreWords]");
|
|
2433
|
+
if (iDict.suggestWords?.length) inline.push("[suggestWords]");
|
|
2434
|
+
return inline.length ? inline : void 0;
|
|
2435
|
+
}
|
|
2436
|
+
function extractSpecialDictionaries(config) {
|
|
2437
|
+
const specialDictionaries = [];
|
|
2438
|
+
if (config.words?.length) specialDictionaries.push(inlineDictionaries["[words]"]);
|
|
2439
|
+
if (config.flagWords?.length) specialDictionaries.push(inlineDictionaries["[flagWords]"]);
|
|
2440
|
+
if (config.ignoreWords?.length) specialDictionaries.push(inlineDictionaries["[ignoreWords]"]);
|
|
2441
|
+
if (config.suggestWords?.length) specialDictionaries.push(inlineDictionaries["[suggestWords]"]);
|
|
2442
|
+
return specialDictionaries;
|
|
2443
|
+
}
|
|
2444
|
+
async function listDictionaries(options) {
|
|
2445
|
+
const configFile = await readConfig(options.config, void 0);
|
|
2446
|
+
const loadDefault = options.defaultConfiguration ?? configFile.config.loadDefaultConfiguration ?? true;
|
|
2447
|
+
const configBase = mergeSettings(await getDefaultSettings(loadDefault), await getGlobalSettingsAsync(), configFile.config);
|
|
2448
|
+
const useFileType = options.fileType === "text" ? "plaintext" : options.fileType;
|
|
2449
|
+
if (options.locale) configBase.language = options.locale;
|
|
2450
|
+
const config = combineTextAndLanguageSettings(configBase, "", useFileType || configBase.languageId || "plaintext");
|
|
2451
|
+
const dictionaryLocalesAndFileTypes = extractDictionaryLocalesAndFileTypes(config);
|
|
2452
|
+
const enabledDictionaries = new Set(config.dictionaries || []);
|
|
2453
|
+
function toListDictionariesResult(dict) {
|
|
2454
|
+
const inline = extractInlineDictionaries(dict);
|
|
2455
|
+
return {
|
|
2456
|
+
name: dict.name,
|
|
2457
|
+
description: dict.description,
|
|
2458
|
+
enabled: enabledDictionaries.has(dict.name),
|
|
2459
|
+
path: dict.path,
|
|
2460
|
+
inline,
|
|
2461
|
+
locales: [...dictionaryLocalesAndFileTypes.get(dict.name)?.locales || []].sort(),
|
|
2462
|
+
fileTypes: [...dictionaryLocalesAndFileTypes.get(dict.name)?.fileTypes || []].sort()
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
function filterDicts(dict) {
|
|
2466
|
+
if (options.enabled === void 0) return true;
|
|
2467
|
+
return options.enabled === enabledDictionaries.has(dict.name);
|
|
2468
|
+
}
|
|
2469
|
+
const dictionaryDefinitions = (config.dictionaryDefinitions || []).filter(filterDicts);
|
|
2470
|
+
dictionaryDefinitions.sort((a, b) => a.name.localeCompare(b.name));
|
|
2471
|
+
const specialDicts = options.enabled !== false ? extractSpecialDictionaries(config) : [];
|
|
2472
|
+
return [...specialDicts, ...dictionaryDefinitions.map(toListDictionariesResult)];
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
//#endregion
|
|
2476
|
+
//#region src/application.mts
|
|
2477
|
+
function lint(fileGlobs, options, reporter) {
|
|
2478
|
+
options = fixLegacy(options);
|
|
2479
|
+
const unknownWordsConfig = extractUnknownWordsConfig(options);
|
|
2480
|
+
const useOptions = {
|
|
2481
|
+
...options,
|
|
2482
|
+
...unknownWordsConfig
|
|
2483
|
+
};
|
|
2484
|
+
const reporterOptions = {
|
|
2485
|
+
...useOptions,
|
|
2486
|
+
console
|
|
2487
|
+
};
|
|
2488
|
+
const cfg = new LintRequest(fileGlobs, useOptions, finalizeReporter(reporter) ?? getReporter({
|
|
2489
|
+
...useOptions,
|
|
2490
|
+
fileGlobs
|
|
2491
|
+
}, reporterOptions));
|
|
2492
|
+
return runLint(cfg);
|
|
2493
|
+
}
|
|
2494
|
+
async function* trace(words, options) {
|
|
2495
|
+
options = fixLegacy(options);
|
|
2496
|
+
const iWords = options.stdin ? toAsyncIterable(words, readStdin()) : words;
|
|
2497
|
+
const { languageId, locale, allowCompoundWords, ignoreCase } = options;
|
|
2498
|
+
const configFile = await readConfig(options.config, void 0);
|
|
2499
|
+
const loadDefault = options.defaultConfiguration ?? configFile.config.loadDefaultConfiguration ?? true;
|
|
2500
|
+
const additionalSettings = {};
|
|
2501
|
+
if (options.dictionary) additionalSettings.dictionaries = options.dictionary;
|
|
2502
|
+
const config = mergeSettings(await getDefaultSettings(loadDefault), await getGlobalSettingsAsync(), configFile.config, additionalSettings);
|
|
2503
|
+
yield* traceWordsAsync(iWords, config, clean({
|
|
2504
|
+
languageId,
|
|
2505
|
+
locale,
|
|
2506
|
+
ignoreCase,
|
|
2507
|
+
allowCompoundWords
|
|
2508
|
+
}));
|
|
2509
|
+
}
|
|
2510
|
+
async function checkText(filename, options) {
|
|
2511
|
+
options = fixLegacy(options);
|
|
2512
|
+
const fileInfo = await readFileInfo(filename);
|
|
2513
|
+
const { locale, languageId, validateDirectives } = options;
|
|
2514
|
+
const doc = fileInfoToDocument(fileInfo, languageId, locale);
|
|
2515
|
+
const checkOptions = {
|
|
2516
|
+
configFile: options.config,
|
|
2517
|
+
validateDirectives
|
|
2518
|
+
};
|
|
2519
|
+
const settingsFromCommandLine = clean({
|
|
2520
|
+
languageId,
|
|
2521
|
+
language: locale,
|
|
2522
|
+
loadDefaultConfiguration: options.defaultConfiguration
|
|
2523
|
+
});
|
|
2524
|
+
return checkTextDocument(doc, clean({ ...checkOptions }), settingsFromCommandLine);
|
|
2525
|
+
}
|
|
2526
|
+
async function* suggestions(words, options) {
|
|
2527
|
+
options = fixLegacy(options);
|
|
2528
|
+
const configFile = await readConfig(options.config, void 0);
|
|
2529
|
+
let timer;
|
|
2530
|
+
function tapStart() {
|
|
2531
|
+
timer = getTimeMeasurer();
|
|
2532
|
+
}
|
|
2533
|
+
function mapStart(v) {
|
|
2534
|
+
tapStart();
|
|
2535
|
+
return v;
|
|
2536
|
+
}
|
|
2537
|
+
function mapEnd(v) {
|
|
2538
|
+
const elapsedTimeMs = timer?.();
|
|
2539
|
+
return elapsedTimeMs ? {
|
|
2540
|
+
...v,
|
|
2541
|
+
elapsedTimeMs
|
|
2542
|
+
} : v;
|
|
2543
|
+
}
|
|
2544
|
+
const iWords = options.repl ? pipeAsync(toAsyncIterable(words, simpleRepl()), opTap(tapStart)) : options.useStdin ? pipeAsync(toAsyncIterable(words, readStdin()), opTap(tapStart)) : words.map(mapStart);
|
|
2545
|
+
try {
|
|
2546
|
+
const results = pipeAsync(suggestionsForWords(iWords, clean({ ...options }), configFile.config), opMap(mapEnd));
|
|
2547
|
+
yield* results;
|
|
2548
|
+
} catch (e) {
|
|
2549
|
+
if (!(e instanceof SuggestionError)) throw e;
|
|
2550
|
+
console.error(e.message);
|
|
2551
|
+
process.exitCode = 1;
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
function createInit(options) {
|
|
2555
|
+
return configInit(options);
|
|
2556
|
+
}
|
|
2557
|
+
function registerApplicationFeatureFlags() {
|
|
2558
|
+
const ff = getFeatureFlags();
|
|
2559
|
+
const flags = [{
|
|
2560
|
+
name: "timer",
|
|
2561
|
+
description: "Display elapsed time for command."
|
|
2562
|
+
}];
|
|
2563
|
+
flags.forEach((flag) => ff.register(flag));
|
|
2564
|
+
return ff;
|
|
2565
|
+
}
|
|
2566
|
+
function parseApplicationFeatureFlags(flags) {
|
|
2567
|
+
const ff = registerApplicationFeatureFlags();
|
|
2568
|
+
return parseFeatureFlags(flags, ff);
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
//#endregion
|
|
2572
|
+
export { ApplicationError, CheckFailed, DEFAULT_CACHE_LOCATION, IncludeExcludeFlag, ReportChoicesAll, checkText, console, createInit, getReporter, lint, listDictionaries, npmPackage, parseApplicationFeatureFlags, suggestions, trace };
|
|
2573
|
+
//# sourceMappingURL=application-D-NwS6qb.js.map
|