cspell-lib 8.0.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/esm/Settings/CSpellSettingsServer.d.ts +3 -12
  2. package/dist/esm/Settings/CSpellSettingsServer.js +19 -55
  3. package/dist/esm/Settings/Controller/configLoader/configLoader.d.ts +55 -126
  4. package/dist/esm/Settings/Controller/configLoader/configLoader.js +241 -336
  5. package/dist/esm/Settings/Controller/configLoader/configLocations.d.ts +3 -0
  6. package/dist/esm/Settings/Controller/configLoader/configLocations.js +57 -0
  7. package/dist/esm/Settings/Controller/configLoader/configSearch.d.ts +12 -0
  8. package/dist/esm/Settings/Controller/configLoader/configSearch.js +116 -0
  9. package/dist/esm/Settings/Controller/configLoader/configToRawSettings.d.ts +5 -0
  10. package/dist/esm/Settings/Controller/configLoader/configToRawSettings.js +36 -0
  11. package/dist/esm/Settings/Controller/configLoader/defaultConfigLoader.d.ts +34 -0
  12. package/dist/esm/Settings/Controller/configLoader/defaultConfigLoader.js +67 -0
  13. package/dist/esm/Settings/Controller/configLoader/extractImportErrors.d.ts +1 -0
  14. package/dist/esm/Settings/Controller/configLoader/extractImportErrors.js +4 -0
  15. package/dist/esm/Settings/Controller/configLoader/index.d.ts +3 -1
  16. package/dist/esm/Settings/Controller/configLoader/index.js +3 -1
  17. package/dist/esm/Settings/Controller/configLoader/normalizeRawSettings.d.ts +8 -6
  18. package/dist/esm/Settings/Controller/configLoader/normalizeRawSettings.js +31 -14
  19. package/dist/esm/Settings/Controller/configLoader/readSettings.d.ts +6 -5
  20. package/dist/esm/Settings/Controller/configLoader/readSettings.js +10 -5
  21. package/dist/esm/Settings/Controller/configLoader/readSettingsFiles.d.ts +1 -1
  22. package/dist/esm/Settings/Controller/configLoader/readSettingsFiles.js +3 -2
  23. package/dist/esm/Settings/Controller/pnpLoader.d.ts +4 -12
  24. package/dist/esm/Settings/Controller/pnpLoader.js +22 -48
  25. package/dist/esm/Settings/DefaultSettings.d.ts +10 -2
  26. package/dist/esm/Settings/DefaultSettings.js +33 -22
  27. package/dist/esm/Settings/DictionarySettings.d.ts +4 -4
  28. package/dist/esm/Settings/DictionarySettings.js +8 -5
  29. package/dist/esm/Settings/GlobalSettings.d.ts +6 -3
  30. package/dist/esm/Settings/GlobalSettings.js +24 -19
  31. package/dist/esm/Settings/InDocSettings.js +2 -2
  32. package/dist/esm/Settings/LanguageSettings.js +10 -15
  33. package/dist/esm/Settings/TextDocumentSettings.js +4 -4
  34. package/dist/esm/Settings/calcOverrideSettings.d.ts +3 -0
  35. package/dist/esm/Settings/calcOverrideSettings.js +11 -0
  36. package/dist/esm/Settings/checkFilenameMatchesGlob.d.ts +10 -0
  37. package/dist/esm/Settings/checkFilenameMatchesGlob.js +10 -0
  38. package/dist/esm/Settings/index.d.ts +5 -3
  39. package/dist/esm/Settings/index.js +5 -3
  40. package/dist/esm/Settings/link.d.ts +3 -3
  41. package/dist/esm/Settings/link.js +20 -13
  42. package/dist/esm/Settings/mergeCache.d.ts +13 -0
  43. package/dist/esm/Settings/mergeCache.js +22 -0
  44. package/dist/esm/Settings/mergeList.d.ts +18 -0
  45. package/dist/esm/Settings/mergeList.js +34 -0
  46. package/dist/esm/Settings/patterns.js +13 -2
  47. package/dist/esm/SpellingDictionary/DictionaryController/DictionaryLoader.d.ts +1 -0
  48. package/dist/esm/SpellingDictionary/DictionaryController/DictionaryLoader.js +40 -38
  49. package/dist/esm/clearCachedFiles.d.ts +14 -0
  50. package/dist/esm/clearCachedFiles.js +29 -3
  51. package/dist/esm/events/events.d.ts +17 -0
  52. package/dist/esm/events/events.js +28 -0
  53. package/dist/esm/events/index.d.ts +3 -0
  54. package/dist/esm/events/index.js +2 -0
  55. package/dist/esm/getDictionary.js +1 -1
  56. package/dist/esm/globs/checkFilenameMatchesGlob.d.ts +8 -0
  57. package/dist/esm/globs/checkFilenameMatchesGlob.js +11 -0
  58. package/dist/esm/globs/getGlobMatcher.d.ts +4 -0
  59. package/dist/esm/globs/getGlobMatcher.js +31 -0
  60. package/dist/esm/index.d.ts +4 -2
  61. package/dist/esm/index.js +3 -2
  62. package/dist/esm/perf/index.d.ts +3 -0
  63. package/dist/esm/perf/index.js +2 -0
  64. package/dist/esm/perf/perf.d.ts +14 -0
  65. package/dist/esm/perf/perf.js +27 -0
  66. package/dist/esm/perf/timer.d.ts +11 -0
  67. package/dist/esm/perf/timer.js +37 -0
  68. package/dist/esm/spellCheckFile.d.ts +8 -1
  69. package/dist/esm/spellCheckFile.js +33 -4
  70. package/dist/esm/suggestions.js +5 -5
  71. package/dist/esm/textValidation/ValidateTextOptions.d.ts +5 -0
  72. package/dist/esm/textValidation/determineTextDocumentSettings.d.ts +1 -1
  73. package/dist/esm/textValidation/determineTextDocumentSettings.js +2 -2
  74. package/dist/esm/textValidation/docValidator.d.ts +4 -9
  75. package/dist/esm/textValidation/docValidator.js +53 -91
  76. package/dist/esm/trace.js +8 -1
  77. package/dist/esm/util/AutoResolve.d.ts +21 -4
  78. package/dist/esm/util/AutoResolve.js +25 -4
  79. package/dist/esm/util/errors.js +4 -4
  80. package/dist/esm/util/fileReader.d.ts +3 -3
  81. package/dist/esm/util/findUp.d.ts +9 -0
  82. package/dist/esm/util/findUp.js +43 -0
  83. package/dist/esm/util/resolveFile.d.ts +31 -1
  84. package/dist/esm/util/resolveFile.js +107 -27
  85. package/dist/esm/util/url.d.ts +29 -0
  86. package/dist/esm/util/url.js +91 -0
  87. package/dist/esm/util/wordSplitter.js +4 -4
  88. package/package.json +16 -17
  89. package/dist/esm/util/debugPerf.d.ts +0 -9
  90. package/dist/esm/util/debugPerf.js +0 -18
  91. package/dist/esm/util/timer.d.ts +0 -26
  92. package/dist/esm/util/timer.js +0 -51
