diff --git a/packages/cspell-lib/api/api.d.ts b/packages/cspell-lib/api/api.d.ts index f99e857a6c6..1b57c04facc 100644 --- a/packages/cspell-lib/api/api.d.ts +++ b/packages/cspell-lib/api/api.d.ts @@ -1,5 +1,5 @@ /// -import { Glob, CSpellSettingsWithSourceTrace, ReplaceMap, DictionaryInformation, DictionaryDefinitionPreferred, DictionaryDefinitionAugmented, DictionaryDefinitionCustom, TextOffset, TextDocumentOffset, PnPSettings, ImportFileRef, CSpellUserSettings, LocaleId, CSpellSettings } from '@cspell/cspell-types'; +import { Glob, CSpellSettingsWithSourceTrace, ReplaceMap, DictionaryInformation, DictionaryDefinitionPreferred, DictionaryDefinitionAugmented, DictionaryDefinitionCustom, TextOffset, TextDocumentOffset, PnPSettings as PnPSettings$1, ImportFileRef, CSpellUserSettings, LocaleId, CSpellSettings } from '@cspell/cspell-types'; export * from '@cspell/cspell-types'; import { CompoundWordsMethod, SuggestionResult, SuggestionCollector, WeightMap } from 'cspell-trie-lib'; export { CompoundWordsMethod, SuggestionCollector, SuggestionResult } from 'cspell-trie-lib'; @@ -208,6 +208,19 @@ declare class SpellingDictionaryCollection implements SpellingDictionary { private _isNoSuggestWord; } +/** + * The keys of an object where the values cannot be undefined. + */ +declare type OptionalKeys = Exclude<{ + [P in keyof T]: T[P] extends Exclude ? never : P; +}[keyof T], undefined>; +/** + * Allow undefined in optional fields + */ +declare type OptionalOrUndefined = { + [P in keyof T]: P extends OptionalKeys ? T[P] | undefined : T[P]; +}; + declare const SymbolCSpellSettingsInternal: unique symbol; interface CSpellSettingsInternal extends Omit { [SymbolCSpellSettingsInternal]: true; @@ -361,6 +374,7 @@ declare type LoaderResult = URI | undefined; declare type CSpellSettingsWST$1 = CSpellSettingsWithSourceTrace; declare type CSpellSettingsI$1 = CSpellSettingsInternal; +declare type PnPSettings = OptionalOrUndefined; declare const sectionCSpell = "cSpell"; declare const defaultFileName = "cspell.json"; declare const defaultConfigFilenames: readonly string[]; @@ -395,18 +409,19 @@ interface ImportFileRefWithError$1 extends ImportFileRef { declare function extractImportErrors(settings: CSpellSettingsWST$1): ImportFileRefWithError$1[]; declare type CSpellSettingsWST = CSpellSettingsWithSourceTrace; +declare type CSpellSettingsWSTO = OptionalOrUndefined; declare type CSpellSettingsI = CSpellSettingsInternal; declare const currentSettingsFileVersion = "0.2"; declare const ENV_CSPELL_GLOB_ROOT = "CSPELL_GLOB_ROOT"; -declare function mergeSettings(left: CSpellSettingsWST | CSpellSettingsI, ...settings: (CSpellSettingsWST | CSpellSettingsI)[]): CSpellSettingsI; -declare function mergeInDocSettings(left: CSpellSettingsWST, right: CSpellSettingsWST): CSpellSettingsWST; -declare function calcOverrideSettings(settings: CSpellSettingsWST, filename: string): CSpellSettingsI; +declare function mergeSettings(left: CSpellSettingsWSTO | CSpellSettingsI, ...settings: (CSpellSettingsWSTO | CSpellSettingsI)[]): CSpellSettingsI; +declare function mergeInDocSettings(left: CSpellSettingsWSTO, right: CSpellSettingsWSTO): CSpellSettingsWST; +declare function calcOverrideSettings(settings: CSpellSettingsWSTO, filename: string): CSpellSettingsI; /** * * @param settings - settings to finalize * @returns settings where all globs and file paths have been resolved. */ -declare function finalizeSettings(settings: CSpellSettingsWST | CSpellSettingsI): CSpellSettingsI; +declare function finalizeSettings(settings: CSpellSettingsWSTO | CSpellSettingsI): CSpellSettingsI; /** * @param filename - filename * @param globs - globs @@ -419,7 +434,7 @@ declare function checkFilenameMatchesGlob(filename: string, globs: Glob | Glob[] * Return a list of Setting Sources used to create this Setting. * @param settings the settings to search */ -declare function getSources(settings: CSpellSettingsWST): CSpellSettingsWST[]; +declare function getSources(settings: CSpellSettingsWSTO): CSpellSettingsWSTO[]; interface ImportFileRefWithError extends ImportFileRef { error: Error; } @@ -427,12 +442,12 @@ interface ConfigurationDependencies { configFiles: string[]; dictionaryFiles: string[]; } -declare function extractDependencies(settings: CSpellSettingsWST | CSpellSettingsI): ConfigurationDependencies; +declare function extractDependencies(settings: CSpellSettingsWSTO | CSpellSettingsI): ConfigurationDependencies; declare function getDefaultSettings(): CSpellSettingsInternal; declare class ImportError extends Error { - readonly cause?: Error; + readonly cause: Error | undefined; constructor(msg: string, cause?: Error | unknown); } diff --git a/packages/cspell-lib/cspell.config.json b/packages/cspell-lib/cspell.config.json index 7891f0803c2..ce870355e16 100644 --- a/packages/cspell-lib/cspell.config.json +++ b/packages/cspell-lib/cspell.config.json @@ -17,6 +17,6 @@ ], "allowCompoundWords": false, "dictionaryDefinitions": [], - "ignoreWords": [], + "ignoreWords": ["CSpellSettingsWSTO"], "import": ["../../cspell.json"] } diff --git a/packages/cspell-lib/src/Models/CSpellSettingsInternalDef.ts b/packages/cspell-lib/src/Models/CSpellSettingsInternalDef.ts index cdedd1fd8af..870600f48a3 100644 --- a/packages/cspell-lib/src/Models/CSpellSettingsInternalDef.ts +++ b/packages/cspell-lib/src/Models/CSpellSettingsInternalDef.ts @@ -5,6 +5,8 @@ import { DictionaryDefinitionPreferred, } from '@cspell/cspell-types'; import { WeightMap } from 'cspell-trie-lib'; +import { OptionalOrUndefined } from '../util/types'; +import { clean } from '../util/util'; export const SymbolCSpellSettingsInternal = Symbol('CSpellSettingsInternal'); @@ -31,15 +33,20 @@ export interface DictionaryDefinitionInternalWithSource extends DictionaryDefini readonly __source: string; } -export function createCSpellSettingsInternal(parts: Partial = {}): CSpellSettingsInternal { - return { +export function createCSpellSettingsInternal( + parts: OptionalOrUndefined> = {} +): CSpellSettingsInternal { + return clean({ ...parts, [SymbolCSpellSettingsInternal]: true, - }; + }); } export function isCSpellSettingsInternal( - cs: CSpellSettingsInternal | CSpellSettingsWithSourceTrace + cs: + | CSpellSettingsInternal + | CSpellSettingsWithSourceTrace + | OptionalOrUndefined ): cs is CSpellSettingsInternal { return !!(cs)[SymbolCSpellSettingsInternal]; } diff --git a/packages/cspell-lib/src/Settings/CSpellSettingsServer.ts b/packages/cspell-lib/src/Settings/CSpellSettingsServer.ts index a27db4ced65..0cdfc20134b 100644 --- a/packages/cspell-lib/src/Settings/CSpellSettingsServer.ts +++ b/packages/cspell-lib/src/Settings/CSpellSettingsServer.ts @@ -13,11 +13,13 @@ import { CSpellSettingsInternal, isCSpellSettingsInternal, } from '../Models/CSpellSettingsInternalDef'; +import { OptionalOrUndefined } from '../util/types'; import * as util from '../util/util'; import { calcDictionaryDefsToLoad, mapDictDefsToInternal } from './DictionarySettings'; import { resolvePatterns } from './patterns'; type CSpellSettingsWST = CSpellSettingsWithSourceTrace; +type CSpellSettingsWSTO = OptionalOrUndefined; type CSpellSettingsI = CSpellSettingsInternal; export const configSettingsFileVersion0_1 = '0.1'; @@ -104,8 +106,8 @@ function replaceIfNotEmpty(left: Array = [], right: Array = []) { } export function mergeSettings( - left: CSpellSettingsWST | CSpellSettingsI, - ...settings: (CSpellSettingsWST | CSpellSettingsI)[] + left: CSpellSettingsWSTO | CSpellSettingsI, + ...settings: (CSpellSettingsWSTO | CSpellSettingsI)[] ): CSpellSettingsI { const rawSettings = settings.reduce(merge, toInternalSettings(left)); return util.clean(rawSettings); @@ -116,7 +118,10 @@ function isEmpty(obj: Object) { return Object.keys(obj).length === 0 && obj.constructor === Object; } -function merge(left: CSpellSettingsWST | CSpellSettingsI, right: CSpellSettingsWST | CSpellSettingsI): CSpellSettingsI { +function merge( + left: CSpellSettingsWSTO | CSpellSettingsI, + right: CSpellSettingsWSTO | CSpellSettingsI +): CSpellSettingsI { const _left = toInternalSettings(left); const _right = toInternalSettings(right); if (left === right) { @@ -196,7 +201,7 @@ function versionBasedMergeList( * @param left - setting on the left side of a merge * @param right - setting on the right side of a merge */ -function isLeftAncestorOfRight(left: CSpellSettingsWST, right: CSpellSettingsWST): boolean { +function isLeftAncestorOfRight(left: CSpellSettingsWSTO, right: CSpellSettingsWSTO): boolean { return hasAncestor(right, left, 0); } @@ -206,11 +211,11 @@ function isLeftAncestorOfRight(left: CSpellSettingsWST, right: CSpellSettingsWST * @param left - setting on the left side of a merge * @param right - setting on the right side of a merge */ -function doesLeftHaveRightAncestor(left: CSpellSettingsWST, right: CSpellSettingsWST): boolean { +function doesLeftHaveRightAncestor(left: CSpellSettingsWSTO, right: CSpellSettingsWSTO): boolean { return hasAncestor(left, right, 1); } -function hasAncestor(s: CSpellSettingsWST, ancestor: CSpellSettingsWST, side: number): boolean { +function hasAncestor(s: CSpellSettingsWSTO, ancestor: CSpellSettingsWSTO, side: number): boolean { const sources = s.source?.sources; if (!sources) return false; // calc the first or last index of the source array. @@ -219,12 +224,12 @@ function hasAncestor(s: CSpellSettingsWST, ancestor: CSpellSettingsWST, side: nu return src === ancestor || (src && hasAncestor(src, ancestor, side)) || false; } -export function mergeInDocSettings(left: CSpellSettingsWST, right: CSpellSettingsWST): CSpellSettingsWST { +export function mergeInDocSettings(left: CSpellSettingsWSTO, right: CSpellSettingsWSTO): CSpellSettingsWST { const merged = { ...mergeSettings(left, right), includeRegExpList: mergeListUnique(left.includeRegExpList, right.includeRegExpList), }; - return merged; + return util.clean(merged); } /** @@ -243,7 +248,7 @@ function takeRightOtherwiseLeft(left: T[] | undefined, right: T[] | undefined return left || right; } -export function calcOverrideSettings(settings: CSpellSettingsWST, filename: string): CSpellSettingsI { +export function calcOverrideSettings(settings: CSpellSettingsWSTO, filename: string): CSpellSettingsI { const _settings = toInternalSettings(settings); const overrides = _settings.overrides || []; @@ -258,7 +263,7 @@ export function calcOverrideSettings(settings: CSpellSettingsWST, filename: stri * @param settings - settings to finalize * @returns settings where all globs and file paths have been resolved. */ -export function finalizeSettings(settings: CSpellSettingsWST | CSpellSettingsI): CSpellSettingsI { +export function finalizeSettings(settings: CSpellSettingsWSTO | CSpellSettingsI): CSpellSettingsI { return _finalizeSettings(toInternalSettings(settings)); } @@ -278,9 +283,9 @@ function _finalizeSettings(settings: CSpellSettingsI): CSpellSettingsI { } export function toInternalSettings(settings: undefined): undefined; -export function toInternalSettings(settings: CSpellSettingsI | CSpellSettingsWST): CSpellSettingsI; -export function toInternalSettings(settings?: CSpellSettingsI | CSpellSettingsWST): CSpellSettingsI | undefined; -export function toInternalSettings(settings?: CSpellSettingsI | CSpellSettingsWST): CSpellSettingsI | undefined { +export function toInternalSettings(settings: CSpellSettingsI | CSpellSettingsWSTO): CSpellSettingsI; +export function toInternalSettings(settings?: CSpellSettingsI | CSpellSettingsWSTO): CSpellSettingsI | undefined; +export function toInternalSettings(settings?: CSpellSettingsI | CSpellSettingsWSTO): CSpellSettingsI | undefined { if (settings === undefined) return undefined; if (isCSpellSettingsInternal(settings)) return settings; @@ -310,12 +315,12 @@ export function checkFilenameMatchesGlob(filename: string, globs: Glob | Glob[]) return m.match(filename); } -function mergeSources(left: CSpellSettingsWST, right: CSpellSettingsWST): Source { +function mergeSources(left: CSpellSettingsWSTO, right: CSpellSettingsWSTO): Source { const { source: a = { name: 'left' } } = left; const { source: b = { name: 'right' } } = right; return { name: [left.name || a.name, right.name || b.name].join('|'), - sources: [left, right], + sources: [left as CSpellSettingsWithSourceTrace, right as CSpellSettingsWithSourceTrace], }; } @@ -333,11 +338,11 @@ function max(a: T | undefined, b: T | undefined): T | undefined { * Return a list of Setting Sources used to create this Setting. * @param settings the settings to search */ -export function getSources(settings: CSpellSettingsWST): CSpellSettingsWST[] { - const visited = new Set(); - const sources: CSpellSettingsWST[] = []; +export function getSources(settings: CSpellSettingsWSTO): CSpellSettingsWSTO[] { + const visited = new Set(); + const sources: CSpellSettingsWSTO[] = []; - function _walkSourcesTree(settings: CSpellSettingsWST | undefined): void { + function _walkSourcesTree(settings: CSpellSettingsWSTO | undefined): void { if (!settings || visited.has(settings)) return; visited.add(settings); if (!settings.source?.sources?.length) { @@ -352,9 +357,9 @@ export function getSources(settings: CSpellSettingsWST): CSpellSettingsWST[] { return sources; } -type Imports = CSpellSettingsWST['__imports']; +type Imports = CSpellSettingsWSTO['__imports']; -function mergeImportRefs(left: CSpellSettingsWST, right: CSpellSettingsWST = {}): Imports { +function mergeImportRefs(left: CSpellSettingsWSTO, right: CSpellSettingsWSTO = {}): Imports { const imports = new Map(left.__imports || []); if (left.__importRef) { imports.set(left.__importRef.filename, left.__importRef); @@ -378,7 +383,7 @@ export interface ConfigurationDependencies { dictionaryFiles: string[]; } -export function extractDependencies(settings: CSpellSettingsWST | CSpellSettingsI): ConfigurationDependencies { +export function extractDependencies(settings: CSpellSettingsWSTO | CSpellSettingsI): ConfigurationDependencies { const settingsI = toInternalSettings(settings); const configFiles = [...(mergeImportRefs(settingsI) || [])].map(([filename]) => filename); const dictionaryFiles = calcDictionaryDefsToLoad(settingsI).map((dict) => dict.path); diff --git a/packages/cspell-lib/src/Settings/DefaultSettings.ts b/packages/cspell-lib/src/Settings/DefaultSettings.ts index adce583c681..3bb9b2cce12 100644 --- a/packages/cspell-lib/src/Settings/DefaultSettings.ts +++ b/packages/cspell-lib/src/Settings/DefaultSettings.ts @@ -136,7 +136,11 @@ const getSettings = (function () { if (!settings) { const jsonSettings = readSettings(defaultConfigFile); settings = mergeSettings(_defaultSettings, jsonSettings); - settings.name = jsonSettings.name; + if (jsonSettings.name !== undefined) { + settings.name = jsonSettings.name; + } else { + delete settings.name; + } } return settings; }; diff --git a/packages/cspell-lib/src/Settings/DictionarySettings.ts b/packages/cspell-lib/src/Settings/DictionarySettings.ts index 0c393f48f67..a9dda76fc1a 100644 --- a/packages/cspell-lib/src/Settings/DictionarySettings.ts +++ b/packages/cspell-lib/src/Settings/DictionarySettings.ts @@ -19,6 +19,7 @@ import { createDictionaryReferenceCollection } from './DictionaryReferenceCollec import { mapDictionaryInformationToWeightMap, WeightMap } from 'cspell-trie-lib'; import { DictionaryInformation } from '@cspell/cspell-types'; import { RequireOptional, UnionFields } from '../util/types'; +import { clean } from '../util/util'; export type DefMapArrayItem = [string, DictionaryDefinitionInternal]; @@ -116,19 +117,19 @@ type DictDef = Partial< UnionFields, DictionaryDefinitionCustom> >; -class _DictionaryDefinitionInternalWithSource implements RequireOptional { +class _DictionaryDefinitionInternalWithSource implements DictionaryDefinitionInternalWithSource { private _weightMap: WeightMap | undefined; readonly name: string; readonly path: string; - readonly addWords: boolean | undefined; - readonly description: string | undefined; - readonly dictionaryInformation: DictionaryInformation | undefined; - readonly type: DictionaryFileTypes | undefined; - readonly file: undefined; - readonly repMap: ReplaceMap | undefined; - readonly useCompounds: boolean | undefined; - readonly noSuggest: boolean | undefined; - readonly scope: CustomDictionaryScope | CustomDictionaryScope[] | undefined; + readonly addWords?: boolean; + readonly description?: string; + readonly dictionaryInformation?: DictionaryInformation; + readonly type?: DictionaryFileTypes; + readonly file?: undefined; + readonly repMap?: ReplaceMap; + readonly useCompounds?: boolean; + readonly noSuggest?: boolean; + readonly scope?: CustomDictionaryScope | CustomDictionaryScope[]; constructor(def: DictionaryDefinition, readonly __source: string) { // this bit of assignment is to have the compiler help use if any new fields are added. const defAll: DictDef = def; @@ -164,17 +165,10 @@ class _DictionaryDefinitionInternalWithSource implements RequireOptional; +type PnPSettings = OptionalOrUndefined; const supportedCSpellConfigVersions: CSpellSettingsVersion[] = [configSettingsFileVersion0_2]; @@ -465,14 +467,11 @@ function toGlobDef( return g.map((g) => toGlobDef(g, root, source)); } if (typeof g === 'string') { - return toGlobDef( - { - glob: g, - root, - }, - root, - source - ); + const glob: GlobDef = { glob: g }; + if (root !== undefined) { + glob.root = root; + } + return toGlobDef(glob, root, source); } if (source) { return { ...g, source }; @@ -480,7 +479,9 @@ function toGlobDef( return g; } -type NormalizeDictionaryDefsParams = Pick; +type NormalizeDictionaryDefsParams = OptionalOrUndefined< + Pick +>; function normalizeDictionaryDefs(settings: NormalizeDictionaryDefsParams, pathToSettingsFile: string) { const dictionaryDefinitions = mapDictDefsToInternal(settings.dictionaryDefinitions, pathToSettingsFile); @@ -550,7 +551,7 @@ function normalizeLanguageSettings(languageSettings: LanguageSetting[] | undefin function fixLocale(s: LanguageSetting): LanguageSetting { const { local: locale, ...rest } = s; - return { locale, ...rest }; + return util.clean({ locale, ...rest }); } return languageSettings.map(fixLocale); diff --git a/packages/cspell-lib/src/Settings/link.test.ts b/packages/cspell-lib/src/Settings/link.test.ts index 200643f9bdd..ace2ce4ac23 100644 --- a/packages/cspell-lib/src/Settings/link.test.ts +++ b/packages/cspell-lib/src/Settings/link.test.ts @@ -106,12 +106,11 @@ describe('Validate Link.ts', () => { expect(r.resolvedSettings).toHaveLength(3); expect(r).toEqual({ - error: undefined, success: true, resolvedSettings: [ - expect.objectContaining({ filename: pathCpp, error: undefined }), - expect.objectContaining({ filename: pathPython, error: undefined }), - expect.objectContaining({ filename: pathHtml, error: undefined }), + expect.objectContaining({ filename: pathCpp }), + expect.objectContaining({ filename: pathPython }), + expect.objectContaining({ filename: pathHtml }), ], }); expect(mockSetData).toHaveBeenCalledWith({ @@ -133,8 +132,8 @@ describe('Validate Link.ts', () => { error: 'Unable to resolve files.', success: false, resolvedSettings: [ - expect.objectContaining({ filename: pathCpp, error: undefined }), - expect.objectContaining({ filename: pathPython, error: undefined }), + expect.objectContaining({ filename: pathCpp }), + expect.objectContaining({ filename: pathPython }), expect.objectContaining({ filename: pathNotFound, error: expect.stringContaining('Failed to read config'), diff --git a/packages/cspell-lib/src/Settings/link.ts b/packages/cspell-lib/src/Settings/link.ts index 97c1f00e4ce..bf173baf48e 100644 --- a/packages/cspell-lib/src/Settings/link.ts +++ b/packages/cspell-lib/src/Settings/link.ts @@ -1,6 +1,7 @@ import type { CSpellSettingsWithSourceTrace } from '@cspell/cspell-types'; import * as fs from 'fs'; import * as Path from 'path'; +import { clean } from '../util/util'; import { readRawSettings } from './configLoader'; import { getRawGlobalSettings, writeRawGlobalSettings } from './GlobalSettings'; @@ -163,12 +164,12 @@ function resolveSettings(filename: string): ResolveSettingsResult { const resolvedToFilename = ref?.filename; const error = ref?.error?.message || (!resolvedToFilename && 'File not Found') || undefined; - return { + return clean({ filename, resolvedToFilename, error, settings, - }; + }); } function normalizeImports(imports: CSpellSettingsWithSourceTrace['import']): string[] { diff --git a/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts b/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts index f52b828a6d3..ee66498a84b 100644 --- a/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts +++ b/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import { DictionaryDefinitionInternal } from '../Models/CSpellSettingsInternalDef'; +import { clean } from '../util/util'; import { loadDictionary, loadDictionarySync, LoadOptions, refreshCacheEntries, testing } from './DictionaryLoader'; jest.mock('../util/logger'); @@ -155,7 +156,7 @@ describe('Validate DictionaryLoader', () => { async ({ word, hasWord, ignoreCase }: { word: string; hasWord: boolean; ignoreCase?: boolean }) => { const file = sample('words.txt'); const d = await loadDictionary(file, dDef({ name: 'words', path: file })); - expect(d.has(word, { ignoreCase })).toBe(hasWord); + expect(d.has(word, clean({ ignoreCase }))).toBe(hasWord); } ); diff --git a/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryCollection.ts b/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryCollection.ts index 02637ab2162..033b763c35b 100644 --- a/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryCollection.ts +++ b/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryCollection.ts @@ -2,7 +2,7 @@ import { CASE_INSENSITIVE_PREFIX } from 'cspell-trie-lib'; import { genSequence } from 'gensequence'; import { getDefaultSettings } from '../Settings'; import { memorizer, memorizerKeyBy } from '../util/Memorizer'; -import { isDefined } from '../util/util'; +import { clean, isDefined } from '../util/util'; import { CompoundWordsMethod, FindResult, @@ -80,16 +80,13 @@ export class SpellingDictionaryCollection implements SpellingDictionary { } public _suggest(word: string, suggestOptions: SuggestOptions): SuggestionResult[] { - const _suggestOptions = { ...suggestOptions }; const { numSuggestions = getDefaultSettings().numSuggestions || defaultNumSuggestions, numChanges, - compoundMethod, ignoreCase, includeTies, timeout, } = suggestOptions; - _suggestOptions.compoundMethod = this.options.useCompounds ? CompoundWordsMethod.JOIN_WORDS : compoundMethod; const prefixNoCase = CASE_INSENSITIVE_PREFIX; const filter = (word: string, _cost: number) => { return ( @@ -98,14 +95,17 @@ export class SpellingDictionaryCollection implements SpellingDictionary { !this.isNoSuggestWord(word, suggestOptions) ); }; - const collector = suggestionCollector(word, { - numSuggestions, - filter, - changeLimit: numChanges, - includeTies, - ignoreCase, - timeout, - }); + const collector = suggestionCollector( + word, + clean({ + numSuggestions, + filter, + changeLimit: numChanges, + includeTies, + ignoreCase, + timeout, + }) + ); this.genSuggestions(collector, suggestOptions); return collector.suggestions.map((r) => ({ ...r, word: r.word })); } diff --git a/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryFromTrie.ts b/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryFromTrie.ts index fbc61bfd7b0..200ec8174e4 100644 --- a/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryFromTrie.ts +++ b/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryFromTrie.ts @@ -9,6 +9,7 @@ import { CompoundWordsMethod, importTrie, suggestionCollector, Trie } from 'cspe import { getDefaultSettings } from '../Settings'; import { memorizer } from '../util/Memorizer'; import { createMapper } from '../util/repMap'; +import { clean } from '../util/util'; import { FindResult, HasOptions, @@ -161,15 +162,18 @@ export class SpellingDictionaryFromTrie implements SpellingDictionary { function filter(_word: string): boolean { return true; } - const collector = suggestionCollector(word, { - numSuggestions, - filter, - changeLimit: numChanges, - includeTies, - ignoreCase, - timeout, - weightMap: this.weightMap, - }); + const collector = suggestionCollector( + word, + clean({ + numSuggestions, + filter, + changeLimit: numChanges, + includeTies, + ignoreCase, + timeout, + weightMap: this.weightMap, + }) + ); this.genSuggestions(collector, suggestOptions); return collector.suggestions.map((r) => ({ ...r, word: r.word })); } diff --git a/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryMethods.ts b/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryMethods.ts index af39bf6bc2c..4b3fd9e4512 100644 --- a/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryMethods.ts +++ b/packages/cspell-lib/src/SpellingDictionary/SpellingDictionaryMethods.ts @@ -1,5 +1,6 @@ import { DictionaryInformation } from '@cspell/cspell-types'; import { CompoundWordsMethod, mapDictionaryInformationToWeightMap, SuggestionResult, WeightMap } from 'cspell-trie-lib'; +import { clean } from 'cspell-trie-lib/dist/lib/trie-util'; import { genSequence } from 'gensequence'; import { isUpperCase, removeAccents, ucFirst } from '../util/text'; import { HasOptions, SearchOptions, SpellingDictionary, SuggestOptions } from './SpellingDictionary'; @@ -108,14 +109,14 @@ export function suggestArgsToSuggestOptions(args: SuggestArgs): SuggestOptions { const suggestOptions: SuggestOptions = typeof options === 'object' ? options - : { + : clean({ numSuggestions: options, compoundMethod, numChanges, ignoreCase, includeTies: undefined, timeout: undefined, - }; + }); return suggestOptions; } export function createWeightMapFromDictionaryInformation(di: undefined): undefined; diff --git a/packages/cspell-lib/src/spellCheckFile.ts b/packages/cspell-lib/src/spellCheckFile.ts index 07e9d2ca49e..8b71bab3919 100644 --- a/packages/cspell-lib/src/spellCheckFile.ts +++ b/packages/cspell-lib/src/spellCheckFile.ts @@ -15,6 +15,7 @@ import { import { combineTextAndLanguageSettings } from './Settings/TextDocumentSettings'; import { isError } from './util/errors'; import { memorizer } from './util/Memorizer'; +import { clean } from './util/util'; import { validateText, ValidateTextOptions, ValidationIssue } from './validator'; const getLanguagesForExt = memorizer(_getLanguagesForExt); @@ -170,7 +171,7 @@ async function spellCheckFullDocument( const shouldCheck = !matcher.match(uri.fsPath) && (docSettings.settings.enabled ?? true); const { generateSuggestions, numSuggestions } = options; - const validateOptions = { generateSuggestions, numSuggestions }; + const validateOptions = clean({ generateSuggestions, numSuggestions }); const issues = shouldCheck ? await validateText(document.text, docSettings.settings, validateOptions) : []; @@ -303,10 +304,10 @@ export function fileToDocument( languageId?: string, locale?: string ): Document | DocumentWithText { - return { + return clean({ uri: URI.file(file).toString(), text, languageId, locale, - }; + }); } diff --git a/packages/cspell-lib/src/suggestions.ts b/packages/cspell-lib/src/suggestions.ts index d4a86eae364..f5369bea523 100644 --- a/packages/cspell-lib/src/suggestions.ts +++ b/packages/cspell-lib/src/suggestions.ts @@ -100,16 +100,19 @@ export async function suggestionsForWord( dictionaryCollection: SpellingDictionaryCollection; allDictionaryCollection: SpellingDictionaryCollection; }> { - const withLocale = mergeSettings(config, { - language: language || config.language, - // dictionaries: dictionaries?.length ? dictionaries : config.dictionaries, - }); + const withLocale = mergeSettings( + config, + util.clean({ + language: language || config.language, + // dictionaries: dictionaries?.length ? dictionaries : config.dictionaries, + }) + ); const withLanguageId = calcSettingsForLanguageId( withLocale, languageId ?? withLocale.languageId ?? 'plaintext' ); const settings = finalizeSettings(withLanguageId); - settings.dictionaries = dictionaries?.length ? dictionaries : settings.dictionaries; + settings.dictionaries = dictionaries?.length ? dictionaries : settings.dictionaries || []; validateDictionaries(settings, dictionaries); const dictionaryCollection = await getDictionaryInternal(settings); settings.dictionaries = settings.dictionaryDefinitions?.map((def) => def.name) || []; diff --git a/packages/cspell-lib/src/textValidator.ts b/packages/cspell-lib/src/textValidator.ts index 97864cd1200..86d1f3fbae9 100644 --- a/packages/cspell-lib/src/textValidator.ts +++ b/packages/cspell-lib/src/textValidator.ts @@ -5,6 +5,7 @@ import * as RxPat from './Settings/RegExpPatterns'; import { HasOptions, SpellingDictionary } from './SpellingDictionary/SpellingDictionary'; import * as Text from './util/text'; import * as TextRange from './util/TextRange'; +import { clean } from './util/util'; import { split } from './util/wordSplitter'; export interface ValidationOptions extends IncludeExcludeOptions { @@ -143,7 +144,7 @@ function lineValidator(dict: SpellingDictionary, options: ValidationOptions): Li const isIgnored = isWordIgnored(word.text); const { isFlagged = !isIgnored && testForFlaggedWord(word) } = word; const isFound = isFlagged ? undefined : isIgnored || isWordValid(dictCol, word, word.line, options); - return { ...word, isFlagged, isFound }; + return clean({ ...word, isFlagged, isFound }); } const fn: LineValidator = (lineSegment: TextOffset) => { @@ -256,10 +257,10 @@ export function hasWordCheck(dict: SpellingDictionary, word: string, options: Ha function convertCheckOptionsToHasOptions(opt: HasWordOptions): HasOptions { const { ignoreCase, useCompounds } = opt; - return { + return clean({ ignoreCase, useCompounds, - }; + }); } /** diff --git a/packages/cspell-lib/src/trace.ts b/packages/cspell-lib/src/trace.ts index 02a2434bdeb..f945378dc65 100644 --- a/packages/cspell-lib/src/trace.ts +++ b/packages/cspell-lib/src/trace.ts @@ -57,10 +57,13 @@ export async function* traceWordsAsync( config: CSpellSettings; dicts: SpellingDictionaryCollection; }> { - const withLocale = mergeSettings(config, { - language: language || config.language, - allowCompoundWords: allowCompoundWords ?? config.allowCompoundWords, - }); + const withLocale = mergeSettings( + config, + util.clean({ + language: language || config.language, + allowCompoundWords: allowCompoundWords ?? config.allowCompoundWords, + }) + ); const withLanguageId = calcSettingsForLanguageId( withLocale, languageId ?? withLocale.languageId ?? 'plaintext' @@ -83,7 +86,7 @@ export async function* traceWordsAsync( await refreshDictionaryCache(); const { config, dicts, activeDictionaries } = await finalize(settings); const setOfActiveDicts = new Set(activeDictionaries); - const opts: HasOptions = { ignoreCase, useCompounds: config.allowCompoundWords }; + const opts: HasOptions = util.clean({ ignoreCase, useCompounds: config.allowCompoundWords }); function normalizeErrors(errors: Error[] | undefined): Error[] | undefined { if (!errors?.length) return undefined; diff --git a/packages/cspell-lib/src/util/types.ts b/packages/cspell-lib/src/util/types.ts index 26ea864ed88..77eb65212cf 100644 --- a/packages/cspell-lib/src/util/types.ts +++ b/packages/cspell-lib/src/util/types.ts @@ -64,6 +64,20 @@ export type OnlyOptional = { export type MakeOptional = OnlyRequired & Partial>; +/** + * Like Required, but keeps the Optional. + */ +export type RemoveUndefined = { + [P in keyof T]: Exclude; +}; + +/** + * Allow undefined in optional fields + */ +export type OptionalOrUndefined = { + [P in keyof T]: P extends OptionalKeys ? T[P] | undefined : T[P]; +}; + /* * Experimental */ diff --git a/packages/cspell-lib/src/util/util.ts b/packages/cspell-lib/src/util/util.ts index e631bb6b278..028f4c3faf1 100644 --- a/packages/cspell-lib/src/util/util.ts +++ b/packages/cspell-lib/src/util/util.ts @@ -1,3 +1,5 @@ +import { RemoveUndefined } from './types'; + // alias for uniqueFilterFnGenerator export const uniqueFn = uniqueFilterFnGenerator; @@ -22,7 +24,7 @@ export function unique(src: T[]): T[] { * Delete all `undefined` fields from an object. * @param src - object to be cleaned */ -export function clean(src: T): T { +export function clean(src: T): RemoveUndefined { const r = src; type keyOfT = keyof T; type keysOfT = keyOfT[]; @@ -31,7 +33,7 @@ export function clean(src: T): T { delete r[key]; } } - return r; + return r as RemoveUndefined; } /** diff --git a/packages/cspell-lib/src/validator.ts b/packages/cspell-lib/src/validator.ts index 58145dbe4fd..f06c0d538a7 100644 --- a/packages/cspell-lib/src/validator.ts +++ b/packages/cspell-lib/src/validator.ts @@ -2,6 +2,7 @@ import type { CSpellUserSettings } from '@cspell/cspell-types'; import * as Settings from './Settings'; import { CompoundWordsMethod, getDictionaryInternal } from './SpellingDictionary'; import * as TV from './textValidator'; +import { clean } from './util/util'; export const diagSource = 'cSpell Checker'; @@ -31,14 +32,17 @@ export async function validateText( } const withSugs = issues.map((t) => { const suggestions = dict - .suggest(t.text, { - numSuggestions: options.numSuggestions, - compoundMethod: CompoundWordsMethod.NONE, - includeTies: false, - ignoreCase: !(settings.caseSensitive ?? false), - timeout: settings.suggestionsTimeout, - numChanges: settings.suggestionNumChanges, - }) + .suggest( + t.text, + clean({ + numSuggestions: options.numSuggestions, + compoundMethod: CompoundWordsMethod.NONE, + includeTies: false, + ignoreCase: !(settings.caseSensitive ?? false), + timeout: settings.suggestionsTimeout, + numChanges: settings.suggestionNumChanges, + }) + ) .map((r) => r.word); return { ...t, suggestions }; }); diff --git a/packages/cspell-lib/tsconfig.json b/packages/cspell-lib/tsconfig.json index 26d66a8e5c0..068cb7eea1b 100644 --- a/packages/cspell-lib/tsconfig.json +++ b/packages/cspell-lib/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "skipLibCheck": true, - "exactOptionalPropertyTypes": false, // make this true + "exactOptionalPropertyTypes": true, // make this true "strictFunctionTypes": false, "outDir": "dist" }, diff --git a/packages/cspell-trie-lib/src/lib/trie-util.ts b/packages/cspell-trie-lib/src/lib/trie-util.ts index 8b6dca35235..add24abc69d 100644 --- a/packages/cspell-trie-lib/src/lib/trie-util.ts +++ b/packages/cspell-trie-lib/src/lib/trie-util.ts @@ -1,7 +1,7 @@ import { genSequence, Sequence } from 'gensequence'; import { defaultTrieOptions } from './constants'; import { ChildMap, FLAG_WORD, PartialTrieOptions, TrieNode, TrieOptions, TrieRoot } from './TrieNode'; -import type { Mandatory, PartialWithUndefined } from './types'; +import type { PartialWithUndefined, RemoveUndefined } from './types'; import { walker, YieldResult } from './walker'; export function insert(text: string, node: TrieNode = {}): TrieNode { @@ -230,12 +230,12 @@ export function isDefined(t: T | undefined): t is T { return t !== undefined; } -export function clean(t: T): Mandatory { +export function clean(t: T): RemoveUndefined { const copy = { ...t }; for (const key of Object.keys(copy) as (keyof T)[]) { if (copy[key] === undefined) { delete copy[key]; } } - return copy as Mandatory; + return copy as RemoveUndefined; } diff --git a/packages/cspell-types/src/CSpellSettingsDef.ts b/packages/cspell-types/src/CSpellSettingsDef.ts index 65143d7d801..35a1a576b77 100644 --- a/packages/cspell-types/src/CSpellSettingsDef.ts +++ b/packages/cspell-types/src/CSpellSettingsDef.ts @@ -21,12 +21,12 @@ export interface CSpellSettings extends FileSettings, LegacySettings { export interface ImportFileRef { filename: string; - error?: Error; + error?: Error | undefined; referencedBy?: Source[]; } export interface CSpellSettingsWithSourceTrace extends CSpellSettings { - source?: Source; + source?: Source | undefined; __importRef?: ImportFileRef; __imports?: Map; }