cspell-lib 9.0.2 → 9.1.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.
@@ -1,4 +1,4 @@
1
- import type { AdvancedCSpellSettingsWithSourceTrace, CSpellSettingsWithSourceTrace, DictionaryDefinition, DictionaryDefinitionAugmented, DictionaryDefinitionCustom, DictionaryDefinitionInline, DictionaryDefinitionPreferred, Parser } from '@cspell/cspell-types';
1
+ import type { AdvancedCSpellSettingsWithSourceTrace, CSpellSettingsWithSourceTrace, DictionaryDefinition, DictionaryDefinitionAugmented, DictionaryDefinitionCustom, DictionaryDefinitionInline, DictionaryDefinitionPreferred, DictionaryDefinitionSimple, Parser } from '@cspell/cspell-types';
2
2
  import type { WeightMap } from 'cspell-trie-lib';
3
3
  import type { OptionalOrUndefined } from '../util/types.js';
4
4
  export declare const SymbolCSpellSettingsInternal: unique symbol;
@@ -13,11 +13,15 @@ export interface CSpellSettingsInternalFinalized extends CSpellSettingsInternal
13
13
  includeRegExpList: RegExp[];
14
14
  }
15
15
  type DictionaryDefinitionCustomUniqueFields = Omit<DictionaryDefinitionCustom, keyof DictionaryDefinitionPreferred>;
16
- export type DictionaryDefinitionInternal = DictionaryFileDefinitionInternal | DictionaryDefinitionInlineInternal;
16
+ export type DictionaryDefinitionInternal = DictionaryFileDefinitionInternal | DictionaryDefinitionInlineInternal | DictionaryDefinitionSimpleInternal;
17
17
  export type DictionaryDefinitionInlineInternal = DictionaryDefinitionInline & {
18
18
  /** The path to the config file that contains this dictionary definition */
19
19
  readonly __source?: string | undefined;
20
20
  };
21
+ export type DictionaryDefinitionSimpleInternal = DictionaryDefinitionSimple & {
22
+ /** The path to the config file that contains this dictionary definition */
23
+ readonly __source?: string | undefined;
24
+ };
21
25
  export interface DictionaryFileDefinitionInternal extends Readonly<DictionaryDefinitionPreferred>, Readonly<Partial<DictionaryDefinitionCustomUniqueFields>>, Readonly<DictionaryDefinitionAugmented> {
22
26
  /**
23
27
  * Optional weight map used to improve suggestions.
@@ -36,5 +40,6 @@ export declare function cleanCSpellSettingsInternal(parts?: OptionalOrUndefined<
36
40
  export declare function createCSpellSettingsInternal(parts?: OptionalOrUndefined<Partial<CSpellSettingsInternal>>): CSpellSettingsInternal;
37
41
  export declare function isCSpellSettingsInternal(cs: CSpellSettingsInternal | CSpellSettingsWithSourceTrace | OptionalOrUndefined<CSpellSettingsInternal | CSpellSettingsWithSourceTrace>): cs is CSpellSettingsInternal;
38
42
  export declare function isDictionaryDefinitionInlineInternal(def: DictionaryDefinitionInternal | DictionaryDefinitionInline | DictionaryDefinition): def is DictionaryDefinitionInlineInternal;
43
+ export declare function isDictionaryFileDefinitionInternal(def: DictionaryDefinitionInternal | DictionaryDefinitionInline | DictionaryDefinition): def is DictionaryFileDefinitionInternal;
39
44
  export {};
40
45
  //# sourceMappingURL=CSpellSettingsInternalDef.d.ts.map
@@ -17,4 +17,7 @@ export function isDictionaryDefinitionInlineInternal(def) {
17
17
  const defInline = def;
18
18
  return !!(defInline.words || defInline.flagWords || defInline.ignoreWords || defInline.suggestWords);
19
19
  }
20
+ export function isDictionaryFileDefinitionInternal(def) {
21
+ return !!(def.path || def.file);
22
+ }
20
23
  //# sourceMappingURL=CSpellSettingsInternalDef.js.map
@@ -1,5 +1,5 @@
1
1
  import type { Issue, TextOffset as TextOffsetRW } from '@cspell/cspell-types';
2
- export interface ValidationResult extends TextOffsetRW, Pick<Issue, 'message' | 'issueType'> {
2
+ export interface ValidationResult extends TextOffsetRW, Pick<Issue, 'message' | 'issueType' | 'hasPreferredSuggestions' | 'hasSimpleSuggestions'> {
3
3
  line: TextOffsetRW;
4
4
  isFlagged?: boolean | undefined;
5
5
  isFound?: boolean | undefined;
@@ -5,6 +5,7 @@ import { AutoResolveCache } from '../../../util/AutoResolve.js';
5
5
  import { FileResolver } from '../../../util/resolveFile.js';
6
6
  import type { LoaderResult } from '../pnpLoader.js';
7
7
  import { ConfigSearch } from './configSearch.js';
8
+ import { StopSearchAt } from './defaultConfigLoader.js';
8
9
  import { normalizeCacheSettings } from './normalizeRawSettings.js';
9
10
  import type { PnPSettingsOptional } from './PnPSettings.js';
10
11
  import type { CSpellSettingsI, CSpellSettingsWST } from './types.js';
@@ -27,6 +28,11 @@ interface ImportedConfigEntry {
27
28
  /** Set of all references used to catch circular references */
28
29
  referencedSet: Set<string>;
29
30
  }
