@codemerge/hunspell 2.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,35 @@
1
+ @codemerge/hunspell
2
+ ======================
3
+
4
+ This package includes a TypeScript Hunspell-style dictionary engine derived from
5
+ Typo.js (https://github.com/cfinke/Typo.js) by Christopher Finke.
6
+
7
+ Typo.js is licensed under the BSD 3-Clause License:
8
+
9
+ Copyright (c) Christopher Finke
10
+ All rights reserved.
11
+
12
+ Redistribution and use in source and binary forms, with or without
13
+ modification, are permitted provided that the following conditions are met:
14
+
15
+ 1. Redistributions of source code must retain the above copyright notice, this
16
+ list of conditions and the following disclaimer.
17
+
18
+ 2. Redistributions in binary form must reproduce the above copyright notice,
19
+ this list of conditions and the following disclaimer in the documentation
20
+ and/or other materials provided with the distribution.
21
+
22
+ 3. Neither the name of the copyright holder nor the names of its
23
+ contributors may be used to endorse or promote products derived from
24
+ this software without specific prior written permission.
25
+
26
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
30
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
34
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,6 @@
1
+ import { AffixRule, AffixRules } from './types';
2
+ /**
3
+ * Apply one affix rule (and any continuation classes) to a stem.
4
+ */
5
+ export declare function applyRule(word: string, rule: AffixRule, rules: AffixRules, depth?: number): string[];
6
+ //# sourceMappingURL=applyRule.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { AffixRule, AffixRules } from './types';
2
+ /**
3
+ * Apply one affix rule (and any continuation classes) to a stem.
4
+ */
5
+ export declare function applyRule(word: string, rule: AffixRule, rules: AffixRules, depth?: number): string[];
6
+ //# sourceMappingURL=applyRule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applyRule.d.ts","sourceRoot":"","sources":["../src/applyRule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErD;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,SAAI,GAAG,MAAM,EAAE,CA+B/F"}
@@ -0,0 +1,14 @@
1
+ import { HunspellState } from './types';
2
+ /**
3
+ * Whether `word` carries the named affix flag (e.g. `KEEPCASE`, `NOSUGGEST`).
4
+ */
5
+ export declare function hasFlag(state: HunspellState, word: string, flag: string, wordFlags?: string[]): boolean;
6
+ /**
7
+ * Exact dictionary lookup (no capitalization folding). Also matches compound rules.
8
+ */
9
+ export declare function checkExact(state: HunspellState, word: string): boolean;
10
+ /**
11
+ * Spell-check with capitalization variants (all-caps / Capitalized).
12
+ */
13
+ export declare function check(state: HunspellState, aWord: string): boolean;
14
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { HunspellState } from './types';
2
+ /**
3
+ * Whether `word` carries the named affix flag (e.g. `KEEPCASE`, `NOSUGGEST`).
4
+ */
5
+ export declare function hasFlag(state: HunspellState, word: string, flag: string, wordFlags?: string[]): boolean;
6
+ /**
7
+ * Exact dictionary lookup (no capitalization folding). Also matches compound rules.
8
+ */
9
+ export declare function checkExact(state: HunspellState, word: string): boolean;
10
+ /**
11
+ * Spell-check with capitalization variants (all-caps / Capitalized).
12
+ */
13
+ export declare function check(state: HunspellState, aWord: string): boolean;
14
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAc7C;;GAEG;AACH,wBAAgB,OAAO,CACrB,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAOT;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAiCtE;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAmClE"}
@@ -0,0 +1,7 @@
1
+ import { HunspellDictionary } from './types';
2
+ /**
3
+ * Build a Hunspell dictionary from raw `.aff` and `.dic` file contents.
4
+ * Dictionaries are not bundled — callers fetch/load the files.
5
+ */
6
+ export declare function createDictionary(affData: string, dicData: string): HunspellDictionary;
7
+ //# sourceMappingURL=dictionary.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { HunspellDictionary } from './types';
2
+ /**
3
+ * Build a Hunspell dictionary from raw `.aff` and `.dic` file contents.
4
+ * Dictionaries are not bundled — callers fetch/load the files.
5
+ */
6
+ export declare function createDictionary(affData: string, dicData: string): HunspellDictionary;
7
+ //# sourceMappingURL=dictionary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictionary.d.ts","sourceRoot":"","sources":["../src/dictionary.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAiB,MAAM,SAAS,CAAC;AA8BjE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,kBAAkB,CA0CrF"}
package/dist/index.cjs ADDED
@@ -0,0 +1,435 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ function escapeRegExp(text) {
3
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4
+ }
5
+ //#endregion
6
+ //#region src/check.ts
7
+ /** Typographic apostrophe → ASCII (dictionary-en stems use U+0027). */
8
+ function normalizeWord(word) {
9
+ return word.replaceAll("’", "'");
10
+ }
11
+ function flattenFlags(ruleCodes) {
12
+ if (ruleCodes === null || ruleCodes === void 0) return [];
13
+ return ruleCodes.flat();
14
+ }
15
+ /**
16
+ * Whether `word` carries the named affix flag (e.g. `KEEPCASE`, `NOSUGGEST`).
17
+ */
18
+ function hasFlag(state, word, flag, wordFlags) {
19
+ const flagValue = state.flags[flag];
20
+ if (flagValue === void 0) return false;
21
+ return (wordFlags ?? flattenFlags(state.dictionaryTable.get(word) ?? void 0)).includes(flagValue);
22
+ }
23
+ /**
24
+ * Exact dictionary lookup (no capitalization folding). Also matches compound rules.
25
+ */
26
+ function checkExact(state, word) {
27
+ if (word.length > 256) return false;
28
+ const ruleCodes = state.dictionaryTable.get(word);
29
+ if (ruleCodes === void 0) {
30
+ const compoundMin = state.flags.COMPOUNDMIN;
31
+ if (compoundMin !== void 0 && word.length >= Number(compoundMin) && word.length <= 256) {
32
+ for (const rule of state.compoundRules) if (rule.test(word)) return true;
33
+ }
34
+ return false;
35
+ }
36
+ if (ruleCodes === null) return true;
37
+ for (const codes of ruleCodes) if (!hasFlag(state, word, "ONLYINCOMPOUND", codes)) return true;
38
+ return false;
39
+ }
40
+ /**
41
+ * Spell-check with capitalization variants (all-caps / Capitalized).
42
+ */
43
+ function check(state, aWord) {
44
+ if (aWord.length === 0) return false;
45
+ const trimmedWord = normalizeWord(aWord).replace(/^\s+/, "").replace(/\s+$/, "");
46
+ if (checkExact(state, trimmedWord)) return true;
47
+ const first = trimmedWord.charAt(0);
48
+ if (trimmedWord.toUpperCase() === trimmedWord) {
49
+ const capitalizedWord = first + trimmedWord.slice(1).toLowerCase();
50
+ if (hasFlag(state, capitalizedWord, "KEEPCASE")) return false;
51
+ if (checkExact(state, capitalizedWord)) return true;
52
+ if (checkExact(state, trimmedWord.toLowerCase())) return true;
53
+ }
54
+ const uncapitalizedWord = first.toLowerCase() + trimmedWord.slice(1);
55
+ if (uncapitalizedWord !== trimmedWord) {
56
+ if (hasFlag(state, uncapitalizedWord, "KEEPCASE")) return false;
57
+ if (checkExact(state, uncapitalizedWord)) return true;
58
+ }
59
+ return false;
60
+ }
61
+ //#endregion
62
+ //#region src/parseRuleCodes.ts
63
+ /**
64
+ * Split a Hunspell flag string according to the `.aff` `FLAG` mode.
65
+ * Default: one character per flag.
66
+ */
67
+ function parseRuleCodes(textCodes, flags) {
68
+ if (!textCodes) return [];
69
+ if (!("FLAG" in flags)) return textCodes.split("");
70
+ if (flags.FLAG === "long") {
71
+ const out = [];
72
+ for (let i = 0; i < textCodes.length; i += 2) out.push(textCodes.slice(i, i + 2));
73
+ return out;
74
+ }
75
+ if (flags.FLAG === "num") return textCodes.split(",");
76
+ if (flags.FLAG === "UTF-8") return Array.from(textCodes);
77
+ return textCodes.split("");
78
+ }
79
+ //#endregion
80
+ //#region src/parseAff.ts
81
+ function removeAffixComments(line) {
82
+ if (/^\s*#/.test(line)) return "";
83
+ return line;
84
+ }
85
+ function tryCompileAffixRegex(source) {
86
+ if (source.length > 80) return;
87
+ try {
88
+ return new RegExp(source);
89
+ } catch {
90
+ return;
91
+ }
92
+ }
93
+ /**
94
+ * Parse Hunspell `.aff` content into rules, named flags, compound patterns, and REP pairs.
95
+ */
96
+ function parseAff(data) {
97
+ const rules = {};
98
+ const flags = {};
99
+ const compoundRulePatterns = [];
100
+ const replacementTable = [];
101
+ const lines = data.split(/\r?\n/);
102
+ for (let i = 0; i < lines.length; i++) {
103
+ let line = removeAffixComments(lines[i] ?? "").trim();
104
+ if (!line) continue;
105
+ const definitionParts = line.split(/\s+/);
106
+ const ruleType = definitionParts[0];
107
+ if (!ruleType) continue;
108
+ if (ruleType === "PFX" || ruleType === "SFX") {
109
+ const ruleCode = definitionParts[1];
110
+ const combineable = definitionParts[2];
111
+ const numEntries = Math.trunc(Number(definitionParts[3] ?? "0"));
112
+ if (!ruleCode || Number.isNaN(numEntries) || numEntries < 0 || numEntries > 1e4) {
113
+ if (!Number.isNaN(numEntries) && numEntries > 0) i += Math.min(numEntries, lines.length);
114
+ continue;
115
+ }
116
+ const entries = [];
117
+ for (let j = i + 1; j < i + 1 + numEntries; j++) {
118
+ const lineParts = (lines[j] ?? "").split(/\s+/);
119
+ const charactersToRemove = lineParts[2] ?? "0";
120
+ const additionParts = (lineParts[3] ?? "0").split("/");
121
+ let charactersToAdd = additionParts[0] ?? "";
122
+ if (charactersToAdd === "0") charactersToAdd = "";
123
+ const continuationClasses = parseRuleCodes(additionParts[1], flags);
124
+ const regexToMatch = lineParts[4] ?? ".";
125
+ const entry = { add: charactersToAdd };
126
+ if (continuationClasses.length > 0) entry.continuationClasses = continuationClasses;
127
+ if (regexToMatch !== ".") {
128
+ const match = ruleType === "SFX" ? tryCompileAffixRegex(`${regexToMatch}$`) : tryCompileAffixRegex(`^${regexToMatch}`);
129
+ if (match === void 0) continue;
130
+ entry.match = match;
131
+ }
132
+ if (charactersToRemove !== "0") {
133
+ if (ruleType === "SFX") {
134
+ const remove = tryCompileAffixRegex(`${charactersToRemove}$`);
135
+ if (remove === void 0) continue;
136
+ entry.remove = remove;
137
+ } else if (charactersToRemove.length <= 80) entry.remove = charactersToRemove;
138
+ else continue;
139
+ }
140
+ entries.push(entry);
141
+ }
142
+ rules[ruleCode] = {
143
+ type: ruleType,
144
+ combineable: combineable === "Y",
145
+ entries
146
+ };
147
+ i += numEntries;
148
+ } else if (ruleType === "COMPOUNDRULE") {
149
+ const numEntries = Math.trunc(Number(definitionParts[1] ?? "0"));
150
+ if (Number.isNaN(numEntries) || numEntries < 0 || numEntries > 1e4) {
151
+ if (!Number.isNaN(numEntries) && numEntries > 0) i += Math.min(numEntries, lines.length);
152
+ continue;
153
+ }
154
+ for (let j = i + 1; j < i + 1 + numEntries; j++) {
155
+ const pattern = (lines[j] ?? "").split(/\s+/)[1];
156
+ if (pattern) compoundRulePatterns.push(pattern);
157
+ }
158
+ i += numEntries;
159
+ } else if (ruleType === "REP") {
160
+ const from = definitionParts[1];
161
+ const to = definitionParts[2];
162
+ if (from !== void 0 && to !== void 0 && definitionParts.length === 3) replacementTable.push([from, to]);
163
+ } else {
164
+ const value = definitionParts[1];
165
+ if (value !== void 0) flags[ruleType] = value;
166
+ }
167
+ }
168
+ return {
169
+ rules,
170
+ flags,
171
+ compoundRulePatterns,
172
+ replacementTable
173
+ };
174
+ }
175
+ //#endregion
176
+ //#region src/applyRule.ts
177
+ /**
178
+ * Apply one affix rule (and any continuation classes) to a stem.
179
+ */
180
+ function applyRule(word, rule, rules, depth = 0) {
181
+ const newWords = [];
182
+ if (depth > 12) return newWords;
183
+ for (const entry of rule.entries) {
184
+ if (entry.match !== void 0 && !entry.match.test(word)) continue;
185
+ let newWord = word;
186
+ if (entry.remove !== void 0) newWord = newWord.replace(entry.remove, "");
187
+ newWord = rule.type === "SFX" ? newWord + entry.add : entry.add + newWord;
188
+ newWords.push(newWord);
189
+ const cont = entry.continuationClasses;
190
+ if (cont === void 0) continue;
191
+ for (const code of cont) {
192
+ const continuationRule = rules[code];
193
+ if (continuationRule !== void 0) newWords.push(...applyRule(newWord, continuationRule, rules, depth + 1));
194
+ }
195
+ }
196
+ return newWords;
197
+ }
198
+ //#endregion
199
+ //#region src/parseDic.ts
200
+ function removeDicComments(data) {
201
+ return data.replace(/^\t.*$/gm, "");
202
+ }
203
+ /**
204
+ * Parse Hunspell `.dic` content into a word→flags lookup table.
205
+ * Also expands affix forms into the table (Typo.js approach).
206
+ */
207
+ function parseDic(data, options) {
208
+ const { rules, flags, compoundRuleCodes } = options;
209
+ const lines = removeDicComments(data).split(/\r?\n/);
210
+ const dictionaryTable = /* @__PURE__ */ new Map();
211
+ function addWord(word, ruleCodes) {
212
+ if (!dictionaryTable.has(word)) dictionaryTable.set(word, null);
213
+ if (ruleCodes.length === 0) return;
214
+ if (dictionaryTable.get(word) === null) dictionaryTable.set(word, []);
215
+ const existing = dictionaryTable.get(word);
216
+ if (Array.isArray(existing)) existing.push(ruleCodes);
217
+ }
218
+ for (let i = 1; i < lines.length; i++) {
219
+ const line = lines[i];
220
+ if (line === void 0 || line.length === 0) continue;
221
+ const parts = line.replace(/\s.*$/, "").split("/", 2);
222
+ const word = parts[0] ?? "";
223
+ if (word.length === 0) continue;
224
+ if (parts.length > 1) {
225
+ const ruleCodesArray = parseRuleCodes(parts[1], flags);
226
+ const needAffix = flags.NEEDAFFIX;
227
+ if (needAffix === void 0 || !ruleCodesArray.includes(needAffix)) addWord(word, ruleCodesArray);
228
+ for (let j = 0; j < ruleCodesArray.length; j++) {
229
+ const code = ruleCodesArray[j];
230
+ if (code === void 0) continue;
231
+ const rule = rules[code];
232
+ if (rule !== void 0) {
233
+ const newWords = applyRule(word, rule, rules);
234
+ for (const newWord of newWords) {
235
+ addWord(newWord, []);
236
+ if (rule.combineable) for (let k = j + 1; k < ruleCodesArray.length; k++) {
237
+ const combineCode = ruleCodesArray[k];
238
+ if (combineCode === void 0) continue;
239
+ const combineRule = rules[combineCode];
240
+ if (combineRule !== void 0 && combineRule.combineable && rule.type !== combineRule.type) for (const other of applyRule(newWord, combineRule, rules)) addWord(other, []);
241
+ }
242
+ }
243
+ }
244
+ const compoundStems = compoundRuleCodes[code];
245
+ if (compoundStems !== void 0) compoundStems.push(word);
246
+ }
247
+ } else addWord(word.trim(), []);
248
+ }
249
+ return dictionaryTable;
250
+ }
251
+ //#endregion
252
+ //#region src/suggest.ts
253
+ function ensureAlphabet(state) {
254
+ if (state.alphabet.length > 0) return state.alphabet;
255
+ const LATIN_FALLBACK = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
256
+ let alphabet = "";
257
+ if (state.flags.TRY !== void 0 && state.flags.TRY.length > 0) alphabet = state.flags.TRY;
258
+ else alphabet = LATIN_FALLBACK;
259
+ if (state.flags.WORDCHARS !== void 0) alphabet += state.flags.WORDCHARS;
260
+ const seen = /* @__PURE__ */ new Set();
261
+ let unique = "";
262
+ for (const ch of alphabet) if (!seen.has(ch)) {
263
+ seen.add(ch);
264
+ unique += ch;
265
+ }
266
+ state.alphabet = unique;
267
+ return unique;
268
+ }
269
+ function edits1(state, words, knownOnly) {
270
+ const alphabet = ensureAlphabet(state);
271
+ const rv = {};
272
+ const bump = (edit) => {
273
+ if (knownOnly && !check(state, edit)) return;
274
+ rv[edit] = (rv[edit] ?? 0) + 1;
275
+ };
276
+ for (const word of Object.keys(words)) for (let i = 0; i <= word.length; i++) {
277
+ const left = word.slice(0, i);
278
+ const right = word.slice(i);
279
+ if (right.length > 0) bump(left + right.slice(1));
280
+ const right0 = right[0];
281
+ const right1 = right[1];
282
+ if (right.length > 1 && right1 !== void 0 && right0 !== void 0 && right1 !== right0) bump(left + right1 + right0 + right.slice(2));
283
+ if (right.length > 0) {
284
+ const lettercase = right.slice(0, 1).toUpperCase() === right.slice(0, 1) ? "uppercase" : "lowercase";
285
+ for (const ch of alphabet) {
286
+ let replacementLetter = ch;
287
+ if (lettercase === "uppercase") replacementLetter = replacementLetter.toUpperCase();
288
+ if (replacementLetter !== right.slice(0, 1)) bump(left + replacementLetter + right.slice(1));
289
+ }
290
+ }
291
+ if (right.length > 0) {
292
+ const leftTail = left.slice(-1);
293
+ const rightHead = right.slice(0, 1);
294
+ const lettercase = leftTail.toUpperCase() === leftTail && rightHead.toUpperCase() === rightHead ? "uppercase" : "lowercase";
295
+ for (const ch of alphabet) {
296
+ let replacementLetter = ch;
297
+ if (lettercase === "uppercase") replacementLetter = replacementLetter.toUpperCase();
298
+ bump(left + replacementLetter + right);
299
+ }
300
+ }
301
+ }
302
+ return rv;
303
+ }
304
+ function rankCorrections(state, word, limit) {
305
+ const tier1 = edits1(state, { [word]: true }, true);
306
+ let weighted = tier1;
307
+ if (Object.keys(tier1).length === 0) {
308
+ const ed1 = edits1(state, { [word]: true }, false);
309
+ const unknownSeeds = {};
310
+ let seedCount = 0;
311
+ const MAX_ED2_SEEDS = 64;
312
+ for (const [candidate, weight] of Object.entries(ed1)) {
313
+ if (check(state, candidate)) {
314
+ weighted[candidate] = (weighted[candidate] ?? 0) + weight;
315
+ continue;
316
+ }
317
+ if (seedCount >= MAX_ED2_SEEDS) continue;
318
+ unknownSeeds[candidate] = weight;
319
+ seedCount += 1;
320
+ }
321
+ if (Object.keys(unknownSeeds).length > 0) {
322
+ const ed2 = edits1(state, unknownSeeds, true);
323
+ for (const [candidate, weight] of Object.entries(ed2)) weighted[candidate] = (weighted[candidate] ?? 0) + weight;
324
+ }
325
+ }
326
+ const sorted = [];
327
+ for (const [candidate, weight] of Object.entries(weighted)) {
328
+ let w = weight;
329
+ if (hasFlag(state, candidate, "PRIORITYSUGGEST")) w += 1e3;
330
+ sorted.push([candidate, w]);
331
+ }
332
+ sorted.sort((a, b) => {
333
+ if (a[1] !== b[1]) return b[1] - a[1];
334
+ return a[0].localeCompare(b[0]);
335
+ });
336
+ let capitalizationScheme = "lowercase";
337
+ if (word.toUpperCase() === word) capitalizationScheme = "uppercase";
338
+ else if (word.slice(0, 1).toUpperCase() + word.slice(1).toLowerCase() === word) capitalizationScheme = "capitalized";
339
+ const rv = [];
340
+ let workingLimit = limit;
341
+ for (let i = 0; i < Math.min(workingLimit, sorted.length); i++) {
342
+ const entry = sorted[i];
343
+ if (entry === void 0) break;
344
+ let suggestion = entry[0];
345
+ if (capitalizationScheme === "uppercase") suggestion = suggestion.toUpperCase();
346
+ else if (capitalizationScheme === "capitalized") suggestion = suggestion.slice(0, 1).toUpperCase() + suggestion.slice(1);
347
+ if (!hasFlag(state, suggestion, "NOSUGGEST") && !rv.includes(suggestion)) rv.push(suggestion);
348
+ else workingLimit++;
349
+ }
350
+ return rv;
351
+ }
352
+ /**
353
+ * Suggest corrections for a misspelling (REP table + edit-distance ranking).
354
+ */
355
+ function suggest(state, word, limit = 5) {
356
+ if (word.length === 0 || word.length > 64) return [];
357
+ const normalized = word.replaceAll("’", "'");
358
+ const memo = state.memoized[normalized];
359
+ if (memo !== void 0 && (limit <= memo.limit || memo.suggestions.length < memo.limit)) return memo.suggestions.slice(0, limit);
360
+ if (check(state, normalized)) return [];
361
+ for (const [from, to] of state.replacementTable) if (normalized.includes(from)) {
362
+ const corrected = normalized.replace(from, to);
363
+ if (check(state, corrected)) {
364
+ const suggestions = [corrected];
365
+ remember(state, normalized, suggestions, limit);
366
+ return suggestions;
367
+ }
368
+ }
369
+ const suggestions = rankCorrections(state, normalized, limit);
370
+ remember(state, normalized, suggestions, limit);
371
+ return suggestions;
372
+ }
373
+ function remember(state, word, suggestions, limit) {
374
+ const keys = Object.keys(state.memoized);
375
+ if (keys.length >= 512) {
376
+ const drop = keys[0];
377
+ if (drop !== void 0) delete state.memoized[drop];
378
+ }
379
+ state.memoized[word] = {
380
+ suggestions,
381
+ limit
382
+ };
383
+ }
384
+ //#endregion
385
+ //#region src/dictionary.ts
386
+ function buildCompoundRegexes(patterns, compoundRuleCodes) {
387
+ const out = [];
388
+ for (const ruleText of patterns) {
389
+ let expressionText = "";
390
+ for (const character of ruleText) if (Object.hasOwn(compoundRuleCodes, character)) {
391
+ const stems = compoundRuleCodes[character] ?? [];
392
+ expressionText += `(${stems.map(escapeRegExp).join("|")})`;
393
+ } else expressionText += character;
394
+ if (expressionText.length > 4096) continue;
395
+ try {
396
+ out.push(new RegExp(`^${expressionText}$`, "i"));
397
+ } catch {}
398
+ }
399
+ return out;
400
+ }
401
+ /**
402
+ * Build a Hunspell dictionary from raw `.aff` and `.dic` file contents.
403
+ * Dictionaries are not bundled — callers fetch/load the files.
404
+ */
405
+ function createDictionary(affData, dicData) {
406
+ if (affData.length > 8388608 || dicData.length > 8388608) throw new Error("dictionary too large");
407
+ const { rules, flags, compoundRulePatterns, replacementTable } = parseAff(affData);
408
+ const compoundRuleCodes = {};
409
+ for (const pattern of compoundRulePatterns) for (const ch of pattern) compoundRuleCodes[ch] = [];
410
+ if (flags.ONLYINCOMPOUND !== void 0) compoundRuleCodes[flags.ONLYINCOMPOUND] = [];
411
+ const dictionaryTable = parseDic(dicData, {
412
+ rules,
413
+ flags,
414
+ compoundRuleCodes
415
+ });
416
+ for (const code of Object.keys(compoundRuleCodes)) if ((compoundRuleCodes[code] ?? []).length === 0) delete compoundRuleCodes[code];
417
+ const state = {
418
+ dictionaryTable,
419
+ compoundRules: buildCompoundRegexes(compoundRulePatterns, compoundRuleCodes),
420
+ replacementTable,
421
+ flags,
422
+ memoized: {},
423
+ alphabet: ""
424
+ };
425
+ return {
426
+ loaded: true,
427
+ check: (word) => check(state, word),
428
+ checkExact: (word) => checkExact(state, word),
429
+ suggest: (word, limit) => suggest(state, word, limit)
430
+ };
431
+ }
432
+ //#endregion
433
+ exports.createDictionary = createDictionary;
434
+
435
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/limits.ts","../src/check.ts","../src/parseRuleCodes.ts","../src/parseAff.ts","../src/applyRule.ts","../src/parseDic.ts","../src/suggest.ts","../src/dictionary.ts"],"sourcesContent":["/** Resource bounds for untrusted / oversized Hunspell inputs. */\n\nexport const MAX_AFF_BYTES = 8_388_608;\nexport const MAX_DIC_BYTES = 8_388_608;\nexport const MAX_AFFIX_ENTRIES = 10_000;\nexport const MAX_APPLY_RULE_DEPTH = 12;\nexport const MAX_COMPOUND_REGEX_SOURCE = 4096;\nexport const MAX_AFFIX_PATTERN_LEN = 80;\nexport const MAX_CHECK_WORD_LEN = 256;\nexport const MAX_SUGGEST_WORD_LEN = 64;\nexport const MAX_SUGGEST_MEMO = 512;\n\nexport function escapeRegExp(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","import { MAX_CHECK_WORD_LEN } from './limits';\nimport type { HunspellState } from './types';\n\n/** Typographic apostrophe → ASCII (dictionary-en stems use U+0027). */\nfunction normalizeWord(word: string): string {\n return word.replaceAll('\\u2019', \"'\");\n}\n\nfunction flattenFlags(ruleCodes: string[][] | null | undefined): string[] {\n if (ruleCodes === null || ruleCodes === undefined) {\n return [];\n }\n return ruleCodes.flat();\n}\n\n/**\n * Whether `word` carries the named affix flag (e.g. `KEEPCASE`, `NOSUGGEST`).\n */\nexport function hasFlag(\n state: HunspellState,\n word: string,\n flag: string,\n wordFlags?: string[]\n): boolean {\n const flagValue = state.flags[flag];\n if (flagValue === undefined) {\n return false;\n }\n const codes = wordFlags ?? flattenFlags(state.dictionaryTable.get(word) ?? undefined);\n return codes.includes(flagValue);\n}\n\n/**\n * Exact dictionary lookup (no capitalization folding). Also matches compound rules.\n */\nexport function checkExact(state: HunspellState, word: string): boolean {\n if (word.length > MAX_CHECK_WORD_LEN) {\n return false;\n }\n\n const ruleCodes = state.dictionaryTable.get(word);\n\n if (ruleCodes === undefined) {\n const compoundMin = state.flags.COMPOUNDMIN;\n if (\n compoundMin !== undefined &&\n word.length >= Number(compoundMin) &&\n word.length <= MAX_CHECK_WORD_LEN\n ) {\n for (const rule of state.compoundRules) {\n if (rule.test(word)) {\n return true;\n }\n }\n }\n return false;\n }\n\n if (ruleCodes === null) {\n return true;\n }\n\n for (const codes of ruleCodes) {\n if (!hasFlag(state, word, 'ONLYINCOMPOUND', codes)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Spell-check with capitalization variants (all-caps / Capitalized).\n */\nexport function check(state: HunspellState, aWord: string): boolean {\n if (aWord.length === 0) {\n return false;\n }\n\n const trimmedWord = normalizeWord(aWord).replace(/^\\s+/, '').replace(/\\s+$/, '');\n if (checkExact(state, trimmedWord)) {\n return true;\n }\n\n const first = trimmedWord.charAt(0);\n if (trimmedWord.toUpperCase() === trimmedWord) {\n const capitalizedWord = first + trimmedWord.slice(1).toLowerCase();\n if (hasFlag(state, capitalizedWord, 'KEEPCASE')) {\n return false;\n }\n if (checkExact(state, capitalizedWord)) {\n return true;\n }\n if (checkExact(state, trimmedWord.toLowerCase())) {\n return true;\n }\n }\n\n const uncapitalizedWord = first.toLowerCase() + trimmedWord.slice(1);\n if (uncapitalizedWord !== trimmedWord) {\n if (hasFlag(state, uncapitalizedWord, 'KEEPCASE')) {\n return false;\n }\n if (checkExact(state, uncapitalizedWord)) {\n return true;\n }\n }\n\n return false;\n}\n","import type { AffFlags } from './types';\n\n/**\n * Split a Hunspell flag string according to the `.aff` `FLAG` mode.\n * Default: one character per flag.\n */\nexport function parseRuleCodes(textCodes: string | undefined, flags: AffFlags): string[] {\n if (!textCodes) {\n return [];\n }\n if (!('FLAG' in flags)) {\n return textCodes.split('');\n }\n if (flags.FLAG === 'long') {\n const out: string[] = [];\n for (let i = 0; i < textCodes.length; i += 2) {\n out.push(textCodes.slice(i, i + 2));\n }\n return out;\n }\n if (flags.FLAG === 'num') {\n return textCodes.split(',');\n }\n if (flags.FLAG === 'UTF-8') {\n return Array.from(textCodes);\n }\n return textCodes.split('');\n}\n","import type { AffFlags, AffixEntry, AffixRules, ReplacementPair } from './types';\nimport { MAX_AFFIX_ENTRIES, MAX_AFFIX_PATTERN_LEN } from './limits';\nimport { parseRuleCodes } from './parseRuleCodes';\n\nexport type AffParseResult = {\n rules: AffixRules;\n flags: AffFlags;\n /** Raw COMPOUNDRULE pattern strings (not yet RegExp). */\n compoundRulePatterns: string[];\n replacementTable: ReplacementPair[];\n};\n\nfunction removeAffixComments(line: string): string {\n // Only strip full-line comments — COMPOUNDRULE may use `#` as a flag char.\n if (/^\\s*#/.test(line)) {\n return '';\n }\n return line;\n}\n\nfunction tryCompileAffixRegex(source: string): RegExp | undefined {\n if (source.length > MAX_AFFIX_PATTERN_LEN) {\n return undefined;\n }\n try {\n return new RegExp(source);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Parse Hunspell `.aff` content into rules, named flags, compound patterns, and REP pairs.\n */\nexport function parseAff(data: string): AffParseResult {\n const rules: AffixRules = {};\n const flags: AffFlags = {};\n const compoundRulePatterns: string[] = [];\n const replacementTable: ReplacementPair[] = [];\n\n const lines = data.split(/\\r?\\n/);\n\n for (let i = 0; i < lines.length; i++) {\n let line = removeAffixComments(lines[i] ?? '').trim();\n if (!line) {\n continue;\n }\n\n const definitionParts = line.split(/\\s+/);\n const ruleType = definitionParts[0];\n if (!ruleType) {\n continue;\n }\n\n if (ruleType === 'PFX' || ruleType === 'SFX') {\n const ruleCode = definitionParts[1];\n const combineable = definitionParts[2];\n const numEntries = Math.trunc(Number(definitionParts[3] ?? '0'));\n if (\n !ruleCode ||\n Number.isNaN(numEntries) ||\n numEntries < 0 ||\n numEntries > MAX_AFFIX_ENTRIES\n ) {\n if (!Number.isNaN(numEntries) && numEntries > 0) {\n i += Math.min(numEntries, lines.length);\n }\n continue;\n }\n\n const entries: AffixEntry[] = [];\n for (let j = i + 1; j < i + 1 + numEntries; j++) {\n const subline = lines[j] ?? '';\n const lineParts = subline.split(/\\s+/);\n const charactersToRemove = lineParts[2] ?? '0';\n const additionParts = (lineParts[3] ?? '0').split('/');\n let charactersToAdd = additionParts[0] ?? '';\n if (charactersToAdd === '0') {\n charactersToAdd = '';\n }\n const continuationClasses = parseRuleCodes(additionParts[1], flags);\n const regexToMatch = lineParts[4] ?? '.';\n\n const entry: AffixEntry = { add: charactersToAdd };\n if (continuationClasses.length > 0) {\n entry.continuationClasses = continuationClasses;\n }\n if (regexToMatch !== '.') {\n const match =\n ruleType === 'SFX'\n ? tryCompileAffixRegex(`${regexToMatch}$`)\n : tryCompileAffixRegex(`^${regexToMatch}`);\n if (match === undefined) {\n continue;\n }\n entry.match = match;\n }\n if (charactersToRemove !== '0') {\n if (ruleType === 'SFX') {\n const remove = tryCompileAffixRegex(`${charactersToRemove}$`);\n if (remove === undefined) {\n continue;\n }\n entry.remove = remove;\n } else if (charactersToRemove.length <= MAX_AFFIX_PATTERN_LEN) {\n entry.remove = charactersToRemove;\n } else {\n continue;\n }\n }\n entries.push(entry);\n }\n\n rules[ruleCode] = {\n type: ruleType,\n combineable: combineable === 'Y',\n entries,\n };\n i += numEntries;\n } else if (ruleType === 'COMPOUNDRULE') {\n const numEntries = Math.trunc(Number(definitionParts[1] ?? '0'));\n if (Number.isNaN(numEntries) || numEntries < 0 || numEntries > MAX_AFFIX_ENTRIES) {\n if (!Number.isNaN(numEntries) && numEntries > 0) {\n i += Math.min(numEntries, lines.length);\n }\n continue;\n }\n for (let j = i + 1; j < i + 1 + numEntries; j++) {\n const parts = (lines[j] ?? '').split(/\\s+/);\n const pattern = parts[1];\n if (pattern) {\n compoundRulePatterns.push(pattern);\n }\n }\n i += numEntries;\n } else if (ruleType === 'REP') {\n const from = definitionParts[1];\n const to = definitionParts[2];\n if (from !== undefined && to !== undefined && definitionParts.length === 3) {\n replacementTable.push([from, to]);\n }\n } else {\n // ONLYINCOMPOUND, COMPOUNDMIN, FLAG, KEEPCASE, NEEDAFFIX, TRY, WORDCHARS, NOSUGGEST, …\n const value = definitionParts[1];\n if (value !== undefined) {\n flags[ruleType] = value;\n }\n }\n }\n\n return { rules, flags, compoundRulePatterns, replacementTable };\n}\n","import { MAX_APPLY_RULE_DEPTH } from './limits';\nimport type { AffixRule, AffixRules } from './types';\n\n/**\n * Apply one affix rule (and any continuation classes) to a stem.\n */\nexport function applyRule(word: string, rule: AffixRule, rules: AffixRules, depth = 0): string[] {\n const newWords: string[] = [];\n if (depth > MAX_APPLY_RULE_DEPTH) {\n return newWords;\n }\n\n for (const entry of rule.entries) {\n if (entry.match !== undefined && !entry.match.test(word)) {\n continue;\n }\n\n let newWord = word;\n if (entry.remove !== undefined) {\n newWord = newWord.replace(entry.remove, '');\n }\n newWord = rule.type === 'SFX' ? newWord + entry.add : entry.add + newWord;\n newWords.push(newWord);\n\n const cont = entry.continuationClasses;\n if (cont === undefined) {\n continue;\n }\n for (const code of cont) {\n const continuationRule = rules[code];\n if (continuationRule !== undefined) {\n newWords.push(...applyRule(newWord, continuationRule, rules, depth + 1));\n }\n }\n }\n\n return newWords;\n}\n","import { applyRule } from './applyRule';\nimport { parseRuleCodes } from './parseRuleCodes';\nimport type { AffFlags, AffixRules, DictionaryTable } from './types';\n\nfunction removeDicComments(data: string): string {\n // de_DE and some others use tab-indented comment lines.\n return data.replace(/^\\t.*$/gm, '');\n}\n\nexport type ParseDicOptions = {\n rules: AffixRules;\n flags: AffFlags;\n /** Mutated: flag code → stems that carry that compound flag. */\n compoundRuleCodes: Record<string, string[]>;\n};\n\n/**\n * Parse Hunspell `.dic` content into a word→flags lookup table.\n * Also expands affix forms into the table (Typo.js approach).\n */\nexport function parseDic(data: string, options: ParseDicOptions): DictionaryTable {\n const { rules, flags, compoundRuleCodes } = options;\n const cleaned = removeDicComments(data);\n const lines = cleaned.split(/\\r?\\n/);\n const dictionaryTable: DictionaryTable = new Map();\n\n function addWord(word: string, ruleCodes: string[]): void {\n if (!dictionaryTable.has(word)) {\n dictionaryTable.set(word, null);\n }\n if (ruleCodes.length === 0) {\n return;\n }\n if (dictionaryTable.get(word) === null) {\n dictionaryTable.set(word, []);\n }\n const existing = dictionaryTable.get(word);\n if (Array.isArray(existing)) {\n existing.push(ruleCodes);\n }\n }\n\n // First line is the word count — skip it.\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i];\n if (line === undefined || line.length === 0) {\n continue;\n }\n\n // Drop morphological fields (`xx:abc`); keep `word` or `word/flags`.\n const justWordAndFlags = line.replace(/\\s.*$/, '');\n const parts = justWordAndFlags.split('/', 2);\n const word = parts[0] ?? '';\n if (word.length === 0) {\n continue;\n }\n\n if (parts.length > 1) {\n const ruleCodesArray = parseRuleCodes(parts[1], flags);\n const needAffix = flags.NEEDAFFIX;\n if (needAffix === undefined || !ruleCodesArray.includes(needAffix)) {\n addWord(word, ruleCodesArray);\n }\n\n for (let j = 0; j < ruleCodesArray.length; j++) {\n const code = ruleCodesArray[j];\n if (code === undefined) {\n continue;\n }\n const rule = rules[code];\n if (rule !== undefined) {\n const newWords = applyRule(word, rule, rules);\n for (const newWord of newWords) {\n addWord(newWord, []);\n if (rule.combineable) {\n for (let k = j + 1; k < ruleCodesArray.length; k++) {\n const combineCode = ruleCodesArray[k];\n if (combineCode === undefined) {\n continue;\n }\n const combineRule = rules[combineCode];\n if (\n combineRule !== undefined &&\n combineRule.combineable &&\n rule.type !== combineRule.type\n ) {\n for (const other of applyRule(newWord, combineRule, rules)) {\n addWord(other, []);\n }\n }\n }\n }\n }\n }\n const compoundStems = compoundRuleCodes[code];\n if (compoundStems !== undefined) {\n compoundStems.push(word);\n }\n }\n } else {\n addWord(word.trim(), []);\n }\n }\n\n return dictionaryTable;\n}\n","import { check, hasFlag } from './check';\nimport { MAX_SUGGEST_MEMO, MAX_SUGGEST_WORD_LEN } from './limits';\nimport type { HunspellState } from './types';\n\nfunction ensureAlphabet(state: HunspellState): string {\n if (state.alphabet.length > 0) {\n return state.alphabet;\n }\n\n // Hunspell: suggestion edits come from TRY (+ WORDCHARS). Latin fallback is only\n // for incomplete .aff files — do not force English letters onto every locale.\n const LATIN_FALLBACK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';\n let alphabet = '';\n if (state.flags.TRY !== undefined && state.flags.TRY.length > 0) {\n alphabet = state.flags.TRY;\n } else {\n alphabet = LATIN_FALLBACK;\n }\n if (state.flags.WORDCHARS !== undefined) {\n alphabet += state.flags.WORDCHARS;\n }\n\n const seen = new Set<string>();\n let unique = '';\n for (const ch of alphabet) {\n if (!seen.has(ch)) {\n seen.add(ch);\n unique += ch;\n }\n }\n state.alphabet = unique;\n return unique;\n}\n\ntype EditCounts = Record<string, number>;\n\nfunction edits1(\n state: HunspellState,\n words: Record<string, true | number>,\n knownOnly: boolean\n): EditCounts {\n const alphabet = ensureAlphabet(state);\n const rv: EditCounts = {};\n\n const bump = (edit: string): void => {\n if (knownOnly && !check(state, edit)) {\n return;\n }\n rv[edit] = (rv[edit] ?? 0) + 1;\n };\n\n for (const word of Object.keys(words)) {\n for (let i = 0; i <= word.length; i++) {\n const left = word.slice(0, i);\n const right = word.slice(i);\n\n if (right.length > 0) {\n bump(left + right.slice(1));\n }\n\n const right0 = right[0];\n const right1 = right[1];\n if (right.length > 1 && right1 !== undefined && right0 !== undefined && right1 !== right0) {\n bump(left + right1 + right0 + right.slice(2));\n }\n\n if (right.length > 0) {\n const lettercase =\n right.slice(0, 1).toUpperCase() === right.slice(0, 1) ? 'uppercase' : 'lowercase';\n for (const ch of alphabet) {\n let replacementLetter = ch;\n if (lettercase === 'uppercase') {\n replacementLetter = replacementLetter.toUpperCase();\n }\n if (replacementLetter !== right.slice(0, 1)) {\n bump(left + replacementLetter + right.slice(1));\n }\n }\n }\n\n if (right.length > 0) {\n // Capitalize insert when both neighbors are uppercase.\n const leftTail = left.slice(-1);\n const rightHead = right.slice(0, 1);\n const lettercase =\n leftTail.toUpperCase() === leftTail && rightHead.toUpperCase() === rightHead\n ? 'uppercase'\n : 'lowercase';\n for (const ch of alphabet) {\n let replacementLetter = ch;\n if (lettercase === 'uppercase') {\n replacementLetter = replacementLetter.toUpperCase();\n }\n bump(left + replacementLetter + right);\n }\n }\n }\n }\n\n return rv;\n}\n\nfunction rankCorrections(state: HunspellState, word: string, limit: number): string[] {\n // Norvig tier-1: known edit-distance-1 first (fast path for near-misses).\n const tier1 = edits1(state, { [word]: true }, true);\n let weighted: EditCounts = tier1;\n\n if (Object.keys(tier1).length === 0) {\n // Tier-2: expand unknown ed1 seeds, then keep only known ed2 forms.\n const ed1 = edits1(state, { [word]: true }, false);\n const unknownSeeds: Record<string, true | number> = {};\n let seedCount = 0;\n const MAX_ED2_SEEDS = 64;\n for (const [candidate, weight] of Object.entries(ed1)) {\n if (check(state, candidate)) {\n weighted[candidate] = (weighted[candidate] ?? 0) + weight;\n continue;\n }\n if (seedCount >= MAX_ED2_SEEDS) {\n continue;\n }\n unknownSeeds[candidate] = weight;\n seedCount += 1;\n }\n if (Object.keys(unknownSeeds).length > 0) {\n const ed2 = edits1(state, unknownSeeds, true);\n for (const [candidate, weight] of Object.entries(ed2)) {\n weighted[candidate] = (weighted[candidate] ?? 0) + weight;\n }\n }\n }\n\n const sorted: Array<[string, number]> = [];\n for (const [candidate, weight] of Object.entries(weighted)) {\n let w = weight;\n if (hasFlag(state, candidate, 'PRIORITYSUGGEST')) {\n w += 1000;\n }\n sorted.push([candidate, w]);\n }\n\n sorted.sort((a, b) => {\n if (a[1] !== b[1]) {\n return b[1] - a[1];\n }\n return a[0].localeCompare(b[0]);\n });\n\n let capitalizationScheme: 'lowercase' | 'uppercase' | 'capitalized' = 'lowercase';\n if (word.toUpperCase() === word) {\n capitalizationScheme = 'uppercase';\n } else if (word.slice(0, 1).toUpperCase() + word.slice(1).toLowerCase() === word) {\n capitalizationScheme = 'capitalized';\n }\n\n const rv: string[] = [];\n let workingLimit = limit;\n for (let i = 0; i < Math.min(workingLimit, sorted.length); i++) {\n const entry = sorted[i];\n if (entry === undefined) {\n break;\n }\n let suggestion = entry[0];\n if (capitalizationScheme === 'uppercase') {\n suggestion = suggestion.toUpperCase();\n } else if (capitalizationScheme === 'capitalized') {\n suggestion = suggestion.slice(0, 1).toUpperCase() + suggestion.slice(1);\n }\n if (!hasFlag(state, suggestion, 'NOSUGGEST') && !rv.includes(suggestion)) {\n rv.push(suggestion);\n } else {\n workingLimit++;\n }\n }\n return rv;\n}\n\n/**\n * Suggest corrections for a misspelling (REP table + edit-distance ranking).\n */\nexport function suggest(state: HunspellState, word: string, limit = 5): string[] {\n if (word.length === 0 || word.length > MAX_SUGGEST_WORD_LEN) {\n return [];\n }\n\n const normalized = word.replaceAll('\\u2019', \"'\");\n const memo = state.memoized[normalized];\n if (memo !== undefined && (limit <= memo.limit || memo.suggestions.length < memo.limit)) {\n return memo.suggestions.slice(0, limit);\n }\n\n if (check(state, normalized)) {\n return [];\n }\n\n for (const [from, to] of state.replacementTable) {\n if (normalized.includes(from)) {\n const corrected = normalized.replace(from, to);\n if (check(state, corrected)) {\n const suggestions = [corrected];\n remember(state, normalized, suggestions, limit);\n return suggestions;\n }\n }\n }\n\n const suggestions = rankCorrections(state, normalized, limit);\n remember(state, normalized, suggestions, limit);\n return suggestions;\n}\n\nfunction remember(state: HunspellState, word: string, suggestions: string[], limit: number): void {\n const keys = Object.keys(state.memoized);\n if (keys.length >= MAX_SUGGEST_MEMO) {\n const drop = keys[0];\n if (drop !== undefined) {\n delete state.memoized[drop];\n }\n }\n state.memoized[word] = { suggestions, limit };\n}\n","import { check, checkExact } from './check';\nimport { escapeRegExp, MAX_AFF_BYTES, MAX_COMPOUND_REGEX_SOURCE, MAX_DIC_BYTES } from './limits';\nimport { parseAff } from './parseAff';\nimport { parseDic } from './parseDic';\nimport { suggest } from './suggest';\nimport type { HunspellDictionary, HunspellState } from './types';\n\nfunction buildCompoundRegexes(\n patterns: string[],\n compoundRuleCodes: Record<string, string[]>\n): RegExp[] {\n const out: RegExp[] = [];\n for (const ruleText of patterns) {\n let expressionText = '';\n for (const character of ruleText) {\n if (Object.hasOwn(compoundRuleCodes, character)) {\n const stems = compoundRuleCodes[character] ?? [];\n expressionText += `(${stems.map(escapeRegExp).join('|')})`;\n } else {\n // Hunspell COMPOUNDRULE operators (e.g. `*` in `n*1t`) must stay unescaped.\n expressionText += character;\n }\n }\n if (expressionText.length > MAX_COMPOUND_REGEX_SOURCE) {\n continue;\n }\n try {\n out.push(new RegExp(`^${expressionText}$`, 'i'));\n } catch {\n // Malformed pattern after escaping — skip this compound rule.\n }\n }\n return out;\n}\n\n/**\n * Build a Hunspell dictionary from raw `.aff` and `.dic` file contents.\n * Dictionaries are not bundled — callers fetch/load the files.\n */\nexport function createDictionary(affData: string, dicData: string): HunspellDictionary {\n if (affData.length > MAX_AFF_BYTES || dicData.length > MAX_DIC_BYTES) {\n throw new Error('dictionary too large');\n }\n\n const { rules, flags, compoundRulePatterns, replacementTable } = parseAff(affData);\n\n const compoundRuleCodes: Record<string, string[]> = {};\n for (const pattern of compoundRulePatterns) {\n for (const ch of pattern) {\n compoundRuleCodes[ch] = [];\n }\n }\n if (flags.ONLYINCOMPOUND !== undefined) {\n compoundRuleCodes[flags.ONLYINCOMPOUND] = [];\n }\n\n const dictionaryTable = parseDic(dicData, { rules, flags, compoundRuleCodes });\n\n for (const code of Object.keys(compoundRuleCodes)) {\n if ((compoundRuleCodes[code] ?? []).length === 0) {\n delete compoundRuleCodes[code];\n }\n }\n\n const compoundRules = buildCompoundRegexes(compoundRulePatterns, compoundRuleCodes);\n\n const state: HunspellState = {\n dictionaryTable,\n compoundRules,\n replacementTable,\n flags,\n memoized: {},\n alphabet: '',\n };\n\n return {\n loaded: true,\n check: (word: string) => check(state, word),\n checkExact: (word: string) => checkExact(state, word),\n suggest: (word: string, limit?: number) => suggest(state, word, limit),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { createDictionary } from './dictionary';
2
+ export type { AffFlags, AffixEntry, AffixRule, AffixRules, DictionaryTable, HunspellDictionary, ReplacementPair, } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { createDictionary } from './dictionary';
2
+ export type { AffFlags, AffixEntry, AffixRule, AffixRules, DictionaryTable, HunspellDictionary, ReplacementPair, } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EACV,QAAQ,EACR,UAAU,EACV,SAAS,EACT,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,eAAe,GAChB,MAAM,SAAS,CAAC"}