diff --git a/cspell.schema.json b/cspell.schema.json index 1573eddb9b2c..926eaacc7fd4 100644 --- a/cspell.schema.json +++ b/cspell.schema.json @@ -681,7 +681,7 @@ "type": "boolean" }, "flagWords": { - "description": "List of words to always be considered incorrect.", + "description": "List of words to always be considered incorrect. Words found in `flagWords` override `words`.\n\nFormat of `flagWords`\n- single word entry - `word`\n- with suggestions - `word:suggestion` or `word->suggestion, suggestions`\n\nExample: ```ts \"flagWords\": [ \"color: colour\", \"incase: in case, encase\", \"canot->cannot\", \"cancelled->canceled\" ] ```", "items": { "type": "string" }, @@ -769,7 +769,7 @@ "type": "array" }, "words": { - "description": "List of words to be always considered correct.", + "description": "List of words to be considered correct.", "items": { "type": "string" }, @@ -853,7 +853,7 @@ "description": "Glob pattern or patterns to match against." }, "flagWords": { - "description": "List of words to always be considered incorrect.", + "description": "List of words to always be considered incorrect. Words found in `flagWords` override `words`.\n\nFormat of `flagWords`\n- single word entry - `word`\n- with suggestions - `word:suggestion` or `word->suggestion, suggestions`\n\nExample: ```ts \"flagWords\": [ \"color: colour\", \"incase: in case, encase\", \"canot->cannot\", \"cancelled->canceled\" ] ```", "items": { "type": "string" }, @@ -963,7 +963,7 @@ "type": "boolean" }, "words": { - "description": "List of words to be always considered correct.", + "description": "List of words to be considered correct.", "items": { "type": "string" }, @@ -1293,7 +1293,7 @@ "type": "array" }, "flagWords": { - "description": "List of words to always be considered incorrect.", + "description": "List of words to always be considered incorrect. Words found in `flagWords` override `words`.\n\nFormat of `flagWords`\n- single word entry - `word`\n- with suggestions - `word:suggestion` or `word->suggestion, suggestions`\n\nExample: ```ts \"flagWords\": [ \"color: colour\", \"incase: in case, encase\", \"canot->cannot\", \"cancelled->canceled\" ] ```", "items": { "type": "string" }, @@ -1501,7 +1501,7 @@ "description": "Configuration format version of the settings file.\n\nThis controls how the settings in the configuration file behave." }, "words": { - "description": "List of words to be always considered correct.", + "description": "List of words to be considered correct.", "items": { "type": "string" }, diff --git a/packages/cspell-dictionary/src/SpellingDictionary/FlagWordsDictionary.ts b/packages/cspell-dictionary/src/SpellingDictionary/FlagWordsDictionary.ts index 818ff8773287..606205f541d8 100644 --- a/packages/cspell-dictionary/src/SpellingDictionary/FlagWordsDictionary.ts +++ b/packages/cspell-dictionary/src/SpellingDictionary/FlagWordsDictionary.ts @@ -2,7 +2,6 @@ import { opMap, pipe } from '@cspell/cspell-pipe/sync'; import type { CompoundWordsMethod, SuggestionResult, Trie } from 'cspell-trie-lib'; import { buildTrieFast, parseDictionaryLines } from 'cspell-trie-lib'; -import { defaultOptions } from './createSpellingDictionary'; import * as Defaults from './defaults'; import type { FindResult, @@ -13,6 +12,7 @@ import type { SuggestArgs, SuggestOptions, } from './SpellingDictionary'; +import { defaultOptions } from './SpellingDictionary'; import { SpellingDictionaryFromTrie } from './SpellingDictionaryFromTrie'; import { suggestArgsToSuggestOptions } from './SpellingDictionaryMethods'; import type { TyposDictionary } from './TyposDictionary'; diff --git a/packages/cspell-dictionary/src/SpellingDictionary/SpellingDictionary.ts b/packages/cspell-dictionary/src/SpellingDictionary/SpellingDictionary.ts index d896e7281f2d..8522684b686f 100644 --- a/packages/cspell-dictionary/src/SpellingDictionary/SpellingDictionary.ts +++ b/packages/cspell-dictionary/src/SpellingDictionary/SpellingDictionary.ts @@ -1,7 +1,8 @@ import type { DictionaryInformation, ReplaceMap } from '@cspell/cspell-types'; import type { CompoundWordsMethod, SuggestionCollector, SuggestionResult, WeightMap } from 'cspell-trie-lib'; -export { CompoundWordsMethod, SuggestionCollector, SuggestionResult } from 'cspell-trie-lib'; +export type { DictionaryDefinitionInline } from '@cspell/cspell-types'; +export { CompoundWordsMethod, type SuggestionCollector, type SuggestionResult } from 'cspell-trie-lib'; export interface SearchOptions { /** @@ -165,3 +166,7 @@ export type SuggestArgs = ignoreCase?: boolean ) => SuggestionResult[] >; + +export const defaultOptions: SpellingDictionaryOptions = Object.freeze({ + weightMap: undefined, +}); diff --git a/packages/cspell-dictionary/src/SpellingDictionary/createInlineSpellingDictionary.test.ts b/packages/cspell-dictionary/src/SpellingDictionary/createInlineSpellingDictionary.test.ts new file mode 100644 index 000000000000..c43000418825 --- /dev/null +++ b/packages/cspell-dictionary/src/SpellingDictionary/createInlineSpellingDictionary.test.ts @@ -0,0 +1,15 @@ +import type { DictionaryDefinitionInline } from '@cspell/cspell-types'; + +import { createInlineSpellingDictionary } from './createInlineSpellingDictionary'; + +describe('Validate createSpellingDictionary', () => { + test('createInlineSpellingDictionary', () => { + const words = ['one', 'two', 'three', 'left-right']; + const def: DictionaryDefinitionInline = { + name: 'test-dict', + words, + }; + const d = createInlineSpellingDictionary(def, __filename); + words.forEach((w) => expect(d.has(w)).toBe(true)); + }); +}); diff --git a/packages/cspell-dictionary/src/SpellingDictionary/createInlineSpellingDictionary.ts b/packages/cspell-dictionary/src/SpellingDictionary/createInlineSpellingDictionary.ts new file mode 100644 index 000000000000..618c72b165f6 --- /dev/null +++ b/packages/cspell-dictionary/src/SpellingDictionary/createInlineSpellingDictionary.ts @@ -0,0 +1,21 @@ +import { isDefined } from '../util/util'; +import { createSpellingDictionary } from './createSpellingDictionary'; +import { createFlagWordsDictionary } from './FlagWordsDictionary'; +import { createIgnoreWordsDictionary } from './IgnoreWordsDictionary'; +import type { DictionaryDefinitionInline, SpellingDictionary } from './SpellingDictionary'; +import { createCollection } from './SpellingDictionaryCollection'; + +export function createInlineSpellingDictionary( + inlineDict: DictionaryDefinitionInline, + source: string +): SpellingDictionary { + const { words, flagWords, ignoreWords, name } = inlineDict; + + const dictSources = [ + words && createSpellingDictionary(words, name + '-words', source, inlineDict), + flagWords && createFlagWordsDictionary(flagWords, name + '-flag-words', source), + ignoreWords && createIgnoreWordsDictionary(ignoreWords, name + '-ignore-words', source), + ].filter(isDefined); + + return createCollection(dictSources, name); +} diff --git a/packages/cspell-dictionary/src/SpellingDictionary/createSpellingDictionary.ts b/packages/cspell-dictionary/src/SpellingDictionary/createSpellingDictionary.ts index 6ad047b4faec..12678dca8e7a 100644 --- a/packages/cspell-dictionary/src/SpellingDictionary/createSpellingDictionary.ts +++ b/packages/cspell-dictionary/src/SpellingDictionary/createSpellingDictionary.ts @@ -4,13 +4,10 @@ import { deepEqual } from 'fast-equals'; import type { IterableLike } from '../util/IterableLike'; import { AutoWeakCache, SimpleCache } from '../util/simpleCache'; import type { DictionaryInfo, SpellingDictionary, SpellingDictionaryOptions } from './SpellingDictionary'; +import { defaultOptions } from './SpellingDictionary'; import { SpellingDictionaryFromTrie } from './SpellingDictionaryFromTrie'; import { createWeightMapFromDictionaryInformation } from './SpellingDictionaryMethods'; -export const defaultOptions: SpellingDictionaryOptions = Object.freeze({ - weightMap: undefined, -}); - type CreateSpellingDictionaryParams = Parameters; const cachedDictionaries = new AutoWeakCache( diff --git a/packages/cspell-dictionary/src/SpellingDictionary/index.ts b/packages/cspell-dictionary/src/SpellingDictionary/index.ts index ee4edaeec5df..b3fe043dff45 100644 --- a/packages/cspell-dictionary/src/SpellingDictionary/index.ts +++ b/packages/cspell-dictionary/src/SpellingDictionary/index.ts @@ -1,4 +1,5 @@ export { CachingDictionary, createCachingDictionary } from './CachingDictionary'; +export { createInlineSpellingDictionary } from './createInlineSpellingDictionary'; export { createFailedToLoadDictionary, createSpellingDictionary } from './createSpellingDictionary'; export { createFlagWordsDictionary, @@ -6,6 +7,7 @@ export { } from './FlagWordsDictionary'; export { createIgnoreWordsDictionary } from './IgnoreWordsDictionary'; export type { + DictionaryDefinitionInline, FindOptions, FindResult, HasOptions, diff --git a/packages/cspell-dictionary/src/__snapshots__/index.test.ts.snap b/packages/cspell-dictionary/src/__snapshots__/index.test.ts.snap index 12d0f63c24f4..821712bec7b0 100644 --- a/packages/cspell-dictionary/src/__snapshots__/index.test.ts.snap +++ b/packages/cspell-dictionary/src/__snapshots__/index.test.ts.snap @@ -8,6 +8,7 @@ exports[`index verify api 1`] = ` "createFlagWordsDictionary", "createForbiddenWordsDictionary", "createIgnoreWordsDictionary", + "createInlineSpellingDictionary", "createSpellingDictionary", "createSpellingDictionaryFromTrieFile", ] diff --git a/packages/cspell-dictionary/src/index.ts b/packages/cspell-dictionary/src/index.ts index 287b14753ca5..111f9576f116 100644 --- a/packages/cspell-dictionary/src/index.ts +++ b/packages/cspell-dictionary/src/index.ts @@ -18,6 +18,7 @@ export { createFlagWordsDictionary, createForbiddenWordsDictionary, createIgnoreWordsDictionary, + createInlineSpellingDictionary, createSpellingDictionary, createSpellingDictionaryFromTrieFile, } from './SpellingDictionary'; diff --git a/packages/cspell-lib/api/api.d.ts b/packages/cspell-lib/api/api.d.ts index 4a36e86ac7ae..0b76f422c4ce 100644 --- a/packages/cspell-lib/api/api.d.ts +++ b/packages/cspell-lib/api/api.d.ts @@ -1,12 +1,11 @@ /// -import { Glob, CSpellSettingsWithSourceTrace, TextOffset, TextDocumentOffset, AdvancedCSpellSettingsWithSourceTrace, Parser, DictionaryDefinitionPreferred, DictionaryDefinitionAugmented, DictionaryDefinitionCustom, PnPSettings as PnPSettings$1, ImportFileRef, CSpellUserSettings, Issue, MappedText, ParsedText, LocaleId, CSpellSettings } from '@cspell/cspell-types'; +import { Glob, CSpellSettingsWithSourceTrace, TextOffset, TextDocumentOffset, AdvancedCSpellSettingsWithSourceTrace, Parser, DictionaryDefinitionInline, DictionaryDefinitionPreferred, DictionaryDefinitionAugmented, DictionaryDefinitionCustom, PnPSettings as PnPSettings$1, ImportFileRef, CSpellUserSettings, Issue, MappedText, ParsedText, LocaleId, CSpellSettings } from '@cspell/cspell-types'; export * from '@cspell/cspell-types'; import { URI } from 'vscode-uri'; import { WeightMap } from 'cspell-trie-lib'; export { CompoundWordsMethod } from 'cspell-trie-lib'; -import * as cspellDictModule from 'cspell-dictionary'; import { CachingDictionary, SpellingDictionaryCollection, SuggestionResult } from 'cspell-dictionary'; -export { SpellingDictionary, SpellingDictionaryCollection, SuggestOptions, SuggestionCollector, SuggestionResult } from 'cspell-dictionary'; +export { SpellingDictionary, SpellingDictionaryCollection, SuggestOptions, SuggestionCollector, SuggestionResult, createSpellingDictionary, createCollection as createSpellingDictionaryCollection } from 'cspell-dictionary'; export { asyncIterableToArray, readFile, readFileSync, writeToFile, writeToFileIterable, writeToFileIterableP } from 'cspell-io'; type ExclusionFunction = (fileUri: string) => boolean; @@ -365,7 +364,12 @@ interface CSpellSettingsInternalFinalized extends CSpellSettingsInternal { includeRegExpList: RegExp[]; } type DictionaryDefinitionCustomUniqueFields = Omit; -interface DictionaryDefinitionInternal extends Readonly, Readonly>, Readonly { +type DictionaryDefinitionInternal = DictionaryFileDefinitionInternal | DictionaryDefinitionInlineInternal; +type DictionaryDefinitionInlineInternal = DictionaryDefinitionInline & { + /** The path to the config file that contains this dictionary definition */ + readonly __source?: string | undefined; +}; +interface DictionaryFileDefinitionInternal extends Readonly, Readonly>, Readonly { /** * Optional weight map used to improve suggestions. */ @@ -501,9 +505,6 @@ interface ValidationIssue extends ValidationResult { declare function refreshDictionaryCache(maxAge?: number): Promise; -declare const createSpellingDictionary: typeof cspellDictModule.createSpellingDictionary; -declare const createCollection: typeof cspellDictModule.createCollection; - type LoadOptions = DictionaryDefinitionInternal; declare class SpellingDictionaryLoadError extends Error { @@ -898,4 +899,4 @@ declare function clearCachedFiles(): Promise; */ declare function getDictionary(settings: CSpellUserSettings): Promise; -export { CheckTextInfo, ConfigurationDependencies, CreateTextDocumentParams, DetermineFinalDocumentSettingsResult, Document, DocumentValidator, DocumentValidatorOptions, ENV_CSPELL_GLOB_ROOT, ExcludeFilesGlobMap, ExclusionFunction, exclusionHelper_d as ExclusionHelper, FeatureFlag, FeatureFlags, ImportError, ImportFileRefWithError, IncludeExcludeFlag, IncludeExcludeOptions, index_link_d as Link, Logger, SpellCheckFileOptions, SpellCheckFileResult, SpellingDictionaryLoadError, SuggestedWord, SuggestionError, SuggestionOptions, SuggestionsForWordResult, text_d as Text, TextDocument, TextDocumentLine, TextInfoItem, TraceOptions, TraceResult, UnknownFeatureFlagError, ValidationIssue, calcOverrideSettings, checkFilenameMatchesGlob, checkText, checkTextDocument, clearCachedFiles, clearCachedSettingsFiles, combineTextAndLanguageSettings, combineTextAndLanguageSettings as constructSettingsForText, createSpellingDictionary, createCollection as createSpellingDictionaryCollection, createTextDocument, currentSettingsFileVersion, defaultConfigFilenames, defaultFileName, defaultFileName as defaultSettingsFilename, determineFinalDocumentSettings, extractDependencies, extractImportErrors, fileToDocument, fileToTextDocument, finalizeSettings, getCachedFileSize, getDefaultBundledSettings, getDefaultSettings, getDictionary, getGlobalSettings, getLanguagesForBasename as getLanguageIdsForBaseFilename, getLanguagesForExt, getLogger, getSources, getSystemFeatureFlags, isBinaryFile, isSpellingDictionaryLoadError, loadConfig, loadPnP, loadPnPSync, mergeInDocSettings, mergeSettings, readRawSettings, readSettings, readSettingsFiles, refreshDictionaryCache, resolveFile, searchForConfig, sectionCSpell, setLogger, spellCheckDocument, spellCheckFile, suggestionsForWord, suggestionsForWords, traceWords, traceWordsAsync, updateTextDocument, validateText }; +export { CheckTextInfo, ConfigurationDependencies, CreateTextDocumentParams, DetermineFinalDocumentSettingsResult, Document, DocumentValidator, DocumentValidatorOptions, ENV_CSPELL_GLOB_ROOT, ExcludeFilesGlobMap, ExclusionFunction, exclusionHelper_d as ExclusionHelper, FeatureFlag, FeatureFlags, ImportError, ImportFileRefWithError, IncludeExcludeFlag, IncludeExcludeOptions, index_link_d as Link, Logger, SpellCheckFileOptions, SpellCheckFileResult, SpellingDictionaryLoadError, SuggestedWord, SuggestionError, SuggestionOptions, SuggestionsForWordResult, text_d as Text, TextDocument, TextDocumentLine, TextInfoItem, TraceOptions, TraceResult, UnknownFeatureFlagError, ValidationIssue, calcOverrideSettings, checkFilenameMatchesGlob, checkText, checkTextDocument, clearCachedFiles, clearCachedSettingsFiles, combineTextAndLanguageSettings, combineTextAndLanguageSettings as constructSettingsForText, createTextDocument, currentSettingsFileVersion, defaultConfigFilenames, defaultFileName, defaultFileName as defaultSettingsFilename, determineFinalDocumentSettings, extractDependencies, extractImportErrors, fileToDocument, fileToTextDocument, finalizeSettings, getCachedFileSize, getDefaultBundledSettings, getDefaultSettings, getDictionary, getGlobalSettings, getLanguagesForBasename as getLanguageIdsForBaseFilename, getLanguagesForExt, getLogger, getSources, getSystemFeatureFlags, isBinaryFile, isSpellingDictionaryLoadError, loadConfig, loadPnP, loadPnPSync, mergeInDocSettings, mergeSettings, readRawSettings, readSettings, readSettingsFiles, refreshDictionaryCache, resolveFile, searchForConfig, sectionCSpell, setLogger, spellCheckDocument, spellCheckFile, suggestionsForWord, suggestionsForWords, traceWords, traceWordsAsync, updateTextDocument, validateText }; diff --git a/packages/cspell-lib/fixtures/dictionaries/inline/README.md b/packages/cspell-lib/fixtures/dictionaries/inline/README.md new file mode 100644 index 000000000000..f2b363167bbe --- /dev/null +++ b/packages/cspell-lib/fixtures/dictionaries/inline/README.md @@ -0,0 +1,3 @@ +# Inline Dictionaries + +It is possible to fully define a dictionary in the file. diff --git a/packages/cspell-lib/fixtures/dictionaries/inline/cspell.config.yaml b/packages/cspell-lib/fixtures/dictionaries/inline/cspell.config.yaml new file mode 100644 index 000000000000..703ab90ed2cd --- /dev/null +++ b/packages/cspell-lib/fixtures/dictionaries/inline/cspell.config.yaml @@ -0,0 +1,34 @@ +dictionaryDefinitions: + - name: cities + words: + - Amsterdam + - Angles + - cities + - City + - Configuration + - Default + - define + - Definitions + - Delhi + - dictionaries + - dictionary + - "false" + - file + - Francisco + - fully + - Inline + - Las + - load + - London + - Mexico + - name + - New + - Paris + - possible + - San + - words + - York + +dictionaries: + - cities +loadDefaultConfiguration: false diff --git a/packages/cspell-lib/fixtures/dictionaries/inline/test.txt b/packages/cspell-lib/fixtures/dictionaries/inline/test.txt new file mode 100644 index 000000000000..8b2a95f16fe8 --- /dev/null +++ b/packages/cspell-lib/fixtures/dictionaries/inline/test.txt @@ -0,0 +1,8 @@ +New York +New Amsterdam +Las Angles +San Francisco +New Delhi +Mexico City +London +Paris diff --git a/packages/cspell-lib/src/LanguageIds.ts b/packages/cspell-lib/src/LanguageIds.ts index 537135d330fd..58fde5773ea4 100644 --- a/packages/cspell-lib/src/LanguageIds.ts +++ b/packages/cspell-lib/src/LanguageIds.ts @@ -6,6 +6,8 @@ * ``` */ +import { autoResolve } from './util/AutoResolve'; + // cspell:ignore cljs cljx cson iname pcregrep fsscript gradle shtml xhtml mdoc aspx jshtm gitconfig bowerrc // cspell:ignore jshintrc jscsrc eslintrc babelrc webmanifest mdown markdn psgi phtml pssc psrc gypi rhistory // cspell:ignore rprofile cshtml gemspec cginc ebuild zshrc zprofile zlogin zlogout zshenv dsql ascx axml @@ -310,15 +312,8 @@ function doesSetContainAnyOf( export function buildLanguageExtensionMapSet(defs: LanguageDefinitions): ExtensionToLanguageIdMapSet { return defs.reduce((map, def) => { - function getMapSet(value: string) { - const found = map.get(value); - if (found) return found; - const s = new Set(); - map.set(value, s); - return s; - } function addId(value: string) { - getMapSet(value).add(def.id); + autoResolve(map, value, () => new Set()).add(def.id); } def.extensions.forEach(addId); diff --git a/packages/cspell-lib/src/Models/CSpellSettingsInternalDef.ts b/packages/cspell-lib/src/Models/CSpellSettingsInternalDef.ts index 189dcfaedca6..fda7d9cd129b 100644 --- a/packages/cspell-lib/src/Models/CSpellSettingsInternalDef.ts +++ b/packages/cspell-lib/src/Models/CSpellSettingsInternalDef.ts @@ -1,8 +1,10 @@ import type { AdvancedCSpellSettingsWithSourceTrace, CSpellSettingsWithSourceTrace, + DictionaryDefinition, DictionaryDefinitionAugmented, DictionaryDefinitionCustom, + DictionaryDefinitionInline, DictionaryDefinitionPreferred, Parser, } from '@cspell/cspell-types'; @@ -27,7 +29,14 @@ export interface CSpellSettingsInternalFinalized extends CSpellSettingsInternal type DictionaryDefinitionCustomUniqueFields = Omit; -export interface DictionaryDefinitionInternal +export type DictionaryDefinitionInternal = DictionaryFileDefinitionInternal | DictionaryDefinitionInlineInternal; + +export type DictionaryDefinitionInlineInternal = DictionaryDefinitionInline & { + /** The path to the config file that contains this dictionary definition */ + readonly __source?: string | undefined; +}; + +export interface DictionaryFileDefinitionInternal extends Readonly, Readonly>, Readonly { @@ -39,10 +48,14 @@ export interface DictionaryDefinitionInternal readonly __source?: string | undefined; } -export interface DictionaryDefinitionInternalWithSource extends DictionaryDefinitionInternal { +export interface DictionaryFileDefinitionInternalWithSource extends DictionaryFileDefinitionInternal { readonly __source: string; } +export type DictionaryDefinitionInternalWithSource = DictionaryDefinitionInternal & { + readonly __source: string; +}; + export function createCSpellSettingsInternal( parts: OptionalOrUndefined> = {} ): CSpellSettingsInternal { @@ -60,3 +73,11 @@ export function isCSpellSettingsInternal( ): cs is CSpellSettingsInternal { return !!(cs)[SymbolCSpellSettingsInternal]; } + +export function isDictionaryDefinitionInlineInternal( + def: DictionaryDefinitionInternal | DictionaryDefinitionInline | DictionaryDefinition +): def is DictionaryDefinitionInlineInternal { + if (def.path) return false; + const defInline = def as DictionaryDefinitionInline; + return !!(defInline.words || defInline.flagWords || defInline.ignoreWords); +} diff --git a/packages/cspell-lib/src/Settings/CSpellSettingsServer.ts b/packages/cspell-lib/src/Settings/CSpellSettingsServer.ts index bdeefdb947f9..86e8e772c706 100644 --- a/packages/cspell-lib/src/Settings/CSpellSettingsServer.ts +++ b/packages/cspell-lib/src/Settings/CSpellSettingsServer.ts @@ -14,6 +14,7 @@ import * as path from 'path'; import type { CSpellSettingsInternal, CSpellSettingsInternalFinalized } from '../Models/CSpellSettingsInternalDef'; import { createCSpellSettingsInternal as csi, isCSpellSettingsInternal } from '../Models/CSpellSettingsInternalDef'; +import { AutoResolveWeakCache } from '../util/AutoResolve'; import type { OptionalOrUndefined } from '../util/types'; import * as util from '../util/util'; import { configSettingsFileVersion0_1, ENV_CSPELL_GLOB_ROOT } from './constants'; @@ -380,7 +381,9 @@ export interface 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); + const dictionaryFiles = calcDictionaryDefsToLoad(settingsI) + .map((dict) => dict.path) + .filter(util.isDefined); return { configFiles, @@ -407,26 +410,26 @@ function resolveParser(settings: CSpellSettingsI): Parser | undefined { return parser; } -const parserCache = new WeakMap, Map>(); +const parserCache = new AutoResolveWeakCache, Map>(); const emptyParserMap = new Map(); -function extractParsers(plugins: CSpellSettingsI['plugins']): Map { - if (!plugins || !plugins.length) return emptyParserMap; - const found = parserCache.get(plugins); - if (found) return found; - - function* parsers(plugins: Plugin[]) { - for (const plugin of plugins) { - if (!plugin.parsers) continue; - for (const parser of plugin.parsers) { - yield [parser.name, parser] as const; - } +function* parsers(plugins: Plugin[]) { + for (const plugin of plugins) { + if (!plugin.parsers) continue; + for (const parser of plugin.parsers) { + yield [parser.name, parser] as const; } } +} + +function mapPlugins(plugins: Exclude): Map { + return new Map(parsers(plugins)); +} + +function extractParsers(plugins: CSpellSettingsI['plugins']): Map { + if (!plugins || !plugins.length) return emptyParserMap; - const map = new Map(parsers(plugins)); - parserCache.set(plugins, map); - return map; + return parserCache.get(plugins, mapPlugins); } export const __testing__ = { diff --git a/packages/cspell-lib/src/Settings/DictionarySettings.ts b/packages/cspell-lib/src/Settings/DictionarySettings.ts index c2aa5c1c7f85..de06e4601b8a 100644 --- a/packages/cspell-lib/src/Settings/DictionarySettings.ts +++ b/packages/cspell-lib/src/Settings/DictionarySettings.ts @@ -15,7 +15,10 @@ import type { CSpellSettingsInternal, DictionaryDefinitionInternal, DictionaryDefinitionInternalWithSource, + DictionaryFileDefinitionInternalWithSource, } from '../Models/CSpellSettingsInternalDef'; +import { isDictionaryDefinitionInlineInternal } from '../Models/CSpellSettingsInternalDef'; +import { AutoResolveWeakCache } from '../util/AutoResolve'; import { resolveFile } from '../util/resolveFile'; import type { RequireOptional, UnionFields } from '../util/types'; import { clean } from '../util/util'; @@ -77,23 +80,29 @@ export function mapDictDefsToInternal( return defs?.map((def) => mapDictDefToInternal(def, pathToSettingsFile)); } +const internalDefs = new AutoResolveWeakCache(); + export function mapDictDefToInternal( def: DictionaryDefinition, pathToSettingsFile: string +): DictionaryDefinitionInternalWithSource { + return internalDefs.get(def, (def) => _mapDictDefToInternal(def, pathToSettingsFile)); +} + +function _mapDictDefToInternal( + def: DictionaryDefinition, + pathToSettingsFile: string ): DictionaryDefinitionInternalWithSource { if (isDictionaryDefinitionWithSource(def)) { return def; } + if (isDictionaryDefinitionInlineInternal(def)) { + return { ...def, __source: pathToSettingsFile }; + } return new _DictionaryDefinitionInternalWithSource(def, pathToSettingsFile); } -export function isDictionaryDefinitionWithSource( - d: DictionaryDefinition | DictionaryDefinitionInternalWithSource -): d is DictionaryDefinitionInternalWithSource { - return d instanceof _DictionaryDefinitionInternalWithSource; -} - function determineName(filename: string, options: DictionaryDefinition): string { return options.name || path.basename(filename); } @@ -114,15 +123,33 @@ type DictDef = Partial< UnionFields, DictionaryDefinitionCustom> >; +export function isDictionaryDefinitionWithSource( + d: DictionaryDefinition | DictionaryDefinitionInternalWithSource +): d is DictionaryDefinitionInternalWithSource { + return isDictionaryFileDefinitionInternalWithSource(d) || isDictionaryDefinitionInlineInternalWithSource(d); +} + export function isDictionaryDefinitionInternal( def: DictionaryDefinition | DictionaryDefinitionInternal ): def is DictionaryDefinitionInternal { return def instanceof _DictionaryDefinitionInternalWithSource; } -type DDI = Omit, '__source' | 'weightMap' | 'toJSON'>; +export function isDictionaryFileDefinitionInternalWithSource( + def: DictionaryDefinition | DictionaryDefinitionInternal +): def is DictionaryFileDefinitionInternalWithSource { + return def instanceof _DictionaryDefinitionInternalWithSource; +} + +export function isDictionaryDefinitionInlineInternalWithSource( + def: DictionaryDefinition | DictionaryDefinitionInternal +): def is DictionaryDefinitionInternalWithSource { + return isDictionaryDefinitionInlineInternal(def) && !!def.__source; +} + +type DDI = Omit, '__source' | 'weightMap' | 'toJSON'>; -class _DictionaryDefinitionInternalWithSource implements DictionaryDefinitionInternalWithSource { +class _DictionaryDefinitionInternalWithSource implements DictionaryFileDefinitionInternalWithSource { private _weightMap: WeightMap | undefined; readonly name: string; readonly path: string; diff --git a/packages/cspell-lib/src/Settings/InDocSettings.ts b/packages/cspell-lib/src/Settings/InDocSettings.ts index 2b6eda35c178..697ae75d6a53 100644 --- a/packages/cspell-lib/src/Settings/InDocSettings.ts +++ b/packages/cspell-lib/src/Settings/InDocSettings.ts @@ -4,7 +4,7 @@ import type { Sequence } from 'gensequence'; import { genSequence } from 'gensequence'; import type { ExtendedSuggestion } from '../Models/Suggestion'; -import { getSpellDictInterface } from '../SpellingDictionary'; +import { createSpellingDictionary } from '../SpellingDictionary'; import * as Text from '../util/text'; import { clean, isDefined } from '../util/util'; import { mergeInDocSettings } from './CSpellSettingsServer'; @@ -76,14 +76,9 @@ const allDirectiveSuggestions: ExtendedSuggestion[] = [ ), ]; -const dictInDocSettings = getSpellDictInterface().createSpellingDictionary( - allDirectives, - 'Directives', - 'Directive List', - { - supportNonStrictSearches: false, - } -); +const dictInDocSettings = createSpellingDictionary(allDirectives, 'Directives', 'Directive List', { + supportNonStrictSearches: false, +}); const EmptyWords: string[] = []; Object.freeze(EmptyWords); diff --git a/packages/cspell-lib/src/SpellingDictionary/DictionaryController/DictionaryLoader.test.ts b/packages/cspell-lib/src/SpellingDictionary/DictionaryController/DictionaryLoader.test.ts index 15b4f35f56d0..74fa28abb44d 100644 --- a/packages/cspell-lib/src/SpellingDictionary/DictionaryController/DictionaryLoader.test.ts +++ b/packages/cspell-lib/src/SpellingDictionary/DictionaryController/DictionaryLoader.test.ts @@ -1,6 +1,10 @@ import * as path from 'path'; -import type { DictionaryDefinitionInternal } from '../../Models/CSpellSettingsInternalDef'; +import type { + DictionaryDefinitionInlineInternal, + DictionaryDefinitionInternal, + DictionaryFileDefinitionInternal, +} from '../../Models/CSpellSettingsInternalDef'; import { mapDictDefToInternal } from '../../Settings/DictionarySettings'; import { getCSpellIO } from '../../static'; import { clean } from '../../util/util'; @@ -168,11 +172,28 @@ describe('Validate DictionaryLoader', () => { ${dDef({ name: 'words', path: dict('cities.txt'), type: 'S' })} | ${'York'} | ${false} ${dDef({ name: 'words', path: dict('cities.txt'), type: 'W' })} | ${'New York'} | ${false} ${dDef({ name: 'words', path: dict('cities.txt'), type: 'W' })} | ${'York'} | ${true} + ${dDef({ name: 'words', path: dict('cities.txt'), type: 'W' })} | ${'York'} | ${true} `('sync load dict has word $def $word', ({ def, word, hasWord }) => { const d = dictionaryLoader.loadDictionarySync(def); expect(d.has(word)).toBe(hasWord); }); + test.each` + def | word | hasWord + ${dDef({ name: 'words', words: ['New', 'York'] })} | ${'York'} | ${true} + `('sync load inline dict has word $def $word', ({ def, word, hasWord }) => { + const d = dictionaryLoader.loadDictionarySync(def); + expect(d.has(word)).toBe(hasWord); + }); + + test.each` + def | word | hasWord + ${dDef({ name: 'words', words: ['New', 'York'] })} | ${'York'} | ${true} + `('sync load inline dict has word $def $word', async ({ def, word, hasWord }) => { + const d = await dictionaryLoader.loadDictionary(def); + expect(d.has(word)).toBe(hasWord); + }); + test.each` def | expected ${dDef({ name: 'words', path: dict('cities.trie.gz'), type: 'S' })} | ${[]} @@ -197,11 +218,14 @@ function dict(file: string): string { return path.resolve(dictDir, file); } -interface DDef extends Partial { +interface DDefFile extends Partial { name: string; path: string; } +type DDef = DDefFile | DictionaryDefinitionInlineInternal; + function dDef(opts: DDef): DictionaryDefinitionInternal { - return di(opts, __filename); + const def = di(opts, __filename); + return def; } diff --git a/packages/cspell-lib/src/SpellingDictionary/DictionaryController/DictionaryLoader.ts b/packages/cspell-lib/src/SpellingDictionary/DictionaryController/DictionaryLoader.ts index 329bc58e06e8..188cb362787b 100644 --- a/packages/cspell-lib/src/SpellingDictionary/DictionaryController/DictionaryLoader.ts +++ b/packages/cspell-lib/src/SpellingDictionary/DictionaryController/DictionaryLoader.ts @@ -4,12 +4,19 @@ import { StrongWeakMap } from '@cspell/strong-weak-map'; import type { SpellingDictionary } from 'cspell-dictionary'; import { createFailedToLoadDictionary, + createInlineSpellingDictionary, createSpellingDictionary, createSpellingDictionaryFromTrieFile, } from 'cspell-dictionary'; import type { CSpellIO, Stats } from 'cspell-io'; -import type { DictionaryDefinitionInternal } from '../../Models/CSpellSettingsInternalDef'; +import type { + DictionaryDefinitionInlineInternal, + DictionaryDefinitionInternal, + DictionaryFileDefinitionInternal, +} from '../../Models/CSpellSettingsInternalDef'; +import { isDictionaryDefinitionInlineInternal } from '../../Models/CSpellSettingsInternalDef'; +import { AutoResolveWeakCache } from '../../util/AutoResolve'; import { toError } from '../../util/errors'; import { SpellingDictionaryLoadError } from '../SpellingDictionaryError'; @@ -33,6 +40,8 @@ const loadersSync: SyncLoaders = { export type LoadOptions = DictionaryDefinitionInternal; +type LoadFileOptions = DictionaryFileDefinitionInternal; + enum LoadingState { Loaded = 0, Loading = 1, @@ -40,7 +49,7 @@ enum LoadingState { interface CacheEntry { uri: string; - options: LoadOptions; + options: LoadFileOptions; ts: number; stat: Stats | Error | undefined; dictionary: SpellingDictionary | undefined; @@ -78,6 +87,7 @@ interface SyncLoaders { export class DictionaryLoader { private dictionaryCache = new StrongWeakMap(); + private inlineDictionaryCache = new AutoResolveWeakCache(); private dictionaryCacheByDef = new StrongWeakMap< DictionaryDefinitionInternal, { key: string; entry: CacheEntry } @@ -91,6 +101,9 @@ export class DictionaryLoader { } public loadDictionary(def: DictionaryDefinitionInternal): Promise { + if (isDictionaryDefinitionInlineInternal(def)) { + return Promise.resolve(this.loadInlineDict(def)); + } const { key, entry } = this.getCacheEntry(def); if (entry) { return entry.pending.then(([dictionary]) => dictionary); @@ -101,6 +114,9 @@ export class DictionaryLoader { } public loadDictionarySync(def: DictionaryDefinitionInternal): SpellingDictionary { + if (isDictionaryDefinitionInlineInternal(def)) { + return this.loadInlineDict(def); + } const { key, entry } = this.getCacheEntry(def); if (entry?.dictionary && entry.loadingState === LoadingState.Loaded) { return entry.dictionary; @@ -119,7 +135,7 @@ export class DictionaryLoader { await Promise.all([...this.dictionaryCache.values()].map((entry) => this.refreshEntry(entry, maxAge, now))); } - private getCacheEntry(def: DictionaryDefinitionInternal): { key: string; entry: CacheEntry | undefined } { + private getCacheEntry(def: DictionaryFileDefinitionInternal): { key: string; entry: CacheEntry | undefined } { const defEntry = this.dictionaryCacheByDef.get(def); if (defEntry) { return defEntry; @@ -158,7 +174,7 @@ export class DictionaryLoader { } } - private loadEntry(uri: string, options: LoadOptions, now = Date.now()): CacheEntry { + private loadEntry(uri: string, options: LoadFileOptions, now = Date.now()): CacheEntry { options = this.normalizeOptions(uri, options); const pDictionary = load(this.reader, uri, options).catch((e) => createFailedToLoadDictionary( @@ -191,7 +207,7 @@ export class DictionaryLoader { return entry; } - private loadEntrySync(uri: string, options: LoadOptions, now = Date.now()): CacheEntrySync { + private loadEntrySync(uri: string, options: LoadFileOptions, now = Date.now()): CacheEntrySync { options = this.normalizeOptions(uri, options); const stat = this.getStatSync(uri); const sig = now + Math.random(); @@ -250,10 +266,16 @@ export class DictionaryLoader { return !isError(b) && !this.cspellIO.compareStats(a, b); } - private normalizeOptions(uri: string, options: LoadOptions): LoadOptions { + private normalizeOptions(uri: string, options: LoadFileOptions): LoadFileOptions { if (options.name) return options; return { ...options, name: this.cspellIO.uriBasename(uri) }; } + + private loadInlineDict(def: DictionaryDefinitionInlineInternal): SpellingDictionary { + return this.inlineDictionaryCache.get(def, (def) => + createInlineSpellingDictionary(def, def.__source || 'memory') + ); + } } function toReader(cspellIO: CSpellIO): Reader { @@ -272,7 +294,7 @@ function toReaderSync(cspellIO: CSpellIO): ReaderSync { const importantOptionKeys: (keyof DictionaryDefinitionInternal)[] = ['name', 'noSuggest', 'useCompounds', 'type']; -function calcKey(def: DictionaryDefinitionInternal) { +function calcKey(def: DictionaryFileDefinitionInternal) { const path = def.path; const loaderType = determineType(path, def); const optValues = importantOptionKeys.map((k) => def[k]?.toString() || ''); diff --git a/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts b/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts index c02cc371d2d1..9bf58f2be66e 100644 --- a/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts +++ b/packages/cspell-lib/src/SpellingDictionary/DictionaryLoader.test.ts @@ -1,6 +1,9 @@ import * as path from 'path'; -import type { DictionaryDefinitionInternal } from '../Models/CSpellSettingsInternalDef'; +import type { + DictionaryDefinitionInternal, + DictionaryFileDefinitionInternal, +} from '../Models/CSpellSettingsInternalDef'; import { mapDictDefToInternal } from '../Settings/DictionarySettings'; import { clean } from '../util/util'; import type { LoadOptions } from './DictionaryLoader'; @@ -135,6 +138,25 @@ describe('Validate DictionaryLoader', () => { ${dDef({ name: 'words', path: dict('cities.txt'), type: 'S' })} | ${'York'} | ${false} ${dDef({ name: 'words', path: dict('cities.txt'), type: 'W' })} | ${'New York'} | ${false} ${dDef({ name: 'words', path: dict('cities.txt'), type: 'W' })} | ${'York'} | ${true} + ${{ name: 'cities', words: ['Paris', 'New York'] }} | ${'New York'} | ${true} + `('sync load dict has word $def $word', async ({ def, word, hasWord }) => { + const d = await loadDictionary(def); + expect(d.has(word)).toBe(hasWord); + }); + + test.each` + def | word | hasWord + ${dDef({ name: 'words', path: sample('words.txt.gz') })} | ${'apple'} | ${true} + ${dDef({ name: 'words', path: dict('cities.trie.gz') })} | ${'apple'} | ${false} + ${dDef({ name: 'words', path: dict('cities.trie.gz') })} | ${'New York'} | ${true} + ${dDef({ name: 'words', path: dict('cities.txt') })} | ${'York'} | ${false} + ${dDef({ name: 'words', path: dict('cities.txt'), type: 'C' })} | ${'New York'} | ${false} + ${dDef({ name: 'words', path: dict('cities.txt'), type: 'C' })} | ${'York'} | ${true} + ${dDef({ name: 'words', path: dict('cities.txt'), type: 'S' })} | ${'New York'} | ${true} + ${dDef({ name: 'words', path: dict('cities.txt'), type: 'S' })} | ${'York'} | ${false} + ${dDef({ name: 'words', path: dict('cities.txt'), type: 'W' })} | ${'New York'} | ${false} + ${dDef({ name: 'words', path: dict('cities.txt'), type: 'W' })} | ${'York'} | ${true} + ${{ name: 'cities', words: ['Paris', 'New York'] }} | ${'New York'} | ${true} `('sync load dict has word $def $word', ({ def, word, hasWord }) => { const d = loadDictionarySync(def); expect(d.has(word)).toBe(hasWord); @@ -164,7 +186,7 @@ function dict(file: string): string { return path.resolve(dictDir, file); } -interface DDef extends Partial { +interface DDef extends Partial { name: string; path: string; } diff --git a/packages/cspell-lib/src/SpellingDictionary/SpellingDictionary.ts b/packages/cspell-lib/src/SpellingDictionary/SpellingDictionary.ts index f294a141efb0..4dc429a9fa73 100644 --- a/packages/cspell-lib/src/SpellingDictionary/SpellingDictionary.ts +++ b/packages/cspell-lib/src/SpellingDictionary/SpellingDictionary.ts @@ -1,16 +1,3 @@ -import * as cspellDictModule from 'cspell-dictionary'; -export { CompoundWordsMethod } from 'cspell-trie-lib'; - -const SpellingDictionaryModule = { - createCollection: cspellDictModule.createCollection, - createForbiddenWordsDictionary: cspellDictModule.createForbiddenWordsDictionary, - createSpellingDictionary: cspellDictModule.createSpellingDictionary, - createIgnoreWordsDictionary: cspellDictModule.createIgnoreWordsDictionary, - createSpellingDictionaryFromTrieFile: cspellDictModule.createSpellingDictionaryFromTrieFile, -} as const; - -type SpellDictInterface = typeof SpellingDictionaryModule; - export type { FindOptions, FindResult, @@ -23,10 +10,5 @@ export type { SuggestionResult, SuggestOptions, } from 'cspell-dictionary'; - -export function getSpellDictInterface(): SpellDictInterface { - return SpellingDictionaryModule; -} - -export const createSpellingDictionary = getSpellDictInterface().createSpellingDictionary; -export const createCollection = getSpellDictInterface().createCollection; +export { createCollection, createInlineSpellingDictionary, createSpellingDictionary } from 'cspell-dictionary'; +export { CompoundWordsMethod } from 'cspell-trie-lib'; diff --git a/packages/cspell-lib/src/util/AutoResolve.test.ts b/packages/cspell-lib/src/util/AutoResolve.test.ts new file mode 100644 index 000000000000..92f6b454d48b --- /dev/null +++ b/packages/cspell-lib/src/util/AutoResolve.test.ts @@ -0,0 +1,43 @@ +import { createAutoResolveCache, createAutoResolveWeakCache } from './AutoResolve'; + +describe('AutoResolve', () => { + test('createAutoResolveCache', () => { + const cache = createAutoResolveCache(); + + const resolver = jest.fn((s: string) => s.toUpperCase()); + + expect(cache.get('hello')).toBe(undefined); + expect(cache.get('hello', resolver)).toBe('HELLO'); + expect(resolver).toHaveBeenCalledTimes(1); + expect(cache.get('hello', resolver)).toBe('HELLO'); + expect(resolver).toHaveBeenCalledTimes(1); + cache.set('hello', 'hello'); + expect(cache.get('hello', resolver)).toBe('hello'); + expect(resolver).toHaveBeenCalledTimes(1); + expect(cache.get('a', resolver)).toBe('A'); + expect(resolver).toHaveBeenCalledTimes(2); + }); + + test('createAutoResolveWeakCache', () => { + const cache = createAutoResolveWeakCache<{ name: string }, string>(); + + const resolver = jest.fn((v: { name: string }) => v.name.toUpperCase()); + + const tagHello = { name: 'hello' }; + const tagHello2 = { ...tagHello }; + const tagA = { name: 'a' }; + expect(cache.get(tagHello)).toBe(undefined); + expect(cache.get(tagHello, resolver)).toBe('HELLO'); + expect(resolver).toHaveBeenCalledTimes(1); + expect(cache.get(tagHello, resolver)).toBe('HELLO'); + expect(resolver).toHaveBeenCalledTimes(1); + cache.set(tagHello, 'hello'); + expect(cache.get(tagHello, resolver)).toBe('hello'); + expect(resolver).toHaveBeenCalledTimes(1); + expect(cache.get(tagA, resolver)).toBe('A'); + expect(resolver).toHaveBeenCalledTimes(2); + expect(cache.get(tagHello2)).toBe(undefined); + expect(cache.get(tagHello2, resolver)).toBe('HELLO'); + expect(resolver).toHaveBeenCalledTimes(3); + }); +}); diff --git a/packages/cspell-lib/src/util/AutoResolve.ts b/packages/cspell-lib/src/util/AutoResolve.ts new file mode 100644 index 000000000000..c23d249c8930 --- /dev/null +++ b/packages/cspell-lib/src/util/AutoResolve.ts @@ -0,0 +1,63 @@ +export function autoResolve(map: Map, key: K, resolve: (k: K) => V): V { + const found = map.get(key); + if (found !== undefined || map.has(key)) return found as V; + const value = resolve(key); + map.set(key, value); + return value; +} + +export class AutoResolveCache { + readonly map = new Map(); + + get(k: K): V | undefined; + get(k: K, resolve: (k: K) => V): V; + get(k: K, resolve?: (k: K) => V): V | undefined; + get(k: K, resolve?: (k: K) => V): V | undefined { + return resolve ? autoResolve(this.map, k, resolve) : this.map.get(k); + } + + has(k: K): boolean { + return this.map.has(k); + } + + set(k: K, v: V): this { + this.map.set(k, v); + return this; + } +} + +export function createAutoResolveCache(): AutoResolveCache { + return new AutoResolveCache(); +} + +export function autoResolveWeak(map: WeakMap, key: K, resolve: (k: K) => V): V { + const found = map.get(key); + if (found !== undefined || map.has(key)) return found as V; + const value = resolve(key); + map.set(key, value); + return value; +} + +export class AutoResolveWeakCache { + readonly map = new WeakMap(); + + get(k: K): V | undefined; + get(k: K, resolve: (k: K) => V): V; + get(k: K, resolve?: (k: K) => V): V | undefined; + get(k: K, resolve?: (k: K) => V): V | undefined { + return resolve ? autoResolveWeak(this.map, k, resolve) : this.map.get(k); + } + + has(k: K): boolean { + return this.map.has(k); + } + + set(k: K, v: V): this { + this.map.set(k, v); + return this; + } +} + +export function createAutoResolveWeakCache(): AutoResolveWeakCache { + return new AutoResolveWeakCache(); +} diff --git a/packages/cspell-types/cspell.schema.json b/packages/cspell-types/cspell.schema.json index 1573eddb9b2c..926eaacc7fd4 100644 --- a/packages/cspell-types/cspell.schema.json +++ b/packages/cspell-types/cspell.schema.json @@ -681,7 +681,7 @@ "type": "boolean" }, "flagWords": { - "description": "List of words to always be considered incorrect.", + "description": "List of words to always be considered incorrect. Words found in `flagWords` override `words`.\n\nFormat of `flagWords`\n- single word entry - `word`\n- with suggestions - `word:suggestion` or `word->suggestion, suggestions`\n\nExample: ```ts \"flagWords\": [ \"color: colour\", \"incase: in case, encase\", \"canot->cannot\", \"cancelled->canceled\" ] ```", "items": { "type": "string" }, @@ -769,7 +769,7 @@ "type": "array" }, "words": { - "description": "List of words to be always considered correct.", + "description": "List of words to be considered correct.", "items": { "type": "string" }, @@ -853,7 +853,7 @@ "description": "Glob pattern or patterns to match against." }, "flagWords": { - "description": "List of words to always be considered incorrect.", + "description": "List of words to always be considered incorrect. Words found in `flagWords` override `words`.\n\nFormat of `flagWords`\n- single word entry - `word`\n- with suggestions - `word:suggestion` or `word->suggestion, suggestions`\n\nExample: ```ts \"flagWords\": [ \"color: colour\", \"incase: in case, encase\", \"canot->cannot\", \"cancelled->canceled\" ] ```", "items": { "type": "string" }, @@ -963,7 +963,7 @@ "type": "boolean" }, "words": { - "description": "List of words to be always considered correct.", + "description": "List of words to be considered correct.", "items": { "type": "string" }, @@ -1293,7 +1293,7 @@ "type": "array" }, "flagWords": { - "description": "List of words to always be considered incorrect.", + "description": "List of words to always be considered incorrect. Words found in `flagWords` override `words`.\n\nFormat of `flagWords`\n- single word entry - `word`\n- with suggestions - `word:suggestion` or `word->suggestion, suggestions`\n\nExample: ```ts \"flagWords\": [ \"color: colour\", \"incase: in case, encase\", \"canot->cannot\", \"cancelled->canceled\" ] ```", "items": { "type": "string" }, @@ -1501,7 +1501,7 @@ "description": "Configuration format version of the settings file.\n\nThis controls how the settings in the configuration file behave." }, "words": { - "description": "List of words to be always considered correct.", + "description": "List of words to be considered correct.", "items": { "type": "string" }, diff --git a/packages/cspell-types/src/CSpellSettingsDef.ts b/packages/cspell-types/src/CSpellSettingsDef.ts index 340af3596420..e72739f55fff 100644 --- a/packages/cspell-types/src/CSpellSettingsDef.ts +++ b/packages/cspell-types/src/CSpellSettingsDef.ts @@ -1,6 +1,7 @@ import type { ReporterConfigurationBase } from './CSpellReporter.js'; import type { DictionaryDefinition, DictionaryReference } from './DictionaryDefinition.js'; import type { Features } from './features.js'; +import type { InlineDictionary } from './InlineDictionary.js'; import type { Parser, ParserName } from './Parser/index.js'; import type { Serializable } from './types.js'; @@ -405,7 +406,7 @@ export interface OverrideFilterFields { filename: Glob | Glob[]; } -export interface BaseSetting extends ExperimentalBaseSettings { +export interface BaseSetting extends InlineDictionary, ExperimentalBaseSettings { /** Optional identifier. */ id?: string; @@ -421,18 +422,6 @@ export interface BaseSetting extends ExperimentalBaseSettings { */ enabled?: boolean; - /** List of words to be always considered correct. */ - words?: string[]; - - /** List of words to always be considered incorrect. */ - flagWords?: string[]; - - /** - * List of words to be ignored. An ignored word will not show up as an error, even if it is - * also in the `flagWords`. - */ - ignoreWords?: string[]; - /** * True to enable compound word checking. See [Case Sensitivity](https://cspell.org/docs/case-sensitive/) for more details. * diff --git a/packages/cspell-types/src/DictionaryDefinition.ts b/packages/cspell-types/src/DictionaryDefinition.ts index 1d22ba9ffc3d..e593c60f0c7d 100644 --- a/packages/cspell-types/src/DictionaryDefinition.ts +++ b/packages/cspell-types/src/DictionaryDefinition.ts @@ -1,9 +1,11 @@ import type { DictionaryInformation } from './DictionaryInformation.js'; +import type { InlineDictionary } from './InlineDictionary.js'; export type DictionaryDefinition = | DictionaryDefinitionPreferred | DictionaryDefinitionCustom | DictionaryDefinitionAugmented + | DictionaryDefinitionInline | DictionaryDefinitionAlternate | DictionaryDefinitionLegacy; @@ -61,12 +63,60 @@ export interface DictionaryDefinitionPreferred extends DictionaryDefinitionBase */ file?: undefined; } + /** * Used to provide extra data related to the dictionary */ export interface DictionaryDefinitionAugmented extends DictionaryDefinitionPreferred { dictionaryInformation?: DictionaryInformation; } + +/** + * Inline Dictionary Definition + * + * All words are defined inline. + */ +interface DictionaryDefinitionInlineBase extends DictionaryDefinitionBase, InlineDictionary { + /** + * Not used + * @hidden + */ + path?: undefined; + /** + * Note used + * @deprecated true + * @deprecationMessage Use `path` instead. + * @hidden + */ + file?: undefined; +} + +export interface DictionaryDefinitionInlineWords + extends DictionaryDefinitionInlineBase, + Required> { + words: string[]; +} + +export interface DictionaryDefinitionInlineFlagWords + extends DictionaryDefinitionInlineBase, + Required> { + flagWords: string[]; +} + +export interface DictionaryDefinitionInlineIgnoreWords + extends DictionaryDefinitionInlineBase, + Required> { + ignoreWords: string[]; +} + +/** + * @hidden + */ +export type DictionaryDefinitionInline = + | DictionaryDefinitionInlineWords + | DictionaryDefinitionInlineIgnoreWords + | DictionaryDefinitionInlineFlagWords; + /** * Only for legacy dictionary definitions. * @deprecated true diff --git a/packages/cspell-types/src/InlineDictionary.ts b/packages/cspell-types/src/InlineDictionary.ts new file mode 100644 index 000000000000..ed6063895aca --- /dev/null +++ b/packages/cspell-types/src/InlineDictionary.ts @@ -0,0 +1,32 @@ +export interface InlineDictionary { + /** + * List of words to be considered correct. + */ + words?: string[]; + + // cspell:ignore colour color canot + /** + * List of words to always be considered incorrect. Words found in `flagWords` override `words`. + * + * Format of `flagWords` + * - single word entry - `word` + * - with suggestions - `word:suggestion` or `word->suggestion, suggestions` + * + * Example: + * ```ts + * "flagWords": [ + * "color: colour", + * "incase: in case, encase", + * "canot->cannot", + * "cancelled->canceled" + * ] + * ``` + */ + flagWords?: string[]; + + /** + * List of words to be ignored. An ignored word will not show up as an error, even if it is + * also in the `flagWords`. + */ + ignoreWords?: string[]; +} diff --git a/packages/cspell-types/src/index.ts b/packages/cspell-types/src/index.ts index cb88cbf6aeaa..f4229e39f317 100644 --- a/packages/cspell-types/src/index.ts +++ b/packages/cspell-types/src/index.ts @@ -90,6 +90,10 @@ export type { DictionaryDefinitionAugmented, DictionaryDefinitionBase, DictionaryDefinitionCustom, + DictionaryDefinitionInline, + DictionaryDefinitionInlineFlagWords, + DictionaryDefinitionInlineIgnoreWords, + DictionaryDefinitionInlineWords, DictionaryDefinitionLegacy, DictionaryDefinitionPreferred, DictionaryFileTypes,