31
+ export interface SearchForConfigFileOptions {
32
+ stopSearchAt?: StopSearchAt;
33
+ }
34
+ export interface SearchForConfigOptions extends SearchForConfigFileOptions, PnPSettingsOptional {
35
+ }
30
36
  interface CacheMergeConfigFileWithImports {
31
37
  pnpSettings: PnPSettingsOptional | undefined;
32
38
  referencedBy: string[] | undefined;
@@ -45,10 +51,10 @@ export interface IConfigLoader {
45
51
  /**
46
52
  * This is an alias for `searchForConfigFile` and `mergeConfigFileWithImports`.
47
53
  * @param searchFrom the directory / file URL to start searching from.
48
- * @param pnpSettings - related to Using Yarn PNP.
54
+ * @param options - Optional settings including stop location and Yarn PnP configuration.
49
55
  * @returns the resulting settings
50
56
  */
51
- searchForConfig(searchFrom: URL | string | undefined, pnpSettings?: PnPSettingsOptional): Promise<CSpellSettingsI | undefined>;
57
+ searchForConfig(searchFrom: URL | string | undefined, options?: SearchForConfigOptions): Promise<CSpellSettingsI | undefined>;
52
58
  resolveConfigFileLocation(filenameOrURL: string | URL, relativeTo?: string | URL): Promise<URL | undefined>;
53
59
  getGlobalSettingsAsync(): Promise<CSpellSettingsI>;
54
60
  /**
@@ -89,6 +95,7 @@ export interface IConfigLoader {
89
95
  setIsTrusted(isTrusted: boolean): void;
90
96
  }
91
97
  export declare class ConfigLoader implements IConfigLoader {
98
+ #private;
92
99
  readonly fs: VFileSystem;
93
100
  readonly templateVariables: Record<string, string>;
94
101
  onReady: Promise<void>;
@@ -108,20 +115,21 @@ export declare class ConfigLoader implements IConfigLoader {
108
115
  protected globalSettings: CSpellSettingsI | undefined;
109
116
  protected cspellConfigFileReaderWriter: CSpellConfigFileReaderWriter;
110
117
  protected configSearch: ConfigSearch;
118
+ protected stopSearchAtCache: WeakMap<SearchForConfigFileOptions, URL[] | undefined>;
111
119
  protected toDispose: {
112
120
  dispose: () => void;
113
121
  }[];
114
122
  readSettingsAsync(filename: string | URL, relativeTo?: string | URL, pnpSettings?: PnPSettingsOptional): Promise<CSpellSettingsI>;
115
123
  readConfigFile(filenameOrURL: string | URL, relativeTo?: string | URL): Promise<CSpellConfigFile | Error>;
116
- searchForConfigFileLocation(searchFrom: URL | string | undefined): Promise<URL | undefined>;
117
- searchForConfigFile(searchFrom: URL | string | undefined): Promise<CSpellConfigFile | undefined>;
124
+ searchForConfigFileLocation(searchFrom: URL | string | undefined, stopSearchAt?: URL[] | undefined): Promise<URL | undefined>;
125
+ searchForConfigFile(searchFrom: URL | string | undefined, stopSearchAt?: URL[]): Promise<CSpellConfigFile | undefined>;
118
126
  /**
119
127
  *
120
128
  * @param searchFrom the directory / file URL to start searching from.
121
- * @param pnpSettings - related to Using Yarn PNP.
129
+ * @param options - Optional settings including stop location and Yarn PnP configuration.
122
130
  * @returns the resulting settings
123
131
  */
124
- searchForConfig(searchFrom: URL | string | undefined, pnpSettings?: PnPSettingsOptional): Promise<CSpellSettingsI | undefined>;
132
+ searchForConfig(searchFrom: URL | string | undefined, options?: SearchForConfigOptions): Promise<CSpellSettingsI | undefined>;
125
133
  getGlobalSettings(): CSpellSettingsI;
126
134
  getGlobalSettingsAsync(): Promise<CSpellSettingsI>;
127
135
  clearCachedSettingsFiles(): void;
@@ -67,6 +67,7 @@ export class ConfigLoader {
67
67
  globalSettings;
68
68
  cspellConfigFileReaderWriter;
69
69
  configSearch;
70
+ stopSearchAtCache = new WeakMap();
70
71
  toDispose = [];
71
72
  async readSettingsAsync(filename, relativeTo, pnpSettings) {
72
73
  await this.onReady;
@@ -99,18 +100,12 @@ export class ConfigLoader {
99
100
  }
100
101
  });
101
102
  }
102
- async searchForConfigFileLocation(searchFrom) {
103
- const url = toFileURL(searchFrom || cwdURL(), cwdURL());
104
- if (typeof searchFrom === 'string' &&
105
- !isUrlLike(searchFrom) &&
106
- url.protocol === 'file:' && // check to see if it is a directory
107
- (await isDirectory(this.fs, url))) {
108
- return this.configSearch.searchForConfig(addTrailingSlash(url));
109
- }
110
- return this.configSearch.searchForConfig(url);
103
+ async searchForConfigFileLocation(searchFrom, stopSearchAt) {
104
+ const searchFromURL = (await this.#normalizeDirURL(searchFrom)) || cwdURL();
105
+ return this.configSearch.searchForConfig(searchFromURL, stopSearchAt);
111
106
  }
112
- async searchForConfigFile(searchFrom) {
113
- const location = await this.searchForConfigFileLocation(searchFrom);
107
+ async searchForConfigFile(searchFrom, stopSearchAt) {
108
+ const location = await this.searchForConfigFileLocation(searchFrom, stopSearchAt);
114
109
  if (!location)
115
110
  return undefined;
116
111
  const file = await this.readConfigFile(location);
@@ -119,14 +114,15 @@ export class ConfigLoader {
119
114
  /**
120
115
  *
121
116
  * @param searchFrom the directory / file URL to start searching from.
122
- * @param pnpSettings - related to Using Yarn PNP.
117
+ * @param options - Optional settings including stop location and Yarn PnP configuration.
123
118
  * @returns the resulting settings
124
119
  */
125
- async searchForConfig(searchFrom, pnpSettings = defaultPnPSettings) {
126
- const configFile = await this.searchForConfigFile(searchFrom);
120
+ async searchForConfig(searchFrom, options) {
121
+ const stopAt = await this.#extractStopSearchAtURLs(options);
122
+ const configFile = await this.searchForConfigFile(searchFrom, stopAt);
127
123
  if (!configFile)
128
124
  return undefined;
129
- return this.mergeConfigFileWithImports(configFile, pnpSettings);
125
+ return this.mergeConfigFileWithImports(configFile, options);
130
126
  }
131
127
  getGlobalSettings() {
132
128
  assert(this.globalSettings, 'Global settings not loaded');
@@ -386,6 +382,33 @@ export class ConfigLoader {
386
382
  this.configSearch = new ConfigSearch(searchPlaces, isTrusted ? trustedSearch : unTrustedSearch, this.fs);
387
383
  this.cspellConfigFileReaderWriter.setUntrustedExtensions(isTrusted ? [] : defaultJsExtensions);
388
384
  }
385
+ async #extractStopSearchAtURLs(options) {
386
+ if (!options?.stopSearchAt)
387
+ return undefined;
388
+ if (this.stopSearchAtCache.has(options)) {
389
+ return this.stopSearchAtCache.get(options);
390
+ }
391
+ const rawStops = Array.isArray(options.stopSearchAt) ? options.stopSearchAt : [options.stopSearchAt];
392
+ const stopURLs = await Promise.all(rawStops.map((s) => this.#normalizeDirURL(s)));
393
+ this.stopSearchAtCache.set(options, stopURLs);
394
+ return stopURLs;
395
+ }
396
+ async #normalizeDirURL(input) {
397
+ if (!input)
398
+ return undefined;
399
+ const url = toFileURL(input, cwdURL());
400
+ if (url.pathname.endsWith('/'))
401
+ return url;
402
+ if (input instanceof URL)
403
+ return new URL('.', url);
404
+ if (typeof input === 'string' &&
405
+ !isUrlLike(input) &&
406
+ url.protocol === 'file:' &&
407
+ (await isDirectory(this.fs, url))) {
408
+ return addTrailingSlash(url);
409
+ }
410
+ return new URL('.', url);
411
+ }
389
412
  }
390
413
  class ConfigLoaderInternal extends ConfigLoader {
391
414
  constructor(vfs) {
@@ -7,7 +7,7 @@ export declare class ConfigSearch {
7
7
  * @param fs - The file system to use.
8
8
  */
9
9
  constructor(searchPlaces: readonly string[], allowedExtensionsByProtocol: Map<string, readonly string[]>, fs: VFileSystem);
10
- searchForConfig(searchFromURL: URL): Promise<URL | undefined>;
10
+ searchForConfig(searchFromURL: URL, stopSearchAtURL?: URL[]): Promise<URL | undefined>;
11
11
  clearCache(): void;
12
12
  }
13
13
  /**
@@ -19,15 +19,18 @@ export class ConfigSearch {
19
19
  constructor(searchPlaces, allowedExtensionsByProtocol, fs) {
20
20
  this.#scanner = new DirConfigScanner(searchPlaces, allowedExtensionsByProtocol, fs);
21
21
  }
22
- searchForConfig(searchFromURL) {
23
- const dirUrl = searchFromURL.pathname.endsWith('/') ? searchFromURL : new URL('.', searchFromURL);
24
- return this.#findUp(dirUrl);
22
+ async searchForConfig(searchFromURL, stopSearchAtURL) {
23
+ const dirUrl = searchFromURL.pathname.endsWith('/') ? searchFromURL : new URL('./', searchFromURL);
24
+ const stopDirUrls = stopSearchAtURL
25
+ ? stopSearchAtURL.map((url) => (url.pathname.endsWith('/') ? url : new URL('./', url)))
26
+ : undefined;
27
+ return this.#findUp(dirUrl, stopDirUrls);
25
28
  }
26
29
  clearCache() {
27
30
  this.#searchCache.clear();
28
31
  this.#scanner.clearCache();
29
32
  }
30
- #findUp(fromDir) {
33
+ #findUp(fromDir, stopDirUrls) {
31
34
  const searchDirCache = this.#searchCache;
32
35
  const cached = searchDirCache.get(fromDir.href);
33
36
  if (cached) {
@@ -39,7 +42,7 @@ export class ConfigSearch {
39
42
  visit(dir);
40
43
  return this.#scanner.scanDirForConfigFile(dir);
41
44
  };
42
- result = findUpFromUrl(predicate, fromDir, { type: 'file' });
45
+ result = findUpFromUrl(predicate, fromDir, { type: 'file', ...(stopDirUrls && { stopAt: stopDirUrls }) });
43
46
  searchDirCache.set(fromDir.href, result);
44
47
  visited.forEach((dir) => searchDirCache.set(dir.href, result));
45
48
  return result;
@@ -1,16 +1,17 @@
1
1
  import type { CSpellSettings } from '@cspell/cspell-types';
2
2
  import type { CSpellConfigFile, ICSpellConfigFile } from 'cspell-config-lib';
3
- import type { IConfigLoader } from './configLoader.js';
3
+ import type { IConfigLoader, SearchForConfigOptions } from './configLoader.js';
4
4
  import type { PnPSettingsOptional } from './PnPSettings.js';
5
5
  import type { CSpellSettingsI, CSpellSettingsWST } from './types.js';
6
6
  export type { CSpellConfigFile, ICSpellConfigFile } from 'cspell-config-lib';
7
+ export type StopSearchAt = URL | string | (URL | string)[] | undefined;
7
8
  /**
8
9
  *
9
10
  * @param searchFrom the directory / file to start searching from.
10
- * @param pnpSettings - related to Using Yarn PNP.
11
+ * @param options - Optional settings including stop location and Yarn PnP configuration.
11
12
  * @returns the resulting settings
12
13
  */
13
- export declare function searchForConfig(searchFrom: URL | string | undefined, pnpSettings?: PnPSettingsOptional): Promise<CSpellSettingsI | undefined>;
14
+ export declare function searchForConfig(searchFrom: URL | string | undefined, options?: SearchForConfigOptions): Promise<CSpellSettingsI | undefined>;
14
15
  /**
15
16
  * Load a CSpell configuration files.
16
17
  * @param file - path or package reference to load.
@@ -2,16 +2,15 @@ import { toError } from '../../../util/errors.js';
2
2
  import { toFileUrl } from '../../../util/url.js';
3
3
  import { getDefaultConfigLoaderInternal } from './configLoader.js';
4
4
  import { configErrorToRawSettings, configToRawSettings } from './configToRawSettings.js';
5
- import { defaultPnPSettings } from './PnPSettings.js';
6
5
  const gcl = getDefaultConfigLoaderInternal;
7
6
  /**
8
7
  *
9
8
  * @param searchFrom the directory / file to start searching from.
10
- * @param pnpSettings - related to Using Yarn PNP.
9
+ * @param options - Optional settings including stop location and Yarn PnP configuration.
11
10
  * @returns the resulting settings
12
11
  */
13
- export function searchForConfig(searchFrom, pnpSettings = defaultPnPSettings) {
14
- return gcl().searchForConfig(searchFrom, pnpSettings);
12
+ export function searchForConfig(searchFrom, options) {
13
+ return gcl().searchForConfig(searchFrom, options);
15
14
  }
16
15
  /**
17
16
  * Load a CSpell configuration files.
@@ -29,6 +29,7 @@ export declare function normalizeDictionaryDefs(settings: NormalizeDictionaryDef
29
29
  ignoreWords?: string[];
30
30
  suggestWords?: string[];
31
31
  parser?: import("@cspell/cspell-types").ParserName;
32
+ unknownWords?: import("@cspell/cspell-types").UnknownWordsChoices | undefined;
32
33
  }>[] | undefined;
33
34
  }>;
34
35
  type NormalizeOverrides = Pick<CSpellUserSettings, 'globRoot' | 'overrides'>;
@@ -26,6 +26,7 @@ export declare class DictionaryLoader {
26
26
  private isEqual;
27
27
  private normalizeOptions;
28
28
  private loadInlineDict;
29
+ private loadSimpleDict;
29
30
  private calcKey;
30
31
  }
31
32
  //# sourceMappingURL=DictionaryLoader.d.ts.map
@@ -2,7 +2,7 @@ import { opConcatMap, opFilter, opMap, pipe } from '@cspell/cspell-pipe/sync';
2
2
  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
- import { isDictionaryDefinitionInlineInternal } from '../../Models/CSpellSettingsInternalDef.js';
5
+ import { isDictionaryDefinitionInlineInternal, isDictionaryFileDefinitionInternal, } from '../../Models/CSpellSettingsInternalDef.js';
6
6
  import { AutoResolveWeakCache, AutoResolveWeakWeakCache } from '../../util/AutoResolve.js';
7
7
  import { toError } from '../../util/errors.js';
8
8
  import { SimpleCache } from '../../util/simpleCache.js';
@@ -37,14 +37,17 @@ export class DictionaryLoader {
37
37
  if (isDictionaryDefinitionInlineInternal(def)) {
38
38
  return Promise.resolve(this.loadInlineDict(def));
39
39
  }
40
- const { key, entry } = this.getCacheEntry(def);
41
- if (entry) {
42
- return entry.pending.then(([dictionary]) => dictionary);
40
+ if (isDictionaryFileDefinitionInternal(def)) {
41
+ const { key, entry } = this.getCacheEntry(def);
42
+ if (entry) {
43
+ return entry.pending.then(([dictionary]) => dictionary);
44
+ }
45
+ const loadedEntry = this.loadEntry(def.path, def);
46
+ this.setCacheEntry(key, loadedEntry, def);
47
+ this.keepAliveCache.set(def, loadedEntry);
48
+ return loadedEntry.pending.then(([dictionary]) => dictionary);
43
49
  }
44
- const loadedEntry = this.loadEntry(def.path, def);
45
- this.setCacheEntry(key, loadedEntry, def);
46
- this.keepAliveCache.set(def, loadedEntry);
47
- return loadedEntry.pending.then(([dictionary]) => dictionary);
50
+ return Promise.resolve(this.loadSimpleDict(def));
48
51
  }
49
52
  /**
50
53
  * Check to see if any of the cached dictionaries have changed. If one has changed, reload it.
@@ -138,6 +141,9 @@ export class DictionaryLoader {
138
141
  loadInlineDict(def) {
139
142
  return this.inlineDictionaryCache.get(def, (def) => createInlineSpellingDictionary(def, def.__source || 'memory'));
140
143
  }
144
+ loadSimpleDict(def) {
145
+ return createInlineSpellingDictionary({ name: def.name, words: [] }, def.__source || 'memory');
146
+ }
141
147
  calcKey(def) {
142
148
  const path = def.path;
143
149
  const loaderType = determineType(toFileURL(path), def);
@@ -3,7 +3,7 @@ import { 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';
6
- export interface SpellCheckFileOptions extends ValidateTextOptions {
6
+ export interface SpellCheckFileOptions extends ValidateTextOptions, Pick<CSpellUserSettings, 'unknownWords'> {
7
7
  /**
8
8
  * Optional path to a configuration file.
9
9
  * If given, it will be used instead of searching for a configuration file.
@@ -1,9 +1,8 @@
1
- import type { MappedText, TextOffset as TextOffsetRW } from '@cspell/cspell-types';
2
- import type { ExtendedSuggestion } from '../Models/Suggestion.js';
1
+ import type { Issue, MappedText, ReportingConfiguration, TextOffset as TextOffsetRW } from '@cspell/cspell-types';
3
2
  import type { ValidationIssue } from '../Models/ValidationIssue.js';
4
3
  export type { TextOffset as TextOffsetRW } from '@cspell/cspell-types';
5
4
  export type TextOffsetRO = Readonly<TextOffsetRW>;
6
- export interface ValidationOptions extends IncludeExcludeOptions {
5
+ export interface ValidationOptions extends IncludeExcludeOptions, ReportingConfiguration {
7
6
  maxNumberOfProblems?: number;
8
7
  maxDuplicateProblems?: number;
9
8
  minWordLength?: number;
@@ -35,10 +34,8 @@ export interface LineSegment {
35
34
  /** A segment of text from the line, the offset is relative to the beginning of the document. */
36
35
  segment: TextOffsetRO;
37
36
  }
38
- export interface MappedTextValidationResult extends MappedText {
39
- isFlagged?: boolean | undefined;
37
+ export interface MappedTextValidationResult extends MappedText, Pick<Issue, 'hasSimpleSuggestions' | 'hasPreferredSuggestions' | 'isFlagged' | 'suggestionsEx'> {
40
38
  isFound?: boolean | undefined;
41
- suggestionsEx?: ExtendedSuggestion[] | undefined;
42
39
  }
43
40
  export type TextValidatorFn = (text: MappedText) => Iterable<MappedTextValidationResult>;
44
41
  //# sourceMappingURL=ValidationTypes.d.ts.map
@@ -174,14 +174,24 @@ export class DocumentValidator {
174
174
  const document = this._document;
175
175
  let line = undefined;
176
176
  function mapToIssue(issue) {
177
- const { range, text, isFlagged, isFound, suggestionsEx } = issue;
177
+ const { range, text, isFlagged, isFound, suggestionsEx, hasPreferredSuggestions, hasSimpleSuggestions } = issue;
178
178
  const offset = range[0];
179
179
  const length = range[1] - range[0];
180
180
  assert(!line || line.offset <= offset);
181
181
  if (!line || line.offset + line.text.length <= offset) {
182
182
  line = document.lineAt(offset);
183
183
  }
184
- return { text, offset, line, length, isFlagged, isFound, suggestionsEx };
184
+ return {
185
+ text,
186
+ offset,
187
+ line,
188
+ length,
189
+ isFlagged,
190
+ isFound,
191
+ suggestionsEx,
192
+ hasPreferredSuggestions,
193
+ hasSimpleSuggestions,
194
+ };
185
195
  }
186
196
  const issues = [...pipeSync(segmenter(parsedText), opConcatMap(textValidator.validate), opMap(mapToIssue))];
187
197
  if (!this.options.generateSuggestions) {
@@ -1,8 +1,9 @@
1
1
  import assert from 'node:assert';
2
2
  import { opConcatMap, opFilter, pipe } from '@cspell/cspell-pipe/sync';
3
- import { defaultCSpellSettings } from '@cspell/cspell-types';
3
+ import { defaultCSpellSettings, unknownWordsChoices } from '@cspell/cspell-types';
4
4
  import { createCachingDictionary } from 'cspell-dictionary';
5
5
  import * as RxPat from '../Settings/RegExpPatterns.js';
6
+ import { autoResolve } from '../util/AutoResolve.js';
6
7
  import { extractPossibleWordsFromTextOffset, extractText, extractWordsFromTextOffset, splitWordWithOffset, } from '../util/text.js';
7
8
  import { regExpCamelCaseWordBreaksWithEnglishSuffix } from '../util/textRegex.js';
8
9
  import { split } from '../util/wordSplitter.js';
@@ -12,7 +13,7 @@ import { isWordValidWithEscapeRetry } from './isWordValid.js';
12
13
  import { mapRangeBackToOriginalPos } from './parsedText.js';
13
14
  const MIN_HEX_SEQUENCE_LENGTH = 8;
14
15
  export function lineValidatorFactory(sDict, options) {
15
- const { minWordLength = defaultMinWordLength, flagWords = [], allowCompoundWords = false, ignoreCase = true, ignoreRandomStrings = defaultCSpellSettings.ignoreRandomStrings, minRandomLength = defaultCSpellSettings.minRandomLength, } = options;
16
+ const { minWordLength = defaultMinWordLength, flagWords = [], allowCompoundWords = false, ignoreCase = true, ignoreRandomStrings = defaultCSpellSettings.ignoreRandomStrings, minRandomLength = defaultCSpellSettings.minRandomLength, unknownWords = unknownWordsChoices.ReportAll, } = options;
16
17
  const hasWordOptions = {
17
18
  ignoreCase,
18
19
  useCompounds: allowCompoundWords || undefined, // let the dictionaries decide on useCompounds if allow is false
@@ -61,8 +62,23 @@ export function lineValidatorFactory(sDict, options) {
61
62
  function isWordIgnored(word) {
62
63
  return calcIgnored(getWordInfo(word));
63
64
  }
64
- function getSuggestions(word) {
65
- return dictCol.getPreferredSuggestions(word);
65
+ const cacheGetPreferredSuggestions = new Map();
66
+ function getPreferredSuggestions(word) {
67
+ return autoResolve(cacheGetPreferredSuggestions, word, () => dictCol.getPreferredSuggestions(word));
68
+ }
69
+ const cacheHasSimpleSuggestions = new Map();
70
+ function hasSimpleSuggestions(word) {
71
+ return autoResolve(cacheHasSimpleSuggestions, word, () => {
72
+ const sugs = dictCol.suggest(word, {
73
+ numSuggestions: 1,
74
+ compoundMethod: 0,
75
+ includeTies: false,
76
+ ignoreCase,
77
+ timeout: 100,
78
+ numChanges: 1.8, // Only consider very simple changes (1 edit distance plus case changes)
79
+ });
80
+ return !!sugs.length;
81
+ });
66
82
  }
67
83
  function isWordFlagged(wo) {
68
84
  return calcFlagged(getWordInfo(wo.text));
@@ -72,10 +88,17 @@ export function lineValidatorFactory(sDict, options) {
72
88
  return word;
73
89
  }
74
90
  function annotateIssue(issue) {
75
- const sugs = getSuggestions(issue.text);
76
- if (sugs && sugs.length) {
77
- issue.suggestionsEx = sugs;
91
+ const sugs = getPreferredSuggestions(issue.text);
92
+ if (!sugs?.length) {
93
+ issue.hasPreferredSuggestions = sugs !== undefined ? false : undefined;
94
+ if (unknownWords === unknownWordsChoices.ReportSimple) {
95
+ issue.hasSimpleSuggestions = hasSimpleSuggestions(issue.text);
96
+ }
97
+ return issue;
78
98
  }
99
+ issue.suggestionsEx = sugs;
100
+ issue.hasPreferredSuggestions = true;
101
+ issue.hasSimpleSuggestions = true;
79
102
  return issue;
80
103
  }
81
104
  const isFlaggedOrMinLength = (wo) => wo.text.length >= minWordLength || !!wo.isFlagged;
@@ -274,14 +297,14 @@ export function lineValidatorFactory(sDict, options) {
274
297
  if (!mismatches.length)
275
298
  return mismatches;
276
299
  const hexSequences = !ignoreRandomStrings
277
- ? []
300
+ ? undefined
278
301
  : extractHexSequences(possibleWord.text, MIN_HEX_SEQUENCE_LENGTH)
279
302
  .filter(
280
303
  // Only consider hex sequences that are all upper case or all lower case and contain a `-` or a digit.
281
304
  (w) => (w.text === w.text.toLowerCase() || w.text === w.text.toUpperCase()) &&
282
305
  /[\d-]/.test(w.text))
283
306
  .map((w) => ((w.offset += possibleWord.offset), w));
284
- if (hexSequences.length) {
307
+ if (hexSequences?.length) {
285
308
  mismatches = filterExcludedTextOffsets(mismatches, hexSequences);
286
309
  }
287
310
  if (mismatches.length) {
@@ -330,10 +353,10 @@ export function textValidatorFactory(dict, options) {
330
353
  const segment = { text, offset: 0 };
331
354
  const lineSegment = { line: segment, segment };
332
355
  function mapBackToOriginSimple(vr) {
333
- const { text, offset, isFlagged, isFound, suggestionsEx } = vr;
356
+ const { text, offset, isFlagged, isFound, suggestionsEx, hasPreferredSuggestions, hasSimpleSuggestions } = vr;
334
357
  const r = mapRangeBackToOriginalPos([offset, offset + text.length], map);
335
358
  const range = [r[0] + srcOffset, r[1] + srcOffset];
336
- return { text, range, isFlagged, isFound, suggestionsEx };
359
+ return { text, range, isFlagged, isFound, suggestionsEx, hasPreferredSuggestions, hasSimpleSuggestions };
337
360
  }
338
361
  return [...lineValidatorFn(lineSegment)].map(mapBackToOriginSimple);
339
362
  }
@@ -343,7 +366,7 @@ export function textValidatorFactory(dict, options) {
343
366
  };
344
367
  }
345
368
  function filterExcludedTextOffsets(issues, excluded) {
346
- if (!excluded.length)
369
+ if (!excluded?.length)
347
370
  return issues;
348
371
  const keep = [];
349
372
  let i = 0;
@@ -4,6 +4,7 @@ export function settingsToValidateOptions(settings) {
4
4
  ignoreCase: !(settings.caseSensitive ?? false),
5
5
  ignoreRandomStrings: settings.ignoreRandomStrings,
6
6
  minRandomLength: settings.minRandomLength,
7
+ unknownWords: settings.unknownWords || 'report-all',
7
8
  };
8
9
  return opt;
9
10
  }
@@ -3,7 +3,7 @@ type EntryType = 'file' | 'directory' | '!file' | '!directory';
3
3
  export type FindUpFileSystem = Pick<VFileSystem, 'findUp'>;
4
4
  export interface FindUpURLOptions {
5
5
  type?: EntryType;
6
- stopAt?: URL;
6
+ stopAt?: URL | URL[];
7
7
  fs?: FindUpFileSystem;
8
8
  }
9
9
  export type FindUpPredicate = (dir: URL) => URL | undefined | Promise<URL | undefined>;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "provenance": true
6
6
  },
7
- "version": "9.0.2",
7
+ "version": "9.1.1",
8
8
  "description": "A library of useful functions used across various cspell tools.",
9
9
  "type": "module",
10
10
  "sideEffects": false,
@@ -64,22 +64,22 @@
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.0.2",
68
- "@cspell/cspell-pipe": "9.0.2",
69
- "@cspell/cspell-resolver": "9.0.2",
70
- "@cspell/cspell-types": "9.0.2",
71
- "@cspell/dynamic-import": "9.0.2",
72
- "@cspell/filetypes": "9.0.2",
73
- "@cspell/strong-weak-map": "9.0.2",
74
- "@cspell/url": "9.0.2",
67
+ "@cspell/cspell-bundled-dicts": "9.1.1",
68
+ "@cspell/cspell-pipe": "9.1.1",
69
+ "@cspell/cspell-resolver": "9.1.1",
70
+ "@cspell/cspell-types": "9.1.1",
71
+ "@cspell/dynamic-import": "9.1.1",
72
+ "@cspell/filetypes": "9.1.1",
73
+ "@cspell/strong-weak-map": "9.1.1",
74
+ "@cspell/url": "9.1.1",
75
75
  "clear-module": "^4.1.2",
76
76
  "comment-json": "^4.2.5",
77
- "cspell-config-lib": "9.0.2",
78
- "cspell-dictionary": "9.0.2",
79
- "cspell-glob": "9.0.2",
80
- "cspell-grammar": "9.0.2",
81
- "cspell-io": "9.0.2",
82
- "cspell-trie-lib": "9.0.2",
77
+ "cspell-config-lib": "9.1.1",
78
+ "cspell-dictionary": "9.1.1",
79
+ "cspell-glob": "9.1.1",
80
+ "cspell-grammar": "9.1.1",
81
+ "cspell-io": "9.1.1",
82
+ "cspell-trie-lib": "9.1.1",
83
83
  "env-paths": "^3.0.0",
84
84
  "fast-equals": "^5.2.2",
85
85
  "gensequence": "^7.0.0",
@@ -108,5 +108,5 @@
108
108
  "lorem-ipsum": "^2.0.8",
109
109
  "perf-insight": "^1.2.0"
110
110
  },
111
- "gitHead": "39dbd9ab9b8943a023d9eda7f65f81e822f939b5"
111
+ "gitHead": "40c6e1a331f38a0c008aacf2cb527270752c35ad"
112
112
  }