cspell-lib 10.3.4-alpha.0 → 10.3.5

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.
@@ -281,7 +281,13 @@ function resolveParser(settings) {
281
281
  assert(typeof parserName === 'string');
282
282
  const parsers = extractParsers(settings.plugins);
283
283
  const parser = parsers.get(parserName);
284
- assert(parser, `Parser "${parserName}" not found.`);
284
+ try {
285
+ assert(parser);
286
+ }
287
+ catch {
288
+ // lazy assertion for better error message
289
+ assert(parser, `Parser "${parserName}" not found among available parsers: ${[...parsers.keys()].join(', ')}`);
290
+ }
285
291
  return parser;
286
292
  }
287
293
  function* parsers(plugins) {
@@ -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;
@@ -509,11 +509,11 @@ export async function shouldCheckDocument(doc, options, settings) {
509
509
  const config = mergeSettings(settings, localConfig);
510
510
  const matcher = getGlobMatcherForExcluding(localConfig?.ignorePaths);
511
511
  // eslint-disable-next-line unicorn/prefer-regexp-test
512
- if (matcher.match(uriToFilePath(doc.uri))) {
512
+ if (!options.forceCheck && matcher.match(uriToFilePath(doc.uri))) {
513
513
  return { errors, shouldCheck: false, settings: localConfig, reason: 'Excluded by ignorePaths.' };
514
514
  }
515
515
  const docSettings = await determineTextDocumentSettings(doc, config);
516
- const shouldCheck = docSettings.enabled ?? true;
516
+ const shouldCheck = options.forceCheck || (docSettings.enabled ?? true);
517
517
  const reason = shouldCheck ? undefined : 'Excluded by overrides or languageSettings.';
518
518
  return { errors, shouldCheck, settings: docSettings, reason };
519
519
  }
@@ -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
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "provenance": true
6
6
  },
7
- "version": "10.3.4-alpha.0",
7
+ "version": "10.3.5",
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.3.4-alpha.0",
79
- "@cspell/cspell-performance-monitor": "10.3.4-alpha.0",
80
- "@cspell/cspell-pipe": "10.3.4-alpha.0",
81
- "@cspell/cspell-resolver": "10.3.4-alpha.0",
82
- "@cspell/cspell-types": "10.3.4-alpha.0",
83
- "@cspell/dynamic-import": "10.3.4-alpha.0",
84
- "@cspell/filetypes": "10.3.4-alpha.0",
85
- "@cspell/rpc": "10.3.4-alpha.0",
86
- "@cspell/strong-weak-map": "10.3.4-alpha.0",
87
- "@cspell/url": "10.3.4-alpha.0",
88
- "cspell-config-lib": "10.3.4-alpha.0",
89
- "cspell-dictionary": "10.3.4-alpha.0",
90
- "cspell-glob": "10.3.4-alpha.0",
91
- "cspell-grammar": "10.3.4-alpha.0",
92
- "cspell-io": "10.3.4-alpha.0",
93
- "cspell-trie-lib": "10.3.4-alpha.0",
78
+ "@cspell/cspell-bundled-dicts": "10.3.5",
79
+ "@cspell/cspell-performance-monitor": "10.3.5",
80
+ "@cspell/cspell-pipe": "10.3.5",
81
+ "@cspell/cspell-resolver": "10.3.5",
82
+ "@cspell/cspell-types": "10.3.5",
83
+ "@cspell/dynamic-import": "10.3.5",
84
+ "@cspell/filetypes": "10.3.5",
85
+ "@cspell/rpc": "10.3.5",
86
+ "@cspell/strong-weak-map": "10.3.5",
87
+ "@cspell/url": "10.3.5",
88
+ "cspell-config-lib": "10.3.5",
89
+ "cspell-dictionary": "10.3.5",
90
+ "cspell-glob": "10.3.5",
91
+ "cspell-grammar": "10.3.5",
92
+ "cspell-io": "10.3.5",
93
+ "cspell-trie-lib": "10.3.5",
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.3.4-alpha.0",
106
+ "@cspell/cspell-tools": "10.3.5",
107
107
  "@cspell/dict-cpp": "^7.1.0",
108
108
  "@cspell/dict-csharp": "^4.0.8",
109
109
  "@cspell/dict-css": "^4.1.2",
@@ -123,5 +123,5 @@
123
123
  "lorem-ipsum": "^3.0.0",
124
124
  "perf-insight": "^2.0.1"
125
125
  },
126
- "gitHead": "f52ee7e2f24189eaf8fb5139c0b029c8cb80fc57"
126
+ "gitHead": "f37a244a54b686fc1ac11272d6019b9a059b062a"
127
127
  }