@@ -1,237 +1,266 @@
1
- import * as json from 'comment-json';
2
- import { cosmiconfig, cosmiconfigSync } from 'cosmiconfig';
1
+ import assert from 'assert';
2
+ import { createReaderWriter, CSpellConfigFileInMemory } from 'cspell-config-lib';
3
3
  import { getDefaultCSpellIO } from 'cspell-io';
4
4
  import * as path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { onClearCache } from '../../../events/index.js';
5
7
  import { createCSpellSettingsInternal as csi } from '../../../Models/CSpellSettingsInternalDef.js';
6
- import { AutoResolveLRUCache } from '../../../util/AutoResolveLRUCache.js';
8
+ import { AutoResolveCache } from '../../../util/AutoResolve.js';
7
9
  import { logError, logWarning } from '../../../util/logger.js';
8
10
  import { resolveFile } from '../../../util/resolveFile.js';
9
- import { toUri } from '../../../util/Uri.js';
11
+ import { cwdURL, toFilePathOrHref } from '../../../util/url.js';
10
12
  import { configSettingsFileVersion0_1, configSettingsFileVersion0_2, currentSettingsFileVersion, ENV_CSPELL_GLOB_ROOT, } from '../../constants.js';
11
13
  import { mergeSettings } from '../../CSpellSettingsServer.js';
12
- import { getRawGlobalSettings } from '../../GlobalSettings.js';
14
+ import { getGlobalConfig } from '../../GlobalSettings.js';
13
15
  import { ImportError } from '../ImportError.js';
14
16
  import { pnpLoader } from '../pnpLoader.js';
17
+ import { searchPlaces } from './configLocations.js';
18
+ import { ConfigSearch } from './configSearch.js';
19
+ import { configToRawSettings } from './configToRawSettings.js';
15
20
  import { defaultSettings } from './defaultSettings.js';
16
- import { normalizeCacheSettings, normalizeDictionaryDefs, normalizeGitignoreRoot, normalizeLanguageSettings, normalizeOverrides, normalizeRawConfig, normalizeReporters, normalizeSettingsGlobs, } from './normalizeRawSettings.js';
21
+ import { normalizeCacheSettings, normalizeDictionaryDefs, normalizeGitignoreRoot, normalizeImport, normalizeLanguageSettings, normalizeOverrides, normalizeReporters, normalizeSettingsGlobs, } from './normalizeRawSettings.js';
17
22
  import { defaultPnPSettings, normalizePnPSettings } from './PnPSettings.js';
18
23
  const supportedCSpellConfigVersions = [configSettingsFileVersion0_2];
19
24
  const setOfSupportedConfigVersions = Object.freeze(new Set(supportedCSpellConfigVersions));
20
25
  export const sectionCSpell = 'cSpell';
21
26
  export const defaultFileName = 'cspell.json';
