cspell-lib 10.2.0-alpha.7 → 10.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/lib/Settings/LanguageSettings.js +17 -1
  2. package/dist/lib/Settings/sanitizeSettings.js +3 -0
  3. package/dist/lib/Transform/IntlSegmentTextTransformer.d.ts +11 -0
  4. package/dist/lib/Transform/IntlSegmentTextTransformer.js +41 -0
  5. package/dist/lib/Transform/SubstitutionTransformer.d.ts +3 -1
  6. package/dist/lib/Transform/SubstitutionTransformer.js +10 -28
  7. package/dist/lib/Transform/TextMapEdit.d.ts +5 -0
  8. package/dist/lib/Transform/TextMapEdit.js +42 -0
  9. package/dist/lib/Transform/Transformer.d.ts +25 -0
  10. package/dist/lib/Transform/Transformer.js +27 -0
  11. package/dist/lib/Transform/index.d.ts +3 -0
  12. package/dist/lib/Transform/index.js +2 -0
  13. package/dist/lib/textValidation/docValidator.d.ts +3 -2
  14. package/dist/lib/textValidation/docValidator.js +22 -20
  15. package/dist/lib/textValidation/isRandomString.js +4 -3
  16. package/dist/lib/textValidation/lineValidatorFactory.d.ts +2 -2
  17. package/dist/lib/textValidation/lineValidatorFactory.js +7 -2
  18. package/dist/lib/textValidation/textValidator.d.ts +2 -2
  19. package/dist/lib/textValidation/traceWord.js +1 -1
  20. package/dist/lib/util/textRegex.js +1 -1
  21. package/dist/lib/util/wordSplitter/generateWordBreaks.d.ts +32 -0
  22. package/dist/lib/util/wordSplitter/generateWordBreaks.js +120 -0
  23. package/dist/lib/util/wordSplitter/index.d.ts +4 -0
  24. package/dist/lib/util/wordSplitter/index.js +3 -0
  25. package/dist/lib/util/{wordSplitter.d.ts → wordSplitter/wordSplitter.d.ts} +13 -22
  26. package/dist/lib/util/wordSplitter/wordSplitter.js +338 -0
  27. package/dist/lib/util/{wordSplitter.perf.js → wordSplitter/wordSplitter.perf.js} +2 -2
  28. package/package.json +20 -19
  29. package/dist/lib/util/wordSplitter.js +0 -331
  30. /package/dist/lib/util/{wordSplitter.perf.d.ts → wordSplitter/wordSplitter.perf.d.ts} +0 -0
