@cspell/cspell-tools 9.5.0 → 9.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/bin.mjs +1 -1
  2. package/cspell-tools.config.schema.json +29 -0
  3. package/dist/app.d.mts +35 -0
  4. package/dist/app.mjs +292 -0
  5. package/dist/build-OgMPaXPZ.mjs +1403 -0
  6. package/dist/index.d.mts +35 -0
  7. package/dist/index.mjs +3 -0
  8. package/package.json +10 -9
  9. package/dist/AppOptions.d.ts +0 -31
  10. package/dist/AppOptions.js +0 -2
  11. package/dist/FeatureFlags/FeatureFlags.d.ts +0 -34
  12. package/dist/FeatureFlags/FeatureFlags.js +0 -94
  13. package/dist/FeatureFlags/index.d.ts +0 -3
  14. package/dist/FeatureFlags/index.js +0 -3
  15. package/dist/FeatureFlags/parseFlags.d.ts +0 -3
  16. package/dist/FeatureFlags/parseFlags.js +0 -20
  17. package/dist/app.d.ts +0 -4
  18. package/dist/app.js +0 -86
  19. package/dist/build.d.ts +0 -12
  20. package/dist/build.js +0 -36
  21. package/dist/compile.d.ts +0 -5
  22. package/dist/compile.js +0 -41
  23. package/dist/compiler/CompileOptions.d.ts +0 -35
  24. package/dist/compiler/CompileOptions.js +0 -2
  25. package/dist/compiler/CompilerDefinitions.d.ts +0 -2
  26. package/dist/compiler/CompilerDefinitions.js +0 -2
  27. package/dist/compiler/Reader.d.ts +0 -3
  28. package/dist/compiler/Reader.js +0 -24
  29. package/dist/compiler/SourceReader.d.ts +0 -28
  30. package/dist/compiler/SourceReader.js +0 -44
  31. package/dist/compiler/WordsCollection.d.ts +0 -20
  32. package/dist/compiler/WordsCollection.js +0 -3
  33. package/dist/compiler/compile.d.ts +0 -26
  34. package/dist/compiler/compile.js +0 -270
  35. package/dist/compiler/createCompileRequest.d.ts +0 -4
  36. package/dist/compiler/createCompileRequest.js +0 -84
  37. package/dist/compiler/createWordsCollection.d.ts +0 -10
  38. package/dist/compiler/createWordsCollection.js +0 -111
  39. package/dist/compiler/index.d.ts +0 -4
  40. package/dist/compiler/index.js +0 -3
  41. package/dist/compiler/legacyLineToWords.d.ts +0 -4
  42. package/dist/compiler/legacyLineToWords.js +0 -20
  43. package/dist/compiler/logWithTimestamp.d.ts +0 -3
  44. package/dist/compiler/logWithTimestamp.js +0 -5
  45. package/dist/compiler/logger.d.ts +0 -4
  46. package/dist/compiler/logger.js +0 -9
  47. package/dist/compiler/readers/ReaderOptions.d.ts +0 -19
  48. package/dist/compiler/readers/ReaderOptions.js +0 -2
  49. package/dist/compiler/readers/readHunspellFiles.d.ts +0 -3
  50. package/dist/compiler/readers/readHunspellFiles.js +0 -57
  51. package/dist/compiler/readers/readTextFile.d.ts +0 -3
  52. package/dist/compiler/readers/readTextFile.js +0 -20
  53. package/dist/compiler/readers/regHunspellFile.d.ts +0 -2
  54. package/dist/compiler/readers/regHunspellFile.js +0 -2
  55. package/dist/compiler/readers/textFileReader.d.ts +0 -3
  56. package/dist/compiler/readers/textFileReader.js +0 -11
  57. package/dist/compiler/readers/trieFileReader.d.ts +0 -3
  58. package/dist/compiler/readers/trieFileReader.js +0 -16
  59. package/dist/compiler/splitCamelCaseIfAllowed.d.ts +0 -5
  60. package/dist/compiler/splitCamelCaseIfAllowed.js +0 -65
  61. package/dist/compiler/streamSourceWordsFromFile.d.ts +0 -3
  62. package/dist/compiler/streamSourceWordsFromFile.js +0 -6
  63. package/dist/compiler/text.d.ts +0 -10
  64. package/dist/compiler/text.js +0 -28
  65. package/dist/compiler/wordListCompiler.d.ts +0 -21
  66. package/dist/compiler/wordListCompiler.js +0 -205
  67. package/dist/compiler/wordListParser.d.ts +0 -61
  68. package/dist/compiler/wordListParser.js +0 -184
  69. package/dist/compiler/writeTextToFile.d.ts +0 -2
  70. package/dist/compiler/writeTextToFile.js +0 -16
  71. package/dist/config/config.d.ts +0 -200
  72. package/dist/config/config.js +0 -2
  73. package/dist/config/configDefaults.d.ts +0 -9
  74. package/dist/config/configDefaults.js +0 -9
  75. package/dist/config/configUtils.d.ts +0 -5
  76. package/dist/config/configUtils.js +0 -14
  77. package/dist/config/index.d.ts +0 -4
  78. package/dist/config/index.js +0 -3
  79. package/dist/config/normalizeConfig.d.ts +0 -8
  80. package/dist/config/normalizeConfig.js +0 -13
  81. package/dist/gzip/compressFiles.d.ts +0 -16
  82. package/dist/gzip/compressFiles.js +0 -42
  83. package/dist/gzip/gzip.d.ts +0 -3
  84. package/dist/gzip/gzip.js +0 -10
  85. package/dist/gzip/index.d.ts +0 -3
  86. package/dist/gzip/index.js +0 -3
  87. package/dist/shasum/checksum.d.ts +0 -7
  88. package/dist/shasum/checksum.js +0 -19
  89. package/dist/shasum/index.d.ts +0 -3
  90. package/dist/shasum/index.js +0 -3
  91. package/dist/shasum/shasum.d.ts +0 -38
  92. package/dist/shasum/shasum.js +0 -150
  93. package/dist/test/TestHelper.d.ts +0 -53
  94. package/dist/test/TestHelper.js +0 -121
  95. package/dist/test/console.d.ts +0 -9
  96. package/dist/test/console.js +0 -34
  97. package/dist/test/escapeRegEx.d.ts +0 -7
  98. package/dist/test/escapeRegEx.js +0 -9
  99. package/dist/test/normalizeOutput.d.ts +0 -3
  100. package/dist/test/normalizeOutput.js +0 -20
  101. package/dist/types.d.ts +0 -7
  102. package/dist/types.js +0 -2
  103. package/dist/util/errors.d.ts +0 -6
  104. package/dist/util/errors.js +0 -11
  105. package/dist/util/globP.d.ts +0 -7
  106. package/dist/util/globP.js +0 -7
  107. package/dist/util/index.d.ts +0 -2
  108. package/dist/util/index.js +0 -4
  109. package/dist/util/textRegex.d.ts +0 -3
  110. package/dist/util/textRegex.js +0 -109