22
- const gcl = getDefaultConfigLoaderInternal;
23
- const CACHE_SIZE_SEARCH_CONFIG = 32;
24
- /**
25
- * Logic of the locations:
26
- * - Support backward compatibility with the VS Code Spell Checker
27
- * the spell checker extension can only write to `.json` files because
28
- * it would be too difficult to automatically modify a `.js` or `.cjs` file.
29
- * - To support `cspell.config.js` in a VS Code environment, have a `cspell.json` import
30
- * the `cspell.config.js`.
31
- */
32
- const searchPlaces = Object.freeze([
33
- 'package.json',
34
- // Original locations
35
- '.cspell.json',
36
- 'cspell.json',
37
- '.cSpell.json',
38
- 'cSpell.json',
39
- // Original locations jsonc
40
- '.cspell.jsonc',
41
- 'cspell.jsonc',
42
- // Alternate locations
43
- '.vscode/cspell.json',
44
- '.vscode/cSpell.json',
45
- '.vscode/.cspell.json',
46
- // Standard Locations
47
- '.cspell.config.json',
48
- '.cspell.config.jsonc',
49
- '.cspell.config.yaml',
50
- '.cspell.config.yml',
51
- 'cspell.config.json',
52
- 'cspell.config.jsonc',
53
- 'cspell.config.yaml',
54
- 'cspell.config.yml',
55
- '.cspell.yaml',
56
- '.cspell.yml',
57
- 'cspell.yaml',
58
- 'cspell.yml',
59
- // Dynamic config is looked for last
60
- 'cspell.config.js',
61
- 'cspell.config.cjs',
62
- // .config
63
- '.config/.cspell.json',
64
- '.config/cspell.json',
65
- '.config/.cSpell.json',
66
- '.config/cSpell.json',
67
- '.config/.cspell.jsonc',
68
- '.config/cspell.jsonc',
69
- '.config/cspell.config.json',
70
- '.config/cspell.config.jsonc',
71
- '.config/cspell.config.yaml',
72
- '.config/cspell.config.yml',
73
- '.config/cspell.yaml',
74
- '.config/cspell.yml',
75
- '.config/cspell.config.js',
76
- '.config/cspell.config.cjs',
77
- ]);
78
- const cspellCosmiconfig = {
79
- searchPlaces: searchPlaces.concat(),
80
- loaders: {
81
- '.json': parseJson,
82
- '.jsonc': parseJson,
83
- },
84
- };
85
- function parseJson(_filename, content) {
86
- return json.parse(content);
87
- }
88
- export const defaultConfigFilenames = Object.freeze(searchPlaces.concat());
89
27
  let defaultConfigLoader = undefined;