@@ -0,0 +1,120 @@
1
+ import { escapeRegEx } from '../regexHelper.js';
2
+ import { regExDanglingQuote, regExEscapeCharacters, regExPossibleWordBreaks, regExSplitWords, regExSplitWords2, regExTrailingEndings, } from '../textRegex.js';
3
+ export const softHyphen = '\u00AD';
4
+ const ignoreBreak = Object.freeze([]);
5
+ export function generateWordBreaks(line, options) {
6
+ const camelBreaks = genWordBreakCamel(line);
7
+ const symbolBreaks = genSymbolBreaks(line);
8
+ const optionalBreaks = genOptionalWordBreaks(line, options.optionalWordBreakCharacters);
9
+ return mergeSortedBreaks(...camelBreaks, ...symbolBreaks, ...optionalBreaks);
10
+ }
11
+ function offsetRegEx(reg, offset) {
12
+ const r = new RegExp(reg);
13
+ r.lastIndex = offset;
14
+ return r;
15
+ }
16
+ function genWordBreakCamel(line) {
17
+ const breaksCamel1 = [];
18
+ const text = line.line.text.slice(0, line.relEnd);
19
+ // lower,Upper: camelCase -> camel|Case
20
+ for (const m of text.matchAll(offsetRegEx(regExSplitWords, line.relStart))) {
21
+ if (m.index === undefined)
22
+ break;
23
+ const i = m.index + m[1].length;
24
+ breaksCamel1.push({
25
+ offset: m.index,
26
+ breaks: [[i, i], ignoreBreak],
27
+ });
28
+ }
29
+ const breaksCamel2 = [];
30
+ // cspell:ignore ERRORC
31
+ // Upper,Upper,lower: ERRORCodes -> ERROR|Codes, ERRORC|odes
32
+ for (const m of text.matchAll(offsetRegEx(regExSplitWords2, line.relStart))) {
33
+ if (m.index === undefined)
34
+ break;
35
+ const i = m.index + m[1].length;
36
+ const j = i + m[3].length;
37
+ breaksCamel2.push({
38
+ offset: m.index,
39
+ breaks: [[i, i], [j, j], ignoreBreak],
40
+ });
41
+ }
42
+ return [breaksCamel1, breaksCamel2];
43
+ }
44
+ function calcBreaksForSubString(line, subStr, calcBreak) {
45
+ const sb = [];
46
+ const text = line.line.text;
47
+ const inc = subStr.length;
48
+ for (let pos = text.indexOf(subStr, line.relStart); pos >= 0 && pos < line.relEnd; pos = text.indexOf(subStr, pos + inc)) {
49
+ const b = calcBreak(pos, pos + inc);
50
+ if (b) {
51
+ sb.push(b);
52
+ }
53
+ }
54
+ return sb;
55
+ }
56
+ function calcBreaksForRegEx(line, reg, calcBreak) {
57
+ const sb = [];
58
+ const text = line.line.text.slice(0, line.relEnd);
59
+ for (const m of text.matchAll(offsetRegEx(reg, line.relStart))) {
60
+ const b = calcBreak(m);
61
+ if (b) {
62
+ sb.push(b);
63
+ }
64
+ }
65
+ return sb;
66
+ }
67
+ function calcOptionalWordBreak(i, j) {
68
+ return {
69
+ offset: i,
70
+ breaks: [
71
+ [i, j], // Remove the characters
72
+ ignoreBreak,
73
+ ],
74
+ };
75
+ }
76
+ function genOptionalWordBreaks(line, optionalBreakCharacters) {
77
+ function calcBreaks(m) {
78
+ const i = m.index;
79
+ if (i === undefined)
80
+ return;
81
+ const j = i + m[0].length;
82
+ return calcOptionalWordBreak(i, j);
83
+ }
84
+ const breaks = [
85
+ calcBreaksForSubString(line, softHyphen, calcOptionalWordBreak),
86
+ calcBreaksForRegEx(line, regExDanglingQuote, calcBreaks),
87
+ calcBreaksForRegEx(line, regExTrailingEndings, calcBreaks),
88
+ ];
89
+ if (optionalBreakCharacters) {
90
+ const regex = new RegExp(`[${escapeRegEx(optionalBreakCharacters)}]`, 'gu');
91
+ breaks.push(calcBreaksForRegEx(line, regex, calcBreaks));
92
+ }
93
+ return breaks;
94
+ }
95
+ function genSymbolBreaks(line) {
96
+ function calcBreaks(m) {
97
+ const i = m.index;
98
+ if (i === undefined)
99
+ return;
100
+ const j = i + m[0].length;
101
+ return {
102
+ offset: i,
103
+ breaks: [
104
+ [i, j], // Remove the characters
105
+ [i, i], // keep characters with word to right
106
+ [j, j], // keep characters with word to left
107
+ ignoreBreak,
108
+ ],
109
+ };
110
+ }
111
+ return [
112
+ calcBreaksForRegEx(line, regExPossibleWordBreaks, calcBreaks),
113
+ calcBreaksForRegEx(line, /\d+/g, calcBreaks),
114
+ calcBreaksForRegEx(line, regExEscapeCharacters, calcBreaks),
115
+ ];
116
+ }
117
+ function mergeSortedBreaks(...maps) {
118
+ return maps.flat().sort((a, b) => a.offset - b.offset);
119
+ }
120
+ //# sourceMappingURL=generateWordBreaks.js.map
@@ -0,0 +1,4 @@
1
+ export { softHyphen } from './generateWordBreaks.js';
2
+ export type { SplitOptions, SplitResult } from './wordSplitter.js';
3
+ export { split } from './wordSplitter.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { softHyphen } from './generateWordBreaks.js';
2
+ export { split } from './wordSplitter.js';
3
+ //# sourceMappingURL=index.js.map
@@ -1,4 +1,5 @@
1
1
  import type { TextOffset } from '@cspell/cspell-types';
