cspell-lib 9.3.2 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,13 +6,13 @@
6
6
 
7
7
  <!--- @@inject: ../../static/sponsor.md --->
8
8
 
9
- - [![GitHub Sponsors](https://img.shields.io/badge/-black?style=social&logo=githubsponsors&label=GitHub%20Sponsor%3A%20Street%20Side%20Software)](https://github.com/sponsors/streetsidesoftware)
10
- - [![PayPal](https://img.shields.io/badge/-black?style=social&logo=paypal&label=PayPal%20Donate%3A%20Street%20Side%20Software)](https://www.paypal.com/donate/?hosted_button_id=26LNBP2Q6MKCY)
11
- - [![Open Collective](https://img.shields.io/badge/-black?style=social&logo=opencollective&label=Open%20Collective%3A%20CSpell)](https://opencollective.com/cspell)
9
+ [![GitHub Sponsors](https://img.shields.io/badge/-Street_Side_Software-black?style=for-the-badge&logo=githubsponsors&label=GitHub%20Sponsor%3A)](https://github.com/sponsors/streetsidesoftware)
12
10
 
13
- <!---
14
- - [![Patreon](https://img.shields.io/badge/-black?style=social&logo=patreon&label=Patreon%3A%20Street%20Side%20Software)](https://patreon.com/streetsidesoftware)
15
- --->
11
+ [![Open Collective](https://img.shields.io/badge/-CSpell-black?style=for-the-badge&logo=opencollective&label=Open%20Collective%3A)](https://opencollective.com/cspell)
12
+
13
+ [![Street Side Software](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fstreetsidesoftware%2Fcspell%2Frefs%2Fheads%2Fmain%2Fstatic%2Fcspell-badge.json)](https://streetsidesoftware.com/sponsor/)
14
+
15
+ [![PayPal](https://img.shields.io/badge/-Street_Side_Software-black?style=for-the-badge&logo=paypal&label=PayPal%20Donate%3A)](https://www.paypal.com/donate/?hosted_button_id=26LNBP2Q6MKCY)
16
16
 
17
17
  <!--- @@inject-end: ../../static/sponsor.md --->
18
18
 
@@ -2,6 +2,9 @@ import { isError } from '../../util/errors.js';
2
2
  export class ImportError extends Error {
3
3
  cause;
4
4
  constructor(msg, cause) {
5
+ if (isError(cause)) {
6
+ msg += `\n ${cause.message}`;
7
+ }
5
8
  super(msg);
6
9
  this.cause = isError(cause) ? cause : undefined;
7
10
  }
@@ -1,11 +1,13 @@
1
1
  import type { CSpellSettings, CSpellUserSettings, ImportFileRef } from '@cspell/cspell-types';
2
- import { CSpellConfigFile, CSpellConfigFileReaderWriter, ICSpellConfigFile } from 'cspell-config-lib';
2
+ import type { CSpellConfigFileReaderWriter, ICSpellConfigFile } from 'cspell-config-lib';
3
+ import { CSpellConfigFile, CSpellConfigFileWithErrors } from 'cspell-config-lib';
3
4
  import type { VFileSystem } from '../../../fileSystem.js';
4
- import { AutoResolveCache, CacheStats } from '../../../util/AutoResolve.js';
5
+ import type { CacheStats } from '../../../util/AutoResolve.js';
6
+ import { AutoResolveCache } from '../../../util/AutoResolve.js';
5
7
  import { FileResolver } from '../../../util/resolveFile.js';
6
8
  import type { LoaderResult } from '../pnpLoader.js';
7
9
  import { ConfigSearch } from './configSearch.js';
8
- import { StopSearchAt } from './defaultConfigLoader.js';
10
+ import type { StopSearchAt } from './defaultConfigLoader.js';
9
11
  import { normalizeCacheSettings } from './normalizeRawSettings.js';
10
12
  import type { PnPSettingsOptional } from './PnPSettings.js';
11
13
  import type { CSpellSettingsI, CSpellSettingsWST } from './types.js';
@@ -123,7 +125,7 @@ export declare class ConfigLoader implements IConfigLoader {
123
125
  readSettingsAsync(filename: string | URL, relativeTo?: string | URL, pnpSettings?: PnPSettingsOptional): Promise<CSpellSettingsI>;
124
126
  readConfigFile(filenameOrURL: string | URL, relativeTo?: string | URL): Promise<CSpellConfigFile | Error>;
125
127
  searchForConfigFileLocation(searchFrom: URL | string | undefined, stopSearchAt?: URL[] | undefined): Promise<URL | undefined>;
126
- searchForConfigFile(searchFrom: URL | string | undefined, stopSearchAt?: URL[]): Promise<CSpellConfigFile | undefined>;
128
+ searchForConfigFile(searchFrom: URL | string | undefined, stopSearchAt?: URL[]): Promise<CSpellConfigFile | CSpellConfigFileWithErrors | undefined>;
127
129
  /**
128
130
  *
129
131
  * @param searchFrom the directory / file URL to start searching from.
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { CSpellConfigFile } from 'cspell-config-lib';
4
+ import { CSpellConfigFile, CSpellConfigFileWithErrors } from 'cspell-config-lib';
5
5
  import { createReaderWriter } from 'cspell-config-lib';
6
6
  import { isUrlLike, toFileURL } from 'cspell-io';
7
7
  import { URI, Utils as UriUtils } from 'vscode-uri';
@@ -15,13 +15,13 @@ import { FileResolver } from '../../../util/resolveFile.js';
15
15
  import { envToTemplateVars } from '../../../util/templates.js';
16
16
  import { addTrailingSlash, cwdURL, resolveFileWithURL, toFileDirURL, toFilePathOrHref, toFileUrl, windowsDriveLetterToUpper, } from '../../../util/url.js';
17
17
  import { configSettingsFileVersion0_1, configSettingsFileVersion0_2, currentSettingsFileVersion, defaultConfigFileModuleRef, ENV_CSPELL_GLOB_ROOT, } from '../../constants.js';
18
- import { getMergeStats, mergeSettings } from '../../CSpellSettingsServer.js';
18
+ import { getMergeStats, mergeSettings, toInternalSettings } from '../../CSpellSettingsServer.js';
19
19
  import { getGlobalConfig } from '../../GlobalSettings.js';
20
20
  import { ImportError } from '../ImportError.js';
21
21
  import { pnpLoader } from '../pnpLoader.js';
22
22
  import { searchPlaces } from './configLocations.js';
23
23
  import { ConfigSearch } from './configSearch.js';
24
- import { configToRawSettings } from './configToRawSettings.js';
24
+ import { configErrorToRawSettings, configToRawSettings } from './configToRawSettings.js';
25
25
  import { defaultSettings } from './defaultSettings.js';
26
26
  import { normalizeCacheSettings, normalizeDictionaryDefs, normalizeGitignoreRoot, normalizeImport, normalizeLanguageSettings, normalizeOverrides, normalizeReporters, normalizeSettingsGlobs, } from './normalizeRawSettings.js';
27
27
  import { defaultPnPSettings, normalizePnPSettings } from './PnPSettings.js';
@@ -31,7 +31,7 @@ export const sectionCSpell = 'cSpell';
31
31
  export const defaultFileName = 'cspell.json';
32
32
  let defaultConfigLoader = undefined;
33
33
  const defaultExtensions = ['.json', '.yaml', '.yml', '.jsonc', '.toml'];
34
- const defaultJsExtensions = ['.js', '.cjs', '.mjs', '.ts', '.mts'];
34
+ const defaultJsExtensions = ['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts'];
35
35
  export const defaultExtensionsAll = [...defaultExtensions, ...defaultJsExtensions];
36
36
  const trustedSearch = new Map([
37
37
  ['*', defaultExtensions],
@@ -110,7 +110,10 @@ export class ConfigLoader {
110
110
  if (!location)
111
111
  return undefined;
112
112
  const file = await this.readConfigFile(location);
113
- return file instanceof Error ? undefined : file;
113
+ if (file instanceof Error) {
114
+ return new CSpellConfigFileWithErrors(location, configErrorToRawSettings(file, location), file);
115
+ }
116
+ return file;
114
117
  }
115
118
  /**
116
119
  *
@@ -123,6 +126,9 @@ export class ConfigLoader {
123
126
  const configFile = await this.searchForConfigFile(searchFrom, stopAt);
124
127
  if (!configFile)
125
128
  return undefined;
129
+ if (configFile instanceof CSpellConfigFileWithErrors) {
130
+ return toInternalSettings(configFile.settings);
131
+ }
126
132
  return this.mergeConfigFileWithImports(configFile, options);
127
133
  }
128
134
  getGlobalSettings() {
@@ -9,6 +9,7 @@ export const supportedExtensions = [
9
9
  '.toml',
10
10
  '.mts',
11
11
  '.ts',
12
+ '.cts',
12
13
  ];
13
14
  /**
14
15
  * Logic of the locations:
@@ -27,10 +27,12 @@ function fixPath(def) {
27
27
  return def;
28
28
  }
29
29
  const newPath = fixDicPath(def.path, def.file);
30
+ const newBTriePath = def.btrie ? fixDicPath(def.btrie, def.file) : undefined;
30
31
  return {
31
32
  ...def,
32
33
  file: undefined,
33
34
  path: newPath,
35
+ btrie: newBTriePath,
34
36
  };
35
37
  }
36
38
  function fixDicPath(defPath, defFile) {
@@ -103,15 +105,18 @@ class _DictionaryDefinitionInternalWithSource {
103
105
  this.__source = sourceURL.href;
104
106
  // this bit of assignment is to have the compiler help use if any new fields are added.
105
107
  const defAll = def;
106
- const { path: relPath = '', file = '', addWords, description, dictionaryInformation, type, repMap, noSuggest, ignoreForbiddenWords, scope, supportNonStrictSearches, useCompounds, } = defAll;
108
+ const { path: relPath = '', file = '', btrie, addWords, description, dictionaryInformation, type, repMap, noSuggest, ignoreForbiddenWords, scope, supportNonStrictSearches, useCompounds, } = defAll;
107
109
  const defaultPath = sourceURL;
108
110
  const filePath = fixDicPath(relPath, file);
109
111
  const name = determineName(filePath, def);
110
112
  const resolvedPath = toFilePathOrHref(resolveRelativeTo(filePath, defaultPath));
113
+ let bTriePath = btrie ? fixDicPath(btrie, file) : undefined;
114
+ bTriePath = bTriePath ? toFilePathOrHref(resolveRelativeTo(bTriePath, defaultPath)) : undefined;
111
115
  const ddi = {
112
116
  name,
113
117
  file: undefined,
114
118
  path: resolvedPath,
119
+ btrie: bTriePath,
115
120
  addWords,
116
121
  description,
117
122
  dictionaryInformation,
@@ -5,14 +5,14 @@ export const regExHRef = /\bhref\s*=\s*".*?"/gi;
5
5
  export const regExMatchCommonHexFormats = /(?:#[0-9a-f]{3,8})|(?:0x[0-9a-f]+)|(?:\\u[0-9a-f]{4})|(?:\\x\{[0-9a-f]{4}\})/gi;
6
6
  export const regExCommitHash = /\b(?![a-f]+\b)(?:0x)?[0-9a-f]{7,}\b/gi; // Match Commit Hashes that contain at least one digit.
7
7
  export const regExCommitHashLink = /\[[0-9a-f]{7,}\]/gi; // Match Commit Hash Markdown link: [abcdef0].
8
- export const regExCStyleHexValue = /\b0x[0-9a-f_]+\b/gi;
8
+ export const regExCStyleHexValue = /\b0x[0-9a-f_]+n?\b/gi;
9
9
  export const regExCSSHexValue = /#[0-9a-f]{3,8}\b/gi;
10
10
  export const regExUUID = /\b[0-9a-fx]{8}-[0-9a-fx]{4}-[0-9a-fx]{4}-[0-9a-fx]{4}-[0-9a-fx]{12}\b/gi; // x - represents placeholder values
11
11
  export const regExUnicodeRef = /\bU\+[0-9a-f]{4,5}(?:-[0-9a-f]{4,5})?/gi;
12
12
  export const regExSpellingGuardBlock = /(\bc?spell(?:-?checker)?::?)\s*disable(?!-line|-next)\b(?!-)[\s\S]*?((?:\1\s*enable\b)|$)/gi;
13
13
  export const regExSpellingGuardNext = /\bc?spell(?:-?checker)?::?\s*disable-next\b.*\s\s?.*/gi;
14
14
  export const regExSpellingGuardLine = /^.*\bc?spell(?:-?checker)?::?\s*disable-line\b.*/gim;
15
- export const regExIgnoreSpellingDirectives = /\bc?spell(?:-?checker)?::?\s*ignoreRegExp.*/gim;
15
+ export const regExIgnoreSpellingDirectives = /\bc?spell(?:-?checker)?::?\s*(ignoreRegExp|word|ignore).*/gim;
16
16
  export const regExPublicKey = /-{5}BEGIN\s+((?:RSA\s+)?PUBLIC\s+KEY)[\w=+\-/=\\\s]+?END\s+\1-{5}/g;
17
17
  export const regExCert = /-{5}BEGIN\s+(CERTIFICATE|(?:RSA\s+)?(?:PRIVATE|PUBLIC)\s+KEY)[\w=+\-/=\\\s]+?END\s+\1-{5}/g;
18
18
  export const regExSshRSA = /ssh-rsa\s+[a-z0-9/+]{28,}={0,3}(?![a-z0-9/+=])/gi;
@@ -1,4 +1,4 @@
1
- import { CSpellSettings } from '@cspell/cspell-types';
1
+ import type { CSpellSettings } from '@cspell/cspell-types';
2
2
  export declare const legacyLocationDir: string | undefined;
3
3
  export declare const cspellGlobalLocationDir: string;
4
4
  export declare const defaultConfigFileName = "cspell.json";
@@ -78,7 +78,7 @@ export async function removePathsFromGlobalImports(paths) {
78
78
  return c === Path.sep || c === Path.posix.sep;
79
79
  }
80
80
  function matchFilename(pathToRemove) {
81
- return Path.dirname(pathToRemove) != '.'
81
+ return Path.dirname(pathToRemove) !== '.'
82
82
  ? ({ filename }) => compareFilenames(filename, pathToRemove)
83
83
  : () => false;
84
84
  }
@@ -1,4 +1,4 @@
1
- import { CacheStats } from '../util/AutoResolve.js';
1
+ import type { CacheStats } from '../util/AutoResolve.js';
2
2
  interface IDisposable {
3
3
  dispose(): void;
4
4
  }
@@ -1,4 +1,4 @@
1
- import { CacheStats } from '../util/AutoResolve.js';
1
+ import type { CacheStats } from '../util/AutoResolve.js';
2
2
  /**
3
3
  * Merges two lists and removes duplicates. Order is NOT preserved.
4
4
  */
@@ -3,6 +3,7 @@ import { StrongWeakMap } from '@cspell/strong-weak-map';
3
3
  import { createFailedToLoadDictionary, createInlineSpellingDictionary, createSpellingDictionary, createSpellingDictionaryFromTrieFile, } from 'cspell-dictionary';
4
4
  import { compareStats, toFileURL, urlBasename } from 'cspell-io';
5
5
  import { isDictionaryDefinitionInlineInternal, isDictionaryFileDefinitionInternal, } from '../../Models/CSpellSettingsInternalDef.js';
6
+ import { measurePerfFn } from '../../perf/index.js';
6
7
  import { AutoResolveWeakCache, AutoResolveWeakWeakCache } from '../../util/AutoResolve.js';
7
8
  import { toError } from '../../util/errors.js';
8
9
  import { SimpleCache } from '../../util/simpleCache.js';
@@ -42,7 +43,7 @@ export class DictionaryLoader {
42
43
  if (entry) {
43
44
  return entry.pending.then(([dictionary]) => dictionary);
44
45
  }
45
- const loadedEntry = this.loadEntry(def.path, def);
46
+ const loadedEntry = this.loadEntry(def.btrie || def.path, def);
46
47
  this.setCacheEntry(key, loadedEntry, def);
47
48
  this.keepAliveCache.set(def, loadedEntry);
48
49
  return loadedEntry.pending.then(([dictionary]) => dictionary);
@@ -98,8 +99,8 @@ export class DictionaryLoader {
98
99
  loadEntry(fileOrUri, options, now = Date.now()) {
99
100
  const url = toFileURL(fileOrUri);
100
101
  options = this.normalizeOptions(url, options);
101
- const pDictionary = load(this.reader, toFileURL(fileOrUri), options).catch((e) => createFailedToLoadDictionary(options.name, fileOrUri, new SpellingDictionaryLoadError(url.href, options, e, 'failed to load'), options));
102
- const pStat = this.getStat(fileOrUri);
102
+ const pDictionary = load(this.reader, url, options).catch((e) => createFailedToLoadDictionary(options.name, fileOrUri, new SpellingDictionaryLoadError(url.href, options, e, 'failed to load'), options));
103
+ const pStat = this.getStat(url);
103
104
  const pending = Promise.all([pDictionary, pStat]);
104
105
  const sig = now + Math.random();
105
106
  const entry = {
@@ -153,12 +154,19 @@ export class DictionaryLoader {
153
154
  }
154
155
  }
155
156
  function toReader(fs) {
156
- async function readFile(url) {
157
- return (await fs.readFile(url)).getText();
157
+ function readResource(url) {
158
+ return fs.readFile(url);
159
+ }
160
+ async function readText(url) {
161
+ return (await readResource(url)).getText();
162
+ }
163
+ async function read(url) {
164
+ return (await readResource(url)).getBytes();
158
165
  }
159
166
  return {
160
- read: readFile,
161
- readLines: async (filename) => toLines(await readFile(filename)),
167
+ read,
168
+ readText,
169
+ readLines: async (filename) => toLines(await readText(filename)),
162
170
  };
163
171
  }
164
172
  const importantOptionKeys = ['name', 'noSuggest', 'useCompounds', 'type'];
@@ -170,17 +178,17 @@ function determineType(uri, opts) {
170
178
  const t = (opts.type && opts.type in loaders && opts.type) || 'S';
171
179
  const defLoaderType = t;
172
180
  const defType = uri.pathname.endsWith('.trie.gz') ? 'T' : defLoaderType;
173
- const regTrieTest = /\.trie\b/i;
181
+ const regTrieTest = /\.b?trie\b/i;
174
182
  return regTrieTest.test(uri.pathname) ? 'T' : defType;
175
183
  }
176
- function load(reader, uri, options) {
184
+ async function load(reader, uri, options) {
177
185
  const type = determineType(uri, options);
178
186
  const loader = loaders[type] || loaders.default;
179
187
  return loader(reader, uri, options);
180
188
  }
181
189
  async function legacyWordList(reader, filename, options) {
182
190
  const lines = await reader.readLines(filename);
183
- return _legacyWordListSync(lines, filename, options);
191
+ return measurePerfFn('legacyWords', () => _legacyWordListSync(lines, filename, options));
184
192
  }
185
193
  function _legacyWordListSync(lines, filename, options) {
186
194
  const words = pipe(lines,
@@ -188,11 +196,11 @@ function _legacyWordListSync(lines, filename, options) {
188
196
  opMap((line) => line.replaceAll(/#.*/g, '')),
189
197
  // Split on everything else
190
198
  opConcatMap((line) => line.split(/[^\w\p{L}\p{M}'’]+/gu)), opFilter((word) => !!word));
191
- return createSpellingDictionary(words, options.name, filename.toString(), options);
199
+ return createSpellingDictionary(words, options.name, filename.toString(), options, true);
192
200
  }
193
201
  async function wordsPerLineWordList(reader, filename, options) {
194
202
  const lines = await reader.readLines(filename);
195
- return _wordsPerLineWordList(lines, filename.toString(), options);
203
+ return measurePerfFn('wordsPerLineWordList', () => _wordsPerLineWordList(lines, filename.toString(), options));
196
204
  }
197
205
  function _wordsPerLineWordList(lines, filename, options) {
198
206
  const words = pipe(lines,
@@ -200,15 +208,15 @@ function _wordsPerLineWordList(lines, filename, options) {
200
208
  opMap((line) => line.replaceAll(/#.*/g, '')),
201
209
  // Split on everything else
202
210
  opConcatMap((line) => line.split(/\s+/gu)), opFilter((word) => !!word));
203
- return createSpellingDictionary(words, options.name, filename, options);
211
+ return createSpellingDictionary(words, options.name, filename, options, true);
204
212
  }
205
213
  async function loadSimpleWordList(reader, filename, options) {
206
214
  const lines = await reader.readLines(filename);
207
- return createSpellingDictionary(lines, options.name, filename.href, options);
215
+ return measurePerfFn('loadSimpleWordList', () => createSpellingDictionary(lines, options.name, filename.href, options));
208
216
  }
209
217
  async function loadTrie(reader, filename, options) {
210
218
  const content = await reader.read(filename);
211
- return createSpellingDictionaryFromTrieFile(content, options.name, filename.href, options);
219
+ return measurePerfFn('loadTrie', () => createSpellingDictionaryFromTrieFile(content, options.name, filename.href, options));
212
220
  }
213
221
  function toLines(content) {
214
222
  return content.split(/\n|\r\n|\r/);
@@ -1,3 +1,4 @@
1
+ export { measurePerf, measurePerfEnd, measurePerfFn, measurePerfStart } from './performance.js';
1
2
  export type { PerfTimer } from './timer.js';
2
3
  export { createPerfTimer } from './timer.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,3 @@
1
+ export { measurePerf, measurePerfEnd, measurePerfFn, measurePerfStart } from './performance.js';
1
2
  export { createPerfTimer } from './timer.js';
2
3
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ export declare function measurePerfStart(name: string): void;
2
+ export declare function measurePerfEnd(name: string): void;
3
+ /**
4
+ * Creates performance marks and measures the time taken between them.
5
+ * @param name - name of the performance entry
6
+ * @returns a function to stop the timer.
7
+ */
8
+ export declare function measurePerf(name: string): () => void;
9
+ /**
10
+ * Measure the performance of a function.
11
+ * @param name - name of the performance entry
12
+ * @param fn - function to measure
13
+ * @returns the result of the function
14
+ */
15
+ export declare function measurePerfFn<R>(name: string, fn: () => R): R;
16
+ //# sourceMappingURL=performance.d.ts.map
@@ -0,0 +1,34 @@
1
+ export function measurePerfStart(name) {
2
+ performance.mark(name + '-start');
3
+ }
4
+ export function measurePerfEnd(name) {
5
+ performance.mark(name + '-end');
6
+ performance.measure(name, name + '-start', name + '-end');
7
+ }
8
+ /**
9
+ * Creates performance marks and measures the time taken between them.
10
+ * @param name - name of the performance entry
11
+ * @returns a function to stop the timer.
12
+ */
13
+ export function measurePerf(name) {
14
+ measurePerfStart(name);
15
+ return () => {
16
+ measurePerfEnd(name);
17
+ };
18
+ }
19
+ /**
20
+ * Measure the performance of a function.
21
+ * @param name - name of the performance entry
22
+ * @param fn - function to measure
23
+ * @returns the result of the function
24
+ */
25
+ export function measurePerfFn(name, fn) {
26
+ const perfEnd = measurePerf(name);
27
+ try {
28
+ return fn();
29
+ }
30
+ finally {
31
+ perfEnd();
32
+ }
33
+ }
34
+ //# sourceMappingURL=performance.js.map
@@ -1,5 +1,5 @@
1
1
  import type { CSpellSettingsWithSourceTrace, CSpellUserSettings } from '@cspell/cspell-types';
2
- import { ICSpellConfigFile } from 'cspell-config-lib';
2
+ import type { ICSpellConfigFile } from 'cspell-config-lib';
3
3
  import type { Document, DocumentWithText } from './Document/index.js';
4
4
  import type { Uri } from './util/IUri.js';
5
5
  import type { ValidateTextOptions, ValidationIssue } from './validator.js';
@@ -1,5 +1,5 @@
1
1
  import type { CSpellSettings, LocaleId } from '@cspell/cspell-types';
2
- import { ICSpellConfigFile } from 'cspell-config-lib';
2
+ import type { ICSpellConfigFile } from 'cspell-config-lib';
3
3
  import type { LanguageId } from './fileTypes.js';
4
4
  import type { SpellingDictionaryCollection, SuggestionResult, SuggestOptions } from './SpellingDictionary/index.js';
5
5
  export interface WordSuggestion extends SuggestionResult {
@@ -1,5 +1,5 @@
1
1
  import type { CSpellUserSettings, MappedText, ParsedText } from '@cspell/cspell-types';
2
- import { ICSpellConfigFile } from 'cspell-config-lib';
2
+ import type { ICSpellConfigFile } from 'cspell-config-lib';
3
3
  import type { CSpellSettingsInternal, CSpellSettingsInternalFinalized } from '../Models/CSpellSettingsInternalDef.js';
4
4
  import type { ExtendedSuggestion } from '../Models/Suggestion.js';
5
5
  import type { TextDocument, TextDocumentRef } from '../Models/TextDocument.js';
@@ -9,7 +9,7 @@ import type { WordSuggestion } from '../suggestions.js';
9
9
  import type { MatchRange } from '../util/TextRange.js';
10
10
  import type { TextValidator } from './lineValidatorFactory.js';
11
11
  import type { SimpleRange } from './parsedText.js';
12
- import { TraceResult } from './traceWord.js';
12
+ import type { TraceResult } from './traceWord.js';
13
13
  import type { ValidateTextOptions } from './ValidateTextOptions.js';
14
14
  import type { ValidationOptions } from './ValidationTypes.js';
15
15
  export interface DocumentValidatorOptions extends ValidateTextOptions {
@@ -131,9 +131,31 @@ interface Preparations {
131
131
  localConfigFilepath: string | undefined;
132
132
  }
133
133
  interface ShouldCheckDocumentResult {
134
+ /** possible errors found while loading configuration. */
134
135
  errors: Error[];
136
+ /**
137
+ * The calculated result:
138
+ * - `false` if the document should not be checked. Based upon the settings.
139
+ * - `true` if the document should be checked.
140
+ */
135
141
  shouldCheck: boolean;
142
+ /** final settings used to determine the result. */
143
+ settings: CSpellUserSettings;
144
+ /**
145
+ * The reason the document should not be checked.
146
+ */
147
+ reason?: string | undefined;
136
148
  }
149
+ /**
150
+ * Check if a document should be checked based upon the ignorePaths and override settings.
151
+ *
152
+ * This function will search and fetch settings based upon the location of the document if `noConfigSearch` is not true.
153
+ *
154
+ * @param doc - document to check
155
+ * @param options - options to override some of the settings.
156
+ * @param settings - current settings
157
+ * @returns ShouldCheckDocumentResult
158
+ */
137
159
  export declare function shouldCheckDocument(doc: TextDocumentRef, options: DocumentValidatorOptions, settings: CSpellUserSettings): Promise<ShouldCheckDocumentResult>;
138
160
  export declare const __testing__: {
139
161
  sanitizeSuggestion: typeof sanitizeSuggestion;
@@ -5,8 +5,8 @@ import { toFilePathOrHref, toFileURL } from '@cspell/url';
5
5
  import { satisfiesCSpellConfigFile } from 'cspell-config-lib';
6
6
  import { getGlobMatcherForExcluding } from '../globs/getGlobMatcher.js';
7
7
  import { documentUriToURL, updateTextDocument } from '../Models/TextDocument.js';
8
- import { createPerfTimer } from '../perf/index.js';
9
- import { finalizeSettings, loadConfig, mergeSettings, resolveConfigFileImports, resolveSettingsImports, searchForConfig, } from '../Settings/index.js';
8
+ import { createPerfTimer, measurePerf } from '../perf/index.js';
9
+ import { extractImportErrors, finalizeSettings, loadConfig, mergeSettings, resolveConfigFileImports, resolveSettingsImports, searchForConfig, } from '../Settings/index.js';
10
10
  import { validateInDocumentSettings } from '../Settings/InDocSettings.js';
11
11
  import { getDictionaryInternal } from '../SpellingDictionary/index.js';
12
12
  import { calcSuggestionAdjustedToToMatchCase } from '../suggestions.js';
@@ -81,12 +81,15 @@ export class DocumentValidator {
81
81
  : useSearchForConfig
82
82
  ? timePromise(this.perfTiming, '__searchForDocumentConfig', searchForDocumentConfig(this._document, settings, settings))
83
83
  : undefined;
84
- pLocalConfig && timePromise(this.perfTiming, '_loadConfig', pLocalConfig);
84
+ if (pLocalConfig) {
85
+ timePromise(this.perfTiming, '_loadConfig', pLocalConfig).catch(() => undefined);
86
+ }
85
87
  const localConfig = (await catchPromiseError(pLocalConfig, (e) => this.addPossibleError(e))) || {};
86
- this.addPossibleError(localConfig?.__importRef?.error);
88
+ extractImportErrors(localConfig).forEach((e) => this.addPossibleError(e.error));
87
89
  const config = mergeSettings(settings, localConfig);
88
90
  const docSettings = await timePromise(this.perfTiming, '_determineTextDocumentSettings', determineTextDocumentSettings(this._document, config));
89
91
  const dict = await timePromise(this.perfTiming, '_getDictionaryInternal', getDictionaryInternal(docSettings));
92
+ const stopMeasure = measurePerf('DocumentValidator._prepareAsync');
90
93
  const recGlobMatcherTime = recordPerfTime(this.perfTiming, '_GlobMatcher');
91
94
  const matcher = getGlobMatcherForExcluding(localConfig?.ignorePaths);
92
95
  const uri = this._document.uri;
@@ -118,6 +121,7 @@ export class DocumentValidator {
118
121
  this._ready = true;
119
122
  this._preparationTime = timer.elapsed;
120
123
  this.perfTiming.prepTime = this._preparationTime;
124
+ stopMeasure();
121
125
  }
122
126
  async _updatePrep() {
123
127
  assert(this._preparations, ERROR_NOT_PREPARED);
@@ -125,6 +129,7 @@ export class DocumentValidator {
125
129
  const prep = this._preparations;
126
130
  const docSettings = await determineTextDocumentSettings(this._document, prep.config);
127
131
  const dict = await getDictionaryInternal(docSettings);
132
+ const stopMeasure = measurePerf('DocumentValidator._updatePrep');
128
133
  const shouldCheck = docSettings.enabled ?? true;
129
134
  const finalSettings = finalizeSettings(docSettings);
130
135
  const validateOptions = settingsToValidateOptions(finalSettings);
@@ -142,6 +147,7 @@ export class DocumentValidator {
142
147
  textValidator,
143
148
  };
144
149
  this._preparationTime = timer.elapsed;
150
+ stopMeasure();
145
151
  }
146
152
  /**
147
153
  * The amount of time in ms to prepare for validation.
@@ -295,6 +301,7 @@ export class DocumentValidator {
295
301
  const { maxNumberOfProblems = defaultMaxNumberOfProblems, maxDuplicateProblems = defaultMaxDuplicateProblems } = this._preparations.validateOptions;
296
302
  let numProblems = 0;
297
303
  const mapOfProblems = new Map();
304
+ const stopMeasure = measurePerf('DocumentValidator._checkParsedText');
298
305
  for (const pText of parsedTexts) {
299
306
  for (const issue of this.check(pText)) {
300
307
  const { text } = issue;
@@ -307,6 +314,7 @@ export class DocumentValidator {
307
314
  return;
308
315
  }
309
316
  }
317
+ stopMeasure();
310
318
  }
311
319
  addPossibleError(error) {
312
320
  if (!error)
@@ -394,6 +402,16 @@ async function searchForDocumentConfig(document, defaultConfig, pnpSettings) {
394
402
  function mapSug(sug) {
395
403
  return { cost: 999, ...sug };
396
404
  }
405
+ /**
406
+ * Check if a document should be checked based upon the ignorePaths and override settings.
407
+ *
408
+ * This function will search and fetch settings based upon the location of the document if `noConfigSearch` is not true.
409
+ *
410
+ * @param doc - document to check
411
+ * @param options - options to override some of the settings.
412
+ * @param settings - current settings
413
+ * @returns ShouldCheckDocumentResult
414
+ */
397
415
  export async function shouldCheckDocument(doc, options, settings) {
398
416
  const errors = [];
399
417
  function addPossibleError(error) {
@@ -410,14 +428,19 @@ export async function shouldCheckDocument(doc, options, settings) {
410
428
  ? searchForDocumentConfig(doc, settings, settings)
411
429
  : undefined;
412
430
  const localConfig = (await catchPromiseError(pLocalConfig, addPossibleError)) || {};
413
- addPossibleError(localConfig?.__importRef?.error);
431
+ extractImportErrors(localConfig).forEach((e) => addPossibleError(e.error));
414
432
  const config = mergeSettings(settings, localConfig);
415
433
  const matcher = getGlobMatcherForExcluding(localConfig?.ignorePaths);
416
- const docSettings = await determineTextDocumentSettings(doc, config);
417
434
  // eslint-disable-next-line unicorn/prefer-regexp-test
418
- return !matcher.match(uriToFilePath(doc.uri)) && (docSettings.enabled ?? true);
435
+ if (matcher.match(uriToFilePath(doc.uri))) {
436
+ return { errors, shouldCheck: false, settings: localConfig, reason: 'Excluded by ignorePaths.' };
437
+ }
438
+ const docSettings = await determineTextDocumentSettings(doc, config);
439
+ const shouldCheck = docSettings.enabled ?? true;
440
+ const reason = shouldCheck ? undefined : 'Excluded by overrides or languageSettings.';
441
+ return { errors, shouldCheck, settings: docSettings, reason };
419
442
  }
420
- return { errors, shouldCheck: await shouldCheck() };
443
+ return await shouldCheck();
421
444
  }
422
445
  export const __testing__ = {
423
446
  sanitizeSuggestion,
@@ -1,4 +1,4 @@
1
- import { TextOffset } from '@cspell/cspell-types';
1
+ import type { TextOffset } from '@cspell/cspell-types';
2
2
  /**
3
3
  * Try to detect if a string is a random string of characters or is it camel case / snake case words.
4
4
  * @param s - string to check
@@ -13,7 +13,7 @@ import { isWordValidWithEscapeRetry } from './isWordValid.js';
13
13
  import { mapRangeBackToOriginalPos } from './parsedText.js';
14
14
  const MIN_HEX_SEQUENCE_LENGTH = 8;
15
15
  export function lineValidatorFactory(sDict, options) {
16
- const { minWordLength = defaultMinWordLength, flagWords = [], allowCompoundWords = false, ignoreCase = true, ignoreRandomStrings = defaultCSpellSettings.ignoreRandomStrings, minRandomLength = defaultCSpellSettings.minRandomLength, unknownWords = unknownWordsChoices.ReportAll, } = options;
16
+ const { minWordLength = defaultMinWordLength, flagWords = [], allowCompoundWords = false, ignoreCase = true, ignoreRandomStrings = defaultCSpellSettings.ignoreRandomStrings, minRandomLength = defaultCSpellSettings.minRandomLength, unknownWords = unknownWordsChoices.ReportAll, numSuggestions, } = options;
17
17
  const hasWordOptions = {
18
18
  ignoreCase,
19
19
  useCompounds: allowCompoundWords || undefined, // let the dictionaries decide on useCompounds if allow is false
@@ -67,17 +67,21 @@ export function lineValidatorFactory(sDict, options) {
67
67
  return autoResolve(cacheGetPreferredSuggestions, word, () => dictCol.getPreferredSuggestions(word));
68
68
  }
69
69
  const cacheHasSimpleSuggestions = new Map();
70
- function hasSimpleSuggestions(word) {
70
+ function getSimpleSuggestions(word) {
71
+ const numSug = numSuggestions ?? 5;
71
72
  return autoResolve(cacheHasSimpleSuggestions, word, () => {
72
73
  const sugs = dictCol.suggest(word, {
73
74
  numSuggestions: 1,
74
75
  compoundMethod: 0,
75
- includeTies: false,
76
+ includeTies: true, // We want the top suggestions even if there are ties
76
77
  ignoreCase,
77
78
  timeout: 100,
78
79
  numChanges: 1.8, // Only consider very simple changes (1 edit distance plus case changes)
79
80
  });
80
- return !!sugs.length;
81
+ if (sugs.length > numSug) {
82
+ sugs.length = numSug;
83
+ }
84
+ return sugs;
81
85
  });
82
86
  }
83
87
  function isWordFlagged(wo) {
@@ -92,7 +96,11 @@ export function lineValidatorFactory(sDict, options) {
92
96
  if (!sugs?.length) {
93
97
  issue.hasPreferredSuggestions = sugs !== undefined ? false : undefined;
94
98
  if (unknownWords === unknownWordsChoices.ReportSimple) {
95
- issue.hasSimpleSuggestions = hasSimpleSuggestions(issue.text);
99
+ const sug = getSimpleSuggestions(issue.text);
100
+ issue.hasSimpleSuggestions = !!sug.length;
101
+ if (sug.length) {
102
+ issue.suggestionsEx = sug.map((s) => ({ ...s, isPreferred: !!s.isPreferred }));
103
+ }
96
104
  }
97
105
  return issue;
98
106
  }
@@ -31,6 +31,7 @@ export interface TraceResult extends Array<DictionaryTraceResult> {
31
31
  }
32
32
  export interface TraceOptions extends Pick<CSpellSettingsWithSourceTrace, 'source' | 'allowCompoundWords'> {
33
33
  ignoreCase?: boolean;
34
+ compoundSeparator?: string | undefined;
34
35
  }
35
36
  export declare function traceWord(word: string, dictCollection: SpellingDictionaryCollection, config: TraceOptions): TraceResult;
36
37
  export {};
@@ -7,6 +7,7 @@ export function traceWord(word, dictCollection, config) {
7
7
  const opts = {
8
8
  ignoreCase: config.ignoreCase ?? true,
9
9
  useCompounds: config.allowCompoundWords || false,
10
+ compoundSeparator: '•',
10
11
  };
11
12
  const splits = split({ text: word, offset: 0 }, 0, checkWord);
12
13
  const wfSplits = splits.words.map((s) => ({ word: s.text, found: s.isFound }));
@@ -1,5 +1,5 @@
1
1
  import type { CSpellSettings, LocaleId } from '@cspell/cspell-types';
2
- import { ICSpellConfigFile } from 'cspell-config-lib';
2
+ import type { ICSpellConfigFile } from 'cspell-config-lib';
3
3
  import type { LanguageId } from './fileTypes.js';
4
4
  import type { DictionaryTraceResult, WordSplits } from './textValidation/traceWord.js';
5
5
  export interface TraceResult extends DictionaryTraceResult {
@@ -11,6 +11,7 @@ export interface TraceOptions {
11
11
  locale?: LocaleId;
12
12
  ignoreCase?: boolean;
13
13
  allowCompoundWords?: boolean;
14
+ compoundSeparator?: string | undefined;
14
15
  }
15
16
  export interface TraceWordResult extends Array<TraceResult> {
16
17
  splits: readonly WordSplits[];
package/dist/lib/trace.js CHANGED
@@ -15,7 +15,7 @@ export async function traceWords(words, settings, options) {
15
15
  return s;
16
16
  }
17
17
  export async function* traceWordsAsync(words, settingsOrConfig, options) {
18
- const { languageId, locale: language, ignoreCase = true, allowCompoundWords } = options || {};
18
+ const { languageId, locale: language, ignoreCase = true, allowCompoundWords, compoundSeparator } = options || {};
19
19
  const settings = satisfiesCSpellConfigFile(settingsOrConfig)
20
20
  ? await resolveConfigFileImports(settingsOrConfig)
21
21
  : settingsOrConfig;
@@ -44,7 +44,7 @@ export async function* traceWordsAsync(words, settingsOrConfig, options) {
44
44
  const { config, dicts, activeDictionaries } = await finalize(settings);
45
45
  const setOfActiveDicts = new Set(activeDictionaries);
46
46
  function processWord(word) {
47
- const results = traceWord(word, dicts, { ...config, ignoreCase });
47
+ const results = traceWord(word, dicts, { ...config, ignoreCase, compoundSeparator });
48
48
  const r = results.map((r) => ({
49
49
  ...r,
50
50
  dictActive: setOfActiveDicts.has(r.dictName),
@@ -26,7 +26,7 @@ export function isError(e) {
26
26
  if (!e || typeof e !== 'object')
27
27
  return false;
28
28
  const ex = e;
29
- return typeof ex.name == 'string' && typeof ex.message == 'string' && (typeof ex.stack) in allowStringOrUndefined;
29
+ return typeof ex.name === 'string' && typeof ex.message === 'string' && (typeof ex.stack) in allowStringOrUndefined;
30
30
  }
31
31
  export function toError(e, errorFactory = UnknownError) {
32
32
  if (isError(e))
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "provenance": true
6
6
  },
7
- "version": "9.3.2",
7
+ "version": "9.6.0",
8
8
  "description": "A library of useful functions used across various cspell tools.",
9
9
  "type": "module",
10
10
  "sideEffects": false,
@@ -64,21 +64,21 @@
64
64
  },
65
65
  "homepage": "https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell-lib#readme",
66
66
  "dependencies": {
67
- "@cspell/cspell-bundled-dicts": "9.3.2",
68
- "@cspell/cspell-pipe": "9.3.2",
69
- "@cspell/cspell-resolver": "9.3.2",
70
- "@cspell/cspell-types": "9.3.2",
71
- "@cspell/dynamic-import": "9.3.2",
72
- "@cspell/filetypes": "9.3.2",
73
- "@cspell/strong-weak-map": "9.3.2",
74
- "@cspell/url": "9.3.2",
67
+ "@cspell/cspell-bundled-dicts": "9.6.0",
68
+ "@cspell/cspell-pipe": "9.6.0",
69
+ "@cspell/cspell-resolver": "9.6.0",
70
+ "@cspell/cspell-types": "9.6.0",
71
+ "@cspell/dynamic-import": "9.6.0",
72
+ "@cspell/filetypes": "9.6.0",
73
+ "@cspell/strong-weak-map": "9.6.0",
74
+ "@cspell/url": "9.6.0",
75
75
  "clear-module": "^4.1.2",
76
- "cspell-config-lib": "9.3.2",
77
- "cspell-dictionary": "9.3.2",
78
- "cspell-glob": "9.3.2",
79
- "cspell-grammar": "9.3.2",
80
- "cspell-io": "9.3.2",
81
- "cspell-trie-lib": "9.3.2",
76
+ "cspell-config-lib": "9.6.0",
77
+ "cspell-dictionary": "9.6.0",
78
+ "cspell-glob": "9.6.0",
79
+ "cspell-grammar": "9.6.0",
80
+ "cspell-io": "9.6.0",
81
+ "cspell-trie-lib": "9.6.0",
82
82
  "env-paths": "^3.0.0",
83
83
  "gensequence": "^8.0.8",
84
84
  "import-fresh": "^3.3.1",
@@ -91,21 +91,23 @@
91
91
  "node": ">=20"
92
92
  },
93
93
  "devDependencies": {
94
- "@cspell/dict-cpp": "^6.0.14",
95
- "@cspell/dict-csharp": "^4.0.7",
96
- "@cspell/dict-css": "^4.0.18",
94
+ "@cspell/cspell-tools": "9.6.0",
95
+ "@cspell/dict-cpp": "^7.0.2",
96
+ "@cspell/dict-csharp": "^4.0.8",
97
+ "@cspell/dict-css": "^4.0.19",
98
+ "@cspell/dict-en_us": "^4.4.27",
97
99
  "@cspell/dict-fa-ir": "^4.0.5",
98
100
  "@cspell/dict-fr-fr": "^2.3.2",
99
- "@cspell/dict-html": "^4.0.12",
101
+ "@cspell/dict-html": "^4.0.14",
100
102
  "@cspell/dict-nl-nl": "^2.4.2",
101
- "@cspell/dict-python": "^4.2.21",
103
+ "@cspell/dict-python": "^4.2.25",
102
104
  "@types/configstore": "^6.0.2",
103
- "comment-json": "^4.4.1",
105
+ "comment-json": "^4.5.1",
104
106
  "configstore": "^7.1.0",
105
107
  "cspell-dict-nl-nl": "^1.1.2",
106
108
  "leaked-handles": "^5.2.0",
107
109
  "lorem-ipsum": "^2.0.8",
108
110
  "perf-insight": "^2.0.1"
109
111
  },
110
- "gitHead": "595bde79b4a5abf3256b71129995ec3601454b02"
112
+ "gitHead": "163793ddf2a0ad90bc7c90351698a106003297af"
111
113
  }
@@ -1,20 +0,0 @@
1
- interface PerfEntry {
2
- readonly entryType: string;
3
- readonly name: string;
4
- readonly startTime: number;
5
- readonly duration?: number | undefined;
6
- }
7
- interface PerfMark extends PerfEntry {
8
- readonly entryType: 'mark';
9
- }
10
- export declare class PerfMonitor {
11
- private _performance;
12
- private _enabled;
13
- constructor(_performance?: Performance);
14
- mark(name: string): PerfMark;
15
- get enabled(): boolean;
16
- set enabled(value: boolean);
17
- }
18
- export declare const perf: PerfMonitor;
19
- export {};
20
- //# sourceMappingURL=perf.d.ts.map
@@ -1,32 +0,0 @@
1
- export class PerfMonitor {
2
- _performance;
3
- _enabled = false;
4
- constructor(_performance = performance) {
5
- this._performance = _performance;
6
- }
7
- mark(name) {
8
- const mark = ((this._enabled && this._performance.mark(name)) || {
9
- entryType: 'mark',
10
- name,
11
- startTime: performance.now(),
12
- });
13
- return mark;
14
- }
15
- // measure(name: string, startMark: string, endMark: string) {
16
- // return this._enabled && this._performance.measure(name, startMark, endMark);
17
- // }
18
- // clearMarks(name?: string) {
19
- // this._enabled && this._performance.clearMarks(name);
20
- // }
21
- // clearMeasures(name?: string) {
22
- // this._enabled && this._performance.clearMeasures(name);
23
- // }
24
- get enabled() {
25
- return this._enabled;
26
- }
27
- set enabled(value) {
28
- this._enabled = value;
29
- }
30
- }
31
- export const perf = new PerfMonitor();
32
- //# sourceMappingURL=perf.js.map