cspell-lib 9.6.4 → 9.8.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.
Files changed (39) hide show
  1. package/dist/lib/Models/ValidationIssue.d.ts +8 -1
  2. package/dist/lib/Models/ValidationIssue.js +15 -1
  3. package/dist/lib/Models/ValidationResult.d.ts +24 -3
  4. package/dist/lib/Settings/CSpellSettingsServer.js +2 -2
  5. package/dist/lib/Settings/Controller/configLoader/configLoader.d.ts +2 -2
  6. package/dist/lib/Settings/Controller/configLoader/configLoader.js +27 -6
  7. package/dist/lib/Settings/Controller/configLoader/defaultConfigLoader.d.ts +0 -1
  8. package/dist/lib/Settings/DefaultSettings.d.ts +11 -0
  9. package/dist/lib/Settings/DefaultSettings.js +14 -3
  10. package/dist/lib/Settings/constants.js +1 -0
  11. package/dist/lib/Settings/sanitizeSettings.js +6 -0
  12. package/dist/lib/Transform/SourceMap.d.ts +115 -0
  13. package/dist/lib/Transform/SourceMap.js +469 -0
  14. package/dist/lib/Transform/SubstitutionTransformer.d.ts +32 -0
  15. package/dist/lib/Transform/SubstitutionTransformer.js +121 -0
  16. package/dist/lib/Transform/TextMap.d.ts +35 -0
  17. package/dist/lib/Transform/TextMap.js +86 -0
  18. package/dist/lib/{util → Transform}/TextRange.js +6 -7
  19. package/dist/lib/Transform/index.d.ts +9 -0
  20. package/dist/lib/Transform/index.js +5 -0
  21. package/dist/lib/{textValidation → Transform}/parsedText.d.ts +5 -6
  22. package/dist/lib/{textValidation → Transform}/parsedText.js +6 -34
  23. package/dist/lib/Transform/types.d.ts +5 -0
  24. package/dist/lib/Transform/types.js +2 -0
  25. package/dist/lib/spellCheckFile.d.ts +26 -1
  26. package/dist/lib/spellCheckFile.js +21 -8
  27. package/dist/lib/textValidation/docValidator.d.ts +5 -7
  28. package/dist/lib/textValidation/docValidator.js +14 -5
  29. package/dist/lib/textValidation/index.d.ts +1 -1
  30. package/dist/lib/textValidation/lineValidatorFactory.d.ts +5 -1
  31. package/dist/lib/textValidation/lineValidatorFactory.js +5 -3
  32. package/dist/lib/textValidation/settingsToValidateOptions.js +10 -1
  33. package/dist/lib/textValidation/textValidator.d.ts +1 -1
  34. package/dist/lib/textValidation/textValidator.js +1 -1
  35. package/dist/lib/util/fileReader.d.ts +1 -1
  36. package/package.json +26 -26
  37. package/dist/lib/util/TextMap.d.ts +0 -15
  38. package/dist/lib/util/TextMap.js +0 -54
  39. /package/dist/lib/{util → Transform}/TextRange.d.ts +0 -0
