cspell-dictionary 9.4.0 → 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.
@@ -1,3 +1,4 @@
1
+ import type { SuggestionResult } from 'cspell-trie-lib';
1
2
  import type { CacheStats } from '../util/AutoCache.js';
2
3
  import type { PreferredSuggestion, SearchOptions, SpellingDictionary } from './SpellingDictionary.js';
3
4
  import type { SpellingDictionaryCollection } from './SpellingDictionaryCollection.js';
@@ -21,7 +22,7 @@ export interface CachingDictionary {
21
22
  isForbidden(word: string): boolean;
22
23
  stats(): CallStats;
23
24
  getPreferredSuggestions(word: string): PreferredSuggestion[] | undefined;
24
- suggest(word: string, suggestOptions?: SuggestOptionsRO): import('cspell-trie-lib').SuggestionResult[];
25
+ suggest(word: string, suggestOptions?: SuggestOptionsRO): SuggestionResult[];
25
26
  }
26
27
  interface LogEntryBase extends SearchOptions {
27
28
  time: number;
@@ -32,6 +33,7 @@ interface LogEntryBase extends SearchOptions {
32
33
  interface LogEntryHas extends LogEntryBase {
33
34
  method: 'has';
34
35
  value: boolean;
36
+ miss: boolean;
35
37
  }
36
38
  export type LogEntry = LogEntryHas;
37
39
  /**
@@ -41,7 +43,23 @@ export type LogEntry = LogEntryHas;
41
43
  * @returns CachingDictionary
42
44
  */
43
45
  export declare function createCachingDictionary(dict: SpellingDictionary | SpellingDictionaryCollection, options: SearchOptions): CachingDictionary;
44
- export declare function enableLogging(enabled?: boolean): void;
45
- export declare function getLog(): LogEntryBase[];
46
+ /**
47
+ * Enable or disable logging of dictionary requests. Every call to `has` will be logged.
48
+ *
49
+ * This should be set prior to creating any caching dictionaries to ensure all requests are logged.
50
+ *
51
+ * @param enabled - optional - if undefined, it will toggle the setting.
52
+ * @returns the current state of logging.
53
+ */
54
+ export declare function dictionaryCacheEnableLogging(enabled?: boolean): boolean;
55
+ /**
56
+ * Get the log of dictionary requests.
57
+ * @returns the log
58
+ */
59
+ export declare function dictionaryCacheGetLog(): readonly Readonly<LogEntryBase>[];
60
+ /**
61
+ * Clear the log of dictionary requests.
62
+ */
63
+ export declare function dictionaryCacheClearLog(): void;
46
64
  export {};
47
65
  //# sourceMappingURL=CachingDictionary.d.ts.map
@@ -19,8 +19,12 @@ class CachedDict {
19
19
  const has = autoCache((word) => this.dict.has(word, this.options), DefaultAutoCacheSize);
20
20
  const hasAndLog = (word) => {
21
21
  const time = performance.now() - startTime;
22
+ const misses = has.misses;
22
23
  const value = has(word);
23
- log.push({ time, method: 'has', word, value });
24
+ if (logRequests) {
25
+ const miss = has.misses > misses;
26
+ log.push({ time, method: 'has', word, value, miss });
27
+ }
24
28
  return value;
25
29
  };
26
30
  this.#has = has;
@@ -63,10 +67,32 @@ export function createCachingDictionary(dict, options) {
63
67
  knownOptions.set(dict, cached);
64
68
  return cached;
65
69
  }
66
- export function enableLogging(enabled = !logRequests) {
70
+ /**
71
+ * Enable or disable logging of dictionary requests. Every call to `has` will be logged.
72
+ *
73
+ * This should be set prior to creating any caching dictionaries to ensure all requests are logged.
74
+ *
75
+ * @param enabled - optional - if undefined, it will toggle the setting.
76
+ * @returns the current state of logging.
77
+ */
78
+ export function dictionaryCacheEnableLogging(enabled = !logRequests) {
79
+ if (enabled && !logRequests) {
80
+ knownDicts.clear();
81
+ }
67
82
  logRequests = enabled;
83
+ return logRequests;
68
84
  }
69
- export function getLog() {
85
+ /**
86
+ * Get the log of dictionary requests.
87
+ * @returns the log
88
+ */
89
+ export function dictionaryCacheGetLog() {
70
90
  return log;
71
91
  }
92
+ /**
93
+ * Clear the log of dictionary requests.
94
+ */
95
+ export function dictionaryCacheClearLog() {
96
+ log.length = 0;
97
+ }
72
98
  //# sourceMappingURL=CachingDictionary.js.map
@@ -1,4 +1,59 @@
1
- import type { SpellingDictionary } from './SpellingDictionary.js';
1
+ import type { CompoundWordsMethod, ITrie, SuggestionResult } from 'cspell-trie-lib';
2
+ import type { FindResult, HasOptions, IgnoreCaseOption, PreferredSuggestion, SpellingDictionary, SpellingDictionaryOptions } from './SpellingDictionary.js';
3
+ import { SpellingDictionaryFromTrie } from './SpellingDictionaryFromTrie.js';
4
+ import type { SuggestOptions } from './SuggestOptions.js';
5
+ import type { TyposDictionary } from './TyposDictionary.js';
6
+ export declare class FlagWordsDictionaryTrie extends SpellingDictionaryFromTrie {
7
+ readonly name: string;
8
+ readonly source: string;
9
+ readonly containsNoSuggestWords = false;
10
+ readonly options: SpellingDictionaryOptions;
11
+ constructor(trie: ITrie, name: string, source: string);
12
+ /**
13
+ * A Forbidden word list does not "have" valid words.
14
+ * Therefore it always returns false.
15
+ * @param _word - the word
16
+ * @param _options - options
17
+ * @returns always false
18
+ */
19
+ has(_word: string, _options?: HasOptions): boolean;
20
+ find(word: string, hasOptions?: HasOptions): FindResult | undefined;
21
+ suggest(word: string, numSuggestions?: number, compoundMethod?: CompoundWordsMethod, numChanges?: number, ignoreCase?: boolean): SuggestionResult[];
22
+ suggest(word: string, suggestOptions: SuggestOptions): SuggestionResult[];
23
+ genSuggestions(): void;
24
+ readonly isDictionaryCaseSensitive: boolean;
25
+ terms(): Iterable<string>;
26
+ }
27
+ export declare class FlagWordsDictionary implements SpellingDictionary {
28
+ readonly name: string;
29
+ readonly source: string;
30
+ private dictTypos;
31
+ private dictTrie;
32
+ readonly containsNoSuggestWords = false;
33
+ readonly options: SpellingDictionaryOptions;
34
+ readonly type = "flag-words";
35
+ readonly mapWord: undefined;
36
+ constructor(name: string, source: string, dictTypos: TyposDictionary, dictTrie: FlagWordsDictionaryTrie | undefined);
37
+ /**
38
+ * A Forbidden word list does not "have" valid words.
39
+ * Therefore it always returns false.
40
+ * @param word - the word
41
+ * @param options - options
42
+ * @returns always false
43
+ */
44
+ has(word: string, options?: HasOptions): boolean;
45
+ /** A more detailed search for a word, might take longer than `has` */
46
+ find(word: string, options?: HasOptions): FindResult | undefined;
47
+ isForbidden(word: string, ignoreCaseAndAccents?: IgnoreCaseOption): boolean;
48
+ isNoSuggestWord(word: string, options: HasOptions): boolean;
49
+ suggest(word: string, suggestOptions?: SuggestOptions): SuggestionResult[];
50
+ getPreferredSuggestions(word: string): PreferredSuggestion[];
51
+ genSuggestions(): void;
52
+ get size(): number;
53
+ readonly isDictionaryCaseSensitive: boolean;
54
+ getErrors?(): Error[];
55
+ terms(): Iterable<string>;
56
+ }
2
57
  /**
3
58
  * Create a dictionary where all words are to be forbidden.
4
59
  * @param wordList - list of words
@@ -1,11 +1,10 @@
1
- import { opMap, pipe } from '@cspell/cspell-pipe/sync';
2
- import { buildITrieFromWords, parseDictionaryLines } from 'cspell-trie-lib';
1
+ import { parseDictionary, parseDictionaryLines } from 'cspell-trie-lib';
3
2
  import { createAutoResolveWeakCache } from '../util/AutoResolve.js';
4
3
  import * as Defaults from './defaults.js';
5
4
  import { defaultOptions } from './SpellingDictionary.js';
6
5
  import { SpellingDictionaryFromTrie } from './SpellingDictionaryFromTrie.js';
7
6
  import { createTyposDictionary } from './TyposDictionary.js';
8
- class FlagWordsDictionaryTrie extends SpellingDictionaryFromTrie {
7
+ export class FlagWordsDictionaryTrie extends SpellingDictionaryFromTrie {
9
8
  name;
10
9
  source;
11
10
  containsNoSuggestWords = false;
@@ -38,8 +37,11 @@ class FlagWordsDictionaryTrie extends SpellingDictionaryFromTrie {
38
37
  return;
39
38
  }
40
39
  isDictionaryCaseSensitive = true;
40
+ terms() {
41
+ return this.trie.words();
42
+ }
41
43
  }
42
- class FlagWordsDictionary {
44
+ export class FlagWordsDictionary {
43
45
  name;
44
46
  source;
45
47
  dictTypos;
@@ -47,6 +49,7 @@ class FlagWordsDictionary {
47
49
  containsNoSuggestWords = false;
48
50
  options = {};
49
51
  type = 'flag-words';
52
+ mapWord = undefined;
50
53
  constructor(name, source, dictTypos, dictTrie) {
51
54
  this.name = name;
52
55
  this.source = source;
@@ -89,9 +92,6 @@ class FlagWordsDictionary {
89
92
  genSuggestions() {
90
93
  return;
91
94
  }
92
- mapWord(word) {
93
- return word;
94
- }
95
95
  get size() {
96
96
  return this.dictTypos.size + (this.dictTrie?.size || 0);
97
97
  }
@@ -99,6 +99,13 @@ class FlagWordsDictionary {
99
99
  getErrors() {
100
100
  return [];
101
101
  }
102
+ *terms() {
103
+ if (this.dictTrie) {
104
+ yield* this.dictTrie.terms();
105
+ return;
106
+ }
107
+ return;
108
+ }
102
109
  }
103
110
  const createCache = createAutoResolveWeakCache();
104
111
  /**
@@ -113,18 +120,14 @@ export function createFlagWordsDictionary(wordList, name, source) {
113
120
  return createCache.get(wordList, () => {
114
121
  const testSpecialCharacters = /[~*+]/;
115
122
  const { t: specialWords, f: typoWords } = bisect(parseDictionaryLines(wordList, { stripCaseAndAccents: false }), (line) => testSpecialCharacters.test(line));
116
- const trieDict = specialWords.size ? buildTrieDict(specialWords, name, source) : undefined;
123
+ const trie = parseDictionary(specialWords, { stripCaseAndAccents: false, makeWordsForbidden: true });
124
+ const trieDict = new FlagWordsDictionaryTrie(trie, name, source);
117
125
  const typosDict = createTyposDictionary(typoWords, name, source);
118
- if (!trieDict)
126
+ if (!specialWords.size)
119
127
  return typosDict;
120
128
  return new FlagWordsDictionary(name, source, typosDict, trieDict);
121
129
  });
122
130
  }
123
- const regExpCleanIgnore = /^(!!)+/;
124
- function buildTrieDict(words, name, source) {
125
- const trie = buildITrieFromWords(pipe(words, opMap((w) => '!' + w), opMap((w) => w.replace(regExpCleanIgnore, ''))));
126
- return new FlagWordsDictionaryTrie(trie, name, source);
127
- }
128
131
  function bisect(values, predicate) {
129
132
  const t = new Set();
130
133
  const f = new Set();
@@ -12,6 +12,7 @@ class IgnoreWordsDictionary {
12
12
  containsNoSuggestWords = true;
13
13
  options = {};
14
14
  type = 'ignore';
15
+ mapWord = undefined;
15
16
  constructor(name, source, words) {
16
17
  this.name = name;
17
18
  this.source = source;
@@ -62,9 +63,6 @@ class IgnoreWordsDictionary {
62
63
  genSuggestions() {
63
64
  return;
64
65
  }
65
- mapWord(word) {
66
- return word;
67
- }
68
66
  get size() {
69
67
  return this.dict.size;
70
68
  }
@@ -15,7 +15,12 @@ export interface SearchOptions {
15
15
  ignoreCase?: boolean | undefined;
16
16
  }
17
17
  export type SearchOptionsRO = Readonly<SearchOptions>;
18
- export type FindOptions = SearchOptions;
18
+ export interface FindOptions extends SearchOptions {
19
+ /**
20
+ * Separate compound words using the specified separator.
21
+ */
22
+ compoundSeparator?: string | undefined;
23
+ }
19
24
  export type FindOptionsRO = Readonly<FindOptions>;
20
25
  export interface Suggestion {
21
26
  word: string;
@@ -86,12 +91,14 @@ export interface DictionaryInfo {
86
91
  /** Options */
87
92
  readonly options: SpellingDictionaryOptions;
88
93
  }
94
+ export type MapWordSingleFn = (word: string) => string;
95
+ export type MapWordMultipleFn = (word: string) => string[];
89
96
  export interface SpellingDictionary extends DictionaryInfo {
90
97
  readonly type: string;
91
98
  readonly containsNoSuggestWords: boolean;
92
99
  has(word: string, options?: HasOptionsRO): boolean;
93
100
  /** A more detailed search for a word, might take longer than `has` */
94
- find(word: string, options?: SearchOptionsRO): FindResult | undefined;
101
+ find(word: string, options?: FindOptionsRO): FindResult | undefined;
95
102
  /**
96
103
  * Checks if a word is forbidden.
97
104
  * @param word - word to check.
@@ -113,17 +120,32 @@ export interface SpellingDictionary extends DictionaryInfo {
113
120
  suggest(word: string, suggestOptions?: SuggestOptionsRO): SuggestionResult[];
114
121
  getPreferredSuggestions?: (word: string) => PreferredSuggestion[];
115
122
  genSuggestions(collector: SuggestionCollector, suggestOptions: SuggestOptionsRO): void;
116
- mapWord(word: string): string;
123
+ mapWord?: MapWordSingleFn | undefined;
117
124
  /**
118
125
  * Generates all possible word combinations by applying `repMap`.
119
126
  * This acts a bit like brace expansions in globs.
120
127
  * @param word - the word to map
121
128
  * @returns array of adjusted words.
122
129
  */
123
- remapWord?: (word: string) => string[];
130
+ remapWord?: MapWordMultipleFn | undefined;
124
131
  readonly size: number;
125
132
  readonly isDictionaryCaseSensitive: boolean;
126
133
  getErrors?(): Error[];
134
+ /**
135
+ * Get all the terms in the dictionary, they may be formatted according to the dictionary options.
136
+ * @returns the terms in the dictionary.
137
+ */
138
+ terms?: () => Iterable<string>;
139
+ }
140
+ export interface SuggestDictionary extends SpellingDictionary {
141
+ getPreferredSuggestions: (word: string) => PreferredSuggestion[];
142
+ /**
143
+ * Determine if the word can appear in a list of suggestions.
144
+ * @param word - word
145
+ * @param ignoreCaseAndAccents - ignore case.
146
+ * @returns true if a word is suggested, otherwise false.
147
+ */
148
+ isSuggestedWord(word: string, ignoreCaseAndAccents?: IgnoreCaseOption): boolean;
127
149
  }
128
150
  export declare const defaultOptions: SpellingDictionaryOptions;
129
151
  //# sourceMappingURL=SpellingDictionary.d.ts.map
@@ -2,14 +2,11 @@ import { CASE_INSENSITIVE_PREFIX, CompoundWordsMethod } from 'cspell-trie-lib';
2
2
  import { isDefined } from '../util/util.js';
3
3
  import * as Defaults from './defaults.js';
4
4
  import { defaultNumSuggestions, hasOptionToSearchOption, suggestionCollector } from './SpellingDictionaryMethods.js';
5
- function identityString(w) {
6
- return w;
7
- }
8
5
  class SpellingDictionaryCollectionImpl {
9
6
  dictionaries;
10
7
  name;
11
8
  options = { weightMap: undefined };
12
- mapWord = identityString;
9
+ mapWord = undefined;
13
10
  type = 'SpellingDictionaryCollection';
14
11
  source;
15
12
  isDictionaryCaseSensitive;
@@ -1,6 +1,6 @@
1
- import type { Buffer } from 'node:buffer';
2
1
  import type { ITrie, SuggestionCollector, SuggestionResult } from 'cspell-trie-lib';
3
- import type { FindResult, HasOptionsRO, SpellingDictionary, SpellingDictionaryOptionsRO } from './SpellingDictionary.js';
2
+ import type { RepMapper } from '../util/repMap.js';
3
+ import type { FindOptionsRO, FindResult, HasOptionsRO, MapWordMultipleFn, MapWordSingleFn, PreferredSuggestion, SpellingDictionary, SpellingDictionaryOptionsRO } from './SpellingDictionary.js';
4
4
  import type { SuggestOptions } from './SuggestOptions.js';
5
5
  export declare class SpellingDictionaryFromTrie implements SpellingDictionary {
6
6
  #private;
@@ -11,8 +11,9 @@ export declare class SpellingDictionaryFromTrie implements SpellingDictionary {
11
11
  private _size;
12
12
  readonly knownWords: Set<string>;
13
13
  readonly unknownWords: Set<string>;
14
- readonly mapWord: (word: string) => string;
15
- readonly remapWord: (word: string) => string[];
14
+ readonly mapWord: MapWordSingleFn | undefined;
15
+ readonly remapWord: MapWordMultipleFn | undefined;
16
+ readonly repMapper: RepMapper | undefined;
16
17
  readonly type = "SpellingDictionaryFromTrie";
17
18
  readonly isDictionaryCaseSensitive: boolean;
18
19
  readonly containsNoSuggestWords: boolean;
@@ -20,7 +21,7 @@ export declare class SpellingDictionaryFromTrie implements SpellingDictionary {
20
21
  constructor(trie: ITrie, name: string, options: SpellingDictionaryOptionsRO, source?: string, size?: number);
21
22
  get size(): number;
22
23
  has(word: string, hasOptions?: HasOptionsRO): boolean;
23
- find(word: string, hasOptions?: HasOptionsRO): FindResult | undefined;
24
+ find(word: string, hasOptions?: FindOptionsRO): FindResult | undefined;
24
25
  private resolveOptions;
25
26
  private _find;
26
27
  private findAnyForm;
@@ -30,6 +31,7 @@ export declare class SpellingDictionaryFromTrie implements SpellingDictionary {
30
31
  suggest(word: string, suggestOptions?: SuggestOptions): SuggestionResult[];
31
32
  private _suggest;
32
33
  genSuggestions(collector: SuggestionCollector, suggestOptions: SuggestOptions): void;
34
+ getPreferredSuggestions(word: string): PreferredSuggestion[];
33
35
  getErrors(): Error[];
34
36
  }
35
37
  /**
@@ -40,8 +42,8 @@ export declare class SpellingDictionaryFromTrie implements SpellingDictionary {
40
42
  * @param options - options.
41
43
  * @returns SpellingDictionary
42
44
  */
43
- export declare function createSpellingDictionaryFromTrieFile(data: string | Buffer, name: string, source: string, options: SpellingDictionaryOptionsRO): SpellingDictionary;
44
- declare function outerWordForms(word: string, mapWord: (word: string) => string[]): Iterable<string>;
45
+ export declare function createSpellingDictionaryFromTrieFile(data: string | Uint8Array<ArrayBuffer>, name: string, source: string, options: SpellingDictionaryOptionsRO): SpellingDictionary;
46
+ declare function outerWordForms(word: string, repMapper: RepMapper | undefined): Iterable<string>;
45
47
  export declare const __testing__: {
46
48
  outerWordForms: typeof outerWordForms;
47
49
  };
@@ -1,5 +1,6 @@
1
1
  import { CompoundWordsMethod, decodeTrie, suggestionCollector } from 'cspell-trie-lib';
2
2
  import { clean } from '../util/clean.js';
3
+ import { measurePerf } from '../util/performance.js';
3
4
  import { createMapper, createRepMapper } from '../util/repMap.js';
4
5
  import * as Defaults from './defaults.js';
5
6
  import { createWeightMapFromDictionaryInformation, defaultNumSuggestions, hasOptionToSearchOption, impersonateCollector, wordSearchForms, wordSuggestForms, } from './SpellingDictionaryMethods.js';
@@ -13,6 +14,7 @@ export class SpellingDictionaryFromTrie {
13
14
  unknownWords = new Set();
14
15
  mapWord;
15
16
  remapWord;
17
+ repMapper;
16
18
  type = 'SpellingDictionaryFromTrie';
17
19
  isDictionaryCaseSensitive;
18
20
  containsNoSuggestWords;
@@ -25,9 +27,12 @@ export class SpellingDictionaryFromTrie {
25
27
  this.name = name;
26
28
  this.options = options;
27
29
  this.source = source;
28
- this.mapWord = createMapper(options.repMap, options.dictionaryInformation?.ignore);
29
- this.remapWord = createRepMapper(options.repMap, options.dictionaryInformation?.ignore);
30
- this.isDictionaryCaseSensitive = options.caseSensitive ?? trie.isCaseAware;
30
+ const mapWord = createMapper(options.repMap, options.dictionaryInformation?.ignore);
31
+ const repMapper = createRepMapper(options.repMap, options.dictionaryInformation?.ignore);
32
+ this.mapWord = mapWord?.fn;
33
+ this.remapWord = repMapper?.fn;
34
+ this.repMapper = repMapper;
35
+ this.isDictionaryCaseSensitive = options.caseSensitive ?? true;
31
36
  this.containsNoSuggestWords = options.noSuggest || false;
32
37
  this._size = size || 0;
33
38
  this.weightMap = options.weightMap || createWeightMapFromDictionaryInformation(options.dictionaryInformation);
@@ -55,12 +60,12 @@ export class SpellingDictionaryFromTrie {
55
60
  }
56
61
  has(word, hasOptions) {
57
62
  const { useCompounds, ignoreCase } = this.resolveOptions(hasOptions);
58
- const r = this._find(word, useCompounds, ignoreCase);
63
+ const r = this._find(word, useCompounds, ignoreCase, undefined);
59
64
  return (r && !r.forbidden && !!r.found) || false;
60
65
  }
61
66
  find(word, hasOptions) {
62
67
  const { useCompounds, ignoreCase } = this.resolveOptions(hasOptions);
63
- const r = this._find(word, useCompounds, ignoreCase);
68
+ const r = this._find(word, useCompounds, ignoreCase, hasOptions?.compoundSeparator);
64
69
  const { forbidden = this.#isForbidden(word) } = r || {};
65
70
  if (this.#ignoreForbiddenWords && forbidden) {
66
71
  return undefined;
@@ -75,20 +80,23 @@ export class SpellingDictionaryFromTrie {
75
80
  const { useCompounds = this.options.useCompounds, ignoreCase = Defaults.ignoreCase } = hasOptionToSearchOption(hasOptions);
76
81
  return { useCompounds, ignoreCase };
77
82
  }
78
- _find = (word, useCompounds, ignoreCase) => this.findAnyForm(word, useCompounds, ignoreCase);
79
- findAnyForm(word, useCompounds, ignoreCase) {
80
- const outerForms = outerWordForms(word, this.remapWord || ((word) => [this.mapWord(word)]));
83
+ _find = (word, useCompounds, ignoreCase, compoundSeparator) => this.findAnyForm(word, useCompounds, ignoreCase, compoundSeparator);
84
+ findAnyForm(word, useCompounds, ignoreCase, compoundSeparator) {
85
+ const outerForms = outerWordForms(word, this.repMapper);
81
86
  for (const form of outerForms) {
82
- const r = this._findAnyForm(form, useCompounds, ignoreCase);
87
+ const r = this._findAnyForm(form, useCompounds, ignoreCase, compoundSeparator);
83
88
  if (r)
84
89
  return r;
85
90
  }
86
91
  return undefined;
87
92
  }
88
- _findAnyForm(mWord, useCompounds, ignoreCase) {
89
- const opts = ignoreCase
93
+ _findAnyForm(mWord, useCompounds, ignoreCase, compoundSeparator) {
94
+ let opts = ignoreCase
90
95
  ? this.#findWordOptionsNotCaseSensitive
91
96
  : this.#findWordOptionsCaseSensitive;
97
+ if (compoundSeparator) {
98
+ opts = { ...opts, compoundSeparator };
99
+ }
92
100
  const findResult = this.trie.findWord(mWord, opts);
93
101
  if (findResult.found !== false) {
94
102
  return findResult;
@@ -149,6 +157,12 @@ export class SpellingDictionaryFromTrie {
149
157
  this.trie.genSuggestions(impersonateCollector(collector, w), _compoundMethod);
150
158
  }
151
159
  }
160
+ getPreferredSuggestions(word) {
161
+ if (!this.trie.hasPreferredSuggestions)
162
+ return [];
163
+ const sugs = [...this.trie.getPreferredSuggestions(word)];
164
+ return sugs.map((sug, i) => ({ word: sug, cost: i + 1, isPreferred: true }));
165
+ }
152
166
  getErrors() {
153
167
  return [];
154
168
  }
@@ -162,25 +176,49 @@ export class SpellingDictionaryFromTrie {
162
176
  * @returns SpellingDictionary
163
177
  */
164
178
  export function createSpellingDictionaryFromTrieFile(data, name, source, options) {
179
+ const endPerf = measurePerf('createSpellingDictionaryFromTrieFile');
165
180
  const trie = decodeTrie(data);
166
- return new SpellingDictionaryFromTrie(trie, name, options, source);
181
+ const d = new SpellingDictionaryFromTrie(trie, name, options, source);
182
+ endPerf();
183
+ return d;
167
184
  }
168
- function* outerWordForms(word, mapWord) {
185
+ // eslint-disable-next-line no-control-regex
186
+ const isAsciiRange = /^[\u0000-\u007F]*$/;
187
+ function* outerWordForms(word, repMapper) {
169
188
  // Only generate the needed forms.
170
189
  const sent = new Set();
171
190
  let w = word;
172
191
  const ww = w;
173
192
  yield w;
174
- sent.add(w);
175
- w = word.normalize('NFC');
176
- if (w !== ww) {
177
- yield w;
193
+ // this function is called for every word lookup, so needs to be efficient.
194
+ // Check to see if it is a pure ascii word.
195
+ if (!isAsciiRange.test(w)) {
178
196
  sent.add(w);
197
+ w = word.normalize('NFC');
198
+ if (w !== ww) {
199
+ yield w;
200
+ sent.add(w);
201
+ }
202
+ w = word.normalize('NFD');
203
+ if (w !== ww && !sent.has(w)) {
204
+ yield w;
205
+ sent.add(w);
206
+ }
179
207
  }
180
- w = word.normalize('NFD');
181
- if (w !== ww && !sent.has(w)) {
182
- yield w;
183
- sent.add(w);
208
+ if (!repMapper)
209
+ return;
210
+ const mapWord = repMapper.fn;
211
+ // nothing was added to the set, just do the map.
212
+ if (!sent.size) {
213
+ if (!repMapper.test.test(ww))
214
+ return;
215
+ for (const m of mapWord(ww)) {
216
+ if (m !== ww && !sent.has(m)) {
217
+ yield m;
218
+ sent.add(m);
219
+ }
220
+ }
221
+ return;
184
222
  }
185
223
  for (const f of sent) {
186
224
  for (const m of mapWord(f)) {
@@ -1,14 +1,8 @@
1
- import type { IgnoreCaseOption, PreferredSuggestion, SpellingDictionary } from './SpellingDictionary.js';
1
+ import type { SuggestionResult } from 'cspell-trie-lib';
2
+ import type { SuggestDictionary } from './SpellingDictionary.js';
2
3
  import { type TypoEntry, type TyposDef } from './Typos/index.js';
3
- export interface SuggestDictionary extends SpellingDictionary {
4
- getPreferredSuggestions: (word: string) => PreferredSuggestion[];
5
- /**
6
- * Determine if the word can appear in a list of suggestions.
7
- * @param word - word
8
- * @param ignoreCaseAndAccents - ignore case.
9
- * @returns true if a word is suggested, otherwise false.
10
- */
11
- isSuggestedWord(word: string, ignoreCaseAndAccents?: IgnoreCaseOption): boolean;
4
+ export interface PreferredSuggestionResult extends SuggestionResult {
5
+ isPreferred: true;
12
6
  }
13
7
  /**
14
8
  * Create a dictionary where all words are to be forbidden.
@@ -12,6 +12,7 @@ class SuggestDictionaryImpl {
12
12
  options = {};
13
13
  type = 'suggest';
14
14
  size;
15
+ mapWord = undefined;
15
16
  /**
16
17
  * Note: ignoreWordsLower is only suggestions with the case and accents removed.
17
18
  * The logic is that if someone explicity ignored an upper case version, it does not
@@ -89,9 +90,6 @@ class SuggestDictionaryImpl {
89
90
  const sugs = this.suggest(collector.word);
90
91
  sugs.forEach((result) => collector.add(result));
91
92
  }
92
- mapWord(word) {
93
- return word;
94
- }
95
93
  isDictionaryCaseSensitive = true;
96
94
  getErrors() {
97
95
  return [];
@@ -1,4 +1,5 @@
1
1
  import type { TypoEntry, TyposDef } from './typos.js';
2
+ export declare function isSuggestion(v: string): boolean;
2
3
  export declare function createTyposDefFromEntries(entries: Iterable<TypoEntry>): TyposDef;
3
4
  export declare function sanitizeIntoTypoDef(dirtyDef: TyposDef | Record<string, unknown> | unknown): TyposDef | undefined;
4
5
  /**
@@ -1,13 +1,16 @@
1
- import assert from 'node:assert';
2
- import { appendToDef, createTyposDef } from './util.js';
1
+ import { appendToDef, assert, createTyposDef } from './util.js';
3
2
  function assertString(v) {
4
3
  assert(typeof v === 'string', 'A string was expected.');
5
4
  return true;
6
5
  }
7
6
  const suggestionsSeparator = /[,]/;
8
- const typoSuggestionsSeparator = /:|->/;
7
+ // const typoSuggestionsSeparator = /:|->/;
9
8
  const typoEntrySeparator = /[\n;]/;
10
9
  const inlineComment = /#.*/gm;
10
+ const sugFormatRegex = /^\s*(?:[!~:])*(?<word>.*?)(?<separator>(->|:([0-9a-f]{1,2}:)?))(?<sugs>.*)$/;
11
+ export function isSuggestion(v) {
12
+ return sugFormatRegex.test(v);
13
+ }
11
14
  export function createTyposDefFromEntries(entries) {
12
15
  const def = Object.create(null);
13
16
  for (const entry of entries) {
@@ -116,11 +119,33 @@ export function parseTyposLine(line) {
116
119
  }
117
120
  return sanitizeIntoTypoDef(line);
118
121
  }
122
+ /**
123
+ * Split text into multiple lines
124
+ * @param content - text content
125
+ * @returns
126
+ */
119
127
  function splitIntoLines(content) {
120
128
  return trimAndFilter(normalize(content).split(typoEntrySeparator));
121
129
  }
130
+ /**
131
+ * Split a typo entry into key and value
132
+ * Entry format:
133
+ * - `word:suggestion`
134
+ * - `word->suggestion`
135
+ * - `word: first, second, third suggestions`
136
+ * - sequencing values are ignored, e.g.: `:0:`, `:1:`, `:a:`
137
+ * - `word:0:first`
138
+ * - `word:1:second`
139
+ * @param line - the line of text
140
+ * @returns
141
+ */
122
142
  function splitEntry(line) {
123
- return line.split(typoSuggestionsSeparator, 2);
143
+ // Remove any sequencing values like `:1:` or `:a:`
144
+ const m = line.match(sugFormatRegex);
145
+ if (!m?.groups) {
146
+ return [line.trim(), undefined];
147
+ }
148
+ return [m.groups.word.trim(), m.groups.sugs.trim()];
124
149
  }
125
150
  export function parseTyposFile(content) {
126
151
  const lines = splitIntoLines(content.replaceAll(inlineComment, ''));
@@ -28,4 +28,5 @@ export declare function extractAllSuggestions(typosDef: TyposDef): Set<string>;
28
28
  * @returns set of ignored words with the prefix removed.
29
29
  */
30
30
  export declare function extractIgnoreValues(typosDef: TyposDef, ignorePrefix: string): Set<string>;
31
+ export declare function assert(condition: unknown, message?: string): asserts condition;
31
32
  //# sourceMappingURL=util.d.ts.map
@@ -103,4 +103,9 @@ function isArray(v) {
103
103
  function hasSuggestions(v) {
104
104
  return isString(v) || isArray(v);
105
105
  }
106
+ export function assert(condition, message = 'Assert Failed') {
107
+ if (condition)
108
+ return;
109
+ throw new Error(message);
110
+ }
106
111
  //# sourceMappingURL=util.js.map
@@ -12,6 +12,7 @@ class TyposDictionaryImpl {
12
12
  options = {};
13
13
  type = 'typos';
14
14
  size;
15
+ mapWord = undefined;
15
16
  ignoreWords;
16
17
  /**
17
18
  * Note: ignoreWordsLower is only suggestions with the case and accents removed.
@@ -122,9 +123,6 @@ class TyposDictionaryImpl {
122
123
  getPreferredSuggestions(word) {
123
124
  return this._suggest(word) || this._suggest(word.toLowerCase()) || [];
124
125
  }
125
- mapWord(word) {
126
- return word;
127
- }
128
126
  isDictionaryCaseSensitive = true;
129
127
  getErrors() {
130
128
  return [];
@@ -8,7 +8,7 @@ import type { DictionaryInfo, SpellingDictionary, SpellingDictionaryOptions } fr
8
8
  * @param options - dictionary options
9
9
  * @returns a Spelling Dictionary
10
10
  */
11
- export declare function createSpellingDictionary(wordList: readonly string[] | IterableLike<string>, name: string, source: string, options?: SpellingDictionaryOptions | undefined): SpellingDictionary;
11
+ export declare function createSpellingDictionary(wordList: readonly string[] | IterableLike<string>, name: string, source: string, options?: SpellingDictionaryOptions | undefined, disableSuggestionsHandling?: boolean): SpellingDictionary;
12
12
  export interface SpellingDictionaryLoadError extends Error {
13
13
  /** The Error Name */
14
14
  readonly name: string;
@@ -1,6 +1,7 @@
1
1
  import { fileURLToPath } from 'node:url';
2
2
  import { buildITrieFromWords, parseDictionaryLines } from 'cspell-trie-lib';
3
3
  import { deepEqual } from 'fast-equals';
4
+ import { measurePerf } from '../util/performance.js';
4
5
  import { AutoWeakCache, SimpleCache } from '../util/simpleCache.js';
5
6
  import { defaultOptions } from './SpellingDictionary.js';
6
7
  import { SpellingDictionaryFromTrie } from './SpellingDictionaryFromTrie.js';
@@ -16,8 +17,14 @@ const cachedParamsByWordList = new SimpleCache(64);
16
17
  * @param options - dictionary options
17
18
  * @returns a Spelling Dictionary
18
19
  */
19
- export function createSpellingDictionary(wordList, name, source, options) {
20
- const params = [wordList, name, source.toString(), options];
20
+ export function createSpellingDictionary(wordList, name, source, options, disableSuggestionsHandling) {
21
+ const params = [
22
+ wordList,
23
+ name,
24
+ source.toString(),
25
+ options,
26
+ disableSuggestionsHandling,
27
+ ];
21
28
  if (!Array.isArray(wordList)) {
22
29
  return _createSpellingDictionary(params);
23
30
  }
@@ -34,16 +41,20 @@ export function createSpellingDictionary(wordList, name, source, options) {
34
41
  return cachedDictionaries.get(params);
35
42
  }
36
43
  function _createSpellingDictionary(params) {
37
- const [wordList, name, source, options] = params;
44
+ const n = ''; // ':' + params[1]; // Add name to perf name for easier debugging.
45
+ const endPerf = measurePerf('createSpellingDictionary' + n);
46
+ const [wordList, name, source, options, disableSuggestionHandling = false] = params;
38
47
  // console.log(`createSpellingDictionary ${name} ${source}`);
39
- const parseOptions = { stripCaseAndAccents: options?.supportNonStrictSearches ?? true };
48
+ const parseOptions = { stripCaseAndAccents: options?.supportNonStrictSearches ?? true, disableSuggestionHandling };
40
49
  const words = parseDictionaryLines(wordList, parseOptions);
41
50
  const trie = buildITrieFromWords(words);
42
51
  const opts = { ...(options || defaultOptions) };
43
52
  if (opts.weightMap === undefined && opts.dictionaryInformation) {
44
53
  opts.weightMap = createWeightMapFromDictionaryInformation(opts.dictionaryInformation);
45
54
  }
46
- return new SpellingDictionaryFromTrie(trie, name, opts, source);
55
+ const d = new SpellingDictionaryFromTrie(trie, name, opts, source);
56
+ endPerf();
57
+ return d;
47
58
  }
48
59
  export function createFailedToLoadDictionary(name, sourceUrl, error, options) {
49
60
  const sourceHref = typeof sourceUrl === 'string' ? sourceUrl : sourceUrl.href;
@@ -59,7 +70,7 @@ export function createFailedToLoadDictionary(name, sourceUrl, error, options) {
59
70
  isNoSuggestWord: () => false,
60
71
  isForbidden: () => false,
61
72
  suggest: () => [],
62
- mapWord: (a) => a,
73
+ mapWord: undefined,
63
74
  genSuggestions: () => {
64
75
  return;
65
76
  },
@@ -3,7 +3,7 @@ export { createInlineSpellingDictionary } from './createInlineSpellingDictionary
3
3
  export { createFailedToLoadDictionary, createSpellingDictionary } from './createSpellingDictionary.js';
4
4
  export { createFlagWordsDictionary, createFlagWordsDictionary as createForbiddenWordsDictionary, } from './FlagWordsDictionary.js';
5
5
  export { createIgnoreWordsDictionary } from './IgnoreWordsDictionary.js';
6
- export type { DictionaryDefinitionInline, FindOptions, FindResult, HasOptions, PreferredSuggestion, SearchOptions, SpellingDictionary, SpellingDictionaryOptions, } from './SpellingDictionary.js';
6
+ export type { DictionaryDefinitionInline, FindOptions, FindResult, HasOptions, PreferredSuggestion, SearchOptions, SpellingDictionary, SpellingDictionaryOptions, Suggestion, } from './SpellingDictionary.js';
7
7
  export { createCollection, SpellingDictionaryCollection } from './SpellingDictionaryCollection.js';
8
8
  export { createSpellingDictionaryFromTrieFile } from './SpellingDictionaryFromTrie.js';
9
9
  export { createSuggestDictionary } from './SuggestDictionary.js';
package/dist/index.d.ts CHANGED
@@ -1,11 +1,4 @@
1
- import { enableLogging as cacheDictionaryEnableLogging, getLog as cacheDictionaryGetLog } from './SpellingDictionary/CachingDictionary.js';
2
- export type { CachingDictionary, FindOptions, FindResult, HasOptions, PreferredSuggestion, SearchOptions, SpellingDictionary, SpellingDictionaryCollection, SpellingDictionaryOptions, SuggestionCollector, SuggestionResult, SuggestOptions, } from './SpellingDictionary/index.js';
1
+ export { dictionaryCacheClearLog, dictionaryCacheEnableLogging, dictionaryCacheGetLog, } from './SpellingDictionary/CachingDictionary.js';
2
+ export type { CachingDictionary, FindOptions, FindResult, HasOptions, PreferredSuggestion, SearchOptions, SpellingDictionary, SpellingDictionaryCollection, SpellingDictionaryOptions, Suggestion, SuggestionCollector, SuggestionResult, SuggestOptions, } from './SpellingDictionary/index.js';
3
3
  export { createCachingDictionary, createCollection, createFailedToLoadDictionary, createFlagWordsDictionary, createForbiddenWordsDictionary, createIgnoreWordsDictionary, createInlineSpellingDictionary, createSpellingDictionary, createSpellingDictionaryFromTrieFile, createSuggestDictionary, createSuggestOptions, } from './SpellingDictionary/index.js';
4
- /**
5
- * Debugging utilities.
6
- */
7
- export declare const _debug: {
8
- cacheDictionaryEnableLogging: typeof cacheDictionaryEnableLogging;
9
- cacheDictionaryGetLog: typeof cacheDictionaryGetLog;
10
- };
11
4
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,10 +1,3 @@
1
- import { enableLogging as cacheDictionaryEnableLogging, getLog as cacheDictionaryGetLog, } from './SpellingDictionary/CachingDictionary.js';
1
+ export { dictionaryCacheClearLog, dictionaryCacheEnableLogging, dictionaryCacheGetLog, } from './SpellingDictionary/CachingDictionary.js';
2
2
  export { createCachingDictionary, createCollection, createFailedToLoadDictionary, createFlagWordsDictionary, createForbiddenWordsDictionary, createIgnoreWordsDictionary, createInlineSpellingDictionary, createSpellingDictionary, createSpellingDictionaryFromTrieFile, createSuggestDictionary, createSuggestOptions, } from './SpellingDictionary/index.js';
3
- /**
4
- * Debugging utilities.
5
- */
6
- export const _debug = {
7
- cacheDictionaryEnableLogging,
8
- cacheDictionaryGetLog,
9
- };
10
3
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,9 @@
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
+ //# sourceMappingURL=performance.d.ts.map
@@ -0,0 +1,19 @@
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
+ //# sourceMappingURL=performance.js.map
@@ -1,6 +1,9 @@
1
1
  import type { CharacterSet, ReplaceMap } from '@cspell/cspell-types';
2
- export type ReplaceMapper = (src: string) => string;
3
- export declare function createMapper(repMap: ReplaceMap | undefined, ignoreCharset?: string): ReplaceMapper;
2
+ export interface ReplaceMapper {
3
+ test?: RegExp;
4
+ fn: (src: string) => string;
5
+ }
6
+ export declare function createMapper(repMap: ReplaceMap | undefined, ignoreCharset?: string): ReplaceMapper | undefined;
4
7
  declare function charsetToRepMapRegEx(charset: CharacterSet | undefined, replaceWith?: string): ReplaceMap | undefined;
5
8
  declare function createMapperRegExp(repMap: ReplaceMap): RegExp;
6
9
  interface RepTrieNode {
@@ -12,7 +15,11 @@ interface Edit {
12
15
  e: number;
13
16
  r: string;
14
17
  }
15
- export declare function createRepMapper(repMap: ReplaceMap | undefined, ignoreCharset?: string): (word: string) => string[];
18
+ export interface RepMapper {
19
+ test: RegExp;
20
+ fn: (word: string) => string[];
21
+ }
22
+ export declare function createRepMapper(repMap: ReplaceMap | undefined, ignoreCharset?: string): RepMapper | undefined;
16
23
  declare function applyEdits(word: string, edits: Edit[]): string[];
17
24
  declare function calcAllEdits(root: RepTrieNode, word: string): Edit[];
18
25
  declare function createTrie(repMap: ReplaceMap | undefined, ignoreCharset?: string): RepTrieNode;
@@ -3,7 +3,7 @@ import { escapeRegEx } from './regexHelper.js';
3
3
  import { isDefined } from './util.js';
4
4
  export function createMapper(repMap, ignoreCharset) {
5
5
  if (!repMap && !ignoreCharset)
6
- return (a) => a;
6
+ return undefined;
7
7
  repMap = repMap || [];
8
8
  const charsetMap = charsetToRepMapRegEx(ignoreCharset);
9
9
  if (charsetMap) {
@@ -11,7 +11,7 @@ export function createMapper(repMap, ignoreCharset) {
11
11
  }
12
12
  const filteredMap = repMap.filter(([match, _]) => !!match);
13
13
  if (!filteredMap.length) {
14
- return (a) => a;
14
+ return undefined;
15
15
  }
16
16
  const regEx = createMapperRegExp(repMap);
17
17
  const values = repMap.filter(([match, _]) => !!match).map(([_, into]) => into);
@@ -19,8 +19,12 @@ export function createMapper(repMap, ignoreCharset) {
19
19
  const index = matches.findIndex((a) => !!a);
20
20
  return 0 <= index && index < values.length ? values[index] : m;
21
21
  }
22
- return function (s) {
22
+ function fn(s) {
23
23
  return s.replace(regEx, resolve);
24
+ }
25
+ return {
26
+ test: regexpRemoveFlags(regEx, 'gm'),
27
+ fn,
24
28
  };
25
29
  }
26
30
  function charsetToRepMapRegEx(charset, replaceWith = '') {
@@ -68,13 +72,22 @@ function createMapperRegExp(repMap) {
68
72
  return regEx;
69
73
  }
70
74
  export function createRepMapper(repMap, ignoreCharset) {
71
- if (!repMap && !ignoreCharset)
72
- return (word) => [word];
75
+ if (!repMap?.length && !ignoreCharset)
76
+ return undefined;
77
+ let tRepMap = repMap || [];
78
+ const charsetMap = charsetToRepMapRegEx(ignoreCharset);
79
+ if (charsetMap) {
80
+ tRepMap = [...tRepMap, ...charsetMap];
81
+ }
82
+ const regEx = createMapperRegExp(tRepMap);
73
83
  const trie = createTrie(repMap, ignoreCharset);
74
84
  // const root = createTrie(repMap, ignoreCharset);
75
- return (word) => {
76
- const edits = calcAllEdits(trie, word);
77
- return applyEdits(word, edits);
85
+ return {
86
+ test: regexpRemoveFlags(regEx, 'gm'),
87
+ fn: (word) => {
88
+ const edits = calcAllEdits(trie, word);
89
+ return applyEdits(word, edits);
90
+ },
78
91
  };
79
92
  }
80
93
  function applyEdits(word, edits) {
@@ -143,6 +156,11 @@ function addToTrie(node, match, replaceWith) {
143
156
  s.add(replaceWith);
144
157
  node.rep = [...s];
145
158
  }
159
+ function regexpRemoveFlags(re, flagsToRemove) {
160
+ const toRemove = new Set(flagsToRemove);
161
+ const flags = [...re.flags].filter((f) => !toRemove.has(f)).join('');
162
+ return new RegExp(re.source, flags);
163
+ }
146
164
  export const __testing__ = {
147
165
  charsetToRepMap: charsetToRepMapRegEx,
148
166
  createMapperRegExp,
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "provenance": true
6
6
  },
7
- "version": "9.4.0",
7
+ "version": "9.6.0",
8
8
  "description": "A spelling dictionary library useful for checking words and getting suggestions.",
9
9
  "type": "module",
10
10
  "sideEffects": false,
@@ -54,14 +54,15 @@
54
54
  "node": ">=20"
55
55
  },
56
56
  "dependencies": {
57
- "@cspell/cspell-pipe": "9.4.0",
58
- "@cspell/cspell-types": "9.4.0",
59
- "cspell-trie-lib": "9.4.0",
60
- "fast-equals": "^5.3.3"
57
+ "@cspell/cspell-pipe": "9.6.0",
58
+ "@cspell/cspell-types": "9.6.0",
59
+ "cspell-trie-lib": "9.6.0",
60
+ "fast-equals": "^6.0.0"
61
61
  },
62
62
  "devDependencies": {
63
+ "@cspell/dict-de-de": "^4.1.2",
63
64
  "gensequence": "^8.0.8",
64
65
  "lorem-ipsum": "^2.0.8"
65
66
  },
66
- "gitHead": "12dba3d8b880384d1401c765cb2186647f5a266f"
67
+ "gitHead": "163793ddf2a0ad90bc7c90351698a106003297af"
67
68
  }