90
28
  export class ConfigLoader {
91
29
  cspellIO;
30
+ onReady;
92
31
  /**
93
32
  * Use `createConfigLoader`
94
33
  * @param cspellIO - CSpellIO interface for reading files.
95
34
  */
96
35
  constructor(cspellIO) {
97
36
  this.cspellIO = cspellIO;
37
+ this.cspellConfigFileReaderWriter = createReaderWriter(undefined, undefined, createIO(cspellIO));
38
+ this.onReady = this.getGlobalSettingsAsync().then(() => undefined, (e) => logError(e));
39
+ this.subscribeToEvents();
98
40
  }
99
- cachedFiles = new Map();
100
- cspellConfigExplorer = cosmiconfig('cspell', cspellCosmiconfig);
101
- cspellConfigExplorerSync = cosmiconfigSync('cspell', cspellCosmiconfig);
102
- globalSettings;
103
- readSettings(filename, relativeToOrDefault, defaultValue) {
104
- // console.log('Read Settings: %o', { filename, relativeToOrDefault });
105
- const relativeTo = (typeof relativeToOrDefault === 'string' ? relativeToOrDefault : '') || process.cwd();
106
- defaultValue = defaultValue || (typeof relativeToOrDefault !== 'string' ? relativeToOrDefault : undefined);
107
- const ref = resolveFilename(filename, relativeTo);
108
- return this.importSettings(ref, defaultValue, defaultValue || defaultPnPSettings);
41
+ subscribeToEvents() {
42
+ this.toDispose.push(onClearCache(() => this.clearCachedSettingsFiles()));
109
43
  }
44
+ cachedConfig = new Map();
45
+ cachedConfigFiles = new Map();
46
+ cachedPendingConfigFile = new AutoResolveCache();
47
+ cachedMergedConfig = new WeakMap();
48
+ globalSettings;
49
+ cspellConfigFileReaderWriter;
50
+ configSearch = new ConfigSearch(searchPlaces);
51
+ toDispose = [];
110
52
  async readSettingsAsync(filename, relativeTo, pnpSettings) {
111
53
  const ref = resolveFilename(filename, relativeTo || process.cwd());
112
- return this.importSettings(ref, undefined, pnpSettings || defaultPnPSettings);
54
+ const entry = this.importSettings(ref, pnpSettings || defaultPnPSettings, []);
55
+ return entry.onReady;
56
+ }
57
+ async readConfigFile(filenameOrURL, relativeTo) {
58
+ const ref = resolveFilename(filenameOrURL.toString(), relativeTo || process.cwd());
59
+ const url = this.cspellIO.toFileURL(ref.filename);
60
+ const href = url.href;
61
+ if (ref.error)
62
+ return new ImportError(`Failed to read config file: "${ref.filename}"`, ref.error);
63
+ const cached = this.cachedConfigFiles.get(href);
64
+ if (cached)
65
+ return cached;
66
+ return this.cachedPendingConfigFile.get(href, async () => {
67
+ try {
68
+ const file = await this.cspellConfigFileReaderWriter.readConfig(href);
69
+ this.cachedConfigFiles.set(href, file);
70
+ // validateRawConfigVersion(file);
71
+ return file;
72
+ }
73
+ catch (error) {
74
+ // console.warn('Debug: %o', { href, error });
75
+ return new ImportError(`Failed to read config file: "${ref.filename}"`, error);
76
+ }
77
+ finally {
78
+ setTimeout(() => this.cachedPendingConfigFile.delete(href), 1);
79
+ }
80
+ });
81
+ }
82
+ searchForConfigFileLocation(searchFrom) {
83
+ const url = this.cspellIO.toFileURL(searchFrom || cwdURL());
84
+ return this.configSearch.searchForConfig(url);
85
+ }
86
+ async searchForConfigFile(searchFrom) {
87
+ const location = await this.searchForConfigFileLocation(searchFrom);
88
+ if (!location)
89
+ return undefined;
90
+ const file = await this.readConfigFile(location);
91
+ return file instanceof Error ? undefined : file;
113
92
  }
114
93
  /**
115
94
  *
116
- * @param searchFrom the directory / file to start searching from.
95
+ * @param searchFrom the directory / file URL to start searching from.
117
96
  * @param pnpSettings - related to Using Yarn PNP.
118
97
  * @returns the resulting settings
119
98
  */
120
- searchForConfig(searchFrom, pnpSettings = defaultPnPSettings) {
121
- pnpSettings = normalizePnPSettings(pnpSettings);
122
- return this.searchConfigLRU.get({ searchFrom, pnpSettings }, (p) => this._searchForConfig(p));
123
- }
124
- searchConfigLRU = new AutoResolveLRUCache(CACHE_SIZE_SEARCH_CONFIG, (a, b) => a.searchFrom === b.searchFrom && a.pnpSettings === b.pnpSettings);
125
- _searchForConfig(params) {
126
- // console.log('_searchForConfig: %o', { params, stats: this.searchConfigLRU.stats() });
127
- return gcl()
128
- .normalizeSearchForConfigResultAsync(params.searchFrom || process.cwd(), this.cspellConfigExplorer.search(params.searchFrom), params.pnpSettings)
129
- .then((r) => (r.filepath ? r.config : undefined));
99
+ async searchForConfig(searchFrom, pnpSettings = defaultPnPSettings) {
100
+ const configFile = await this.searchForConfigFile(searchFrom);
101
+ if (!configFile)
102
+ return undefined;
103
+ return this.mergeConfigFileWithImports(configFile, pnpSettings);
130
104
  }
131
105
  getGlobalSettings() {
106
+ assert(this.globalSettings);
107
+ return this.globalSettings;
108
+ }
109
+ async getGlobalSettingsAsync() {
132
110
  if (!this.globalSettings) {
133
- const globalConf = getRawGlobalSettings();
134
- this.globalSettings = {
135
- id: 'global_config',
136
- ...this.normalizeSettings(globalConf || {}, './global_config', {}),
137
- };
111
+ const globalConfFile = await getGlobalConfig();
112
+ const normalized = await this.mergeConfigFileWithImports(globalConfFile, undefined);
113
+ normalized.id ??= 'global_config';
114
+ this.globalSettings = normalized;
138
115
  }
139
116
  return this.globalSettings;
140
117
  }
141
118
  clearCachedSettingsFiles() {
142
- this.searchConfigLRU.clear();
143
119
  this.globalSettings = undefined;
144
- this.cachedFiles.clear();
145
- this.cspellConfigExplorer.clearCaches();
146
- this.cspellConfigExplorerSync.clearCaches();
120
+ this.cachedConfig.clear();
121
+ this.cachedConfigFiles.clear();
122
+ this.configSearch.clearCache();
123
+ this.cachedPendingConfigFile.clear();
124
+ this.cspellConfigFileReaderWriter.clearCachedFiles();
125
+ this.cachedMergedConfig = new WeakMap();
147
126
  }
148
- /**
149
- * Read a config file and inject the fileRef.
150
- * @param fileRef - filename plus context, injected into the resulting config.
151
- */
152
- readConfig(fileRef) {
153
- // cspellConfigExplorerSync
154
- const { filename, error } = fileRef;
155
- if (error) {
156
- fileRef.error =
157
- error instanceof ImportError
158
- ? error
159
- : new ImportError(`Failed to read config file: "${filename}"`, error);
160
- return { __importRef: fileRef };
127
+ importSettings(fileRef, pnpSettings, backReferences) {
128
+ const url = this.cspellIO.toFileURL(fileRef.filename);
129
+ const cacheKey = url.href;
130
+ const cachedImport = this.cachedConfig.get(cacheKey);
131
+ if (cachedImport) {
132
+ backReferences.forEach((ref) => cachedImport.referencedSet.add(ref));
133
+ return cachedImport;
161
134
  }
162
- const s = {};
163
- try {
164
- const r = this.cspellConfigExplorerSync.load(filename);
165
- if (!r?.config)
166
- throw new Error(`not found: "${filename}"`);
167
- Object.assign(s, r.config);
168
- normalizeRawConfig(s);
169
- validateRawConfig(s, fileRef);
135
+ if (fileRef.error) {
136
+ const settings = csi({
137
+ __importRef: fileRef,
138
+ source: { name: fileRef.filename, filename: fileRef.filename },
139
+ });
140
+ const importedConfig = {
141
+ href: cacheKey,
142
+ fileRef,
143
+ configFile: undefined,
144
+ settings,
145
+ isReady: true,
146
+ onReady: Promise.resolve(settings),
147
+ onConfigFileReady: Promise.resolve(fileRef.error),
148
+ referencedSet: new Set(backReferences),
149
+ };
150
+ this.cachedConfig.set(cacheKey, importedConfig);
151
+ return importedConfig;
170
152
  }
171
- catch (err) {
172
- fileRef.error =
173
- err instanceof ImportError ? err : new ImportError(`Failed to read config file: "${filename}"`, err);
153
+ const source = {
154
+ name: fileRef.filename,
155
+ filename: fileRef.filename,
156
+ };
157
+ const mergeImports = (cfgFile) => {
158
+ if (cfgFile instanceof Error) {
159
+ fileRef.error = cfgFile;
160
+ return csi({ __importRef: fileRef, source });
161
+ }
162
+ return this.mergeConfigFileWithImports(cfgFile, pnpSettings, backReferences);
163
+ };
164
+ const referencedSet = new Set(backReferences);
165
+ const onConfigFileReady = onConfigFileReadyFixUp(this.readConfigFile(fileRef.filename));
166
+ const importedConfig = {
167
+ href: cacheKey,
168
+ fileRef,
169
+ configFile: undefined,
170
+ settings: undefined,
171
+ isReady: false,
172
+ onReady: onReadyFixUp(onConfigFileReady.then(mergeImports)),
173
+ onConfigFileReady,
174
+ referencedSet,
175
+ };
176
+ this.cachedConfig.set(cacheKey, importedConfig);
177
+ return importedConfig;
178
+ async function onReadyFixUp(pSettings) {
179
+ const settings = await pSettings;
180
+ settings.source ??= source;
181
+ settings.__importRef ??= fileRef;
182
+ importedConfig.isReady = true;
183
+ importedConfig.settings = settings;
184
+ return settings;
185
+ }
186
+ async function onConfigFileReadyFixUp(pCfgFile) {
187
+ const cfgFile = await pCfgFile;
188
+ if (cfgFile instanceof Error) {
189
+ importedConfig.fileRef.error = cfgFile;
190
+ return cfgFile;
191
+ }
192
+ source.name = cfgFile.settings.name || source.name;
193
+ importedConfig.configFile = cfgFile;
194
+ return cfgFile;
174
195
  }
175
- s.__importRef = fileRef;
176
- return s;
177
196
  }
178
- importSettings(fileRef, defaultValues, pnpSettings) {
179
- defaultValues = defaultValues ?? defaultSettings;
180
- const { filename } = fileRef;
181
- const importRef = { ...fileRef };
182
- const cached = this.cachedFiles.get(filename);
183
- if (cached) {
184
- const cachedImportRef = cached.__importRef || importRef;
185
- cachedImportRef.referencedBy = mergeSourceList(cachedImportRef.referencedBy || [], importRef.referencedBy);
186
- cached.__importRef = cachedImportRef;
187
- return cached;
197
+ async setupPnp(cfgFile, pnpSettings) {
198
+ if (!pnpSettings?.usePnP || pnpSettings === defaultPnPSettings)
199
+ return;
200
+ if (cfgFile.url.protocol !== 'file:')
201
+ return;
202
+ // Try to load any .pnp files before reading dictionaries or other config files.
203
+ const { usePnP = pnpSettings.usePnP, pnpFiles = pnpSettings.pnpFiles } = cfgFile.settings;
204
+ const pnpSettingsToUse = normalizePnPSettings({ usePnP, pnpFiles });
205
+ const pathToSettingsDir = new URL('.', cfgFile.url);
206
+ await loadPnP(pnpSettingsToUse, pathToSettingsDir);
207
+ }
208
+ mergeConfigFileWithImports(cfgFile, pnpSettings, referencedBy) {
209
+ const cached = this.cachedMergedConfig.get(cfgFile);
210
+ if (cached && cached.pnpSettings === pnpSettings && cached.referencedBy === referencedBy) {
211
+ return cached.result;
188
212
  }
189
- const id = [path.basename(path.dirname(filename)), path.basename(filename)].join('/');
190
- const name = '';
191
- const finalizeSettings = csi({ id, name, __importRef: importRef });
192
- this.cachedFiles.set(filename, finalizeSettings); // add an empty entry to prevent circular references.
193
- const settings = { ...defaultValues, id, name, ...this.readConfig(importRef) };
194
- Object.assign(finalizeSettings, this.normalizeSettings(settings, filename, pnpSettings));
195
- const finalizeSrc = { name: path.basename(filename), ...finalizeSettings.source };
196
- finalizeSettings.source = { ...finalizeSrc, filename };
197
- this.cachedFiles.set(filename, finalizeSettings);
198
- return finalizeSettings;
213
+ // console.warn('missing cache %o', cfgFile.url.href);
214
+ const result = this._mergeConfigFileWithImports(cfgFile, pnpSettings, referencedBy);
215
+ this.cachedMergedConfig.set(cfgFile, { pnpSettings, referencedBy, result });
216
+ return result;
217
+ }
218
+ async _mergeConfigFileWithImports(cfgFile, pnpSettings, referencedBy = []) {
219
+ await this.setupPnp(cfgFile, pnpSettings);
220
+ const href = cfgFile.url.href;
221
+ const referencedSet = new Set(referencedBy);
222
+ const imports = normalizeImport(cfgFile.settings.import);
223
+ const __imports = imports.map((name) => resolveFilename(name, cfgFile.url));
224
+ const toImport = __imports.map((ref) => this.importSettings(ref, pnpSettings, [...referencedBy, href]));
225
+ // Add ourselves to the import sources.
226
+ toImport.forEach((entry) => {
227
+ entry.referencedSet.add(href);
228
+ });
229
+ const pendingImports = toImport.map((entry) => {
230
+ // Detect circular references, return raw settings if circular.
231
+ return referencedSet.has(entry.href)
232
+ ? entry.settings || configToRawSettings(entry.configFile)
233
+ : entry.onReady;
234
+ });
235
+ const importSettings = await Promise.all(pendingImports);
236
+ const cfg = this.mergeImports(cfgFile, importSettings);
237
+ return cfg;
199
238
  }
200
239
  /**
201
240
  * normalizeSettings handles correcting all relative paths, anchoring globs, and importing other config files.
202
241
  * @param rawSettings - raw configuration settings
203
242
  * @param pathToSettingsFile - path to the source file of the configuration settings.
204
243
  */
205
- normalizeSettings(rawSettings, pathToSettingsFile, pnpSettings) {
206
- const id = rawSettings.id ||
207
- [path.basename(path.dirname(pathToSettingsFile)), path.basename(pathToSettingsFile)].join('/');
208
- const name = rawSettings.name || id;
209
- // Try to load any .pnp files before reading dictionaries or other config files.
210
- const { usePnP = pnpSettings.usePnP, pnpFiles = pnpSettings.pnpFiles } = rawSettings;
211
- const pnpSettingsToUse = normalizePnPSettings({ usePnP, pnpFiles });
212
- const pathToSettingsDir = path.dirname(pathToSettingsFile);
213
- loadPnPSync(pnpSettingsToUse, toUri(pathToSettingsDir));
244
+ mergeImports(cfgFile, importedSettings) {
245
+ const rawSettings = configToRawSettings(cfgFile);
246
+ const url = cfgFile.url;
247
+ const fileRef = rawSettings.__importRef;
248
+ const source = rawSettings.source;
249
+ assert(fileRef);
250
+ assert(source);
214
251
  // Fix up dictionaryDefinitions
215
252
  const settings = {
216
253
  version: defaultSettings.version,
217
254
  ...rawSettings,
218
- id,
219
- name,
220
- globRoot: resolveGlobRoot(rawSettings, pathToSettingsFile),
255
+ globRoot: resolveGlobRoot(rawSettings, cfgFile.url),
221
256
  languageSettings: normalizeLanguageSettings(rawSettings.languageSettings),
222
257
  };
223
- const pathToSettings = path.dirname(pathToSettingsFile);
224
- const normalizedDictionaryDefs = normalizeDictionaryDefs(settings, pathToSettingsFile);
225
- const normalizedSettingsGlobs = normalizeSettingsGlobs(settings, pathToSettingsFile);
226
- const normalizedOverrides = normalizeOverrides(settings, pathToSettingsFile);
227
- const normalizedReporters = normalizeReporters(settings, pathToSettingsFile);
228
- const normalizedGitignoreRoot = normalizeGitignoreRoot(settings, pathToSettingsFile);
229
- const normalizedCacheSettings = normalizeCacheSettings(settings, pathToSettingsDir);
230
- const imports = typeof settings.import === 'string' ? [settings.import] : settings.import || [];
231
- const source = settings.source || {
232
- name: settings.name,
233
- filename: pathToSettingsFile,
234
- };
258
+ const normalizedDictionaryDefs = normalizeDictionaryDefs(settings, url);
259
+ const normalizedSettingsGlobs = normalizeSettingsGlobs(settings, url);
260
+ const normalizedOverrides = normalizeOverrides(settings, url);
261
+ const normalizedReporters = normalizeReporters(settings, url);
262
+ const normalizedGitignoreRoot = normalizeGitignoreRoot(settings, url);
263
+ const normalizedCacheSettings = normalizeCacheSettings(settings, url);
235
264
  const fileSettings = csi({
236
265
  ...settings,
237
266
  source,
@@ -242,132 +271,37 @@ export class ConfigLoader {
242
271
  ...normalizedGitignoreRoot,
243
272
  ...normalizedCacheSettings,
244
273
  });
245
- if (!imports.length) {
274
+ if (!importedSettings.length) {
246
275
  return fileSettings;
247
276
  }
248
- const importedSettings = imports
249
- .map((name) => resolveFilename(name, pathToSettings))
250
- .map((ref) => ((ref.referencedBy = [source]), ref))
251
- .map((ref) => this.importSettings(ref, undefined, pnpSettingsToUse))
252
- .reduce((a, b) => mergeSettings(a, b));
253
- const finalizeSettings = mergeSettings(importedSettings, fileSettings);
277
+ const mergedImportedSettings = importedSettings.reduce((a, b) => mergeSettings(a, b));
278
+ const finalizeSettings = mergeSettings(mergedImportedSettings, fileSettings);
254
279
  finalizeSettings.name = settings.name || finalizeSettings.name || '';
255
280
  finalizeSettings.id = settings.id || finalizeSettings.id || '';
281
+ finalizeSettings.__importRef = fileRef;
256
282
  return finalizeSettings;
257
283
  }
258
- }
259
- class ConfigLoaderInternal extends ConfigLoader {
260
- constructor(cspellIO) {
261
- super(cspellIO);
262
- }
263
- get _cachedFiles() {
264
- return this.cachedFiles;
265
- }
266
- get _cspellConfigExplorer() {
267
- return this.cspellConfigExplorer;
268
- }
269
- get _cspellConfigExplorerSync() {
270
- return this.cspellConfigExplorerSync;
271
- }
272
- _readConfig = this.readConfig.bind(this);
273
- _normalizeSettings = this.normalizeSettings.bind(this);
274
- async normalizeSearchForConfigResultAsync(searchPath, searchResult, pnpSettings) {
275
- let result;
276
- try {
277
- result = (await searchResult) || undefined;
278
- }
279
- catch (cause) {
280
- result = new ImportError(`Failed to find config file at: "${searchPath}"`, cause);
281
- }
282
- return this.normalizeSearchForConfigResult(searchPath, result, pnpSettings);
284
+ createCSpellConfigFile(filename, settings) {
285
+ return new CSpellConfigFileInMemory(this.cspellIO.toFileURL(filename), settings);
283
286
  }
284
- normalizeSearchForConfigResult(searchPath, searchResult, pnpSettings) {
285
- const error = searchResult instanceof ImportError ? searchResult : undefined;
286
- const result = searchResult instanceof ImportError ? undefined : searchResult;
287
- const filepath = result?.filepath;
288
- if (filepath) {
289
- const cached = cachedFiles().get(filepath);
290
- if (cached) {
291
- return {
292
- config: cached,
293
- filepath,
294
- error,
295
- };
287
+ dispose() {
288
+ while (this.toDispose.length) {
289
+ try {
290
+ this.toDispose.pop()?.dispose();
291
+ }
292
+ catch (e) {
293
+ logError(e);
296
294
  }
297
- }
298
- const { config = csi({}) } = result || {};
299
- const filename = result?.filepath ?? searchPath;
300
- const importRef = { filename: filename, error };
301
- const id = [path.basename(path.dirname(filename)), path.basename(filename)].join('/');
302
- const name = result?.filepath ? id : `Config not found: ${id}`;
303
- const finalizeSettings = csi({ id, name, __importRef: importRef });
304
- const settings = { id, ...config };
305
- cachedFiles().set(filename, finalizeSettings); // add an empty entry to prevent circular references.
306
- Object.assign(finalizeSettings, this.normalizeSettings(settings, filename, pnpSettings));
307
- return {
308
- config: finalizeSettings,
309
- filepath,
310
- error,
311
- };
312
- }
313
- }
314
- function mergeSourceList(orig, append) {
315
- const collection = new Map(orig.map((s) => [s.name + (s.filename || ''), s]));
316
- for (const s of append || []) {
317
- const key = s.name + (s.filename || '');
318
- if (!collection.has(key)) {
319
- collection.set(key, s);
320
295
  }
321
296
  }
322
- return [...collection.values()];
323
297
  }
324
- /**
325
- *
326
- * @param searchFrom the directory / file to start searching from.
327
- * @param pnpSettings - related to Using Yarn PNP.
328
- * @returns the resulting settings
329
- */
330
- export function searchForConfig(searchFrom, pnpSettings = defaultPnPSettings) {
331
- return gcl().searchForConfig(searchFrom, pnpSettings);
332
- }
333
- /**
334
- *
335
- * @param searchFrom the directory / file to start searching from.
336
- * @param pnpSettings - related to Using Yarn PNP.
337
- * @returns the resulting settings
338
- * @deprecated
339
- * @deprecationMessage Use `searchForConfig`. It is very difficult to support Sync files when settings include web requests.
340
- */
341
- export function searchForConfigSync(searchFrom, pnpSettings = defaultPnPSettings) {
342
- pnpSettings = normalizePnPSettings(pnpSettings);
343
- let searchResult;
344
- try {
345
- searchResult = cspellConfigExplorerSync().search(searchFrom) || undefined;
298
+ class ConfigLoaderInternal extends ConfigLoader {
299
+ constructor(cspellIO) {
300
+ super(cspellIO);
346
301
  }
347
- catch (err) {
348
- searchResult = new ImportError(`Failed to find config file from: "${searchFrom}"`, err);
302
+ get _cachedFiles() {
303
+ return this.cachedConfig;
349
304
  }
350
- return gcl().normalizeSearchForConfigResult(searchFrom || process.cwd(), searchResult, pnpSettings).config;
351
- }
352
- /**
353
- * Load a CSpell configuration files.
354
- * @param file - path or package reference to load.
355
- * @param pnpSettings - PnP settings
356
- * @returns normalized CSpellSettings
357
- */
358
- export async function loadConfig(file, pnpSettings = defaultPnPSettings) {
359
- return gcl().readSettingsAsync(file, undefined, pnpSettings);
360
- }
361
- /**
362
- * Load a CSpell configuration files.
363
- * @param filename - path or package reference to load.
364
- * @param pnpSettings - PnP settings
365
- * @returns normalized CSpellSettings
366
- * @deprecated
367
- */
368
- export function loadConfigSync(filename, pnpSettings = defaultPnPSettings) {
369
- const pnp = normalizePnPSettings(pnpSettings);
370
- return gcl().readSettings(filename, pnp);
371
305
  }
372
306
  export function loadPnP(pnpSettings, searchFrom) {
373
307
  if (!pnpSettings.usePnP) {
@@ -376,39 +310,21 @@ export function loadPnP(pnpSettings, searchFrom) {
376
310
  const loader = pnpLoader(pnpSettings.pnpFiles);
377
311
  return loader.load(searchFrom);
378
312
  }
379
- export function loadPnPSync(pnpSettings, searchFrom) {
380
- if (!pnpSettings.usePnP) {
381
- return undefined;
382
- }
383
- const loader = pnpLoader(pnpSettings.pnpFiles);
384
- return loader.loadSync(searchFrom);
385
- }
386
- export function readRawSettings(filename, relativeTo) {
387
- relativeTo = relativeTo || process.cwd();
388
- const ref = resolveFilename(filename, relativeTo);
389
- return gcl()._readConfig(ref);
390
- }
391
313
  function resolveFilename(filename, relativeTo) {
314
+ if (filename instanceof URL)
315
+ return { filename: toFilePathOrHref(filename) };
392
316
  const r = resolveFile(filename, relativeTo);
393
317
  return {
394
- filename: r.filename,
318
+ filename: r.filename.startsWith('file:/') ? fileURLToPath(r.filename) : r.filename,
395
319
  error: r.found ? undefined : new Error(`Failed to resolve file: "${filename}"`),
396
320
  };
397
321
  }
398
- export function getGlobalSettings() {
399
- return gcl().getGlobalSettings();
400
- }
401
- export function getCachedFileSize() {
402
- return cachedFiles().size;
403
- }
404
- export function clearCachedSettingsFiles() {
405
- return gcl().clearCachedSettingsFiles();
406
- }
407
322
  const nestedConfigDirectories = {
408
323
  '.vscode': true,
409
324
  '.config': true,
410
325
  };
411
- function resolveGlobRoot(settings, pathToSettingsFile) {
326
+ function resolveGlobRoot(settings, urlSettingsFile) {
327
+ const pathToSettingsFile = fileURLToPath(urlSettingsFile);
412
328
  const settingsFileDirRaw = path.dirname(pathToSettingsFile);
413
329
  const settingsFileDirName = path.basename(settingsFileDirRaw);
414
330
  const isNestedConfig = settingsFileDirName in nestedConfigDirectories;
@@ -425,36 +341,27 @@ function resolveGlobRoot(settings, pathToSettingsFile) {
425
341
  const globRoot = rawRoot.startsWith('${cwd}') ? rawRoot : path.resolve(settingsFileDir, rawRoot);
426
342
  return globRoot;
427
343
  }
428
- function validationMessage(msg, fileRef) {
429
- return msg + `\n File: "${fileRef.filename}"`;
344
+ function validationMessage(msg, url) {
345
+ return msg + `\n File: "${toFilePathOrHref(url)}"`;
430
346
  }
431
- function validateRawConfigVersion(config, fileRef) {
432
- const { version } = config;
347
+ function validateRawConfigVersion(config) {
348
+ const { version } = config.settings;
433
349
  if (version === undefined)
434
350
  return;
435
351
  if (typeof version !== 'string') {
436
- logError(validationMessage(`Unsupported config file version: "${version}", string expected`, fileRef));
352
+ logError(validationMessage(`Unsupported config file version: "${version}", string expected`, config.url));
437
353
  return;
438
354
  }
439
355
  if (setOfSupportedConfigVersions.has(version))
440
356
  return;
441
357
  if (!/^\d+(\.\d+)*$/.test(version)) {
442
- logError(validationMessage(`Unsupported config file version: "${version}"`, fileRef));
358
+ logError(validationMessage(`Unsupported config file version: "${version}"`, config.url));
443
359
  return;
444
360
  }
445
361
  const msg = version > currentSettingsFileVersion
446
362
  ? `Newer config file version found: "${version}". Supported version is "${currentSettingsFileVersion}"`
447
363
  : `Legacy config file version found: "${version}", upgrade to "${currentSettingsFileVersion}"`;
448
- logWarning(validationMessage(msg, fileRef));
449
- }
450
- function validateRawConfigExports(config, fileRef) {
451
- if (config.default) {
452
- throw new ImportError(validationMessage('Module `export default` is not supported.\n Use `module.exports =` instead.', fileRef));
453
- }
454
- }
455
- function validateRawConfig(config, fileRef) {
456
- const validations = [validateRawConfigExports, validateRawConfigVersion];
457
- validations.forEach((fn) => fn(config, fileRef));
364
+ logWarning(validationMessage(msg, config.url));
458
365
  }
459
366
  function createConfigLoaderInternal(cspellIO) {
460
367
  return new ConfigLoaderInternal(cspellIO ?? getDefaultCSpellIO());
@@ -462,24 +369,22 @@ function createConfigLoaderInternal(cspellIO) {
462
369
  export function createConfigLoader(cspellIO) {
463
370
  return createConfigLoaderInternal(cspellIO);
464
371
  }
465
- function getDefaultConfigLoaderInternal() {
372
+ export function getDefaultConfigLoaderInternal() {
466
373
  if (defaultConfigLoader)
467
374
  return defaultConfigLoader;
468
375
  return (defaultConfigLoader = createConfigLoaderInternal());
469
376
  }
470
- export function getDefaultConfigLoader() {
471
- return getDefaultConfigLoaderInternal();
472
- }
473
- function cachedFiles() {
474
- return gcl()._cachedFiles;
475
- }
476
- function cspellConfigExplorerSync() {
477
- return gcl()._cspellConfigExplorerSync;
377
+ function createIO(cspellIO) {
378
+ const readFile = (url) => cspellIO.readFile(url).then((file) => ({ url: file.url, content: file.getText() }));
379
+ const writeFile = (file) => cspellIO.writeFile(file.url, file.content);
380
+ return {
381
+ readFile,
382
+ writeFile,
383
+ };
478
384
  }
479
385
  export const __testing__ = {
480
386
  getDefaultConfigLoaderInternal,
481
387
  normalizeCacheSettings,
482
- validateRawConfigExports,
483
388
  validateRawConfigVersion,
484
389
  };
485
390
  //# sourceMappingURL=configLoader.js.map