@@ -0,0 +1,469 @@
1
+ import assert from 'node:assert';
2
+ class SourceMapCursorImpl {
3
+ sourceMap;
4
+ idx;
5
+ begin0;
6
+ begin1;
7
+ /**
8
+ * The delta in the source
9
+ */
10
+ d0;
11
+ /**
12
+ * The delta in the transformed text.
13
+ */
14
+ d1;
15
+ /**
16
+ * Indicates whether the current segment is linear (1:1) or non-linear.
17
+ * A linear segment has equal deltas in the source and transformed text,
18
+ * while a non-linear segment has different deltas.
19
+ * It is possible that a non-linear segment has the same deltas,
20
+ * but it is not possible for a linear segment to have different deltas.
21
+ */
22
+ linear;
23
+ /**
24
+ * indicates that the cursor has reached the end of the source map.
25
+ */
26
+ done;
27
+ constructor(sourceMap) {
28
+ this.sourceMap = sourceMap;
29
+ this.idx = -2;
30
+ this.begin0 = 0;
31
+ this.begin1 = 0;
32
+ this.d0 = 0;
33
+ this.d1 = 0;
34
+ this.linear = true;
35
+ this.done = false;
36
+ this.next();
37
+ }
38
+ next() {
39
+ if (this.done)
40
+ return false;
41
+ this.idx += 2;
42
+ this.begin0 += this.d0;
43
+ this.begin1 += this.d1;
44
+ this.d0 = this.sourceMap[this.idx] || 0;
45
+ this.d1 = this.sourceMap[this.idx + 1] || 0;
46
+ this.linear = this.d0 === this.d1;
47
+ this.done = this.idx >= this.sourceMap.length;
48
+ if (this.d0 === 0 && this.d1 === 0 && !this.done) {
49
+ this.next();
50
+ this.linear = this.done;
51
+ }
52
+ return !this.done;
53
+ }
54
+ mapOffsetToDest(offsetInSrc) {
55
+ if (offsetInSrc < this.begin0)
56
+ this.reset();
57
+ while (!this.done && offsetInSrc >= this.begin0 + this.d0) {
58
+ this.next();
59
+ }
60
+ if (this.linear) {
61
+ return offsetInSrc - this.begin0 + this.begin1;
62
+ }
63
+ // For a non-linear segment, the offset in the source maps to the start of the segment in the transformed text.
64
+ return this.begin1;
65
+ }
66
+ mapOffsetToSrc(offsetInDst) {
67
+ if (offsetInDst < this.begin1)
68
+ this.reset();
69
+ while (!this.done && offsetInDst >= this.begin1 + this.d1) {
70
+ this.next();
71
+ }
72
+ if (this.linear) {
73
+ return offsetInDst - this.begin1 + this.begin0;
74
+ }
75
+ // For a non-linear segment, the offset in the transformed text maps to the start of the segment in the source text.
76
+ return this.begin0;
77
+ }
78
+ mapRangeToSrc(rangeInDst) {
79
+ return [this.mapOffsetToSrc(rangeInDst[0]), this.mapOffsetToSrc(rangeInDst[1])];
80
+ }
81
+ reset() {
82
+ this.idx = -2;
83
+ this.begin0 = 0;
84
+ this.begin1 = 0;
85
+ this.next();
86
+ }
87
+ }
88
+ export function createSourceMapCursor(sourceMap) {
89
+ if (!sourceMap)
90
+ return undefined;
91
+ assert((sourceMap.length & 1) === 0, 'Map must be pairs of values.');
92
+ return new SourceMapCursorImpl(sourceMap);
93
+ }
94
+ /**
95
+ * Calculated the transformed offset in the destination text based on the source map and the offset in the source text.
96
+ * @param cursor - The cursor to use for the mapping. If undefined or empty, the input offset is returned, assuming it is a 1:1 mapping.
97
+ * @param offsetInSrc - the offset in the source text to map to the transformed text. The offset is relative to the start of the text range.
98
+ * @returns The offset in the transformed text corresponding to the input offset in the source text. The offset is relative to the start of the text range.
99
+ */
100
+ export function calcOffsetInDst(cursor, offsetInSrc) {
101
+ if (!cursor?.sourceMap.length) {
102
+ return offsetInSrc;
103
+ }
104
+ return cursor.mapOffsetToDest(offsetInSrc);
105
+ }
106
+ /**
107
+ * Calculated the transformed offset in the source text based on the source map and the offset in the transformed text.
108
+ * @param cursor - The cursor to use for the mapping. If undefined or empty, the input offset is returned, assuming it is a 1:1 mapping.
109
+ * @param offsetInDst - the offset in the transformed text to map to the source text. The offset is relative to the start of the text range.
110
+ * @returns The offset in the source text corresponding to the input offset in the transformed text. The offset is relative to the start of the text range.
111
+ */
112
+ export function calcOffsetInSrc(cursor, offsetInDst) {
113
+ if (!cursor?.sourceMap.length) {
114
+ return offsetInDst;
115
+ }
116
+ return cursor.mapOffsetToSrc(offsetInDst);
117
+ }
118
+ /**
119
+ * Map offset pairs to a source map. The input map is expected to be pairs of absolute offsets in the source and transformed text.
120
+ * The output map is pairs of lengths.
121
+ * @param map - The input map to convert. The map must be pairs of values (even, odd) where the even values are offsets in the source
122
+ * text and the odd values are offsets in the transformed text. The offsets are absolute offsets from the start of the text range.
123
+ * @returns a SourceMap
124
+ */
125
+ export function mapOffsetPairsToSourceMap(map) {
126
+ if (!map)
127
+ return undefined;
128
+ assert((map.length & 1) === 0, 'Map must be pairs of values.');
129
+ const srcMap = [];
130
+ let base0 = 0;
131
+ let base1 = 0;
132
+ for (let i = 0; i < map.length; i += 2) {
133
+ const d0 = map[i] - base0;
134
+ const d1 = map[i + 1] - base1;
135
+ base0 += d0;
136
+ base1 += d1;
137
+ if (d0 === 0 && d1 === 0)
138
+ continue;
139
+ srcMap.push(d0, d1);
140
+ }
141
+ return srcMap;
142
+ }
143
+ /**
144
+ * Merge two source maps into a single source map. The first map transforms from the
145
+ * original text to an intermediate text, and the second map transforms from the intermediate
146
+ * text to the final text. The resulting map represents the transformation directly from the
147
+ * original text to the final text.
148
+ *
149
+ * Concept:
150
+ * [markdown codeblock] -> <first map> -> [JavaScript code] -> <second map> -> [string value]
151
+ *
152
+ * Some kinds of transforms:
153
+ * - markdown code block extraction
154
+ * - unicode normalization
155
+ * - html entity substitution
156
+ * - url decoding
157
+ * - etc.
158
+ *
159
+ * The result of each transform is a {@link SourceMap}. When multiple transforms are applied,
160
+ * the source maps can be merged to create a single map that represents the cumulative effect
161
+ * of all transforms. This is useful for accurately mapping positions in the final transformed
162
+ * text back to their corresponding positions in the original text, which is essential for
163
+ * reporting spelling issues in the correct context.
164
+ *
165
+ * @param first - The first transformation map from the original text to the intermediate.
166
+ * @param second - The second transformation map from the intermediate, to the final text.
167
+ */
168
+ export function mergeSourceMaps(first, second) {
169
+ if (!second?.length)
170
+ return first?.length ? first : undefined;
171
+ if (!first?.length)
172
+ return second?.length ? second : undefined;
173
+ assert((first.length & 1) === 0, 'First map must be pairs of values.');
174
+ assert((second.length & 1) === 0, 'Second map must be pairs of values.');
175
+ const result = [];
176
+ const cursor1 = new SourceMapMergeCursor(first);
177
+ const cursor2 = new SourceMapMergeCursor(second);
178
+ while (advanceCursors(cursor1, cursor2, result)) {
179
+ // empty
180
+ }
181
+ return result;
182
+ }
183
+ class SourceMapMergeCursor {
184
+ sourceMap;
185
+ idx;
186
+ begin0;
187
+ begin1;
188
+ end0;
189
+ end1;
190
+ /**
191
+ * The last position emitted in the source text.
192
+ */
193
+ p0;
194
+ /**
195
+ * The last position emitted in the transformed text.
196
+ */
197
+ p1;
198
+ /**
199
+ * The delta in the source
200
+ */
201
+ d0;
202
+ /**
203
+ * The delta in the transformed text.
204
+ */
205
+ d1;
206
+ /**
207
+ * Indicates whether the current segment is linear (1:1) or non-linear.
208
+ * A linear segment has equal deltas in the source and transformed text,
209
+ * while a non-linear segment has different deltas.
210
+ * It is possible that a non-linear segment has the same deltas,
211
+ * but it is not possible for a linear segment to have different deltas.
212
+ */
213
+ linear;
214
+ /**
215
+ * indicates that the cursor has reached the end of the source map.
216
+ */
217
+ done;
218
+ constructor(sourceMap, idx = 0) {
219
+ this.sourceMap = sourceMap;
220
+ this.idx = idx;
221
+ this.begin0 = 0;
222
+ this.begin1 = 0;
223
+ this.p0 = 0;
224
+ this.p1 = 0;
225
+ this.d0 = this.sourceMap[this.idx] || 0;
226
+ this.d1 = this.sourceMap[this.idx + 1] || 0;
227
+ this.end0 = this.begin0 + this.d0;
228
+ this.end1 = this.begin1 + this.d1;
229
+ this.linear = this.d0 === this.d1;
230
+ const forceNonLinear = this.d0 === 0 && this.d1 === 0;
231
+ this.done = this.idx >= this.sourceMap.length;
232
+ if (forceNonLinear) {
233
+ this.next(false);
234
+ this.linear = this.done;
235
+ }
236
+ }
237
+ /**
238
+ *
239
+ * @param moveP - Reset the current position.
240
+ * @returns true if not done.
241
+ */
242
+ next(moveP) {
243
+ if (this.done) {
244
+ if (moveP) {
245
+ this.p0 = this.end0;
246
+ this.p1 = this.end1;
247
+ }
248
+ return false;
249
+ }
250
+ this.idx += 2;
251
+ this.begin0 += this.d0;
252
+ this.begin1 += this.d1;
253
+ if (moveP) {
254
+ this.p0 = this.begin0;
255
+ this.p1 = this.begin1;
256
+ }
257
+ this.d0 = this.sourceMap[this.idx] || 0;
258
+ this.d1 = this.sourceMap[this.idx + 1] || 0;
259
+ this.end0 = this.begin0 + this.d0;
260
+ this.end1 = this.begin1 + this.d1;
261
+ this.linear = this.d0 === this.d1;
262
+ const forceNonLinear = this.d0 === 0 && this.d1 === 0;
263
+ this.done = this.idx >= this.sourceMap.length;
264
+ if (forceNonLinear) {
265
+ this.next(false);
266
+ this.linear = this.done;
267
+ }
268
+ return !this.done;
269
+ }
270
+ }
271
+ /**
272
+ * Advance one or both cursors to the next position in their respective source maps and push the corresponding delta pair(s) to the target map.
273
+ * The function compares the end positions of the two cursors and advances the cursor(s) that have the smaller end position.
274
+ * If both cursors have the same end position, both are advanced.
275
+ * The delta pair(s) pushed to the target map represent the change in offsets from the current position to the next position in the source map(s).
276
+ *
277
+ * Cursor1 represents the transformation from A to B, and Cursor2 represents the transformation from B to C.
278
+ * The target map represents the transformation from A to C. B is the shared edge between the two transformations.
279
+ *
280
+ * - A - cursor1.0
281
+ * - B - cursor1.1 and cursor2.0. <- shared edge
282
+ * - C - cursor2.1
283
+ *
284
+ * There are twelve cases to consider when advancing the cursors.
285
+ *
286
+ * ```
287
+ * Linear Shared End. C0 < C1 C0 > C1
288
+ * C0 *---* ?~~~* ?~~* ?~~~*
289
+ * C1 *---* ?~~~* ?~~~* ?~~*
290
+ *
291
+ * C0 *---* ?~~~* ?~~* ?~~~*
292
+ * C1 *--* ?---* ?---* ?--*
293
+ *
294
+ * C0 *--* ?---* ?--* ?---*
295
+ * C1 *---* ?~~~* ?~~~* ?~~*
296
+ *
297
+ * ```
298
+ * - `-` represents a linear segment where the deltas in the source and transformed text are equal.
299
+ * - `~` represents a non-linear segment where the deltas in the source and transformed text are different.
300
+ *
301
+ * @param cursor1 - The cursor for the first transformation map from source to intermediate.
302
+ * @param cursor2 - The cursor for the second transformation map from intermediate to destination.
303
+ * @param target - The target source map from source to destination to push the delta pair(s) to.
304
+ * @return true if there is more work to do, false if both cursors are at the end of their respective source maps.
305
+ */
306
+ function advanceCursors(cursor1, cursor2, target) {
307
+ if (cursor1.done) {
308
+ const adjA = cursor1.end0 - cursor1.p0;
309
+ const adjB = cursor1.end1 - cursor1.p1;
310
+ const adjustment = adjA - adjB;
311
+ const dA = cursor2.end0 - cursor2.p0 + adjustment;
312
+ const dC = cursor2.end1 - cursor2.p1;
313
+ if (!cursor2.done || dA !== 0 || dC !== 0) {
314
+ if (dA === dC && (!cursor2.linear || adjA || adjB)) {
315
+ target.push(0, 0);
316
+ }
317
+ target.push(dA, dC);
318
+ }
319
+ cursor1.next(true);
320
+ return cursor2.next(true);
321
+ }
322
+ if (cursor2.done) {
323
+ const adjB = cursor2.end0 - cursor2.p0;
324
+ const adjC = cursor2.end1 - cursor2.p1;
325
+ const adjustment = adjB - adjC;
326
+ const dA = cursor1.end0 - cursor1.p0;
327
+ const dC = cursor1.end1 - cursor1.p1 + adjustment;
328
+ if (dA === dC && (!cursor1.linear || adjustment !== 0)) {
329
+ target.push(0, 0);
330
+ }
331
+ target.push(dA, dC);
332
+ cursor2.next(true);
333
+ return cursor1.next(true);
334
+ }
335
+ assert(cursor1.p1 === cursor2.p0, 'The shared edge must match between the two cursors.');
336
+ if (cursor1.linear && cursor2.linear) {
337
+ const p = Math.min(cursor1.end1, cursor2.end0);
338
+ const dB = p - cursor1.p1;
339
+ const pA = cursor1.begin0 + dB;
340
+ const pC = cursor2.begin1 + dB;
341
+ const dA = pA - cursor1.p0;
342
+ const dC = pC - cursor2.p1;
343
+ cursor1.p0 = pA;
344
+ cursor1.p1 = p;
345
+ cursor2.p0 = p;
346
+ cursor2.p1 = pC;
347
+ if (cursor1.p1 === cursor1.end1) {
348
+ cursor1.next(true);
349
+ }
350
+ if (cursor2.p0 === cursor2.end0) {
351
+ cursor2.next(true);
352
+ }
353
+ target.push(dA, dC);
354
+ return true;
355
+ }
356
+ // Non-linear
357
+ if (cursor1.end1 === cursor2.end0) {
358
+ const dA = cursor1.end0 - cursor1.p0;
359
+ const dC = cursor2.end1 - cursor2.p1;
360
+ if (dA === dC) {
361
+ // Force a non-linear map when the deltas are the same but one of the cursors is non-linear.
362
+ // This is needed to ensure that the merged map accurately represents the transformations,
363
+ // even when they result in no change in length.
364
+ target.push(0, 0);
365
+ }
366
+ target.push(dA, dC);
367
+ cursor1.next(true);
368
+ cursor2.next(true);
369
+ return true;
370
+ }
371
+ if (cursor1.linear) {
372
+ if (cursor1.end1 < cursor2.end0) {
373
+ // The linear segment in inside the non-linear segment.
374
+ // Advance cursor 1 to the end of the linear segment.
375
+ cursor1.next(false);
376
+ return true;
377
+ }
378
+ // Split cursor 1 at the end of cursor 2 to maintain linearity.
379
+ const p = cursor2.end0;
380
+ const dB = p - cursor1.begin1;
381
+ const pA = cursor1.begin0 + dB;
382
+ const dA = pA - cursor1.p0;
383
+ const dC = cursor2.end1 - cursor2.p1;
384
+ if (dA === dC) {
385
+ // Force a non-linear map when the deltas are the same but one of the cursors is non-linear.
386
+ // This is needed to ensure that the merged map accurately represents the transformations,
387
+ // even when they result in no change in length.
388
+ target.push(0, 0);
389
+ }
390
+ target.push(dA, dC);
391
+ cursor1.p0 = pA;
392
+ cursor1.p1 = p;
393
+ cursor2.next(true);
394
+ return true;
395
+ }
396
+ if (cursor2.linear) {
397
+ if (cursor2.end0 < cursor1.end1) {
398
+ // The linear segment in inside the non-linear segment.
399
+ // Advance cursor 2 to the end of the linear segment.
400
+ cursor2.next(false);
401
+ return true;
402
+ }
403
+ // Split cursor 2 at the end of cursor 1 to maintain linearity.
404
+ const p = cursor1.end1;
405
+ const dB = p - cursor2.begin0;
406
+ const pC = cursor2.begin1 + dB;
407
+ const dA = cursor1.end0 - cursor1.p0;
408
+ const dC = pC - cursor2.p1;
409
+ if (dA === dC) {
410
+ // Force a non-linear map when the deltas are the same but one of the cursors is non-linear.
411
+ // This is needed to ensure that the merged map accurately represents the transformations,
412
+ // even when they result in no change in length.
413
+ target.push(0, 0);
414
+ }
415
+ target.push(dA, dC);
416
+ cursor1.next(true);
417
+ cursor2.p0 = p;
418
+ cursor2.p1 = pC;
419
+ return true;
420
+ }
421
+ // Two non-linear segments. They cannot be split.
422
+ if (cursor1.end1 < cursor2.end0) {
423
+ cursor1.next(false);
424
+ return true;
425
+ }
426
+ cursor2.next(false);
427
+ return true;
428
+ }
429
+ export function sliceSourceMapToSourceRange(map, range) {
430
+ if (!map?.length)
431
+ return map;
432
+ const cursor = createSourceMapCursor(map);
433
+ const [start, end] = range;
434
+ const startDst = cursor.mapOffsetToDest(start);
435
+ if (cursor.done)
436
+ return [];
437
+ const startIdx = cursor.idx;
438
+ const startOffsetSrc = cursor.begin0;
439
+ const startOffsetDst = cursor.begin1;
440
+ const startLinear = cursor.linear;
441
+ cursor.mapOffsetToDest(end);
442
+ let endIdx = cursor.idx;
443
+ if (startIdx === endIdx && startLinear) {
444
+ return [];
445
+ }
446
+ if (!cursor.linear || cursor.begin0 < end) {
447
+ endIdx += 2;
448
+ }
449
+ const newMap = map.slice(startIdx, endIdx);
450
+ // Only adjust the first pair of offsets, the rest are relative to the first pair.
451
+ newMap[0] -= start - startOffsetSrc;
452
+ newMap[1] -= startDst - startOffsetDst;
453
+ if (newMap[0] === newMap[1] && !startLinear) {
454
+ // Force a non-linear map when the deltas are the same but the segment is non-linear.
455
+ newMap.unshift(0, 0);
456
+ }
457
+ return newMap;
458
+ }
459
+ export function reverseSourceMap(map) {
460
+ if (!map?.length)
461
+ return map;
462
+ assert((map.length & 1) === 0, 'Map must be pairs of values.');
463
+ const reversed = [];
464
+ for (let i = 0; i < map.length; i += 2) {
465
+ reversed.push(map[i + 1], map[i]);
466
+ }
467
+ return reversed;
468
+ }
469
+ //# sourceMappingURL=SourceMap.js.map
@@ -0,0 +1,32 @@
1
+ import type { MappedText, SubstitutionDefinitions, Substitutions } from '@cspell/cspell-types';
2
+ type DeepReadonly<T> = T extends object ? {
3
+ readonly [K in keyof T]: DeepReadonly<T[K]>;
4
+ } : T;
5
+ export interface SubstitutionInfo {
6
+ substitutions?: Substitutions | undefined;
7
+ substitutionDefinitions?: SubstitutionDefinitions | undefined;
8
+ }
9
+ export type ReadonlySubstitutionInfo = DeepReadonly<SubstitutionInfo>;
10
+ export declare class SubstitutionTransformer {
11
+ #private;
12
+ constructor(subMap: Map<string, string> | undefined);
13
+ transform(text: string | MappedText): MappedText;
14
+ transformString(text: string): MappedText;
15
+ }
16
+ export interface CreateSubstitutionTransformerResult {
17
+ transformer: SubstitutionTransformer;
18
+ /**
19
+ * The list of substitutions that were requested but not found in the definitions.
20
+ * This is used to report errors to the user about missing substitutions.
21
+ */
22
+ missing: string[] | undefined;
23
+ }
24
+ /**
25
+ * Creates a SubstitutionTransformer based upon the provided SubstitutionInfo.
26
+ * This will create a transformer that can be used to apply the substitutions to a document before spell checking.
27
+ * @param info
28
+ * @returns
29
+ */
30
+ export declare function createSubstitutionTransformer(info: ReadonlySubstitutionInfo): CreateSubstitutionTransformerResult;
31
+ export {};
32
+ //# sourceMappingURL=SubstitutionTransformer.d.ts.map
@@ -0,0 +1,121 @@
1
+ import { GTrie } from 'cspell-trie-lib';
2
+ import { mapOffsetPairsToSourceMap, mergeSourceMaps } from './SourceMap.js';
3
+ export class SubstitutionTransformer {
4
+ #trie;
5
+ constructor(subMap) {
6
+ this.#trie = subMap ? GTrie.fromEntries(subMap) : undefined;
7
+ }
8
+ transform(text) {
9
+ if (typeof text === 'string') {
10
+ return this.transformString(text);
11
+ }
12
+ if (!this.#trie)
13
+ return text;
14
+ const transformed = this.transformString(text.text);
15
+ const result = {
16
+ ...text,
17
+ text: transformed.text,
18
+ map: mergeSourceMaps(text.map, transformed.map),
19
+ };
20
+ return result;
21
+ }
22
+ transformString(text) {
23
+ if (!this.#trie) {
24
+ return { text, range: [0, text.length] };
25
+ }
26
+ const map = [0, 0];
27
+ let repText = '';
28
+ let lastEnd = 0;
29
+ for (const edit of calcEdits(text, this.#trie)) {
30
+ if (edit.range[0] > lastEnd) {
31
+ repText += text.slice(lastEnd, edit.range[0]);
32
+ map.push(edit.range[0], repText.length);
33
+ }
34
+ repText += edit.text;
35
+ map.push(edit.range[1], repText.length);
36
+ lastEnd = edit.range[1];
37
+ }
38
+ if (lastEnd === 0) {
39
+ return { text, range: [0, text.length] };
40
+ }
41
+ if (lastEnd < text.length) {
42
+ repText += text.slice(lastEnd);
43
+ map.push(text.length, repText.length);
44
+ }
45
+ const result = {
46
+ text: repText,
47
+ range: [0, text.length],
48
+ map: mapOffsetPairsToSourceMap(map),
49
+ };
50
+ return result;
51
+ }
52
+ }
53
+ function* calcEdits(text, subTrie) {
54
+ let i = 0;
55
+ while (i < text.length) {
56
+ const edit = findSubString(text, subTrie, i);
57
+ if (edit) {
58
+ yield edit;
59
+ i = edit.range[1];
60
+ continue;
61
+ }
62
+ ++i;
63
+ }
64
+ }
65
+ function findSubString(text, subTrie, start) {
66
+ let node = subTrie.root;
67
+ let i = start;
68
+ let lastMatch = undefined;
69
+ node = node.children?.get(text[i]);
70
+ while (i < text.length && node) {
71
+ ++i;
72
+ if (node.value !== undefined) {
73
+ lastMatch = { text: node.value, range: [start, i] };
74
+ }
75
+ node = node.children?.get(text[i]);
76
+ }
77
+ return lastMatch;
78
+ }
79
+ function calcSubMap(subs, defs) {
80
+ const subMap = new Map();
81
+ const missing = [];
82
+ const defMap = buildDefinitionMap(defs);
83
+ for (const sub of subs) {
84
+ if (typeof sub === 'string') {
85
+ const def = defMap.get(sub);
86
+ if (!def) {
87
+ missing.push(sub);
88
+ continue;
89
+ }
90
+ for (const [find, replacement] of def.entries) {
91
+ subMap.set(find, replacement);
92
+ }
93
+ continue;
94
+ }
95
+ subMap.set(sub[0], sub[1]);
96
+ }
97
+ return { subMap, missing };
98
+ }
99
+ function buildDefinitionMap(defs) {
100
+ const defMap = new Map();
101
+ for (const def of defs) {
102
+ defMap.set(def.name, def);
103
+ }
104
+ return defMap;
105
+ }
106
+ /**
107
+ * Creates a SubstitutionTransformer based upon the provided SubstitutionInfo.
108
+ * This will create a transformer that can be used to apply the substitutions to a document before spell checking.
109
+ * @param info
110
+ * @returns
111
+ */
112
+ export function createSubstitutionTransformer(info) {
113
+ const { subMap, missing } = info.substitutions && info.substitutionDefinitions
114
+ ? calcSubMap(info.substitutions, info.substitutionDefinitions)
115
+ : {};
116
+ return {
117
+ transformer: new SubstitutionTransformer(subMap),
118
+ missing: missing && missing.length > 0 ? missing : undefined,
119
+ };
120
+ }
121
+ //# sourceMappingURL=SubstitutionTransformer.js.map
@@ -0,0 +1,35 @@
1
+ import type { MappedText, Range, SourceMap } from '@cspell/cspell-types';
2
+ /**
3
+ * Extract a substring from a TextMap.
4
+ * @param textMap - A text range with an optional map
5
+ * @param extractRange - The range in the original document to extract
6
+ * @returns The TextMap covering extractRange
7
+ */
8
+ export declare function extractTextMapRangeOrigin(textMap: MappedText, extractRange: Range): MappedText;
9
+ export declare function calculateRangeInDest(srcMap: SourceMap | undefined, rangeOrigin: Range): Range;
10
+ export declare function calculateRangeInSrc(srcMap: SourceMap | undefined, rangeOrigin: Range): Range;
11
+ export declare function calculateTextMapRangeDest(textMap: MappedText, rangeOrigin: Range): Range;
12
+ /**
13
+ * Map an offset in the transformed text back to the original text.
14
+ * It will find the first matching position in the map.
15
+ *
16
+ * @param map - The source map to use for the mapping.
17
+ * If undefined or empty, the input offset is returned, assuming it is a 1:1 mapping.
18
+ * @param offset - the offset in the transformed text to map back to the original text
19
+ */
20
+ export declare function mapOffsetToSource(map: SourceMap | undefined, offset: number): number;
21
+ /**
22
+ * Map an offset in the original text to the transformed text.
23
+ * It will find the first matching position in the map.
24
+ *
25
+ * @param map - The source map to use for the mapping.
26
+ * If undefined or empty, the input offset is returned, assuming it is a 1:1 mapping.
27
+ * @param offset - the offset in the original text to map to the transformed text
28
+ */
29
+ export declare function mapOffsetToDest(map: SourceMap | undefined, offset: number): number;
30
+ interface WithRange {
31
+ readonly range: Range;
32
+ }
33
+ export declare function doesIntersect(textMap: WithRange, rangeOrigin: Range): boolean;
34
+ export {};
35
+ //# sourceMappingURL=TextMap.d.ts.map