diff --git a/packages/cspell-lib/src/SpellingDictionary/Dictionaries.test.ts b/packages/cspell-lib/src/SpellingDictionary/Dictionaries.test.ts index 518353d4a684..98e9e757faed 100644 --- a/packages/cspell-lib/src/SpellingDictionary/Dictionaries.test.ts +++ b/packages/cspell-lib/src/SpellingDictionary/Dictionaries.test.ts @@ -239,6 +239,7 @@ describe('Validate getDictionary', () => { expect(dicts3.map((d) => d.name)).toEqual(['css', 'html', 'node', 'temp', 'not_found']); await Dictionaries.refreshDictionaryCache(0); + await sleep(2); // Give the system a chance to breath (needed for linux systems) const dicts4 = Dictionaries.loadDictionaryDefsSync(defsToLoad); expect(dicts4.map((d) => d.name)).toEqual(['css', 'html', 'node', 'temp', 'not_found']); @@ -277,6 +278,12 @@ describe('Validate getDictionary', () => { ); }); +function sleep(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + function sample(file: string): string { return path.join(samples, file); } diff --git a/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts b/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts index 98d7b18e087e..e0a6a5168670 100644 --- a/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts +++ b/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts @@ -45,8 +45,10 @@ describe('Validate DictionaryLoader', () => { }); const entry = testing.loadEntry(filename, def); - await expect(entry.pStat).resolves.toEqual(expect.objectContaining(expectedError)); - await expect(entry.pDictionary).resolves.not.toBe(undefined); + const [dictionary, stat] = await entry.pending; + + expect(stat).toEqual(expect.objectContaining(expectedError)); + expect(dictionary).not.toBe(undefined); }); test.each` diff --git a/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.ts b/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.ts index d44f77c82b46..7f0392a20d7d 100644 --- a/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.ts +++ b/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.ts @@ -31,14 +31,19 @@ const loadersSync: SyncLoaders = { export type LoadOptions = DictionaryDefinitionInternal; +enum LoadingState { + Loaded = 0, + Loading = 1, +} + interface CacheEntry { uri: string; options: LoadOptions; ts: number; - pStat: Promise; stat: Stats | Error | undefined; - pDictionary: Promise; dictionary: SpellingDictionary | undefined; + pending: Promise; + loadingState: LoadingState; } interface CacheEntrySync extends CacheEntry { @@ -71,17 +76,17 @@ export function loadDictionary(uri: string, options: DictionaryDefinitionInterna const key = calcKey(uri, options); const entry = dictionaryCache.get(key); if (entry) { - return entry.pDictionary; + return entry.pending.then(([dictionary]) => dictionary); } const loadedEntry = loadEntry(uri, options); dictionaryCache.set(key, loadedEntry); - return loadedEntry.pDictionary; + return loadedEntry.pending.then(([dictionary]) => dictionary); } export function loadDictionarySync(uri: string, options: DictionaryDefinitionInternal): SpellingDictionary { const key = calcKey(uri, options); const entry = dictionaryCache.get(key); - if (entry?.dictionary && entry.stat) { + if (entry?.dictionary && entry.loadingState === LoadingState.Loaded) { return entry.dictionary; } const loadedEntry = loadEntrySync(uri, options); @@ -113,7 +118,7 @@ async function refreshEntry(entry: CacheEntry, maxAge: number, now: number): Pro // Write to the ts, so the next one will not do it. entry.ts = now; const pStat = getStat(entry.uri); - const [newStat] = await Promise.all([pStat, entry.pStat]); + const [newStat] = await Promise.all([pStat, entry.pending]); if (entry.ts === now && !isEqual(newStat, entry.stat)) { dictionaryCache.set(calcKey(entry.uri, entry.options), loadEntry(entry.uri, entry.options)); } @@ -136,18 +141,27 @@ function isError(e: StatsOrError): e is Error { } function loadEntry(uri: string, options: LoadOptions, now = Date.now()): CacheEntry { - const dictionary = load(uri, options).catch((e) => + const pDictionary = load(uri, options).catch((e) => createFailedToLoadDictionary(new SpellingDictionaryLoadError(uri, options, e, 'failed to load')) ); + const pStat = getStat(uri); + const pending = Promise.all([pDictionary, pStat]); const entry: CacheEntry = { uri, options, ts: now, - pStat: getStat(uri).then((s) => (entry.stat = s)), stat: undefined, - pDictionary: dictionary.then((d) => (entry.dictionary = d)), dictionary: undefined, + pending, + loadingState: LoadingState.Loading, }; + // eslint-disable-next-line promise/catch-or-return + pending.then(([dictionary, stat]) => { + entry.stat = stat; + entry.dictionary = dictionary; + entry.loadingState = LoadingState.Loaded; + return; + }); return entry; } @@ -155,28 +169,30 @@ function loadEntrySync(uri: string, options: LoadOptions, now = Date.now()): Cac const stat = getStatSync(uri); try { const dictionary = loadSync(uri, options); + const pending = Promise.resolve([dictionary, stat] as const); return { uri, options, ts: now, - pStat: Promise.resolve(stat), stat, - pDictionary: Promise.resolve(dictionary), dictionary, + pending, + loadingState: LoadingState.Loaded, }; } catch (e) { const error = e instanceof Error ? e : new Error(format(e)); const dictionary = createFailedToLoadDictionary( new SpellingDictionaryLoadError(uri, options, error, 'failed to load') ); + const pending = Promise.resolve([dictionary, stat] as const); return { uri, options, ts: now, - pStat: Promise.resolve(stat), stat, - pDictionary: Promise.resolve(dictionary), dictionary, + pending, + loadingState: LoadingState.Loaded, }; } } diff --git a/packages/cspell-lib/src/docValidator.test.ts b/packages/cspell-lib/src/docValidator.test.ts new file mode 100644 index 000000000000..8392dd8dcc34 --- /dev/null +++ b/packages/cspell-lib/src/docValidator.test.ts @@ -0,0 +1,18 @@ +import { DocumentValidator, TextDocument } from './docValidator'; + +describe('docValidator', () => { + test('DocumentValidator', () => { + const doc = td(); + const dVal = new DocumentValidator(doc, {}); + expect(dVal.document).toBe(doc); + expect(dVal.checkText([0, 0], '', [])).toEqual([]); + }); +}); + +function td(...textDocParts: Partial[]): TextDocument { + const doc: TextDocument = { text: '', uri: '', languageId: 'text', version: 1 }; + for (const p of textDocParts) { + Object.assign(doc, p); + } + return doc; +} diff --git a/packages/cspell-lib/src/docValidator.ts b/packages/cspell-lib/src/docValidator.ts index b21b8d545a47..0134d78265f9 100644 --- a/packages/cspell-lib/src/docValidator.ts +++ b/packages/cspell-lib/src/docValidator.ts @@ -1,4 +1,5 @@ -import { CSpellSettingsInternal } from './Models/CSpellSettingsInternalDef'; +import type { CSpellUserSettings } from '@cspell/cspell-types'; +import { ValidationIssue } from '.'; export class DocumentValidator { private _document: TextDocument; @@ -7,15 +8,23 @@ export class DocumentValidator { * @param doc - Document to validate * @param config - configuration to use (not finalized). */ - constructor(doc: TextDocument, readonly config: CSpellSettingsInternal) { + constructor(doc: TextDocument, readonly settings: CSpellUserSettings) { this._document = doc; } + checkText(_range: SimpleRange, _text: string, _scope: string[]): ValidationIssue[] { + return []; + } + get document() { return this._document; } } +export type Offset = number; + +export type SimpleRange = [Offset, Offset]; + export type DocumentUri = string; /**