cspell-lib 10.3.4 → 10.3.6

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.
@@ -335,9 +335,10 @@ function advanceCursors(cursor1, cursor2, target) {
335
335
  assert(cursor1.p1 === cursor2.p0, 'The shared edge must match between the two cursors.');
336
336
  if (cursor1.linear && cursor2.linear) {
337
337
  const p = Math.min(cursor1.end1, cursor2.end0);
338
+ // Both segments are linear, so all three positions advance by the same amount from the current position.
338
339
  const dB = p - cursor1.p1;
339
- const pA = cursor1.begin0 + dB;
340
- const pC = cursor2.begin1 + dB;
340
+ const pA = cursor1.p0 + dB;
341
+ const pC = cursor2.p1 + dB;
341
342
  const dA = pA - cursor1.p0;
342
343
  const dC = pC - cursor2.p1;
343
344
  cursor1.p0 = pA;
@@ -95,9 +95,8 @@ function buildDefinitionMap(defs) {
95
95
  * @returns
96
96
  */
97
97
  export function createSubstitutionTransformer(info) {
98
- const { subMap, missing } = info.substitutions && info.substitutionDefinitions
99
- ? calcSubMap(info.substitutions, info.substitutionDefinitions)
100
- : {};
98
+ const { substitutions, substitutionDefinitions } = info;
99
+ const { subMap, missing } = substitutions ? calcSubMap(substitutions, substitutionDefinitions ?? []) : {};
101
100
  return {
102
101
  transformer: new SubstitutionTransformer(subMap),
103
102
  missing: missing && missing.length > 0 ? missing : undefined,
@@ -25,6 +25,10 @@ export interface SpellCheckFileOptions extends ValidateTextOptions, Pick<CSpellU
25
25
  * @defaultValue undefined
26
26
  */
27
27
  noConfigSearch?: boolean;
28
+ /**
29
+ * If true, the document will be checked even if it would normally be excluded.
30
+ */
31
+ forceCheck?: boolean;
28
32
  }
29
33
  export interface SpellCheckFileOptionsRPC extends SpellCheckFileOptions {
30
34
  measurePerf?: boolean;
@@ -123,15 +123,14 @@ async function spellCheckFullDocument(document, options, settingsOrConfigFile) {
123
123
  const docValOptions = options;
124
124
  const docValidator = await DocumentValidator.create(doc, docValOptions, settingsOrConfigFile).finally(() => timerPrepare.end());
125
125
  Object.assign(perf, Object.fromEntries(Object.entries(docValidator.perfTiming).map(([k, v]) => ['_' + k, v])));
126
- const prep = docValidator._getPreparations();
127
126
  if (docValidator.errors.length) {
128
- const settingsUsed = prep?.localConfig ||
127
+ const settingsUsed = docValidator._getLocalConfig() ||
129
128
  (satisfiesCSpellConfigFile(settingsOrConfigFile) ? settingsOrConfigFile.settings : settingsOrConfigFile);
130
129
  return {
131
130
  document,
132
131
  options,
133
132
  settingsUsed,
134
- localConfigFilepath: prep?.localConfigFilepath,
133
+ localConfigFilepath: docValidator._getLocalConfigFilepath(),
135
134
  issues: [],
136
135
  checked: false,
137
136
  errors: docValidator.errors,
@@ -148,7 +147,7 @@ async function spellCheckFullDocument(document, options, settingsOrConfigFile) {
148
147
  document,
149
148
  options,
150
149
  settingsUsed: docValidator.getFinalizedDocSettings(),
151
- localConfigFilepath: prep?.localConfigFilepath,
150
+ localConfigFilepath: docValidator._getLocalConfigFilepath(),
152
151
  issues,
153
152
  checked: docValidator.shouldCheckDocument(),
154
153
  errors: undefined,
@@ -1,4 +1,3 @@
1
- import assert from 'node:assert';
2
1
  import { resolveDocumentToTextDocument } from '../Document/resolveDocument.js';
3
2
  import { isTextDocument } from '../Models/TextDocument.js';
4
3
  import * as Settings from '../Settings/index.js';
@@ -57,8 +56,6 @@ export async function genCheckText(docValidator) {
57
56
  docValidator.recordCheckedRanges = true;
58
57
  await docValidator.prepare();
59
58
  const issues = docValidator.checkDocument(true);
60
- const preparations = docValidator._getPreparations();
61
- assert(preparations);
62
59
  return genResult(docValidator.document.text, issues, docValidator.getRangesChecked());
63
60
  }
64
61
  function genResult(text, issues, includeRanges) {
@@ -1,13 +1,12 @@
1
- import type { CSpellUserSettings, MappedText, ParsedText } from '@cspell/cspell-types';
1
+ import type { CSpellUserSettings, ParsedText } from '@cspell/cspell-types';
2
2
  import type { ICSpellConfigFile } from 'cspell-config-lib';
3
3
  import type { ExtendedSuggestion } from '../Models/Suggestion.js';
4
4
  import type { TextDocument, TextDocumentRef } from '../Models/TextDocument.js';
5
5
  import type { ValidationIssue } from '../Models/ValidationIssue.js';
6
- import type { CSpellSettingsInternal, CSpellSettingsInternalFinalized, ImportFileRefWithError } from '../Settings/index.js';
7
- import type { SpellingDictionaryCollection } from '../SpellingDictionary/index.js';
6
+ import type { CSpellSettingsInternal, ImportFileRefWithError } from '../Settings/index.js';
8
7
  import type { WordSuggestion } from '../suggestions.js';
9
- import type { MatchRange, SimpleRange, TextTransformer } from '../Transform/index.js';
10
- import type { TextValidationFactoryOptions, TextValidator } from './lineValidatorFactory.js';
8
+ import type { MatchRange, SimpleRange } from '../Transform/index.js';
9
+ import type { Preparations } from './docValidatorPreparations.js';
11
10
  import type { TraceResult } from './traceWord.js';
12
11
  import type { ValidateTextOptions } from './ValidateTextOptions.js';
13
12
  export interface DocumentValidatorOptions extends ValidateTextOptions {
@@ -117,8 +116,20 @@ export declare class DocumentValidator {
117
116
  shouldCheckDocument(): boolean;
118
117
  /**
119
118
  * Internal `cspell-lib` use.
119
+ * @deprecated Will be removed in a future major version. Use {@link _getLocalConfig} and
120
+ * {@link _getLocalConfigFilepath} instead.
120
121
  */
121
122
  _getPreparations(): Preparations | undefined;
123
+ /**
124
+ * Internal `cspell-lib` use.
125
+ * @returns the config file found for the document, if any.
126
+ */
127
+ _getLocalConfig(): CSpellUserSettings | undefined;
128
+ /**
129
+ * Internal `cspell-lib` use.
130
+ * @returns the path of the config file found for the document, if any.
131
+ */
132
+ _getLocalConfigFilepath(): string | undefined;
122
133
  /**
123
134
  * Get the calculated ranges of text that should be included in the spell checking.
124
135
  *
@@ -132,22 +143,6 @@ export declare class DocumentValidator {
132
143
  set recordCheckedRanges(value: boolean);
133
144
  }
134
145
  declare function sanitizeSuggestion(sug: WordSuggestion): ExtendedSuggestion;
135
- interface Preparations {
136
- /** loaded config */
137
- config: CSpellSettingsInternal;
138
- dictionary: SpellingDictionaryCollection;
139
- /** configuration after applying in-doc settings */
140
- docSettings: CSpellSettingsInternal;
141
- finalSettings: CSpellSettingsInternalFinalized;
142
- includeRanges: MatchRange[];
143
- textValidator: TextValidator;
144
- segmenter: (texts: MappedText) => Iterable<MappedText>;
145
- shouldCheck: boolean;
146
- validateOptions: TextValidationFactoryOptions;
147
- localConfig: CSpellUserSettings | undefined;
148
- localConfigFilepath: string | undefined;
149
- transformer: TextTransformer;
150
- }
151
146
  interface ShouldCheckDocumentResult {
152
147
  /** possible errors found while loading configuration. */
153
148
  errors: Error[];
@@ -10,13 +10,14 @@ import { extractImportErrors, finalizeSettings, loadConfig, mergeSettings, resol
10
10
  import { validateInDocumentSettings } from '../Settings/InDocSettings.js';
11
11
  import { getDictionaryInternal } from '../SpellingDictionary/index.js';
12
12
  import { calcSuggestionAdjustedToToMatchCase } from '../suggestions.js';
13
- import { chainTransformers, createIntlSegmentTextTransformer, createMappedTextSegmenter, createSoftWordBreakTextTransformer, createSubstitutionTransformer, } from '../Transform/index.js';
13
+ import { chainTransformers, createIntlSegmentTextTransformer, createSoftWordBreakTextTransformer, createSubstitutionTransformer, } from '../Transform/index.js';
14
14
  import { catchPromiseError, toError } from '../util/errors.js';
15
15
  import { AutoCache } from '../util/simpleCache.js';
16
16
  import { uriToFilePath } from '../util/Uri.js';
17
17
  import { cleanValidationIssue } from './cleanValidationIssue.js';
18
18
  import { defaultMaxDuplicateProblems, defaultMaxNumberOfProblems } from './defaultConstants.js';
19
19
  import { determineTextDocumentSettings } from './determineTextDocumentSettings.js';
20
+ import { DocumentValidatorPreparations } from './docValidatorPreparations.js';
20
21
  import { textValidatorFactory } from './lineValidatorFactory.js';
21
22
  import { settingsToValidateOptions } from './settingsToValidateOptions.js';
22
23
  import { calcTextInclusionRanges } from './textValidator.js';
@@ -108,22 +109,18 @@ export class DocumentValidator {
108
109
  recShouldCheckTime();
109
110
  const recFinalizeTime = recordPerfTime(this.perfTiming, '_finalizeSettings');
110
111
  const finalSettings = finalizeSettings(docSettings);
111
- const { transformer, validateOptions, includeRanges, segmenter, textValidator } = this.#calcSharedPrep(finalSettings, dict);
112
+ const { transformer, validateOptions, calcIncludeRanges, textValidator } = this.#calcSharedPrep(finalSettings, dict);
112
113
  recFinalizeTime();
113
- this._preparations = {
114
- config,
114
+ this._preparations = new DocumentValidatorPreparations(config, localConfig, localConfig?.__importRef?.filename, {
115
115
  dictionary: dict,
116
116
  docSettings,
117
117
  finalSettings,
118
118
  shouldCheck,
119
119
  validateOptions,
120
- includeRanges,
121
- segmenter,
122
120
  textValidator,
123
- localConfig,
124
- localConfigFilepath: localConfig?.__importRef?.filename,
125
121
  transformer,
126
- };
122
+ calcIncludeRanges,
123
+ });
127
124
  this._ready = true;
128
125
  this._preparationTime = timer.elapsed;
129
126
  this.perfTiming.prepTime = this._preparationTime;
@@ -138,19 +135,17 @@ export class DocumentValidator {
138
135
  const stopMeasure = measurePerf('DocumentValidator._updatePrep');
139
136
  const shouldCheck = docSettings.enabled ?? true;
140
137
  const finalSettings = finalizeSettings(docSettings);
141
- const { transformer, validateOptions, includeRanges, segmenter, textValidator } = this.#calcSharedPrep(finalSettings, dict);
142
- this._preparations = {
143
- ...prep,
138
+ const { transformer, validateOptions, calcIncludeRanges, textValidator } = this.#calcSharedPrep(finalSettings, dict);
139
+ prep.update({
144
140
  dictionary: dict,
145
141
  docSettings,
146
142
  finalSettings,
147
143
  shouldCheck,
148
144
  validateOptions,
149
- includeRanges,
150
- segmenter,
151
145
  textValidator,
152
146
  transformer,
153
- };
147
+ calcIncludeRanges,
148
+ });
154
149
  this._preparationTime = timer.elapsed;
155
150
  stopMeasure();
156
151
  }
@@ -169,10 +164,10 @@ export class DocumentValidator {
169
164
  const validateOptions = { ...settingsToValidateOptions(finalSettings), transformer };
170
165
  // Avoid excluding text ranges that have a full substitution applied.
171
166
  const rangeTransformer = finalSettings.substitutions?.length ? sub.transformer : undefined;
172
- const includeRanges = calcTextInclusionRanges(this._document.text, validateOptions, rangeTransformer);
173
- const segmenter = createMappedTextSegmenter(includeRanges);
167
+ const text = this._document.text;
168
+ const calcIncludeRanges = () => calcTextInclusionRanges(text, validateOptions, rangeTransformer);
174
169
  const textValidator = textValidatorFactory(dict, validateOptions);
175
- return { segmenter, textValidator, includeRanges, validateOptions, transformer };
170
+ return { textValidator, calcIncludeRanges, validateOptions, transformer };
176
171
  }
177
172
  /**
178
173
  * The amount of time in ms to prepare for validation.
@@ -415,10 +410,26 @@ export class DocumentValidator {
415
410
  }
416
411
  /**
417
412
  * Internal `cspell-lib` use.
413
+ * @deprecated Will be removed in a future major version. Use {@link _getLocalConfig} and
414
+ * {@link _getLocalConfigFilepath} instead.
418
415
  */
419
416
  _getPreparations() {
420
417
  return this._preparations;
421
418
  }
419
+ /**
420
+ * Internal `cspell-lib` use.
421
+ * @returns the config file found for the document, if any.
422
+ */
423
+ _getLocalConfig() {
424
+ return this._preparations?.localConfig;
425
+ }
426
+ /**
427
+ * Internal `cspell-lib` use.
428
+ * @returns the path of the config file found for the document, if any.
429
+ */
430
+ _getLocalConfigFilepath() {
431
+ return this._preparations?.localConfigFilepath;
432
+ }
422
433
  /**
423
434
  * Get the calculated ranges of text that should be included in the spell checking.
424
435
  *
@@ -509,11 +520,11 @@ export async function shouldCheckDocument(doc, options, settings) {
509
520
  const config = mergeSettings(settings, localConfig);
510
521
  const matcher = getGlobMatcherForExcluding(localConfig?.ignorePaths);
511
522
  // eslint-disable-next-line unicorn/prefer-regexp-test
512
- if (matcher.match(uriToFilePath(doc.uri))) {
523
+ if (!options.forceCheck && matcher.match(uriToFilePath(doc.uri))) {
513
524
  return { errors, shouldCheck: false, settings: localConfig, reason: 'Excluded by ignorePaths.' };
514
525
  }
515
526
  const docSettings = await determineTextDocumentSettings(doc, config);
516
- const shouldCheck = docSettings.enabled ?? true;
527
+ const shouldCheck = options.forceCheck || (docSettings.enabled ?? true);
517
528
  const reason = shouldCheck ? undefined : 'Excluded by overrides or languageSettings.';
518
529
  return { errors, shouldCheck, settings: docSettings, reason };
519
530
  }
@@ -0,0 +1,74 @@
1
+ import type { CSpellUserSettings, MappedText } from '@cspell/cspell-types';
2
+ import type { CSpellSettingsInternal, CSpellSettingsInternalFinalized } from '../Settings/index.js';
3
+ import type { SpellingDictionaryCollection } from '../SpellingDictionary/index.js';
4
+ import type { MatchRange, TextTransformer } from '../Transform/index.js';
5
+ import type { TextValidationFactoryOptions, TextValidator } from './lineValidatorFactory.js';
6
+ /**
7
+ * What a `DocumentValidator` needs to spell check its document.
8
+ */
9
+ export interface Preparations {
10
+ /** loaded config */
11
+ config: CSpellSettingsInternal;
12
+ dictionary: SpellingDictionaryCollection;
13
+ /** configuration after applying in-doc settings */
14
+ docSettings: CSpellSettingsInternal;
15
+ finalSettings: CSpellSettingsInternalFinalized;
16
+ includeRanges: MatchRange[];
17
+ textValidator: TextValidator;
18
+ segmenter: (texts: MappedText) => Iterable<MappedText>;
19
+ shouldCheck: boolean;
20
+ validateOptions: TextValidationFactoryOptions;
21
+ localConfig: CSpellUserSettings | undefined;
22
+ localConfigFilepath: string | undefined;
23
+ transformer: TextTransformer;
24
+ }
25
+ /**
26
+ * The preparations that depend on the document's settings. They are replaced when the document changes.
27
+ */
28
+ export interface DocumentSettingsPreparations {
29
+ dictionary: SpellingDictionaryCollection;
30
+ /** configuration after applying in-doc settings */
31
+ docSettings: CSpellSettingsInternal;
32
+ finalSettings: CSpellSettingsInternalFinalized;
33
+ shouldCheck: boolean;
34
+ validateOptions: TextValidationFactoryOptions;
35
+ textValidator: TextValidator;
36
+ transformer: TextTransformer;
37
+ /**
38
+ * Runs every ignore pattern over the whole document, so it is only called once the include ranges are needed.
39
+ */
40
+ calcIncludeRanges: () => MatchRange[];
41
+ }
42
+ /**
43
+ * The include ranges, and the segmenter built from them, are calculated on first use. A document that
44
+ * is never checked never pays for them.
45
+ */
46
+ export declare class DocumentValidatorPreparations implements Preparations {
47
+ #private;
48
+ readonly config: CSpellSettingsInternal;
49
+ readonly localConfig: CSpellUserSettings | undefined;
50
+ readonly localConfigFilepath: string | undefined;
51
+ dictionary: SpellingDictionaryCollection;
52
+ /** configuration after applying in-doc settings */
53
+ docSettings: CSpellSettingsInternal;
54
+ finalSettings: CSpellSettingsInternalFinalized;
55
+ shouldCheck: boolean;
56
+ validateOptions: TextValidationFactoryOptions;
57
+ textValidator: TextValidator;
58
+ transformer: TextTransformer;
59
+ /**
60
+ * @param config - loaded config
61
+ * @param localConfig - the config file found for the document, if any.
62
+ * @param localConfigFilepath - the path of `localConfig`.
63
+ * @param prep - the preparations that depend on the document's settings.
64
+ */
65
+ constructor(config: CSpellSettingsInternal, localConfig: CSpellUserSettings | undefined, localConfigFilepath: string | undefined, prep: DocumentSettingsPreparations);
66
+ /**
67
+ * Replace the preparations that depend on the document's settings. The include ranges are
68
+ * calculated again on next use.
69
+ */
70
+ update(prep: DocumentSettingsPreparations): void;
71
+ get includeRanges(): MatchRange[];
72
+ get segmenter(): (texts: MappedText) => Iterable<MappedText>;
73
+ }
74
+ //# sourceMappingURL=docValidatorPreparations.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { createMappedTextSegmenter } from '../Transform/index.js';
2
+ /**
3
+ * The include ranges, and the segmenter built from them, are calculated on first use. A document that
4
+ * is never checked never pays for them.
5
+ */
6
+ export class DocumentValidatorPreparations {
7
+ config;
8
+ localConfig;
9
+ localConfigFilepath;
10
+ dictionary;
11
+ /** configuration after applying in-doc settings */
12
+ docSettings;
13
+ finalSettings;
14
+ shouldCheck;
15
+ validateOptions;
16
+ textValidator;
17
+ transformer;
18
+ #calcIncludeRanges;
19
+ #includeRanges;
20
+ #segmenter;
21
+ /**
22
+ * @param config - loaded config
23
+ * @param localConfig - the config file found for the document, if any.
24
+ * @param localConfigFilepath - the path of `localConfig`.
25
+ * @param prep - the preparations that depend on the document's settings.
26
+ */
27
+ constructor(config, localConfig, localConfigFilepath, prep) {
28
+ this.config = config;
29
+ this.localConfig = localConfig;
30
+ this.localConfigFilepath = localConfigFilepath;
31
+ this.dictionary = prep.dictionary;
32
+ this.docSettings = prep.docSettings;
33
+ this.finalSettings = prep.finalSettings;
34
+ this.shouldCheck = prep.shouldCheck;
35
+ this.validateOptions = prep.validateOptions;
36
+ this.textValidator = prep.textValidator;
37
+ this.transformer = prep.transformer;
38
+ this.#calcIncludeRanges = prep.calcIncludeRanges;
39
+ }
40
+ /**
41
+ * Replace the preparations that depend on the document's settings. The include ranges are
42
+ * calculated again on next use.
43
+ */
44
+ update(prep) {
45
+ this.dictionary = prep.dictionary;
46
+ this.docSettings = prep.docSettings;
47
+ this.finalSettings = prep.finalSettings;
48
+ this.shouldCheck = prep.shouldCheck;
49
+ this.validateOptions = prep.validateOptions;
50
+ this.textValidator = prep.textValidator;
51
+ this.transformer = prep.transformer;
52
+ this.#calcIncludeRanges = prep.calcIncludeRanges;
53
+ this.#includeRanges = undefined;
54
+ this.#segmenter = undefined;
55
+ }
56
+ get includeRanges() {
57
+ return (this.#includeRanges ??= this.#calcIncludeRanges());
58
+ }
59
+ get segmenter() {
60
+ return (this.#segmenter ??= createMappedTextSegmenter(this.includeRanges));
61
+ }
62
+ }
63
+ //# sourceMappingURL=docValidatorPreparations.js.map
@@ -1,11 +1,10 @@
1
- import assert from 'node:assert';
2
1
  import { opConcatMap, opFilter, pipe } from '@cspell/cspell-pipe/sync';
3
2
  import { defaultCSpellSettings, unknownWordsChoices } from '@cspell/cspell-types';
4
3
  import { createCachingDictionary } from 'cspell-dictionary';
5
4
  import * as RxPat from '../Settings/RegExpPatterns.js';
6
5
  import { mapRangeBackToOriginalPos } from '../Transform/index.js';
7
6
  import { autoResolve } from '../util/AutoResolve.js';
8
- import { extractPossibleWordsFromTextOffset, extractText, extractWordsFromTextOffset, splitWordWithOffset, } from '../util/text.js';
7
+ import { extractPossibleWordsFromTextOffset, extractText, extractWordsFromTextOffset, numCharacters, splitWordWithOffset, } from '../util/text.js';
9
8
  import { regExpCamelCaseWordBreaksWithEnglishSuffix } from '../util/textRegex.js';
10
9
  import { split } from '../util/wordSplitter/index.js';
11
10
  import { defaultMinWordLength } from './defaultConstants.js';
@@ -131,22 +130,31 @@ export function lineValidatorFactory(sDict, options) {
131
130
  return issue;
132
131
  }
133
132
  const regExUpperCaseWithTrailingCommonEnglishSuffix = /^([\p{Lu}\p{M}]{2,})['’]?(?:s|ing|ies|es|ings|ize|ed|ning)$/u; // cspell:disable-line
134
- const regExpIsLetter = /\p{L}/u;
133
+ // Sticky: test the text at `lastIndex` without creating substrings.
134
+ const regExpIsLetterBefore = /(?<=\p{L})/uy;
135
+ const regExpIsLetterAt = /\p{L}/uy;
135
136
  const fn = (lineSegment) => {
136
137
  const line = lineSegment.line;
138
+ /**
139
+ * A short word is ok if it stands alone in the line, i.e. it is not next to other letters.
140
+ */
137
141
  function isWordTooShort(word, ignoreSuffix = false) {
138
- if (word.text.length >= minWordLength * 2 || [...word.text].length >= minWordLength)
142
+ const tLen = word.text.length;
143
+ const wLen = word.length ?? tLen;
144
+ if (tLen >= minWordLength * 2 || numCharacters(word.text) >= minWordLength)
145
+ return false;
146
+ // A word joined across soft hyphens does not stand alone in the line.
147
+ if (wLen !== tLen)
139
148
  return false;
140
149
  const offset = word.offset - line.offset;
141
- assert.equal(line.text.slice(offset, offset + word.text.length), word.text);
142
- const prefix = [...line.text.slice(Math.max(0, offset - 2), offset)];
143
- const hasLetterPrefix = !!prefix.length && regExpIsLetter.test(prefix[prefix.length - 1]);
150
+ regExpIsLetterBefore.lastIndex = offset;
151
+ const hasLetterPrefix = regExpIsLetterBefore.test(line.text);
144
152
  if (hasLetterPrefix)
145
153
  return false;
146
154
  if (ignoreSuffix)
147
155
  return true;
148
- const suffix = [...line.text.slice(offset + word.text.length, offset + word.text.length + 2)];
149
- const hasLetterSuffix = !!suffix.length && regExpIsLetter.test(suffix[0]);
156
+ regExpIsLetterAt.lastIndex = offset + wLen;
157
+ const hasLetterSuffix = regExpIsLetterAt.test(line.text);
150
158
  return !hasLetterSuffix;
151
159
  }
152
160
  function splitterIsValid(word) {
@@ -173,6 +181,9 @@ export function lineValidatorFactory(sDict, options) {
173
181
  return false;
174
182
  if (check.isFound)
175
183
  return true;
184
+ // A word joined across soft hyphens does not stand alone in the line.
185
+ if (tWord.length !== undefined && tWord.length !== tWord.text.length)
186
+ return false;
176
187
  if (isWordTooShort(v, true))
177
188
  return true;
178
189
  return false;
@@ -374,8 +385,9 @@ export function textValidatorFactory(dict, options) {
374
385
  const segment = { text, offset: 0 };
375
386
  const lineSegment = { line: segment, segment };
376
387
  function mapBackToOriginSimple(vr) {
377
- const { text, offset, isFlagged, isFound, suggestionsEx, hasPreferredSuggestions, hasSimpleSuggestions } = vr;
378
- const r = mapRangeBackToOriginalPos([offset, offset + text.length], map);
388
+ const { text, length, offset, isFlagged, isFound, suggestionsEx, hasPreferredSuggestions, hasSimpleSuggestions, } = vr;
389
+ const wLen = length ?? text.length;
390
+ const r = mapRangeBackToOriginalPos([offset, offset + wLen], map);
379
391
  const range = [r[0] + srcOffset, r[1] + srcOffset];
380
392
  return {
381
393
  text,
@@ -52,6 +52,13 @@ export declare function textOffset(text: string, offset?: number): TextOffset;
52
52
  export declare function extractText(textOffset: TextOffset, startPos: number, endPos: number): string;
53
53
  export declare function calculateTextDocumentOffsets<T extends TextOffset>(uri: string | Uri | URL, doc: string, wordOffsets: T[]): (TextDocumentOffset & T)[];
54
54
  export declare function removeAccents(text: string): string;
55
+ /**
56
+ * Count the number of characters (Unicode code points) in a string.
57
+ * Surrogate pairs count as one character. Same result as `[...text].length`, without creating an array.
58
+ * @param text - the string to count.
59
+ * @returns the number of characters.
60
+ */
61
+ export declare function numCharacters(text: string): number;
55
62
  export declare const __testing__: {
56
63
  regExWords: RegExp;
57
64
  regExWordsAndDigits: RegExp;
@@ -204,6 +204,22 @@ export function removeAccents(text) {
204
204
  regExAccents.lastIndex = 0;
205
205
  return text.normalize('NFD').replace(regExAccents, '');
206
206
  }
207
+ /**
208
+ * Count the number of characters (Unicode code points) in a string.
209
+ * Surrogate pairs count as one character. Same result as `[...text].length`, without creating an array.
210
+ * @param text - the string to count.
211
+ * @returns the number of characters.
212
+ */
213
+ export function numCharacters(text) {
214
+ const len = text.length;
215
+ let n = 0;
216
+ for (let i = 0; i < len; ++i, ++n) {
217
+ // A code point above 0xFFFF is a surrogate pair, skip the second half.
218
+ if ((text.codePointAt(i) || 0) > 0xffff)
219
+ ++i;
220
+ }
221
+ return n;
222
+ }
207
223
  export const __testing__ = {
208
224
  regExWords: regExWords,
209
225
  regExWordsAndDigits: regExWordsAndDigits,
package/package.json CHANGED
@@ -1,15 +1,35 @@
1
1
  {
2
2
  "name": "cspell-lib",
3
+ "version": "10.3.6",
4
+ "description": "A library of useful functions used across various cspell tools.",
3
5
  "publishConfig": {
4
6
  "access": "public",
5
7
  "provenance": true
6
8
  },
7
- "version": "10.3.4",
8
- "description": "A library of useful functions used across various cspell tools.",
9
- "type": "module",
9
+ "homepage": "https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell-lib#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/streetsidesoftware/cspell.git",
13
+ "directory": "packages/cspell-lib"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/streetsidesoftware/cspell/issues?q=is%3Aissue%20state%3Aopen%20label%3Acspell-lib"
17
+ },
18
+ "keywords": [
19
+ "spell",
20
+ "checker",
21
+ "code",
22
+ "camel",
23
+ "case",
24
+ "spelling",
25
+ "spell checker",
26
+ "spelling checker",
27
+ "lint"
28
+ ],
29
+ "license": "MIT",
30
+ "author": "Jason Dent",
10
31
  "sideEffects": false,
11
- "types": "dist/index.d.ts",
12
- "module": "dist/index.js",
32
+ "type": "module",
13
33
  "exports": {
14
34
  ".": {
15
35
  "import": "./dist/index.js"
@@ -27,6 +47,8 @@
27
47
  "import": "./dist/cspell-rpc/client.js"
28
48
  }
29
49
  },
50
+ "module": "dist/index.js",
51
+ "types": "dist/index.d.ts",
30
52
  "files": [
31
53
  "dist",
32
54
  "!**/*.tsbuildInfo",
@@ -38,59 +60,37 @@
38
60
  "!**/*.map"
39
61
  ],
40
62
  "scripts": {
41
- "clean": "shx rm -rf dist temp coverage \"*.tsbuildInfo\"",
42
- "clean-build": "pnpm clean && pnpm build",
43
63
  "build": "tsc -p . && pnpm run build:api",
44
64
  "build:api": "tsdown",
45
- "watch": "tsc -p . --watch",
65
+ "clean": "shx rm -rf dist temp coverage \"*.tsbuildInfo\"",
66
+ "clean-build": "pnpm clean && pnpm build",
46
67
  "coverage": "vitest run --coverage --pool=forks",
47
- "test-watch": "vitest",
48
68
  "prepublishOnly": "pnpm run clean-build",
49
69
  "test": "vitest run --pool=forks",
50
- "test:update-snapshot": "vitest run -u",
51
70
  "test:perf": "NODE_ENV=production insight --file \"**/*.perf.{mjs,js}\"",
71
+ "test:perf:prof": "NODE_ENV=production node --cpu-prof ../../node_modules/perf-insight/bin.mjs --file \"dist/**/*.perf.{mjs,js}\" -t 1000",
52
72
  "test:perf:ts": "NODE_ENV=production insight --file \"**/*.perf.{mts,ts}\" ",
53
- "test:perf:prof": "NODE_ENV=production node --cpu-prof ../../node_modules/perf-insight/bin.mjs --file \"dist/**/*.perf.{mjs,js}\" -t 1000"
54
- },
55
- "repository": {
56
- "type": "git",
57
- "url": "https://github.com/streetsidesoftware/cspell.git",
58
- "directory": "packages/cspell-lib"
59
- },
60
- "keywords": [
61
- "spell",
62
- "checker",
63
- "code",
64
- "camel",
65
- "case",
66
- "spelling",
67
- "spell checker",
68
- "spelling checker",
69
- "lint"
70
- ],
71
- "author": "Jason Dent",
72
- "license": "MIT",
73
- "bugs": {
74
- "url": "https://github.com/streetsidesoftware/cspell/labels/cspell-lib"
73
+ "test:update-snapshot": "vitest run -u",
74
+ "test-watch": "vitest",
75
+ "watch": "tsc -p . --watch"
75
76
  },
76
- "homepage": "https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell-lib#readme",
77
77
  "dependencies": {
78
- "@cspell/cspell-bundled-dicts": "10.3.4",
79
- "@cspell/cspell-performance-monitor": "10.3.4",
80
- "@cspell/cspell-pipe": "10.3.4",
81
- "@cspell/cspell-resolver": "10.3.4",
82
- "@cspell/cspell-types": "10.3.4",
83
- "@cspell/dynamic-import": "10.3.4",
84
- "@cspell/filetypes": "10.3.4",
85
- "@cspell/rpc": "10.3.4",
86
- "@cspell/strong-weak-map": "10.3.4",
87
- "@cspell/url": "10.3.4",
88
- "cspell-config-lib": "10.3.4",
89
- "cspell-dictionary": "10.3.4",
90
- "cspell-glob": "10.3.4",
91
- "cspell-grammar": "10.3.4",
92
- "cspell-io": "10.3.4",
93
- "cspell-trie-lib": "10.3.4",
78
+ "@cspell/cspell-bundled-dicts": "10.3.6",
79
+ "@cspell/cspell-performance-monitor": "10.3.6",
80
+ "@cspell/cspell-pipe": "10.3.6",
81
+ "@cspell/cspell-resolver": "10.3.6",
82
+ "@cspell/cspell-types": "10.3.6",
83
+ "@cspell/dynamic-import": "10.3.6",
84
+ "@cspell/filetypes": "10.3.6",
85
+ "@cspell/rpc": "10.3.6",
86
+ "@cspell/strong-weak-map": "10.3.6",
87
+ "@cspell/url": "10.3.6",
88
+ "cspell-config-lib": "10.3.6",
89
+ "cspell-dictionary": "10.3.6",
90
+ "cspell-glob": "10.3.6",
91
+ "cspell-grammar": "10.3.6",
92
+ "cspell-io": "10.3.6",
93
+ "cspell-trie-lib": "10.3.6",
94
94
  "env-paths": "^4.0.0",
95
95
  "gensequence": "^8.0.8",
96
96
  "import-fresh": "^4.0.0",
@@ -99,11 +99,7 @@
99
99
  "vscode-uri": "^3.2.0",
100
100
  "xdg-basedir": "^5.1.0"
101
101
  },
102
- "engines": {
103
- "node": ">=22.18.0"
104
- },
105
102
  "devDependencies": {
106
- "@cspell/cspell-tools": "10.3.4",
107
103
  "@cspell/dict-cpp": "^7.1.0",
108
104
  "@cspell/dict-csharp": "^4.0.8",
109
105
  "@cspell/dict-css": "^4.1.2",
@@ -123,5 +119,8 @@
123
119
  "lorem-ipsum": "^3.0.0",
124
120
  "perf-insight": "^2.0.1"
125
121
  },
126
- "gitHead": "7bb5c4750ece7c732edbf920c0cea910d9b4d717"
122
+ "engines": {
123
+ "node": ">=22.18.0"
124
+ },
125
+ "gitHead": "8559198ccfcac2f745911db1ffe2ee62c17cba49"
127
126
  }