2
+ import type { WordBreakOptions } from './generateWordBreaks.js';
2
3
  export type IsValidWordFn = (word: TextOffset) => boolean;
3
4
  export interface SplitResult {
4
5
  /** Original line passed to the split function */
@@ -12,36 +13,26 @@ export interface SplitResult {
12
13
  /** the offset at which the split stopped */
13
14
  endOffset: number;
14
15
  }
15
- export interface LineSegment {
16
- line: TextOffset;
17
- relStart: number;
18
- relEnd: number;
16
+ export interface TextOffsetWithOptionalValid extends TextOffset {
17
+ length: number;
18
+ isFound?: boolean;
19
19
  }
20
- export interface TextOffsetWithValid extends TextOffset {
20
+ export interface TextOffsetWithValid extends TextOffsetWithOptionalValid {
21
21
  isFound: boolean;
22
22
  }
23
23
  export interface SplitOptions extends WordBreakOptions {
24
24
  }
25
+ /**
26
+ *
27
+ * @param line - the line of text
28
+ * @param offset - absolute offset, comparable to line.offset.
29
+ * @param isValidWord - predicate function to test if a word is valid.
30
+ * @param options - SplitOptions
31
+ * @returns SplitResult
32
+ */
25
33
  export declare function split(line: TextOffset, offset: number, isValidWord: IsValidWordFn, options?: SplitOptions): SplitResult;
26
34
  declare function findNextWordText({ text, offset }: TextOffset): TextOffset;
27
- type BreakPairs = readonly [number, number];
28
- interface PossibleWordBreak {
29
- /** offset from the start of the string */
30
- offset: number;
31
- /**
32
- * break pairs (start, end)
33
- * (the characters between the start and end are removed)
34
- * With a pure break, start === end.
35
- */
36
- breaks: BreakPairs[];
37
- }
38
- export type SortedBreaks = PossibleWordBreak[];
39
- interface WordBreakOptions {
40
- optionalWordBreakCharacters?: string;
41
- }
42
- declare function generateWordBreaks(line: LineSegment, options: WordBreakOptions): SortedBreaks;
43
35
  export declare const __testing__: {
44
- generateWordBreaks: typeof generateWordBreaks;
45
36
  findNextWordText: typeof findNextWordText;
46
37
  };
47
38
  export {};
@@ -0,0 +1,338 @@
1
+ import { PairingHeap } from '../PairingHeap.js';
2
+ import { regExFirstUpper, regExNumericLiteral, regExWordsAndDigits } from '../textRegex.js';
3
+ import { generateWordBreaks, softHyphen } from './generateWordBreaks.js';
4
+ const ignoreBreak = Object.freeze([]);
5
+ const maxSkippedBreaks = 8; // Maximum number of breaks that can be skipped during word splitting.
6
+ // Quote/backtick characters that `regExWordsAndDigits` treats as word characters, so a quoted
7
+ // or templated string literal in source code gets scanned with its delimiters attached.
8
+ const regExLeadingTrimCharacters = /^['’`\\-]+/;
9
+ const regExTrailingTrimCharacters = /['’`\\-]+$/;
10
+ /**
11
+ *
12
+ * @param line - the line of text
13
+ * @param offset - absolute offset, comparable to line.offset.
14
+ * @param isValidWord - predicate function to test if a word is valid.
15
+ * @param options - SplitOptions
16
+ * @returns SplitResult
17
+ */
18
+ export function split(line, offset, isValidWord, options = {}) {
19
+ const relWordToSplit = findNextWordText({ text: line.text, offset: offset - line.offset });
20
+ const hasSoftHyphen = relWordToSplit.text.includes(softHyphen);
21
+ const lineOffset = line.offset;
22
+ const requested = new Map();
23
+ const regExpIgnoreSegment = /^[-.+\d_eE'`\\\s\u00AD]+$/;
24
+ if (!relWordToSplit.text) {
25
+ const text = rebaseTextOffsetToAbsolute(relWordToSplit);
26
+ return {
27
+ line,
28
+ offset,
29
+ text: text,
30
+ words: [],
31
+ endOffset: text.offset + (text.length ?? text.text.length),
32
+ };
33
+ }
34
+ const lineSegment = {
35
+ line,
36
+ relStart: relWordToSplit.offset,
37
+ relEnd: relWordToSplit.offset + relWordToSplit.text.length,
38
+ };
39
+ const possibleBreaks = generateWordBreaks(lineSegment, options);
40
+ if (!possibleBreaks.length) {
41
+ const text = rebaseTextOffsetToAbsolute(relWordToSplit);
42
+ return {
43
+ line,
44
+ offset,
45
+ text: text,
46
+ words: [{ ...text, length: text.length || text.text.length, isFound: isValidWord(text) }],
47
+ endOffset: text.offset + (text.length ?? text.text.length),
48
+ };
49
+ }
50
+ function rebaseTextOffsetToAbsolute(relText) {
51
+ relText.offset += lineOffset;
52
+ return relText;
53
+ }
54
+ function rebaseTextOffsetToRelative(relText) {
55
+ relText.offset -= lineOffset;
56
+ return relText;
57
+ }
58
+ function createTextOffsetWithValid(start, end, isFound) {
59
+ const word = {
60
+ offset: start,
61
+ length: end - start,
62
+ text: line.text.slice(start, end),
63
+ isFound,
64
+ };
65
+ return word;
66
+ }
67
+ function calcKey(start, end) {
68
+ const i = start;
69
+ const j = end - start;
70
+ const v = i + (j << 20);
71
+ return i < 1 << 20 && j < 1 << 11 ? v : -1;
72
+ }
73
+ function checkTextRange(start, end) {
74
+ const v = calcKey(start, end);
75
+ const f = v >= 0 ? requested.get(v) : undefined;
76
+ if (f)
77
+ return f;
78
+ const validated = createTextOffsetWithValid(start, end, false);
79
+ if (regExpIgnoreSegment.test(validated.text)) {
80
+ validated.isFound = true;
81
+ if (v >= 0) {
82
+ requested.set(v, validated);
83
+ }
84
+ return validated;
85
+ }
86
+ rebaseTextOffsetToAbsolute(validated);
87
+ validated.isFound = isValidWord(validated);
88
+ if (hasSoftHyphen && !validated.isFound && validated.text.includes(softHyphen)) {
89
+ removeSoftHyphen(validated);
90
+ validated.isFound = isValidWord(validated);
91
+ }
92
+ rebaseTextOffsetToRelative(validated);
93
+ if (v >= 0) {
94
+ requested.set(v, validated);
95
+ }
96
+ return validated;
97
+ }
98
+ // Add a dummy break at the end to avoid needing to check for last break.
99
+ possibleBreaks.push({
100
+ offset: lineSegment.relEnd,
101
+ breaks: [ignoreBreak],
102
+ });
103
+ const result = {
104
+ line,
105
+ offset,
106
+ text: rebaseTextOffsetToAbsolute(relWordToSplit),
107
+ words: splitIntoWords(lineSegment, possibleBreaks, checkTextRange).map(rebaseTextOffsetToAbsolute),
108
+ endOffset: lineOffset + lineSegment.relEnd,
109
+ };
110
+ return result;
111
+ }
112
+ function removeSoftHyphen(word) {
113
+ word.text = word.text.replaceAll(softHyphen, '');
114
+ return word;
115
+ }
116
+ function findNextWordText({ text, offset }) {
117
+ const reg = new RegExp(regExWordsAndDigits);
118
+ reg.lastIndex = offset;
119
+ const m = reg.exec(text);
120
+ if (!m) {
121
+ // No more words found, return an empty text with offset at the end of the string within the document.
122
+ return { text: '', offset: offset + text.length };
123
+ }
124
+ // Skip numeric literals.
125
+ if (regExNumericLiteral.test(m[0])) {
126
+ return findNextWordText({ text, offset: offset + m[0].length });
127
+ }
128
+ return {
129
+ text: m[0],
130
+ offset: m.index,
131
+ };
132
+ }
133
+ function splitIntoWords(lineSeg, breaks, checkTextRange) {
134
+ const maxIndex = lineSeg.relEnd;
135
+ const maxAttempts = 1000;
136
+ /**
137
+ * Map of known paths by their starting index in the text.
138
+ * This helps in quickly finding previously computed paths to avoid redundant calculations.
139
+ */
140
+ const knownPathsByIndex = new Map();
141
+ const terminalNode = { n: undefined, i: maxIndex, j: maxIndex, c: 0, nc: 0, text: undefined };
142
+ knownPathsByIndex.set(maxIndex, terminalNode);
143
+ // Seed the search with the whole word treated as a single candidate. This guarantees a
144
+ // correct fallback result (e.g. a whole word matched via ignoreWords/the dictionary, or
145
+ // reporting the word as a single misspelling) even when the segmented search below never
146
+ // finds a strictly cheaper split.
147
+ let wholeWord = checkTextRange(lineSeg.relStart, maxIndex);
148
+ if (!wholeWord.isFound) {
149
+ // `regExWordsAndDigits` treats quote/backtick characters as word characters so that
150
+ // things like `Tom's` or `n'cpp` are scanned as a single word; but it also means a
151
+ // quoted/templated string literal in source code (e.g. `'ignoredWord'`) is scanned
152
+ // including its delimiters. Retry against the span with that framing peeled off so a
153
+ // whole word that is otherwise valid/ignored is still recognized as such.
154
+ const raw = lineSeg.line.text.slice(lineSeg.relStart, maxIndex);
155
+ const lead = raw.match(regExLeadingTrimCharacters)?.[0].length ?? 0;
156
+ const tail = raw.slice(lead).match(regExTrailingTrimCharacters)?.[0].length ?? 0;
157
+ if ((lead || tail) && lead + tail < raw.length) {
158
+ wholeWord = checkTextRange(lineSeg.relStart + lead, maxIndex - tail);
159
+ }
160
+ }
161
+ const wholeWordCost = wholeWord.isFound ? 0 : wholeWord.text.length;
162
+ const wholeWordNode = {
163
+ n: terminalNode,
164
+ i: lineSeg.relStart,
165
+ j: maxIndex,
166
+ c: wholeWordCost,
167
+ nc: wholeWordCost,
168
+ text: wholeWord,
169
+ };
170
+ knownPathsByIndex.set(lineSeg.relStart, wholeWordNode);
171
+ function findNearestBreakIndex(offset, bi) {
172
+ while (bi < breaks.length && breaks[bi].offset < offset) {
173
+ bi += 1;
174
+ }
175
+ return bi < breaks.length ? bi : undefined;
176
+ }
177
+ /**
178
+ * Create a set of possible candidate to consider
179
+ * @param p - prev candidate that lead to this one
180
+ * @param i - offset within the string
181
+ * @param bi - current index into the set of breaks
182
+ * @param currentCost - current cost accrued
183
+ */
184
+ function makeCandidates(p, i, bi, bs, currentCost) {
185
+ const len = maxIndex;
186
+ const nBi = findNearestBreakIndex(i, bi);
187
+ const nBs = findNearestBreakIndex(i, bs);
188
+ if (nBi === undefined || nBs === undefined) {
189
+ return [];
190
+ }
191
+ bi = nBi;
192
+ bs = nBs;
193
+ if (bi - bs > maxSkippedBreaks) {
194
+ return [];
195
+ }
196
+ const br = breaks[bi];
197
+ function calcBreakCost(bp) {
198
+ if (bp.length < 2) {
199
+ // We are skipping this break pair.
200
+ return { p, i, j: len, bi, bs, bp, c: currentCost, ec: currentCost + len - i, text: undefined };
201
+ }
202
+ const startOfBreak = bp[0];
203
+ const endOfBreak = bp[1];
204
+ // cost from the end of the segment to the end of the text
205
+ // const costToEnd = knownPathsByIndex.get(endOfBreak)?.c ?? len - endOfBreak;
206
+ const costToEnd = len - endOfBreak;
207
+ // Guess the cost of the word.
208
+ const costOfWord = (startOfBreak - i) * 0.5;
209
+ const d = costOfWord + costToEnd;
210
+ const ec = currentCost + d;
211
+ return { p, i, j: endOfBreak, bi, bs, bp, c: currentCost, ec, text: undefined };
212
+ }
213
+ return br.breaks.map(calcBreakCost);
214
+ }
215
+ function compare(a, b) {
216
+ return a.bi - a.bs - (b.bi - b.bs) || a.ec - b.ec || b.i - a.i || a.j - b.j;
217
+ }
218
+ function pathToWords(node) {
219
+ const results = [];
220
+ for (let p = node; p; p = p.n) {
221
+ if (p.text) {
222
+ results.push(p.text);
223
+ }
224
+ }
225
+ return results;
226
+ }
227
+ function addToKnownPaths(candidate) {
228
+ let path = knownPathsByIndex.get(candidate.j);
229
+ for (let can = candidate; can !== undefined; can = can.p) {
230
+ const t = can.text;
231
+ const i = can.i;
232
+ const j = can.j;
233
+ const currentNode = knownPathsByIndex.get(i);
234
+ const exitingPath = knownPathsByIndex.get(j);
235
+ // If a better-or-equal path is already known, reuse it and keep propagating
236
+ // the (possibly cheaper) suffix back toward the start instead of abandoning it.
237
+ if (exitingPath && path && exitingPath.c <= path.c) {
238
+ path = exitingPath;
239
+ }
240
+ const nc = !t || t.isFound ? 0 : t.length;
241
+ const cost = nc + (path?.c || 0);
242
+ const node = { n: path, i, j, c: cost, nc, text: t };
243
+ if (!currentNode || currentNode.c > cost) {
244
+ knownPathsByIndex.set(i, node);
245
+ }
246
+ path = node;
247
+ }
248
+ return path;
249
+ }
250
+ // A candidate that only ties the whole-word fallback is still preferred over it, since it
251
+ // represents a more granular split; ties between two genuine split candidates keep whichever
252
+ // was found first. Allow the search to explore up to (and including) a tying cost by giving
253
+ // maxCost one extra unit of slack above the seeded whole-word cost.
254
+ function preferCandidate(candidate, current) {
255
+ if (candidate.c < current.c)
256
+ return true;
257
+ if (candidate.c > current.c)
258
+ return false;
259
+ return current === wholeWordNode && candidate !== wholeWordNode;
260
+ }
261
+ let maxCost = wholeWordCost + 1;
262
+ const candidates = new PairingHeap(compare);
263
+ candidates.append(makeCandidates(undefined, lineSeg.relStart, 0, 0, 0));
264
+ let attempts = 0;
265
+ let bestPath = wholeWordNode;
266
+ while (maxCost && candidates.length && attempts++ < maxAttempts) {
267
+ /** Best Candidate Index */
268
+ const best = candidates.dequeue();
269
+ if (!best || best.c >= maxCost) {
270
+ continue;
271
+ }
272
+ // Does it have a split?
273
+ if (best.bp.length) {
274
+ // yes
275
+ const i = best.bp[0];
276
+ const j = best.bp[1];
277
+ const parentPath = knownPathsByIndex.get(j);
278
+ const parentCost = parentPath?.c || 0;
279
+ if (parentCost + best.c >= maxCost) {
280
+ continue;
281
+ }
282
+ const t = i > best.i ? checkTextRange(best.i, i) : undefined;
283
+ const cost = !t || t.isFound ? 0 : t.length;
284
+ const mc = maxIndex - j;
285
+ best.c += cost;
286
+ best.ec = best.c + mc;
287
+ best.text = t;
288
+ if (best.c >= maxCost) {
289
+ continue;
290
+ }
291
+ if (parentPath) {
292
+ // We found a known apply to candidate
293
+ const f = addToKnownPaths(best);
294
+ bestPath = f && preferCandidate(f, bestPath) ? f : bestPath;
295
+ }
296
+ const c = makeCandidates(t ? best : best.p, j, best.bi + 1, best.bs, best.c);
297
+ candidates.append(c);
298
+ }
299
+ else {
300
+ // It is a pass through
301
+ const c = makeCandidates(best.p, best.i, best.bi + 1, best.bs, best.c);
302
+ candidates.append(c);
303
+ if (!c.length) {
304
+ const j = maxIndex;
305
+ const t = j > best.i ? checkTextRange(best.i, j) : undefined;
306
+ const cost = !t || t.isFound ? 0 : t.length;
307
+ best.c += cost;
308
+ best.ec = best.c;
309
+ best.text = t;
310
+ if (best.c >= maxCost) {
311
+ continue;
312
+ }
313
+ const segText = t || best.p?.text || checkTextRange(best.i, best.i);
314
+ const can = t ? { ...best, text: segText } : { ...best, ...best.p, text: segText };
315
+ can.j = j;
316
+ const f = addToKnownPaths(can);
317
+ bestPath = f && preferCandidate(f, bestPath) ? f : bestPath;
318
+ }
319
+ }
320
+ // While the whole-word fallback is still the best known answer, keep one unit of
321
+ // slack above its cost so a genuine split that only ties it can still be explored
322
+ // to completion and preferred by preferCandidate. Once a real split has taken over,
323
+ // tighten exactly like before.
324
+ const slack = bestPath === wholeWordNode ? 1 : 0;
325
+ if (bestPath.c + slack < maxCost) {
326
+ maxCost = bestPath.c + slack;
327
+ }
328
+ }
329
+ const result = pathToWords(bestPath);
330
+ if (result.length === 2 && result.every((r) => !r.isFound) && regExFirstUpper.test(wholeWord.text)) {
331
+ return [wholeWord];
332
+ }
333
+ return result;
334
+ }
335
+ export const __testing__ = {
336
+ findNextWordText,
337
+ };
338
+ //# sourceMappingURL=wordSplitter.js.map
@@ -1,8 +1,8 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { extname } from 'node:path/posix';
3
3
  import { suite } from 'perf-insight';
4
- import { extractPossibleWordsFromTextOffset, extractWordsFromCode, extractWordsFromText, matchStringToTextOffset, splitCamelCaseWordWithOffset, textOffset, } from './text.js';
5
- import { regExWordsAndDigits } from './textRegex.js';
4
+ import { extractPossibleWordsFromTextOffset, extractWordsFromCode, extractWordsFromText, matchStringToTextOffset, splitCamelCaseWordWithOffset, textOffset, } from '../text.js';
5
+ import { regExWordsAndDigits } from '../textRegex.js';
6
6
  const regExpWord = /\b[\w\p{L}\p{M}]+\b/gu;
7
7
  suite('wordSplitter', async (test) => {
8
8
  const lines = await sampleLines();
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "provenance": true
6
6
  },
7
- "version": "10.2.0-alpha.7",
7
+ "version": "10.2.1",
8
8
  "description": "A library of useful functions used across various cspell tools.",
9
9
  "type": "module",
10
10
  "sideEffects": false,
@@ -75,22 +75,22 @@
75
75
  },
76
76
  "homepage": "https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell-lib#readme",
77
77
  "dependencies": {
78
- "@cspell/cspell-bundled-dicts": "10.2.0-alpha.7",
79
- "@cspell/cspell-performance-monitor": "10.2.0-alpha.7",
80
- "@cspell/cspell-pipe": "10.2.0-alpha.7",
81
- "@cspell/cspell-resolver": "10.2.0-alpha.7",
82
- "@cspell/cspell-types": "10.2.0-alpha.7",
83
- "@cspell/dynamic-import": "10.2.0-alpha.7",
84
- "@cspell/filetypes": "10.2.0-alpha.7",
85
- "@cspell/rpc": "10.2.0-alpha.7",
86
- "@cspell/strong-weak-map": "10.2.0-alpha.7",
87
- "@cspell/url": "10.2.0-alpha.7",
88
- "cspell-config-lib": "10.2.0-alpha.7",
89
- "cspell-dictionary": "10.2.0-alpha.7",
90
- "cspell-glob": "10.2.0-alpha.7",
91
- "cspell-grammar": "10.2.0-alpha.7",
92
- "cspell-io": "10.2.0-alpha.7",
93
- "cspell-trie-lib": "10.2.0-alpha.7",
78
+ "@cspell/cspell-bundled-dicts": "10.2.1",
79
+ "@cspell/cspell-performance-monitor": "10.2.1",
80
+ "@cspell/cspell-pipe": "10.2.1",
81
+ "@cspell/cspell-resolver": "10.2.1",
82
+ "@cspell/cspell-types": "10.2.1",
83
+ "@cspell/dynamic-import": "10.2.1",
84
+ "@cspell/filetypes": "10.2.1",
85
+ "@cspell/rpc": "10.2.1",
86
+ "@cspell/strong-weak-map": "10.2.1",
87
+ "@cspell/url": "10.2.1",
88
+ "cspell-config-lib": "10.2.1",
89
+ "cspell-dictionary": "10.2.1",
90
+ "cspell-glob": "10.2.1",
91
+ "cspell-grammar": "10.2.1",
92
+ "cspell-io": "10.2.1",
93
+ "cspell-trie-lib": "10.2.1",
94
94
  "env-paths": "^4.0.0",
95
95
  "gensequence": "^8.0.8",
96
96
  "import-fresh": "^4.0.0",
@@ -103,7 +103,7 @@
103
103
  "node": ">=22.18.0"
104
104
  },
105
105
  "devDependencies": {
106
- "@cspell/cspell-tools": "10.2.0-alpha.7",
106
+ "@cspell/cspell-tools": "10.2.1",
107
107
  "@cspell/dict-cpp": "^7.1.0",
108
108
  "@cspell/dict-csharp": "^4.0.8",
109
109
  "@cspell/dict-css": "^4.1.2",
@@ -113,6 +113,7 @@
113
113
  "@cspell/dict-html": "^4.0.16",
114
114
  "@cspell/dict-nl-nl": "^2.4.2",
115
115
  "@cspell/dict-python": "^4.5.0",
116
+ "@cspell/dict-th-th": "^1.0.0",
116
117
  "@cspell/dictionary-bundler-plugin": "",
117
118
  "@types/configstore": "^6.0.2",
118
119
  "comment-json": "^5.0.0",
@@ -122,5 +123,5 @@
122
123
  "lorem-ipsum": "^3.0.0",
123
124
  "perf-insight": "^2.0.1"
124
125
  },
125
- "gitHead": "308c7ea1e38518476e0ef060bfb561ca43e24892"
126
+ "gitHead": "67a64ad0f3f4dcb681f9b9deac60c9758619ffbc"
126
127
  }