@@ -0,0 +1,1403 @@
1
+ import { promises } from "node:fs";
2
+ import fs, { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ import fsPath, { resolve, sep } from "node:path";
5
+ import * as Trie from "cspell-trie-lib";
6
+ import { COMPOUND_FIX, FORBID_PREFIX, createDictionaryLineParser, decodeTrie, encodeTrieDataToBTrie, parseDictionary } from "cspell-trie-lib";
7
+ import { promisify } from "node:util";
8
+ import { gunzip, gunzipSync, gzip } from "node:zlib";
9
+ import { opAppend, opCombine, opConcatMap, opFilter, opMap, pipe } from "@cspell/cspell-pipe/sync";
10
+ import * as HR from "hunspell-reader";
11
+ import { uniqueFilter } from "hunspell-reader";
12
+ import { cosmiconfig } from "cosmiconfig";
13
+ import { pipeAsync, toArray } from "@cspell/cspell-pipe";
14
+ import { opAwaitAsync, opMapAsync } from "@cspell/cspell-pipe/operators";
15
+ import { createHash } from "node:crypto";
16
+ import { Buffer } from "node:buffer";
17
+
18
+ //#region src/gzip/compressFiles.ts
19
+ const gzip$1 = promisify(gzip);
20
+ const gunzip$1 = promisify(gunzip);
21
+ let OSFlags = /* @__PURE__ */ function(OSFlags$1) {
22
+ OSFlags$1[OSFlags$1["auto"] = -1] = "auto";
23
+ OSFlags$1[OSFlags$1["FAT"] = 0] = "FAT";
24
+ OSFlags$1[OSFlags$1["Unix"] = 3] = "Unix";
25
+ OSFlags$1[OSFlags$1["HPFS"] = 6] = "HPFS";
26
+ OSFlags$1[OSFlags$1["MACOS"] = 7] = "MACOS";
27
+ OSFlags$1[OSFlags$1["NTFS"] = 11] = "NTFS";
28
+ return OSFlags$1;
29
+ }({});
30
+ const OSSystemIDOffset = 9;
31
+ async function compressFile(file, os) {
32
+ if (file.endsWith(".gz")) return file;
33
+ const targetFile = file + ".gz";
34
+ await writeFile(targetFile, await compress(await readFile(file), os));
35
+ return targetFile;
36
+ }
37
+ async function compress(buf, os) {
38
+ return fixOSSystemID(await gzip$1(buf), os);
39
+ }
40
+ /**
41
+ * Compresses the data if it is not already compressed.
42
+ * @param data
43
+ * @returns
44
+ */
45
+ async function compressIfNeeded(data) {
46
+ return isGZipped(data) ? data : compress(data);
47
+ }
48
+ /**
49
+ * Checks if the data is GZipped.
50
+ * @param data
51
+ * @returns true if the data is GZipped
52
+ */
53
+ function isGZipped(data) {
54
+ if (typeof data === "string") return false;
55
+ return data[0] === 31 && data[1] === 139;
56
+ }
57
+ function fixOSSystemID(zBuf, os = OSFlags.Unix) {
58
+ zBuf[OSSystemIDOffset] = os === OSFlags.auto ? zBuf[OSSystemIDOffset] : os;
59
+ return zBuf;
60
+ }
61
+ async function decompress(buf, encoding) {
62
+ const dBuf = gunzip$1(buf);
63
+ if (!encoding) return dBuf;
64
+ return (await dBuf).toString(encoding);
65
+ }
66
+
67
+ //#endregion
68
+ //#region src/util/writeFile.ts
69
+ /**
70
+ * Writes a file to disk and compress as needed.
71
+ *
72
+ * Strings will be encoded as UTF-8.
73
+ *
74
+ * Compression logic:
75
+ * - If the data is a string and the filePath and in `.gz` it is compressed.
76
+ * - If the data is a Uint8Array and the filePath ends in `.gz` the first
77
+ *
78
+ * @param filePath - path to write the file to
79
+ * @param data - data to write
80
+ */
81
+ async function writeFile$1(filePath, data) {
82
+ const buf = typeof data === "string" ? new TextEncoder().encode(data) : data;
83
+ const toWrite = filePath.endsWith(".gz") ? await compressIfNeeded(buf) : buf;
84
+ return fs.writeFile(filePath, toWrite);
85
+ }
86
+
87
+ //#endregion
88
+ //#region src/compiler/readers/regHunspellFile.ts
89
+ const regHunspellFile = /\.(dic|aff)$/i;
90
+
91
+ //#endregion
92
+ //#region src/compiler/readers/readHunspellFiles.ts
93
+ const DEDUPE_SIZE = 1e3;
94
+ async function readHunspellFiles(filename, options) {
95
+ const dicFile = filename.replace(regHunspellFile, ".dic");
96
+ const affFile = filename.replace(regHunspellFile, ".aff");
97
+ const reader = await HR.IterableHunspellReader.createFromFiles(affFile, dicFile);
98
+ reader.maxDepth = options.maxDepth !== void 0 ? options.maxDepth : reader.maxDepth;
99
+ let trie;
100
+ function lines() {
101
+ return pipe(reader.seqAffWords(), _mapAffWords, dedupeAndSort);
102
+ }
103
+ return {
104
+ filename,
105
+ type: "Hunspell",
106
+ size: reader.dic.length,
107
+ get lines() {
108
+ return lines();
109
+ },
110
+ toTrie: () => {
111
+ if (trie) return trie;
112
+ trie = parseDictionary(lines(), {
113
+ stripCaseAndAccents: false,
114
+ optimize: true
115
+ });
116
+ return trie;
117
+ }
118
+ };
119
+ }
120
+ function* dedupeAndSort(words) {
121
+ const buffer = /* @__PURE__ */ new Set();
122
+ function flush() {
123
+ const result = [...buffer].sort();
124
+ buffer.clear();
125
+ return result;
126
+ }
127
+ for (const word of words) {
128
+ buffer.add(word);
129
+ if (buffer.size >= DEDUPE_SIZE) yield* flush();
130
+ }
131
+ yield* flush();
132
+ }
133
+ function* _mapAffWords(affWords) {
134
+ const hasSpecial = /[~+!]/;
135
+ for (const affWord of affWords) {
136
+ const { word, flags } = affWord;
137
+ if (hasSpecial.test(word)) continue;
138
+ const compound = flags.isCompoundForbidden ? "" : COMPOUND_FIX;
139
+ const forbid = flags.isForbiddenWord ? FORBID_PREFIX : "";
140
+ if (!forbid) {
141
+ if (flags.canBeCompoundBegin || flags.isCompoundPermitted) yield word + compound;
142
+ if (flags.canBeCompoundEnd || flags.isCompoundPermitted) yield compound + word;
143
+ if (flags.canBeCompoundMiddle || flags.isCompoundPermitted) yield compound + word + compound;
144
+ if (!flags.isOnlyAllowedInCompound) yield word;
145
+ } else yield forbid + word;
146
+ }
147
+ }
148
+
149
+ //#endregion
150
+ //#region src/compiler/readers/readTextFile.ts
151
+ const isGzFile$1 = /\.gz$/;
152
+ function readTextFile(filename) {
153
+ return readFile$2(filename).then((buffer) => {
154
+ return new TextDecoder("utf-8").decode(buffer);
155
+ });
156
+ }
157
+ function readFile$2(filename) {
158
+ return promises.readFile(filename).then(async (buffer) => isGzFile$1.test(filename) ? decompress(buffer) : buffer);
159
+ }
160
+
161
+ //#endregion
162
+ //#region src/compiler/readers/textFileReader.ts
163
+ async function textFileReader$1(filename) {
164
+ const words = (await readTextFile(filename)).split("\n").map((s) => s.trim());
165
+ let trie;
166
+ return {
167
+ filename,
168
+ type: "TextFile",
169
+ size: words.length,
170
+ lines: words,
171
+ toTrie: () => {
172
+ if (trie) return trie;
173
+ trie = parseDictionary(words, {
174
+ stripCaseAndAccents: false,
175
+ optimize: true
176
+ });
177
+ return trie;
178
+ }
179
+ };
180
+ }
181
+
182
+ //#endregion
183
+ //#region src/compiler/readers/trieFileReader.ts
184
+ async function trieFileReader(filename) {
185
+ return TrieFileReader.create(filename);
186
+ }
187
+ var TrieFileReader = class TrieFileReader {
188
+ #trie;
189
+ type = "Trie";
190
+ filename;
191
+ constructor(filename, trie) {
192
+ this.filename = filename;
193
+ this.#trie = trie;
194
+ }
195
+ get size() {
196
+ return this.#trie.size;
197
+ }
198
+ get lines() {
199
+ return this.#trie.words();
200
+ }
201
+ hasWord(word, caseSensitive) {
202
+ return this.#trie.hasWord(word, caseSensitive);
203
+ }
204
+ toTrie() {
205
+ return this.#trie;
206
+ }
207
+ static async create(filename) {
208
+ const rawBuffer = await readFile(filename);
209
+ return new TrieFileReader(filename, decodeTrie(filename.endsWith(".gz") ? gunzipSync(rawBuffer) : rawBuffer));
210
+ }
211
+ };
212
+
213
+ //#endregion
214
+ //#region src/compiler/Reader.ts
215
+ const readers = [
216
+ {
217
+ test: /\.trie\b/,
218
+ method: trieFileReader
219
+ },
220
+ {
221
+ test: /\.btrie\b/,
222
+ method: trieFileReader
223
+ },
224
+ {
225
+ test: regHunspellFile,
226
+ method: readHunspellFiles
227
+ }
228
+ ];
229
+ function findMatchingReader(filename, options) {
230
+ for (const reader of readers) if (reader.test.test(filename)) return reader.method(filename, options);
231
+ return textFileReader$1(filename);
232
+ }
233
+ async function createReader(filename, options) {
234
+ const baseReader = await findMatchingReader(filename, options);
235
+ return Object.assign(baseReader, { [Symbol.iterator]: () => baseReader.lines[Symbol.iterator]() });
236
+ }
237
+
238
+ //#endregion
239
+ //#region src/compiler/bTrie.ts
240
+ async function generateBTrieFromFile(file, options) {
241
+ console.log(`Processing file: ${file}`);
242
+ const btrie = await createBTrieFromFile(file, options);
243
+ const outFile = bTrieFileName(file, options);
244
+ await mkdir(fsPath.dirname(outFile), { recursive: true });
245
+ await writeFile$1(outFile, btrie);
246
+ console.log(`Written BTrie to: ${outFile}`);
247
+ }
248
+ async function generateBTrieFromFiles(files, options) {
249
+ console.log(`Generating BTrie for ${files.length} file(s).`);
250
+ for (const file of files) await generateBTrieFromFile(file, options);
251
+ }
252
+ function bTrieFileName(inputFilename, options) {
253
+ let filename = inputFilename;
254
+ filename = filename.replace(/\.gz$/i, "");
255
+ filename = filename.replace(/\.(aff|dic|txt|b?trie)$/i, "");
256
+ filename = filename + `.btrie`;
257
+ if (options.compress) filename += ".gz";
258
+ if (options.output) {
259
+ const base = fsPath.basename(filename);
260
+ filename = fsPath.join(fsPath.resolve(options.output), base);
261
+ }
262
+ return filename;
263
+ }
264
+ async function createBTrieFromFile(file, buildOptions) {
265
+ return encodeTrieDataToBTrie((await createReader(file, {})).toTrie().data, buildOptions);
266
+ }
267
+
268
+ //#endregion
269
+ //#region src/bTrie.ts
270
+ function generateBTrie(files, options) {
271
+ return generateBTrieFromFiles(files, options);
272
+ }
273
+
274
+ //#endregion
275
+ //#region src/config/configUtils.ts
276
+ function isFilePath(source) {
277
+ return typeof source === "string";
278
+ }
279
+ function isFileSource(source) {
280
+ if (!source || isFilePath(source)) return false;
281
+ return source.filename !== void 0;
282
+ }
283
+ function isFileListSource(source) {
284
+ if (!source || isFilePath(source)) return false;
285
+ return source.listFile !== void 0;
286
+ }
287
+
288
+ //#endregion
289
+ //#region src/config/normalizeConfig.ts
290
+ function normalizeConfig(cfg) {
291
+ if (!cfg) return cfg;
292
+ const dir = path.dirname(cfg.filepath);
293
+ const config = cfg.config;
294
+ return {
295
+ config: {
296
+ ...config,
297
+ rootDir: path.resolve(dir, config.rootDir || ".")
298
+ },
299
+ filepath: cfg.filepath
300
+ };
301
+ }
302
+
303
+ //#endregion
304
+ //#region src/shasum/checksum.ts
305
+ function calcChecksum(buf, alg = "SHA1") {
306
+ const hash = createHash(alg);
307
+ hash.update(buf);
308
+ return hash.digest("hex");
309
+ }
310
+ async function calcFileChecksum(filename, alg) {
311
+ return calcChecksum(await readFile(filename), alg);
312
+ }
313
+ async function checkFile(checksum, filename, alg) {
314
+ return await calcFileChecksum(filename, alg) === checksum;
315
+ }
316
+
317
+ //#endregion
318
+ //#region src/util/errors.ts
319
+ function toError(err) {
320
+ if (isError(err)) return err;
321
+ return /* @__PURE__ */ new Error(`${err}`);
322
+ }
323
+ function isError(err) {
324
+ if (err instanceof Error) return true;
325
+ return false;
326
+ }
327
+
328
+ //#endregion
329
+ //#region src/util/index.ts
330
+ function isDefined$1(a) {
331
+ return !!a || a !== null && a !== void 0;
332
+ }
333
+
334
+ //#endregion
335
+ //#region src/shasum/shasum.ts
336
+ async function shasumFile(filename, root) {
337
+ try {
338
+ return `${await calcFileChecksum(resolve(root || ".", filename))} ${filename}`;
339
+ } catch {
340
+ return Promise.reject(`shasum: ${filename}: Unable to read file.`);
341
+ }
342
+ }
343
+ /**
344
+ *
345
+ * @param filename - name of checksum file
346
+ * @param files - optional list of files to check
347
+ * @param root - optional root, default cwd.
348
+ */
349
+ async function checkShasumFile(filename, files, root) {
350
+ files = !files ? files : files.length ? files : void 0;
351
+ const shaFiles = await readAndParseShasumFile(filename);
352
+ const filesToCheck = !files ? shaFiles.map(({ filename: filename$1 }) => filename$1) : files;
353
+ const mapNameToChecksum = new Map(shaFiles.map((r) => [normalizeFilename(r.filename), r.checksum]));
354
+ const resolvedRoot = resolve(root || ".");
355
+ const results = await Promise.all(filesToCheck.map(normalizeFilename).map((filename$1) => {
356
+ return tryToCheckFile(filename$1, resolvedRoot, mapNameToChecksum.get(filename$1));
357
+ }));
358
+ return {
359
+ passed: !results.some((v) => !v.passed),
360
+ results
361
+ };
362
+ }
363
+ async function tryToCheckFile(filename, root, checksum) {
364
+ if (!checksum) return {
365
+ filename,
366
+ passed: false,
367
+ error: /* @__PURE__ */ new Error("Missing Checksum.")
368
+ };
369
+ const file = resolve(root, filename);
370
+ try {
371
+ return {
372
+ filename,
373
+ passed: await checkFile(checksum, file)
374
+ };
375
+ } catch {
376
+ return {
377
+ filename,
378
+ passed: false,
379
+ error: /* @__PURE__ */ new Error("Failed to read file.")
380
+ };
381
+ }
382
+ }
383
+ const regLine = /([a-f0-9]{40,}) {2}(.*)/;
384
+ async function readAndParseShasumFile(filename) {
385
+ return parseShasumFile(await readFile(resolve(filename), "utf8"));
386
+ }
387
+ function parseShasumFile(content) {
388
+ return content.split(/\r?\n|\r/g).map(parseLine).filter(isDefined$1);
389
+ function parseLine(line, index) {
390
+ const m = line.match(regLine);
391
+ const lineNumber = index + 1;
392
+ if (!m) {
393
+ if (line.trim()) throw new Error(`Failed to parse line ${lineNumber} of checksum file.`);
394
+ return;
395
+ }
396
+ return {
397
+ checksum: m[1],
398
+ filename: m[2],
399
+ lineNumber
400
+ };
401
+ }
402
+ }
403
+ async function reportChecksumForFiles(files, options) {
404
+ const root = options.root;
405
+ const filesToCheck = await resolveFileList(files, options.listFile);
406
+ let numFailed = 0;
407
+ return {
408
+ report: (await Promise.all(filesToCheck.map((file) => shasumFile(file, root).catch((e) => {
409
+ ++numFailed;
410
+ if (typeof e !== "string") throw e;
411
+ return e;
412
+ })))).join("\n"),
413
+ passed: !numFailed
414
+ };
415
+ }
416
+ async function reportCheckChecksumFile(filename, files, options) {
417
+ const root = options.root;
418
+ const results = (await checkShasumFile(filename, await resolveFileList(files, options.listFile), root)).results;
419
+ const lines = results.map(({ filename: filename$1, passed: passed$1, error }) => `${filename$1}: ${passed$1 ? "OK" : "FAILED"} ${error ? "- " + error.message : ""}`.trim());
420
+ const withErrors = results.filter((a) => !a.passed);
421
+ const passed = !withErrors.length;
422
+ if (!passed) lines.push(`shasum: WARNING: ${withErrors.length} computed checksum${withErrors.length > 1 ? "s" : ""} did NOT match`);
423
+ return {
424
+ report: lines.join("\n"),
425
+ passed
426
+ };
427
+ }
428
+ async function resolveFileList(files, listFile) {
429
+ files = files || [];
430
+ listFile = listFile || [];
431
+ const setOfFiles = new Set(files);
432
+ const pending = listFile.map((filename) => readFile(filename, "utf8"));
433
+ for await (const content of pending) content.split("\n").map((a) => a.trim()).filter((a) => a).forEach((file) => setOfFiles.add(file));
434
+ return [...setOfFiles].map(normalizeFilename);
435
+ }
436
+ async function calcUpdateChecksumForFiles(filename, files, options) {
437
+ const root = options.root || ".";
438
+ const filesToCheck = await resolveFileList(files, options.listFile);
439
+ const currentEntries = (await readAndParseShasumFile(filename).catch((err) => {
440
+ const e = toError(err);
441
+ if (e.code !== "ENOENT") throw e;
442
+ return [];
443
+ })).map((entry) => ({
444
+ ...entry,
445
+ filename: normalizeFilename(entry.filename)
446
+ }));
447
+ const entriesToUpdate = new Set([...filesToCheck, ...currentEntries.map((e) => e.filename)]);
448
+ const mustExist = new Set(filesToCheck);
449
+ const checksumMap = new Map(currentEntries.map(({ filename: filename$1, checksum }) => [filename$1, checksum]));
450
+ for (const file of entriesToUpdate) try {
451
+ const checksum = await calcFileChecksum(resolve(root, file));
452
+ checksumMap.set(file, checksum);
453
+ } catch (e) {
454
+ if (mustExist.has(file) || toError(e).code !== "ENOENT") throw e;
455
+ checksumMap.delete(file);
456
+ }
457
+ return [...checksumMap].map(([filename$1, checksum]) => ({
458
+ filename: filename$1,
459
+ checksum
460
+ })).sort((a, b) => a.filename < b.filename ? -1 : 1).map((e) => `${e.checksum} ${e.filename}`).join("\n") + "\n";
461
+ }
462
+ async function updateChecksumForFiles(filename, files, options) {
463
+ const content = await calcUpdateChecksumForFiles(filename, files, options);
464
+ await writeFile(filename, content);
465
+ return {
466
+ passed: true,
467
+ report: content
468
+ };
469
+ }
470
+ function normalizeFilename(filename) {
471
+ return filename.split(sep).join("/");
472
+ }
473
+
474
+ //#endregion
475
+ //#region src/util/textRegex.ts
476
+ const regExMatchRegExParts = /^\s*\/([\s\S]*?)\/([gimuxy]*)\s*$/;
477
+ function stringToRegExp(pattern, defaultFlags = "", forceFlags = "") {
478
+ if (pattern instanceof RegExp) return pattern;
479
+ try {
480
+ const [, pat, flag] = [...pattern.match(regExMatchRegExParts) || [
481
+ "",
482
+ pattern.trim(),
483
+ defaultFlags
484
+ ], forceFlags];
485
+ if (pat) {
486
+ const regPattern = flag.includes("x") ? removeVerboseFromRegExp(pat) : pat;
487
+ const flags = [...new Set(forceFlags + flag)].join("").replaceAll(/[^gimuy]/g, "");
488
+ return new RegExp(regPattern, flags);
489
+ }
490
+ } catch {}
491
+ }
492
+ const SPACES = {
493
+ " ": true,
494
+ "\n": true,
495
+ "\r": true,
496
+ " ": true
497
+ };
498
+ /**
499
+ * Remove all whitespace and comments from a regexp string. The format follows Pythons Verbose.
500
+ * Note: this is a best attempt. Special cases for comments: `#` and spaces should be proceeded with a `\`
501
+ *
502
+ * All space must be proceeded by a `\` or in a character class `[]`
503
+ *
504
+ * @param pattern - the pattern to clean
505
+ */
506
+ function removeVerboseFromRegExp(pattern) {
507
+ function escape(acc) {
508
+ if (pattern[acc.idx] !== "\\") return void 0;
509
+ const next = pattern[++acc.idx];
510
+ acc.idx++;
511
+ if (next === "#") {
512
+ acc.result += "#";
513
+ return acc;
514
+ }
515
+ if (!(next in SPACES)) {
516
+ acc.result += "\\" + next;
517
+ return acc;
518
+ }
519
+ acc.result += next;
520
+ if (next === "\r" && pattern[acc.idx] === "\n") {
521
+ acc.result += "\n";
522
+ acc.idx++;
523
+ }
524
+ return acc;
525
+ }
526
+ function braces(acc) {
527
+ const char = pattern[acc.idx];
528
+ if (char !== "[") return void 0;
529
+ acc.result += char;
530
+ acc.idx++;
531
+ let escCount = 0;
532
+ while (acc.idx < pattern.length) {
533
+ const char$1 = pattern[acc.idx];
534
+ acc.result += char$1;
535
+ acc.idx++;
536
+ if (char$1 === "]" && !(escCount & 1)) break;
537
+ escCount = char$1 === "\\" ? escCount + 1 : 0;
538
+ }
539
+ return acc;
540
+ }
541
+ function spaces(acc) {
542
+ if (!(pattern[acc.idx] in SPACES)) return void 0;
543
+ acc.idx++;
544
+ return acc;
545
+ }
546
+ function comments(acc) {
547
+ if (pattern[acc.idx] !== "#") return void 0;
548
+ while (acc.idx < pattern.length && pattern[acc.idx] !== "\n") acc.idx++;
549
+ return acc;
550
+ }
551
+ function copy(acc) {
552
+ const char = pattern[acc.idx++];
553
+ acc.result += char;
554
+ return acc;
555
+ }
556
+ const reducers = [
557
+ escape,
558
+ braces,
559
+ spaces,
560
+ comments,
561
+ copy
562
+ ];
563
+ const result = {
564
+ idx: 0,
565
+ result: ""
566
+ };
567
+ while (result.idx < pattern.length) for (const r of reducers) if (r(result)) break;
568
+ return result.result;
569
+ }
570
+
571
+ //#endregion
572
+ //#region src/compiler/WordsCollection.ts
573
+ const defaultAllowedSplitWords = Object.freeze({
574
+ size: 0,
575
+ has: () => true
576
+ });
577
+ const defaultExcludeWordsCollection = Object.freeze({
578
+ size: 0,
579
+ has: () => false
580
+ });
581
+
582
+ //#endregion
583
+ //#region src/compiler/createWordsCollection.ts
584
+ var AllowedSplitWordsImpl = class {
585
+ collection;
586
+ size;
587
+ constructor(collection) {
588
+ this.collection = collection;
589
+ this.size = collection.size;
590
+ }
591
+ has(word, caseSensitive) {
592
+ return !this.size || this.collection.has(word, caseSensitive);
593
+ }
594
+ };
595
+ async function createAllowedSplitWordsFromFiles(files) {
596
+ if (!files || !files.length) return defaultAllowedSplitWords;
597
+ return new AllowedSplitWordsImpl(await createWordsCollectionFromFiles(files));
598
+ }
599
+ function buildHasFn(dict) {
600
+ function has(word, caseSensitive) {
601
+ const r = dict.hasWord(word, true);
602
+ if (r || caseSensitive) return r;
603
+ const lc = word.toLowerCase();
604
+ if (lc === word) return false;
605
+ return dict.hasWord(lc, true);
606
+ }
607
+ return has;
608
+ }
609
+ async function readFile$1(filename) {
610
+ return await createReader(filename, {});
611
+ }
612
+ function readersToCollection(readers$1) {
613
+ const dictReaders = readers$1.filter(isDictionaryReader).map(dictReaderToCollection);
614
+ const nonDictCollection = lineReadersToCollection(readers$1.filter((a) => !isDictionaryReader(a)));
615
+ const collections = [...dictReaders, nonDictCollection];
616
+ return {
617
+ size: collections.reduce((s, a) => s + a.size, 0),
618
+ has: (word, caseSensitive) => collections.some((a) => a.has(word, caseSensitive))
619
+ };
620
+ }
621
+ const cache = /* @__PURE__ */ new WeakMap();
622
+ async function createWordsCollectionFromFiles(files) {
623
+ files = toFilePathArray(files);
624
+ const cached = cache.get(files);
625
+ if (cached) return cached;
626
+ const collection = readersToCollection(await Promise.all(files.map((file) => readFile$1(file))));
627
+ cache.set(files, collection);
628
+ return collection;
629
+ }
630
+ function isDictionaryReader(reader) {
631
+ return "hasWord" in reader && !!reader.hasWord;
632
+ }
633
+ function dictReaderToCollection(reader) {
634
+ return {
635
+ size: reader.size,
636
+ has: buildHasFn(reader)
637
+ };
638
+ }
639
+ function lineReadersToCollection(readers$1) {
640
+ function* words() {
641
+ for (const reader of readers$1) yield* reader.lines;
642
+ }
643
+ const dict = parseDictionary(words(), { stripCaseAndAccents: false });
644
+ return {
645
+ size: dict.size,
646
+ has: buildHasFn(dict)
647
+ };
648
+ }
649
+ function toFilePathArray(filePathOrArray) {
650
+ return Array.isArray(filePathOrArray) ? filePathOrArray : [filePathOrArray];
651
+ }
652
+
653
+ //#endregion
654
+ //#region src/compiler/logWithTimestamp.ts
655
+ const logWithTimestamp = (message, ...optionalParams) => {
656
+ console.log(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}`, ...optionalParams);
657
+ };
658
+
659
+ //#endregion
660
+ //#region src/config/configDefaults.ts
661
+ const defaultCompileSourceOptions = {
662
+ maxDepth: void 0,
663
+ split: false,
664
+ keepRawCase: false,
665
+ allowedSplitWords: void 0,
666
+ storeSplitWordsAsCompounds: false,
667
+ minCompoundLength: 4
668
+ };
669
+
670
+ //#endregion
671
+ //#region src/compiler/text.ts
672
+ const regExSplitWords = /([\p{Ll}])([\p{Lu}])/gu;
673
+ const regExSplitWords2 = /(\p{Lu})(\p{Lu}\p{Ll})/gu;
674
+ const regExpIsLetter = /^\p{L}\p{M}{0,2}$/u;
675
+ /**
676
+ * Split camelCase words into an array of strings.
677
+ */
678
+ function splitCamelCaseWord(word) {
679
+ return word.replaceAll(regExSplitWords, "$1|$2").replaceAll(regExSplitWords2, "$1|$2").replaceAll(/[\d_]+/g, "|").split("|").filter((a) => !!a);
680
+ }
681
+ function isSingleLetter(c) {
682
+ return regExpIsLetter.test(c);
683
+ }
684
+
685
+ //#endregion
686
+ //#region src/compiler/splitCamelCaseIfAllowed.ts
687
+ const regExpSpaceOrDash = /[- ]+/g;
688
+ const regExpIsNumber = /^\d+$/;
689
+ function splitCamelCaseIfAllowed(word, allowedWords, keepCase, compoundPrefix, minCompoundLength$1) {
690
+ const split = [...splitCamelCase(word)];
691
+ if (split.length === 1) return adjustCases(split, allowedWords, keepCase);
692
+ if (split.some((w) => isUnknown(w, allowedWords))) return [word];
693
+ const wordIndexes = calcWordIndex(word, split);
694
+ const adjusted = adjustCases(split, allowedWords, keepCase);
695
+ return !compoundPrefix ? adjusted : adjusted.map((w, i) => {
696
+ const { px, sx } = wordIndexes[i];
697
+ const canCompound = w.length >= minCompoundLength$1;
698
+ const lc = w.toLowerCase();
699
+ const p = canCompound && isSingleLetter(px) ? compoundPrefix : "";
700
+ const s = canCompound && isSingleLetter(sx) ? compoundPrefix : "";
701
+ if (lc.length < 4 || allowedWords.has(w, true)) return p + w + s;
702
+ return p + lc + s;
703
+ });
704
+ }
705
+ function adjustCases(words, allowedWords, keepCase) {
706
+ return words.map((w) => adjustCase(w, allowedWords, keepCase));
707
+ }
708
+ function adjustCase(word, allowedWords, keepCase) {
709
+ const lc = word.toLowerCase();
710
+ if (!allowedWords.has(lc, true)) return word;
711
+ if (lc === word) return word;
712
+ if (word.slice(1).toLowerCase() === word.slice(1)) return lc;
713
+ if (!keepCase && word.toUpperCase() === word) return word.toLowerCase();
714
+ return word;
715
+ }
716
+ function isUnknown(word, allowedWords) {
717
+ if (word === "ERROR") return !allowedWords.has(word, false);
718
+ return !allowedWords.has(word, false);
719
+ }
720
+ function splitCamelCase(word) {
721
+ const splitWords = splitCamelCaseWord(word).filter((word$1) => !regExpIsNumber.test(word$1));
722
+ if (splitWords.length > 1 && regExpSpaceOrDash.test(word)) return splitWords.flatMap((w) => w.split(regExpSpaceOrDash));
723
+ return splitWords;
724
+ }
725
+ function calcWordIndex(word, words) {
726
+ let i = 0;
727
+ return words.map((w) => {
728
+ const j = word.indexOf(w, i);
729
+ const k = j + w.length;
730
+ const wIndex = {
731
+ word: w,
732
+ i: j,
733
+ px: word[j - 1] || "",
734
+ sx: word[k] || ""
735
+ };
736
+ i = k;
737
+ return wIndex;
738
+ });
739
+ }
740
+
741
+ //#endregion
742
+ //#region src/compiler/legacyLineToWords.ts
743
+ const regNonWord = /[^\p{L}\p{M}' _\d]+/giu;
744
+ const regExpRepeatChars = /(.)\1{5}/i;
745
+ const minCompoundLength = defaultCompileSourceOptions.minCompoundLength;
746
+ function legacyLineToWords(line, keepCase, allowedSplitWords) {
747
+ const wordGroups = line.replaceAll(regNonWord, "|").split("|");
748
+ const _minCompoundLength = minCompoundLength;
749
+ return pipe(wordGroups, opConcatMap((a) => a.split(regExpSpaceOrDash)), opConcatMap((a) => splitCamelCaseIfAllowed(a, allowedSplitWords, keepCase, "", _minCompoundLength)), opMap((a) => a.trim()), opFilter((a) => !!a), opFilter((s) => !regExpRepeatChars.test(s)));
750
+ }
751
+
752
+ //#endregion
753
+ //#region src/compiler/wordListParser.ts
754
+ function normalizeTargetWords(options) {
755
+ const lineParser = createDictionaryLineParser({
756
+ stripCaseAndAccents: options.generateNonStrict,
757
+ stripCaseAndAccentsOnForbidden: true,
758
+ keepOptionalCompoundCharacter: true
759
+ });
760
+ return opCombine(...[
761
+ opFilter((a) => !!a),
762
+ lineParser,
763
+ options.sort ? createInlineBufferedSort(1e4) : void 0,
764
+ opFilter(uniqueFilter(1e4)),
765
+ options.filter ? opFilter(options.filter) : void 0
766
+ ].filter(isDefined));
767
+ }
768
+ function isDefined(v) {
769
+ return v !== void 0;
770
+ }
771
+ function createInlineBufferedSort(bufferSize = 1e3) {
772
+ function* inlineBufferedSort(lines) {
773
+ const buffer = [];
774
+ for (const line of lines) {
775
+ buffer.push(line);
776
+ if (buffer.length >= bufferSize) {
777
+ buffer.sort();
778
+ yield* buffer;
779
+ buffer.length = 0;
780
+ }
781
+ }
782
+ buffer.sort();
783
+ yield* buffer;
784
+ }
785
+ return inlineBufferedSort;
786
+ }
787
+ const commentCharacter = "#";
788
+ const _defaultOptions = {
789
+ keepCase: true,
790
+ legacy: false,
791
+ split: false,
792
+ splitKeepBoth: false,
793
+ allowedSplitWords: {
794
+ has: () => true,
795
+ size: 0
796
+ },
797
+ storeSplitWordsAsCompounds: defaultCompileSourceOptions.storeSplitWordsAsCompounds,
798
+ minCompoundLength: defaultCompileSourceOptions.minCompoundLength
799
+ };
800
+ const defaultParseDictionaryOptions = Object.freeze(_defaultOptions);
801
+ const cSpellToolDirective = "cspell-tools:";
802
+ /**
803
+ * Normalizes a dictionary words based upon prefix / suffixes.
804
+ * Case insensitive versions are also generated.
805
+ * @param options - defines prefixes used when parsing lines.
806
+ * @returns words that have been normalized.
807
+ */
808
+ function createParseFileLineMapper(options) {
809
+ const _options = options || _defaultOptions;
810
+ const { splitKeepBoth = _defaultOptions.splitKeepBoth, allowedSplitWords = _defaultOptions.allowedSplitWords, storeSplitWordsAsCompounds, minCompoundLength: minCompoundLength$1 = _defaultOptions.minCompoundLength } = _options;
811
+ let { legacy = _defaultOptions.legacy } = _options;
812
+ let { split = _defaultOptions.split, keepCase = legacy ? false : _defaultOptions.keepCase } = _options;
813
+ const compoundFix = storeSplitWordsAsCompounds ? "+" : "";
814
+ function isString(line) {
815
+ return typeof line === "string";
816
+ }
817
+ function trim(line) {
818
+ return line.trim();
819
+ }
820
+ function removeComments(line) {
821
+ const idx = line.indexOf(commentCharacter);
822
+ if (idx < 0) return line;
823
+ const idxDirective = line.indexOf(cSpellToolDirective, idx);
824
+ if (idxDirective >= 0) {
825
+ const flags = line.slice(idxDirective).split(/[\s,;]/g).map((s) => s.trim()).filter((a) => !!a);
826
+ for (const flag of flags) switch (flag) {
827
+ case "split":
828
+ split = true;
829
+ break;
830
+ case "no-split":
831
+ split = false;
832
+ break;
833
+ case "keep-case":
834
+ keepCase = true;
835
+ legacy = false;
836
+ break;
837
+ case "no-keep-case":
838
+ keepCase = false;
839
+ break;
840
+ case "legacy":
841
+ keepCase = false;
842
+ legacy = true;
843
+ break;
844
+ }
845
+ }
846
+ return line.slice(0, idx).trim();
847
+ }
848
+ function filterEmptyLines(line) {
849
+ return !!line;
850
+ }
851
+ const regNonWordOrDigit = /[^\p{L}\p{M}'\w-]+/giu;
852
+ function splitLine(line) {
853
+ line = line.replace(/#.*/, "");
854
+ line = line.trim();
855
+ line = line.replaceAll(/\bU\+[0-9A-F]{4}\b/gi, "|");
856
+ line = line.replaceAll(/\\U[0-9A-F]{4}/gi, "|");
857
+ line = line.replaceAll(regNonWordOrDigit, "|");
858
+ line = line.replaceAll(/'(?=\|)/g, "");
859
+ line = line.replace(/'$/, "");
860
+ line = line.replaceAll(/(?<=\|)'/g, "");
861
+ line = line.replace(/^'/, "");
862
+ line = line.replaceAll(/\s*\|\s*/g, "|");
863
+ line = line.replaceAll(/[|]+/g, "|");
864
+ line = line.replace(/^\|/, "");
865
+ line = line.replace(/\|$/, "");
866
+ return line.split("|").map((a) => a.trim()).filter((a) => !!a).filter((a) => !/^[0-9_-]+$/.test(a)).filter((a) => !/^0[xo][0-9A-F]+$/i.test(a));
867
+ }
868
+ function splitWordIntoWords(word) {
869
+ return splitCamelCaseIfAllowed(word, allowedSplitWords, keepCase, compoundFix, minCompoundLength$1);
870
+ }
871
+ function* splitWords(lines) {
872
+ for (const line of lines) {
873
+ if (legacy) {
874
+ yield* legacyLineToWords(line, keepCase, allowedSplitWords);
875
+ continue;
876
+ }
877
+ if (split) {
878
+ const words = splitLine(line);
879
+ yield* !allowedSplitWords.size ? words : words.flatMap((word) => splitWordIntoWords(word));
880
+ if (!splitKeepBoth) continue;
881
+ }
882
+ yield line.replaceAll(/["]/g, "");
883
+ }
884
+ }
885
+ function* unique(lines) {
886
+ const known = /* @__PURE__ */ new Set();
887
+ for (const line of lines) {
888
+ if (known.has(line)) continue;
889
+ known.add(line);
890
+ yield line;
891
+ }
892
+ }
893
+ function* splitLines$1(paragraphs) {
894
+ for (const paragraph of paragraphs) yield* paragraph.split("\n");
895
+ }
896
+ return opCombine(opFilter(isString), splitLines$1, opMap(removeComments), splitWords, opMap(trim), opFilter(filterEmptyLines), unique);
897
+ }
898
+ /**
899
+ * Normalizes a dictionary words based upon prefix / suffixes.
900
+ * Case insensitive versions are also generated.
901
+ * @param lines - one word per line
902
+ * @param _options - defines prefixes used when parsing lines.
903
+ * @returns words that have been normalized.
904
+ */
905
+ function parseFileLines(lines, options) {
906
+ return createParseFileLineMapper(options)(typeof lines === "string" ? [lines] : lines);
907
+ }
908
+
909
+ //#endregion
910
+ //#region src/compiler/SourceReader.ts
911
+ async function createSourceReader(filename, options) {
912
+ const reader = await createReader(filename, options);
913
+ if (reader.type !== "TextFile") return {
914
+ words: splitLines(reader.lines, options),
915
+ get size() {
916
+ return reader.size;
917
+ }
918
+ };
919
+ return textFileReader(reader, options);
920
+ }
921
+ function splitLines(lines, options) {
922
+ if (!options.splitWords) return lines;
923
+ function* split() {
924
+ const regNonWordOrDigit = /[^\p{L}\p{M}'\w-]+/giu;
925
+ for (const line of lines) yield* line.split(regNonWordOrDigit);
926
+ }
927
+ return split();
928
+ }
929
+ async function textFileReader(reader, options) {
930
+ const { legacy, splitWords: split, allowedSplitWords, storeSplitWordsAsCompounds, minCompoundLength: minCompoundLength$1 } = options;
931
+ const parseOptions = {
932
+ legacy,
933
+ split,
934
+ splitKeepBoth: void 0,
935
+ keepCase: void 0,
936
+ allowedSplitWords,
937
+ storeSplitWordsAsCompounds,
938
+ minCompoundLength: minCompoundLength$1
939
+ };
940
+ const words = [...parseFileLines(reader.lines, parseOptions)];
941
+ return {
942
+ size: words.length,
943
+ words
944
+ };
945
+ }
946
+
947
+ //#endregion
948
+ //#region src/compiler/streamSourceWordsFromFile.ts
949
+ async function streamSourceWordsFromFile(filename, options) {
950
+ return (await createSourceReader(filename, options)).words;
951
+ }
952
+
953
+ //#endregion
954
+ //#region src/compiler/logger.ts
955
+ const defaultLogger = console.log;
956
+ let log = defaultLogger;
957
+ function setLogger(logger) {
958
+ log = logger ?? defaultLogger;
959
+ }
960
+ function getLogger() {
961
+ return log;
962
+ }
963
+
964
+ //#endregion
965
+ //#region src/compiler/writeTextToFile.ts
966
+ const isGzFile = /\.gz$/;
967
+ async function writeTextToFile(filename, data, useGzCompress) {
968
+ const dataStr = typeof data === "string" ? data : Array.isArray(data) ? data.join("") : [...data].join("");
969
+ const hasGzExt = isGzFile.test(filename);
970
+ const useGz = useGzCompress ?? hasGzExt;
971
+ if (useGz && !hasGzExt) filename += ".gz";
972
+ const buf = Buffer.from(dataStr, "utf8");
973
+ const buffer = useGz ? await compress(buf) : buf;
974
+ await promises.writeFile(filename, buffer);
975
+ }
976
+
977
+ //#endregion
978
+ //#region src/compiler/wordListCompiler.ts
979
+ const mkdirp = async (p) => {
980
+ await mkdir(p, { recursive: true });
981
+ };
982
+ const wordListHeader = `
983
+ # cspell-tools: keep-case no-split`;
984
+ const wordListHeaderLines = wordListHeader.split("\n").map((a) => a.trim());
985
+ function createWordListCompiler(options) {
986
+ return (lines) => {
987
+ const finalLines = normalize(lines, options);
988
+ const directivesLines = (options.dictionaryDirectives ?? []).map((a) => `# cspell-dictionary: ${a}`);
989
+ return pipe([
990
+ ...wordListHeaderLines,
991
+ ...directivesLines,
992
+ ""
993
+ ], opAppend(finalLines), opMap((a) => a + "\n"));
994
+ };
995
+ }
996
+ function normalize(lines, options) {
997
+ const filter = normalizeTargetWords(options);
998
+ const iter = pipe(options.removeDuplicates ? removeDuplicates(lines) : lines, filter);
999
+ if (!options.sort) return iter;
1000
+ return [...new Set(iter)].sort();
1001
+ }
1002
+ function stripCompoundAFix(word) {
1003
+ return word.replaceAll("*", "").replaceAll("+", "");
1004
+ }
1005
+ function* removeDuplicates(words) {
1006
+ const wordSet = new Set(words);
1007
+ const wordForms = /* @__PURE__ */ new Map();
1008
+ for (const word of wordSet) {
1009
+ const lc = stripCompoundAFix(word.toLowerCase());
1010
+ const forms = wordForms.get(lc) ?? [];
1011
+ forms.push(word);
1012
+ wordForms.set(lc, forms);
1013
+ }
1014
+ for (const forms of wordForms.values()) {
1015
+ if (forms.length <= 1) {
1016
+ yield* forms;
1017
+ continue;
1018
+ }
1019
+ const mForms = removeDuplicateForms(forms);
1020
+ if (mForms.size <= 1) {
1021
+ for (const form of mForms.values()) yield* form;
1022
+ continue;
1023
+ }
1024
+ const lc = [...mForms.keys()][0].toLowerCase();
1025
+ const lcForm = mForms.get(lc);
1026
+ if (!lcForm) {
1027
+ for (const form of mForms.values()) yield* form;
1028
+ continue;
1029
+ }
1030
+ mForms.delete(lc);
1031
+ const sLcForms = new Set(lcForm);
1032
+ yield* lcForm;
1033
+ if (sLcForms.has("*" + lc + "*")) continue;
1034
+ for (const forms$1 of mForms.values()) for (const form of forms$1) {
1035
+ if (sLcForms.has(form.toLowerCase())) continue;
1036
+ yield form;
1037
+ }
1038
+ }
1039
+ }
1040
+ /**
1041
+ * solo
1042
+ * optional_prefix*
1043
+ * optional_suffix*
1044
+ * required_prefix+
1045
+ * required_suffix+
1046
+ */
1047
+ var Flags = /* @__PURE__ */ function(Flags$1) {
1048
+ Flags$1[Flags$1["base"] = 0] = "base";
1049
+ Flags$1[Flags$1["none"] = 1] = "none";
1050
+ Flags$1[Flags$1["both"] = 2] = "both";
1051
+ Flags$1[Flags$1["pfx"] = 4] = "pfx";
1052
+ Flags$1[Flags$1["sfx"] = 8] = "sfx";
1053
+ Flags$1[Flags$1["all"] = 15] = "all";
1054
+ return Flags$1;
1055
+ }(Flags || {});
1056
+ function applyFlags(word, flags) {
1057
+ if (flags === Flags.none) return [word];
1058
+ if (flags === Flags.all) return ["*" + word + "*"];
1059
+ if (flags === Flags.both) return ["+" + word + "+"];
1060
+ if (flags === Flags.pfx) return [word + "+"];
1061
+ if (flags === Flags.sfx) return ["+" + word];
1062
+ if (flags === (Flags.none | Flags.sfx)) return ["*" + word];
1063
+ if (flags === (Flags.none | Flags.pfx)) return [word + "*"];
1064
+ if (flags === (Flags.none | Flags.pfx | Flags.sfx)) return [word + "*", "*" + word];
1065
+ if (flags === (Flags.none | Flags.both)) return ["*" + word + "*"];
1066
+ if (flags === (Flags.none | Flags.both | Flags.sfx)) return [word, "+" + word + "*"];
1067
+ if (flags === (Flags.none | Flags.both | Flags.pfx)) return [word, "*" + word + "+"];
1068
+ if (flags === (Flags.both | Flags.pfx)) return ["*" + word + "+"];
1069
+ if (flags === (Flags.both | Flags.sfx)) return ["+" + word + "*"];
1070
+ if (flags === (Flags.both | Flags.pfx | Flags.sfx)) return ["+" + word + "*", "*" + word + "+"];
1071
+ return ["+" + word, word + "+"];
1072
+ }
1073
+ function removeDuplicateForms(forms) {
1074
+ function flags(word, flag = 0) {
1075
+ const canBePrefix = word.endsWith("*");
1076
+ const mustBePrefix = !canBePrefix && word.endsWith("+");
1077
+ const isPrefix = canBePrefix || mustBePrefix;
1078
+ const canBeSuffix = word.startsWith("*");
1079
+ const mustBeSuffix = !canBeSuffix && word.startsWith("+");
1080
+ const isSuffix = canBeSuffix || mustBeSuffix;
1081
+ if (canBePrefix && canBeSuffix) return flag | Flags.all;
1082
+ if (mustBePrefix && mustBeSuffix) return flag | Flags.both;
1083
+ if (!isPrefix && !isSuffix) return flag | Flags.none;
1084
+ flag |= isPrefix && !isSuffix ? Flags.pfx : 0;
1085
+ flag |= isSuffix && !isPrefix ? Flags.sfx : 0;
1086
+ flag |= canBePrefix && !mustBeSuffix ? Flags.none : 0;
1087
+ flag |= canBeSuffix && !mustBePrefix ? Flags.none : 0;
1088
+ return flag;
1089
+ }
1090
+ const m = /* @__PURE__ */ new Map();
1091
+ for (const form of forms) {
1092
+ const k = stripCompoundAFix(form);
1093
+ m.set(k, flags(form, m.get(k)));
1094
+ }
1095
+ return new Map([...m.entries()].map(([form, flag]) => {
1096
+ return [form, applyFlags(form, flag)];
1097
+ }));
1098
+ }
1099
+ async function createTargetFile(destFilename, seq, compress$1) {
1100
+ const rel$1 = path.relative(process.cwd(), destFilename).replaceAll(path.sep, "/");
1101
+ getLogger()(`Writing to file ${rel$1}${compress$1 ? ".gz" : ""}`);
1102
+ await mkdirp(path.dirname(destFilename));
1103
+ await writeTextToFile(destFilename, seq, compress$1);
1104
+ }
1105
+ function createTrieCompiler(options) {
1106
+ return (words) => {
1107
+ const log$1 = getLogger();
1108
+ log$1("Reading Words into Trie");
1109
+ const base = options.base ?? 32;
1110
+ const version = options.trie4 ? 4 : options.trie3 ? 3 : 1;
1111
+ const root = Trie.buildTrie(words).root;
1112
+ log$1("Reduce duplicate word endings");
1113
+ const trie = Trie.consolidate(root);
1114
+ log$1("Trie compilation complete");
1115
+ return Trie.serializeTrie(trie, {
1116
+ base,
1117
+ comment: "Built by cspell-tools.",
1118
+ version
1119
+ });
1120
+ };
1121
+ }
1122
+
1123
+ //#endregion
1124
+ //#region src/compiler/compile.ts
1125
+ async function compile(request, options) {
1126
+ const { targets } = request;
1127
+ const rootDir = path.resolve(request.rootDir || ".");
1128
+ const cwd = options?.cwd;
1129
+ const targetOptions = {
1130
+ sort: request.sort,
1131
+ generateNonStrict: request.generateNonStrict,
1132
+ removeDuplicates: request.removeDuplicates
1133
+ };
1134
+ const conditional = options?.conditionalBuild || false;
1135
+ const checksumFile = resolveChecksumFile(request.checksumFile || conditional, rootDir);
1136
+ const dictionaryDirectives = request.dictionaryDirectives;
1137
+ const dependencies = /* @__PURE__ */ new Set();
1138
+ for (const target of targets) {
1139
+ if (!(options?.filter?.(target) ?? true)) continue;
1140
+ (await compileTarget({
1141
+ ...targetOptions,
1142
+ ...target
1143
+ }, request, {
1144
+ rootDir,
1145
+ cwd,
1146
+ conditional,
1147
+ checksumFile,
1148
+ dictionaryDirectives
1149
+ })).forEach((dep) => dependencies.add(dep));
1150
+ }
1151
+ if (checksumFile && dependencies.size) {
1152
+ logWithTimestamp("%s", `Update checksum: ${checksumFile}`);
1153
+ await updateChecksumForFiles(checksumFile, [...dependencies], { root: path.dirname(checksumFile) });
1154
+ }
1155
+ logWithTimestamp(`Complete.`);
1156
+ }
1157
+ function resolveChecksumFile(checksumFile, root) {
1158
+ const cFilename = typeof checksumFile === "string" && checksumFile || checksumFile && "./checksum.txt" || void 0;
1159
+ return cFilename && path.resolve(root, cFilename);
1160
+ }
1161
+ async function compileTarget(target, options, compileOptions) {
1162
+ logWithTimestamp(`Start compile: ${target.name}`);
1163
+ const { rootDir, cwd, checksumFile, conditional } = compileOptions;
1164
+ const { format, sources, trieBase, sort = true, generateNonStrict = false, excludeWordsFrom = [], excludeWordsNotFoundIn = [], excludeWordsMatchingRegex } = target;
1165
+ let targetDirectory = target.targetDirectory ?? cwd ?? process.cwd();
1166
+ targetDirectory = targetDirectory.replace("${cwd}", cwd ?? process.cwd());
1167
+ targetDirectory = path.resolve(rootDir, targetDirectory);
1168
+ const dictionaryDirectives = target.dictionaryDirectives ?? compileOptions.dictionaryDirectives;
1169
+ const removeDuplicates$1 = target.removeDuplicates ?? false;
1170
+ const excludeFromFilter = await createExcludeFilter(excludeWordsFrom);
1171
+ const includeFromFilter = await createIncludeFilter(excludeWordsNotFoundIn);
1172
+ const excludeRegexFilter = createExcludeRegexFilter(excludeWordsMatchingRegex);
1173
+ const excludeFilter = (word) => {
1174
+ return excludeFromFilter(word) && includeFromFilter(word) && excludeRegexFilter(word);
1175
+ };
1176
+ const name = normalizeTargetName(target.name);
1177
+ const useTrie = format.startsWith("trie");
1178
+ const generateCompressed = target.compress ?? false;
1179
+ const generateUncompressed = target.keepUncompressed ?? false;
1180
+ const genSet = /* @__PURE__ */ new Set();
1181
+ genSet.add(generateCompressed);
1182
+ if (generateUncompressed) genSet.add(false);
1183
+ const filename = resolveTarget(name, targetDirectory, useTrie);
1184
+ const filesToProcess = await toArray(pipeAsync(readSourceList(sources, rootDir), opMapAsync((src) => readFileSource(src, options)), opAwaitAsync()));
1185
+ const normalizer = normalizeTargetWords({
1186
+ sort: useTrie || sort,
1187
+ generateNonStrict,
1188
+ filter: excludeFilter,
1189
+ dictionaryDirectives
1190
+ });
1191
+ const checksumRoot = checksumFile && path.dirname(checksumFile) || rootDir;
1192
+ const deps = [...calculateDependencies(filename + (generateCompressed ? ".gz" : ""), filesToProcess, [...excludeWordsFrom, ...excludeWordsNotFoundIn], checksumRoot)];
1193
+ if (conditional && checksumFile) {
1194
+ if ((await checkShasumFile(checksumFile, deps, checksumRoot).catch(() => void 0))?.passed) {
1195
+ logWithTimestamp(`Skip ${target.name}, nothing changed.`);
1196
+ return [];
1197
+ }
1198
+ }
1199
+ async function action(words, dst) {
1200
+ const data = iterableToString(pipe(words, normalizer, useTrie ? createTrieCompiler({
1201
+ base: trieBase,
1202
+ trie3: format === "trie3",
1203
+ trie4: format === "trie4"
1204
+ }) : createWordListCompiler({
1205
+ sort,
1206
+ generateNonStrict,
1207
+ dictionaryDirectives,
1208
+ removeDuplicates: removeDuplicates$1
1209
+ })));
1210
+ for (const compress$1 of genSet) await createTargetFile(dst, data, compress$1);
1211
+ }
1212
+ await processFiles({
1213
+ action,
1214
+ filesToProcess,
1215
+ mergeTarget: filename
1216
+ });
1217
+ if (target.bTrie) await generateBTrieFromFile(filename, {
1218
+ compress: true,
1219
+ optimize: true,
1220
+ useStringTable: true,
1221
+ ...typeof target.bTrie === "object" ? target.bTrie : {}
1222
+ });
1223
+ logWithTimestamp(`Done compile: ${target.name}`);
1224
+ return deps;
1225
+ }
1226
+ function calculateDependencies(targetFile, filesToProcess, extraDependencyFiles, rootDir) {
1227
+ const dependencies = /* @__PURE__ */ new Set();
1228
+ addDependency(targetFile);
1229
+ extraDependencyFiles?.forEach((f) => addDependency(f));
1230
+ filesToProcess.forEach((f) => addDependency(f.src));
1231
+ return dependencies;
1232
+ function addDependency(filename) {
1233
+ const rel$1 = path.relative(rootDir, filename);
1234
+ dependencies.add(rel$1);
1235
+ dependencies.add(rel$1.replace(/\.aff$/, ".dic"));
1236
+ dependencies.add(rel$1.replace(/\.dic$/, ".aff"));
1237
+ }
1238
+ }
1239
+ function rel(filePath) {
1240
+ return path.relative(process.cwd(), filePath);
1241
+ }
1242
+ async function processFiles({ action, filesToProcess, mergeTarget }) {
1243
+ const toProcess = filesToProcess;
1244
+ const dst = mergeTarget;
1245
+ await action(pipe(toProcess, opMap((ftp) => {
1246
+ const { src } = ftp;
1247
+ logWithTimestamp("Process \"%s\" to \"%s\"", rel(src), rel(dst));
1248
+ return ftp;
1249
+ }), opConcatMap(function* (ftp) {
1250
+ yield* ftp.words;
1251
+ logWithTimestamp("Done processing %s", rel(ftp.src));
1252
+ }), logProgress()), dst);
1253
+ logWithTimestamp("Done \"%s\"", rel(dst));
1254
+ }
1255
+ function resolveTarget(name, directory, useTrie) {
1256
+ const filename = name + (useTrie && ".trie" || ".txt");
1257
+ return path.resolve(directory, filename);
1258
+ }
1259
+ function readSourceList(sources, rootDir) {
1260
+ async function* mapSrc() {
1261
+ for (const src of sources) {
1262
+ if (isFilePath(src)) {
1263
+ yield { filename: path.resolve(rootDir, src) };
1264
+ continue;
1265
+ }
1266
+ if (isFileSource(src)) {
1267
+ yield {
1268
+ ...src,
1269
+ filename: path.resolve(rootDir, src.filename)
1270
+ };
1271
+ continue;
1272
+ }
1273
+ if (isFileListSource(src)) {
1274
+ const { listFile, ...rest } = src;
1275
+ const absListFile = path.resolve(rootDir, listFile);
1276
+ const listFileDir = path.dirname(absListFile);
1277
+ const files = await readFileList(absListFile);
1278
+ for (const filename of files) yield {
1279
+ ...rest,
1280
+ filename: path.resolve(listFileDir, filename)
1281
+ };
1282
+ }
1283
+ }
1284
+ }
1285
+ return mapSrc();
1286
+ }
1287
+ async function readFileList(fileList) {
1288
+ return (await readTextFile(fileList)).split("\n").map((a) => a.trim()).filter((a) => !!a);
1289
+ }
1290
+ async function readFileSource(fileSource, sourceOptions) {
1291
+ const { filename, keepRawCase = sourceOptions.keepRawCase || false, split = sourceOptions.split || false, maxDepth, storeSplitWordsAsCompounds, minCompoundLength: minCompoundLength$1 } = fileSource;
1292
+ const legacy = split === "legacy";
1293
+ const readerOptions = {
1294
+ maxDepth,
1295
+ legacy,
1296
+ splitWords: legacy ? false : split,
1297
+ keepCase: keepRawCase,
1298
+ allowedSplitWords: await createAllowedSplitWordsFromFiles(fileSource.allowedSplitWords || sourceOptions.allowedSplitWords),
1299
+ storeSplitWordsAsCompounds,
1300
+ minCompoundLength: minCompoundLength$1
1301
+ };
1302
+ logWithTimestamp(`Reading ${path.basename(filename)}`);
1303
+ const stream = await streamSourceWordsFromFile(filename, readerOptions);
1304
+ logWithTimestamp(`Done reading ${path.basename(filename)}`);
1305
+ return {
1306
+ src: filename,
1307
+ words: stream
1308
+ };
1309
+ }
1310
+ function normalizeTargetName(name) {
1311
+ return name.replace(/((\.txt|\.dic|\.aff|\.trie)(\.gz)?)?$/, "").replaceAll(/[^\p{L}\p{M}.\w\\/-]/gu, "_");
1312
+ }
1313
+ function logProgress(freq = 1e5) {
1314
+ function* logProgress$1(iter) {
1315
+ const _freq = freq;
1316
+ let count = 0;
1317
+ for (const v of iter) {
1318
+ ++count;
1319
+ if (!(count % _freq)) logWithTimestamp("Progress: Words Processed - %s", count.toLocaleString());
1320
+ yield v;
1321
+ }
1322
+ }
1323
+ return logProgress$1;
1324
+ }
1325
+ /**
1326
+ * @param excludeWordsFrom - List of files to read words from.
1327
+ * @returns a function that returns true if the word is not in the exclude list.
1328
+ */
1329
+ async function createExcludeFilter(excludeWordsFrom) {
1330
+ if (!excludeWordsFrom || !excludeWordsFrom.length) return () => true;
1331
+ const excludeWords = await createWordsCollectionFromFiles(excludeWordsFrom);
1332
+ return (word) => !excludeWords.has(word, word.toUpperCase() !== word);
1333
+ }
1334
+ /**
1335
+ * @param includeWordsFrom - List of files to read words from.
1336
+ * @returns a function that returns true if the word is in the include list.
1337
+ */
1338
+ async function createIncludeFilter(includeWordsFrom) {
1339
+ if (!includeWordsFrom || !includeWordsFrom.length) return () => true;
1340
+ const excludeWords = await createWordsCollectionFromFiles(includeWordsFrom);
1341
+ return (word) => excludeWords.has(word, word.toUpperCase() !== word);
1342
+ }
1343
+ /**
1344
+ * @param excludeWordsMatchingRegex - List of regex patterns to exclude.
1345
+ * @returns a function that returns true if the word does not match any of the regex patterns.
1346
+ */
1347
+ function createExcludeRegexFilter(excludeWordsMatchingRegex) {
1348
+ if (!excludeWordsMatchingRegex || !excludeWordsMatchingRegex.length) return () => true;
1349
+ const regexes = excludeWordsMatchingRegex.map((a) => stringToRegExp(a)).filter((a, i) => {
1350
+ if (a) return true;
1351
+ console.warn("Invalid regex: \"%s\"", excludeWordsMatchingRegex[i]);
1352
+ return false;
1353
+ });
1354
+ return (word) => !regexes.some((r) => r.test(word));
1355
+ }
1356
+ function iterableToString(iter) {
1357
+ return Array.isArray(iter) ? iter.join("") : [...iter].join("");
1358
+ }
1359
+
1360
+ //#endregion
1361
+ //#region src/build.ts
1362
+ const moduleName = "cspell-tools";
1363
+ const searchPlaces = [
1364
+ `${moduleName}.config.json`,
1365
+ `${moduleName}.config.yaml`,
1366
+ `${moduleName}.config.yml`,
1367
+ "package.json"
1368
+ ];
1369
+ async function build(targets, options) {
1370
+ const allowedTargets = new Set(targets || []);
1371
+ function filter(target) {
1372
+ return !allowedTargets.size || allowedTargets.has(target.name);
1373
+ }
1374
+ const searchDir = path.resolve(options.root || options.cwd || ".");
1375
+ const explorer = cosmiconfig(moduleName, {
1376
+ searchPlaces,
1377
+ stopDir: searchDir,
1378
+ transform: normalizeConfig
1379
+ });
1380
+ const config = await (options.config ? explorer.load(options.config) : explorer.search(searchDir));
1381
+ if (!config?.config) {
1382
+ console.error("root: %s", options.root);
1383
+ throw "cspell-tools.config not found.";
1384
+ }
1385
+ const configDir = path.dirname(config.filepath);
1386
+ await compile(normalizeRequest(config.config, path.resolve(configDir, options.root || configDir)), {
1387
+ filter,
1388
+ cwd: options.cwd,
1389
+ conditionalBuild: options.conditional || false
1390
+ });
1391
+ }
1392
+ function normalizeRequest(buildInfo, root) {
1393
+ const { rootDir = root, targets = [], checksumFile } = buildInfo;
1394
+ return {
1395
+ rootDir: path.resolve(rootDir),
1396
+ targets,
1397
+ checksumFile
1398
+ };
1399
+ }
1400
+
1401
+ //#endregion
1402
+ export { reportCheckChecksumFile as a, toError as c, compressFile as d, logWithTimestamp as i, generateBTrie as l, compile as n, reportChecksumForFiles as o, setLogger as r, updateChecksumForFiles as s, build as t, OSFlags as u };
1403
+ //# sourceMappingURL=build-OgMPaXPZ.mjs.map