cspell-lib 8.1.2 → 8.2.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 (39) hide show
  1. package/dist/esm/Settings/CSpellSettingsServer.d.ts +1 -1
  2. package/dist/esm/Settings/CSpellSettingsServer.js +1 -0
  3. package/dist/esm/Settings/Controller/configLoader/configLoader.d.ts +19 -8
  4. package/dist/esm/Settings/Controller/configLoader/configLoader.js +83 -49
  5. package/dist/esm/Settings/Controller/configLoader/configSearch.d.ts +7 -3
  6. package/dist/esm/Settings/Controller/configLoader/configSearch.js +68 -64
  7. package/dist/esm/Settings/Controller/configLoader/configToRawSettings.js +5 -2
  8. package/dist/esm/Settings/Controller/configLoader/normalizeRawSettings.d.ts +1 -1
  9. package/dist/esm/Settings/Controller/configLoader/normalizeRawSettings.js +6 -6
  10. package/dist/esm/Settings/DefaultSettings.js +5 -4
  11. package/dist/esm/Settings/DictionarySettings.js +4 -3
  12. package/dist/esm/Settings/GlobalSettings.js +5 -4
  13. package/dist/esm/Settings/mergeCache.d.ts +1 -0
  14. package/dist/esm/Settings/mergeCache.js +6 -1
  15. package/dist/esm/Settings/mergeList.d.ts +4 -0
  16. package/dist/esm/Settings/mergeList.js +6 -0
  17. package/dist/esm/SpellingDictionary/DictionaryLoader.js +1 -1
  18. package/dist/esm/fileSystem.d.ts +8 -0
  19. package/dist/esm/fileSystem.js +16 -0
  20. package/dist/esm/index.d.ts +2 -0
  21. package/dist/esm/index.js +1 -0
  22. package/dist/esm/perf/perf.d.ts +11 -5
  23. package/dist/esm/perf/perf.js +16 -11
  24. package/dist/esm/util/AutoResolve.d.ts +12 -0
  25. package/dist/esm/util/AutoResolve.js +50 -1
  26. package/dist/esm/util/Uri.d.ts +10 -11
  27. package/dist/esm/util/Uri.js +5 -13
  28. package/dist/esm/util/findUp.d.ts +1 -1
  29. package/dist/esm/util/findUpFromUrl.d.ts +12 -0
  30. package/dist/esm/util/findUpFromUrl.js +44 -0
  31. package/dist/esm/util/resolveFile.d.ts +56 -35
  32. package/dist/esm/util/resolveFile.js +243 -147
  33. package/dist/esm/util/templates.d.ts +3 -0
  34. package/dist/esm/util/templates.js +36 -0
  35. package/dist/esm/util/url.d.ts +2 -1
  36. package/dist/esm/util/url.js +9 -3
  37. package/package.json +14 -14
  38. package/dist/esm/static.d.ts +0 -3
  39. package/dist/esm/static.js +0 -6
@@ -4,6 +4,7 @@ import type { OptionalOrUndefined } from '../util/types.js';
4
4
  type CSpellSettingsWST = AdvancedCSpellSettingsWithSourceTrace;
5
5
  export type CSpellSettingsWSTO = OptionalOrUndefined<AdvancedCSpellSettingsWithSourceTrace>;
6
6
  export type CSpellSettingsI = CSpellSettingsInternal;
7
+ export { stats as getMergeStats } from './mergeList.js';
7
8
  declare function mergeObjects(left: undefined, right: undefined): undefined;
8
9
  declare function mergeObjects<T>(left: T, right: undefined): T;
9
10
  declare function mergeObjects<T>(left: T, right: T): T;
@@ -35,5 +36,4 @@ export declare function extractDependencies(settings: CSpellSettingsWSTO | CSpel
35
36
  export declare const __testing__: {
36
37
  mergeObjects: typeof mergeObjects;
37
38
  };
38
- export {};
39
39
  //# sourceMappingURL=CSpellSettingsServer.d.ts.map
@@ -9,6 +9,7 @@ import { configSettingsFileVersion0_1, ENV_CSPELL_GLOB_ROOT } from './constants.
9
9
  import { calcDictionaryDefsToLoad, mapDictDefsToInternal } from './DictionarySettings.js';
10
10
  import { mergeList, mergeListUnique } from './mergeList.js';
11
11
  import { resolvePatterns } from './patterns.js';
12
+ export { stats as getMergeStats } from './mergeList.js';
12
13
  const emptyWords = [];
13
14
  Object.freeze(emptyWords);
14
15
  const cachedMerges = new AutoResolveWeakCache();
@@ -1,12 +1,13 @@
1
1
  import type { CSpellUserSettings, ImportFileRef } from '@cspell/cspell-types';
2
2
  import type { CSpellConfigFile, CSpellConfigFileReaderWriter } from 'cspell-config-lib';
3
- import type { CSpellIO } from 'cspell-io';
3
+ import type { VFileSystem } from '../../../fileSystem.js';
4
4
  import { AutoResolveCache } from '../../../util/AutoResolve.js';
5
+ import { FileResolver } from '../../../util/resolveFile.js';
5
6
  import type { LoaderResult } from '../pnpLoader.js';
6
7
  import { ConfigSearch } from './configSearch.js';
7
8
  import { normalizeCacheSettings } from './normalizeRawSettings.js';
8
9
  import type { PnPSettingsOptional } from './PnPSettings.js';
9
- import type { CSpellSettingsI } from './types.js';
10
+ import type { CSpellSettingsI, CSpellSettingsWST } from './types.js';
10
11
  export declare const sectionCSpell = "cSpell";
11
12
  export declare const defaultFileName = "cspell.json";
12
13
  interface ImportedConfigEntry {
@@ -69,15 +70,18 @@ export interface IConfigLoader {
69
70
  * Unsubscribe from any events and dispose of any resources including caches.
70
71
  */
71
72
  dispose(): void;
73
+ getStats(): Readonly<Record<string, Readonly<Record<string, number>>>>;
72
74
  }
73
75
  export declare class ConfigLoader implements IConfigLoader {
74
- readonly cspellIO: CSpellIO;
76
+ readonly fs: VFileSystem;
77
+ readonly templateVariables: Record<string, string>;
75
78
  onReady: Promise<void>;
79
+ readonly fileResolver: FileResolver;
76
80
  /**
77
81
  * Use `createConfigLoader`
78
- * @param cspellIO - CSpellIO interface for reading files.
82
+ * @param virtualFs - virtual file system to use.
79
83
  */
80
- protected constructor(cspellIO: CSpellIO);
84
+ protected constructor(fs: VFileSystem, templateVariables?: Record<string, string>);
81
85
  private subscribeToEvents;
82
86
  protected cachedConfig: Map<string, ImportedConfigEntry>;
83
87
  protected cachedConfigFiles: Map<string, CSpellConfigFile>;
@@ -113,22 +117,29 @@ export declare class ConfigLoader implements IConfigLoader {
113
117
  * @param rawSettings - raw configuration settings
114
118
  * @param pathToSettingsFile - path to the source file of the configuration settings.
115
119
  */
116
- protected mergeImports(cfgFile: CSpellConfigFile, importedSettings: CSpellUserSettings[]): CSpellSettingsI;
120
+ protected mergeImports(cfgFile: CSpellConfigFile, importedSettings: CSpellUserSettings[]): Promise<CSpellSettingsI>;
117
121
  createCSpellConfigFile(filename: URL | string, settings: CSpellUserSettings): CSpellConfigFile;
118
122
  dispose(): void;
123
+ getStats(): {
124
+ cacheMergeListUnique: Readonly<import("../../../util/AutoResolve.js").CacheStats>;
125
+ cacheMergeLists: Readonly<import("../../../util/AutoResolve.js").CacheStats>;
126
+ };
127
+ private resolveFilename;
119
128
  }
120
129
  declare class ConfigLoaderInternal extends ConfigLoader {
121
- constructor(cspellIO: CSpellIO);
130
+ constructor(vfs: VFileSystem);
122
131
  get _cachedFiles(): Map<string, ImportedConfigEntry>;
123
132
  }
124
133
  export declare function loadPnP(pnpSettings: PnPSettingsOptional, searchFrom: URL): Promise<LoaderResult>;
134
+ declare function resolveGlobRoot(settings: CSpellSettingsWST, urlSettingsFile: URL): string;
125
135
  declare function validateRawConfigVersion(config: CSpellConfigFile): void;
126
- export declare function createConfigLoader(cspellIO?: CSpellIO): IConfigLoader;
136
+ export declare function createConfigLoader(fs?: VFileSystem): IConfigLoader;
127
137
  export declare function getDefaultConfigLoaderInternal(): ConfigLoaderInternal;
128
138
  export declare const __testing__: {
129
139
  getDefaultConfigLoaderInternal: typeof getDefaultConfigLoaderInternal;
130
140
  normalizeCacheSettings: typeof normalizeCacheSettings;
131
141
  validateRawConfigVersion: typeof validateRawConfigVersion;
142
+ resolveGlobRoot: typeof resolveGlobRoot;
132
143
  };
133
144
  export {};
134
145
  //# sourceMappingURL=configLoader.d.ts.map
@@ -1,16 +1,19 @@
1
1
  import assert from 'assert';
2
2
  import { createReaderWriter, CSpellConfigFileInMemory } from 'cspell-config-lib';
3
- import { getDefaultCSpellIO } from 'cspell-io';
4
- import * as path from 'path';
3
+ import { isUrlLike, toFileURL } from 'cspell-io';
4
+ import path from 'path';
5
5
  import { fileURLToPath, pathToFileURL } from 'url';
6
+ import { URI, Utils as UriUtils } from 'vscode-uri';
6
7
  import { onClearCache } from '../../../events/index.js';
8
+ import { createTextFileResource, getVirtualFS } from '../../../fileSystem.js';
7
9
  import { createCSpellSettingsInternal as csi } from '../../../Models/CSpellSettingsInternalDef.js';
8
10
  import { AutoResolveCache } from '../../../util/AutoResolve.js';
9
11
  import { logError, logWarning } from '../../../util/logger.js';
10
- import { resolveFile } from '../../../util/resolveFile.js';
11
- import { cwdURL, toFilePathOrHref } from '../../../util/url.js';
12
+ import { FileResolver } from '../../../util/resolveFile.js';
13
+ import { envToTemplateVars } from '../../../util/templates.js';
14
+ import { addTrailingSlash, cwdURL, resolveFileWithURL, toFilePathOrHref, windowsDriveLetterToUpper, } from '../../../util/url.js';
12
15
  import { configSettingsFileVersion0_1, configSettingsFileVersion0_2, currentSettingsFileVersion, ENV_CSPELL_GLOB_ROOT, } from '../../constants.js';
13
- import { mergeSettings } from '../../CSpellSettingsServer.js';
16
+ import { getMergeStats, mergeSettings } from '../../CSpellSettingsServer.js';
14
17
  import { getGlobalConfig } from '../../GlobalSettings.js';
15
18
  import { ImportError } from '../ImportError.js';
16
19
  import { pnpLoader } from '../pnpLoader.js';
@@ -26,15 +29,20 @@ export const sectionCSpell = 'cSpell';
26
29
  export const defaultFileName = 'cspell.json';
27
30
  let defaultConfigLoader = undefined;
28
31
  export class ConfigLoader {
29
- cspellIO;
32
+ fs;
33
+ templateVariables;
30
34
  onReady;
35
+ fileResolver;
31
36
  /**
32
37
  * Use `createConfigLoader`
33
- * @param cspellIO - CSpellIO interface for reading files.
38
+ * @param virtualFs - virtual file system to use.
34
39
  */
35
- constructor(cspellIO) {
36
- this.cspellIO = cspellIO;
37
- this.cspellConfigFileReaderWriter = createReaderWriter(undefined, undefined, createIO(cspellIO));
40
+ constructor(fs, templateVariables = envToTemplateVars(process.env)) {
41
+ this.fs = fs;
42
+ this.templateVariables = templateVariables;
43
+ this.configSearch = new ConfigSearch(searchPlaces, fs);
44
+ this.cspellConfigFileReaderWriter = createReaderWriter(undefined, undefined, createIO(fs));
45
+ this.fileResolver = new FileResolver(fs, this.templateVariables);
38
46
  this.onReady = this.prefetchGlobalSettingsAsync();
39
47
  this.subscribeToEvents();
40
48
  }
@@ -47,17 +55,17 @@ export class ConfigLoader {
47
55
  cachedMergedConfig = new WeakMap();
48
56
  globalSettings;
49
57
  cspellConfigFileReaderWriter;
50
- configSearch = new ConfigSearch(searchPlaces);
58
+ configSearch;
51
59
  toDispose = [];
52
60
  async readSettingsAsync(filename, relativeTo, pnpSettings) {
53
61
  await this.onReady;
54
- const ref = resolveFilename(filename, relativeTo || pathToFileURL('./'));
62
+ const ref = await this.resolveFilename(filename, relativeTo || pathToFileURL('./'));
55
63
  const entry = this.importSettings(ref, pnpSettings || defaultPnPSettings, []);
56
64
  return entry.onReady;
57
65
  }
58
66
  async readConfigFile(filenameOrURL, relativeTo) {
59
- const ref = resolveFilename(filenameOrURL.toString(), relativeTo || pathToFileURL('./'));
60
- const url = this.cspellIO.toFileURL(ref.filename);
67
+ const ref = await this.resolveFilename(filenameOrURL.toString(), relativeTo || pathToFileURL('./'));
68
+ const url = toFileURL(ref.filename);
61
69
  const href = url.href;
62
70
  if (ref.error)
63
71
  return new ImportError(`Failed to read config file: "${ref.filename}"`, ref.error);
@@ -80,8 +88,14 @@ export class ConfigLoader {
80
88
  }
81
89
  });
82
90
  }
83
- searchForConfigFileLocation(searchFrom) {
84
- const url = this.cspellIO.toFileURL(searchFrom || cwdURL());
91
+ async searchForConfigFileLocation(searchFrom) {
92
+ const url = toFileURL(searchFrom || cwdURL(), cwdURL());
93
+ if (typeof searchFrom === 'string' && !isUrlLike(searchFrom) && url.protocol === 'file:') {
94
+ // check to see if it is a directory
95
+ if (await isDirectory(this.fs, url)) {
96
+ return this.configSearch.searchForConfig(addTrailingSlash(url));
97
+ }
98
+ }
85
99
  return this.configSearch.searchForConfig(url);
86
100
  }
87
101
  async searchForConfigFile(searchFrom) {
@@ -131,7 +145,7 @@ export class ConfigLoader {
131
145
  return this.onReady;
132
146
  }
133
147
  importSettings(fileRef, pnpSettings, backReferences) {
134
- const url = this.cspellIO.toFileURL(fileRef.filename);
148
+ const url = toFileURL(fileRef.filename);
135
149
  const cacheKey = url.href;
136
150
  const cachedImport = this.cachedConfig.get(cacheKey);
137
151
  if (cachedImport) {
@@ -226,7 +240,7 @@ export class ConfigLoader {
226
240
  const href = cfgFile.url.href;
227
241
  const referencedSet = new Set(referencedBy);
228
242
  const imports = normalizeImport(cfgFile.settings.import);
229
- const __imports = imports.map((name) => resolveFilename(name, cfgFile.url));
243
+ const __imports = await Promise.all(imports.map((name) => this.resolveFilename(name, cfgFile.url)));
230
244
  const toImport = __imports.map((ref) => this.importSettings(ref, pnpSettings, [...referencedBy, href]));
231
245
  // Add ourselves to the import sources.
232
246
  toImport.forEach((entry) => {
@@ -239,7 +253,7 @@ export class ConfigLoader {
239
253
  : entry.onReady;
240
254
  });
241
255
  const importSettings = await Promise.all(pendingImports);
242
- const cfg = this.mergeImports(cfgFile, importSettings);
256
+ const cfg = await this.mergeImports(cfgFile, importSettings);
243
257
  return cfg;
244
258
  }
245
259
  /**
@@ -247,12 +261,11 @@ export class ConfigLoader {
247
261
  * @param rawSettings - raw configuration settings
248
262
  * @param pathToSettingsFile - path to the source file of the configuration settings.
249
263
  */
250
- mergeImports(cfgFile, importedSettings) {
264
+ async mergeImports(cfgFile, importedSettings) {
251
265
  const rawSettings = configToRawSettings(cfgFile);
252
266
  const url = cfgFile.url;
253
267
  const fileRef = rawSettings.__importRef;
254
268
  const source = rawSettings.source;
255
- assert(fileRef);
256
269
  assert(source);
257
270
  // Fix up dictionaryDefinitions
258
271
  const settings = {
@@ -264,7 +277,7 @@ export class ConfigLoader {
264
277
  const normalizedDictionaryDefs = normalizeDictionaryDefs(settings, url);
265
278
  const normalizedSettingsGlobs = normalizeSettingsGlobs(settings, url);
266
279
  const normalizedOverrides = normalizeOverrides(settings, url);
267
- const normalizedReporters = normalizeReporters(settings, url);
280
+ const normalizedReporters = await normalizeReporters(settings, url);
268
281
  const normalizedGitignoreRoot = normalizeGitignoreRoot(settings, url);
269
282
  const normalizedCacheSettings = normalizeCacheSettings(settings, url);
270
283
  const fileSettings = csi({
@@ -284,11 +297,13 @@ export class ConfigLoader {
284
297
  const finalizeSettings = mergeSettings(mergedImportedSettings, fileSettings);
285
298
  finalizeSettings.name = settings.name || finalizeSettings.name || '';
286
299
  finalizeSettings.id = settings.id || finalizeSettings.id || '';
287
- finalizeSettings.__importRef = fileRef;
300
+ if (fileRef) {
301
+ finalizeSettings.__importRef = fileRef;
302
+ }
288
303
  return finalizeSettings;
289
304
  }
290
305
  createCSpellConfigFile(filename, settings) {
291
- return new CSpellConfigFileInMemory(this.cspellIO.toFileURL(filename), settings);
306
+ return new CSpellConfigFileInMemory(toFileURL(filename), settings);
292
307
  }
293
308
  dispose() {
294
309
  while (this.toDispose.length) {
@@ -300,10 +315,25 @@ export class ConfigLoader {
300
315
  }
301
316
  }
302
317
  }
318
+ getStats() {
319
+ return { ...getMergeStats() };
320
+ }
321
+ async resolveFilename(filename, relativeTo) {
322
+ if (filename instanceof URL)
323
+ return { filename: toFilePathOrHref(filename) };
324
+ const r = await this.fileResolver.resolveFile(filename, relativeTo);
325
+ if (r.warning) {
326
+ logWarning(r.warning);
327
+ }
328
+ return {
329
+ filename: r.filename.startsWith('file:/') ? fileURLToPath(r.filename) : r.filename,
330
+ error: r.found ? undefined : new Error(`Failed to resolve file: "${filename}"`),
331
+ };
332
+ }
303
333
  }
304
334
  class ConfigLoaderInternal extends ConfigLoader {
305
- constructor(cspellIO) {
306
- super(cspellIO);
335
+ constructor(vfs) {
336
+ super(vfs);
307
337
  }
308
338
  get _cachedFiles() {
309
339
  return this.cachedConfig;
@@ -316,26 +346,17 @@ export function loadPnP(pnpSettings, searchFrom) {
316
346
  const loader = pnpLoader(pnpSettings.pnpFiles);
317
347
  return loader.load(searchFrom);
318
348
  }
319
- function resolveFilename(filename, relativeTo) {
320
- if (filename instanceof URL)
321
- return { filename: toFilePathOrHref(filename) };
322
- const r = resolveFile(filename, relativeTo);
323
- return {
324
- filename: r.filename.startsWith('file:/') ? fileURLToPath(r.filename) : r.filename,
325
- error: r.found ? undefined : new Error(`Failed to resolve file: "${filename}"`),
326
- };
327
- }
328
349
  const nestedConfigDirectories = {
329
350
  '.vscode': true,
330
- '.config': true,
351
+ '.config': true, // this should be removed in the future, but it is a breaking change.
331
352
  };
332
353
  function resolveGlobRoot(settings, urlSettingsFile) {
333
- const pathToSettingsFile = fileURLToPath(urlSettingsFile);
334
- const settingsFileDirRaw = path.dirname(pathToSettingsFile);
335
- const settingsFileDirName = path.basename(settingsFileDirRaw);
354
+ const urlSettingsFileDir = new URL('.', urlSettingsFile);
355
+ const uriSettingsFileDir = URI.parse(urlSettingsFileDir.href);
356
+ const settingsFileDirName = UriUtils.basename(uriSettingsFileDir);
336
357
  const isNestedConfig = settingsFileDirName in nestedConfigDirectories;
337
358
  const isVSCode = settingsFileDirName === '.vscode';
338
- const settingsFileDir = isNestedConfig ? path.dirname(settingsFileDirRaw) : settingsFileDirRaw;
359
+ const settingsFileDir = (isNestedConfig ? UriUtils.dirname(uriSettingsFileDir) : uriSettingsFileDir).toString();
339
360
  const envGlobRoot = process.env[ENV_CSPELL_GLOB_ROOT];
340
361
  const defaultGlobRoot = envGlobRoot ?? '${cwd}';
341
362
  const rawRoot = settings.globRoot ??
@@ -344,8 +365,12 @@ function resolveGlobRoot(settings, urlSettingsFile) {
344
365
  (isVSCode && !settings.version)
345
366
  ? defaultGlobRoot
346
367
  : settingsFileDir);
347
- const globRoot = rawRoot.startsWith('${cwd}') ? rawRoot : path.resolve(settingsFileDir, rawRoot);
348
- return globRoot;
368
+ const globRoot = rawRoot.startsWith('${cwd}') ? rawRoot : resolveFileWithURL(rawRoot, new URL(settingsFileDir));
369
+ return typeof globRoot === 'string'
370
+ ? globRoot
371
+ : globRoot.protocol === 'file:'
372
+ ? windowsDriveLetterToUpper(path.resolve(fileURLToPath(globRoot)))
373
+ : addTrailingSlash(globRoot).href;
349
374
  }
350
375
  function validationMessage(msg, url) {
351
376
  return msg + `\n File: "${toFilePathOrHref(url)}"`;
@@ -369,28 +394,37 @@ function validateRawConfigVersion(config) {
369
394
  : `Legacy config file version found: "${version}", upgrade to "${currentSettingsFileVersion}"`;
370
395
  logWarning(validationMessage(msg, config.url));
371
396
  }
372
- function createConfigLoaderInternal(cspellIO) {
373
- return new ConfigLoaderInternal(cspellIO ?? getDefaultCSpellIO());
397
+ function createConfigLoaderInternal(fs) {
398
+ return new ConfigLoaderInternal(fs ?? getVirtualFS().fs);
374
399
  }
375
- export function createConfigLoader(cspellIO) {
376
- return createConfigLoaderInternal(cspellIO);
400
+ export function createConfigLoader(fs) {
401
+ return createConfigLoaderInternal(fs);
377
402
  }
378
403
  export function getDefaultConfigLoaderInternal() {
379
404
  if (defaultConfigLoader)
380
405
  return defaultConfigLoader;
381
406
  return (defaultConfigLoader = createConfigLoaderInternal());
382
407
  }
383
- function createIO(cspellIO) {
384
- const readFile = (url) => cspellIO.readFile(url).then((file) => ({ url: file.url, content: file.getText() }));
385
- const writeFile = (file) => cspellIO.writeFile(file.url, file.content);
408
+ function createIO(fs) {
409
+ const readFile = (url) => fs.readFile(url).then((file) => ({ url: file.url, content: createTextFileResource(file).getText() }));
410
+ const writeFile = (file) => fs.writeFile(file);
386
411
  return {
387
412
  readFile,
388
413
  writeFile,
389
414
  };
390
415
  }
416
+ async function isDirectory(fs, path) {
417
+ try {
418
+ return (await fs.stat(path)).isDirectory();
419
+ }
420
+ catch (e) {
421
+ return false;
422
+ }
423
+ }
391
424
  export const __testing__ = {
392
425
  getDefaultConfigLoaderInternal,
393
426
  normalizeCacheSettings,
394
427
  validateRawConfigVersion,
428
+ resolveGlobRoot,
395
429
  };
396
430
  //# sourceMappingURL=configLoader.js.map
@@ -1,12 +1,16 @@
1
+ import { type VFileSystem } from '../../../fileSystem.js';
1
2
  export declare class ConfigSearch {
2
3
  readonly searchPlaces: readonly string[];
4
+ private fs;
3
5
  private searchCache;
4
6
  private searchDirCache;
5
- constructor(searchPlaces: readonly string[]);
6
- searchForConfig(searchFrom: URL | string): Promise<URL | undefined>;
7
+ constructor(searchPlaces: readonly string[], fs: VFileSystem);
8
+ searchForConfig(searchFromURL: URL): Promise<URL | undefined>;
7
9
  clearCache(): void;
8
- private findUpConfig;
9
10
  private findUpConfigPath;
10
11
  private hasConfig;
12
+ private createHasFileDirSearch;
13
+ private createHasFileStatCheck;
14
+ private hasConfigDir;
11
15
  }
12
16
  //# sourceMappingURL=configSearch.d.ts.map
@@ -1,35 +1,19 @@
1
- import { readdir, readFile, stat } from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { pathToFileURL } from 'url';
1
+ import { urlBasename } from 'cspell-io';
2
+ import { createTextFileResource } from '../../../fileSystem.js';
4
3
  import { createAutoResolveCache } from '../../../util/AutoResolve.js';
5
- import { findUp } from '../../../util/findUp.js';
6
- import { addTrailingSlash, fileURLOrPathToPath, toFileDirUrl, toURL } from '../../../util/url.js';
4
+ import { findUpFromUrl } from '../../../util/findUpFromUrl.js';
7
5
  export class ConfigSearch {
8
6
  searchPlaces;
7
+ fs;
9
8
  searchCache = new Map();
10
9
  searchDirCache = new Map();
11
- constructor(searchPlaces) {
10
+ constructor(searchPlaces, fs) {
12
11
  this.searchPlaces = searchPlaces;
12
+ this.fs = fs;
13
13
  this.searchPlaces = searchPlaces;
14
14
  }
15
- async searchForConfig(searchFrom) {
16
- if (typeof searchFrom === 'string') {
17
- if (!searchFrom.startsWith('file:')) {
18
- return undefined;
19
- }
20
- }
21
- else {
22
- if (searchFrom.protocol !== 'file:') {
23
- return undefined;
24
- }
25
- }
26
- const searchFromURL = toURL(searchFrom);
27
- let dirUrl = new URL('.', searchFrom);
28
- if (dirUrl.toString() !== searchFrom.toString()) {
29
- // check to see if searchFrom is a directory
30
- const isDir = await isDirectory(searchFrom);
31
- dirUrl = isDir ? addTrailingSlash(searchFromURL) : dirUrl;
32
- }
15
+ searchForConfig(searchFromURL) {
16
+ const dirUrl = new URL('.', searchFromURL);
33
17
  const searchHref = dirUrl.href;
34
18
  const searchCache = this.searchCache;
35
19
  const cached = searchCache.get(searchHref);
@@ -37,80 +21,100 @@ export class ConfigSearch {
37
21
  return cached;
38
22
  }
39
23
  const toPatchCache = [];
40
- const foundPath = await this.findUpConfigPath(fileURLOrPathToPath(dirUrl), storeVisit);
41
- const foundUrl = foundPath ? pathToFileURL(foundPath) : undefined;
42
- const pFoundPath = Promise.resolve(foundPath);
43
- const pFoundUrl = Promise.resolve(foundUrl);
24
+ const pFoundUrl = this.findUpConfigPath(dirUrl, storeVisit);
25
+ this.searchCache.set(searchHref, pFoundUrl);
44
26
  const searchDirCache = this.searchDirCache;
45
- for (const dir of toPatchCache) {
46
- searchDirCache.set(dir, searchDirCache.get(dir) || pFoundPath);
47
- const dirHref = toFileDirUrl(dir).href;
48
- searchCache.set(dirHref, searchCache.get(dirHref) || pFoundUrl);
49
- }
50
- const result = searchCache.get(searchHref) || pFoundUrl;
51
- searchCache.set(searchHref, result);
52
- return result;
27
+ const patch = async () => {
28
+ try {
29
+ await pFoundUrl;
30
+ for (const dir of toPatchCache) {
31
+ searchDirCache.set(dir.href, searchDirCache.get(dir.href) || pFoundUrl);
32
+ searchCache.set(dir.href, searchCache.get(dir.href) || pFoundUrl);
33
+ }
34
+ const result = searchCache.get(searchHref) || pFoundUrl;
35
+ searchCache.set(searchHref, result);
36
+ }
37
+ catch (e) {
38
+ // ignore
39
+ }
40
+ };
41
+ patch();
42
+ return pFoundUrl;
53
43
  function storeVisit(dir) {
54
44
  toPatchCache.push(dir);
55
45
  }
56
46
  }
57
47
  clearCache() {
58
48
  this.searchCache.clear();
59
- }
60
- async findUpConfig(searchFromPath, visit) {
61
- const cwd = fileURLOrPathToPath(searchFromPath);
62
- const found = await this.findUpConfigPath(cwd, visit);
63
- return found ? pathToFileURL(found) : undefined;
49
+ this.searchDirCache.clear();
64
50
  }
65
51
  findUpConfigPath(cwd, visit) {
66
52
  const searchDirCache = this.searchDirCache;
67
- const cached = searchDirCache.get(cwd);
53
+ const cached = searchDirCache.get(cwd.href);
68
54
  if (cached)
69
55
  return cached;
70
- return findUp((dir) => this.hasConfig(dir, visit), { cwd, type: 'file' });
56
+ return findUpFromUrl((dir) => this.hasConfig(dir, visit), cwd, { type: 'file' });
71
57
  }
72
- async hasConfig(dir, visited) {
73
- dir = path.normalize(dir + '/');
74
- const cached = this.searchDirCache.get(dir);
58
+ hasConfig(dir, visited) {
59
+ const cached = this.searchDirCache.get(dir.href);
75
60
  if (cached)
76
61
  return cached;
77
62
  visited(dir);
63
+ const result = this.hasConfigDir(dir);
64
+ this.searchDirCache.set(dir.href, result);
65
+ return result;
66
+ }
67
+ createHasFileDirSearch() {
78
68
  const dirInfoCache = createAutoResolveCache();
79
- async function hasFile(filename) {
80
- const dirInfo = await dirInfoCache.get(path.dirname(filename), async (dir) => new Map((await readdir(dir, { withFileTypes: true }).catch(() => [])).map((ent) => [ent.name, ent])));
81
- const name = path.basename(filename);
69
+ const hasFile = async (filename) => {
70
+ const dir = new URL('.', filename);
71
+ const dirUrlHref = dir.href;
72
+ const dirInfo = await dirInfoCache.get(dirUrlHref, async () => new Map((await this.fs.readDirectory(dir).catch(() => [])).map((ent) => [ent.name, ent])));
73
+ const name = urlBasename(filename);
82
74
  const found = dirInfo.get(name);
83
75
  return !!found?.isFile();
84
- }
76
+ };
77
+ return hasFile;
78
+ }
79
+ createHasFileStatCheck() {
80
+ const hasFile = async (filename) => {
81
+ const stat = await this.fs.stat(filename).catch(() => undefined);
82
+ return !!stat?.isFile();
83
+ };
84
+ return hasFile;
85
+ }
86
+ async hasConfigDir(dir) {
87
+ const hasFile = this.fs.getCapabilities(dir).readDirectory
88
+ ? this.createHasFileDirSearch()
89
+ : this.createHasFileStatCheck();
85
90
  for (const searchPlace of this.searchPlaces) {
86
- const file = path.join(dir, searchPlace);
91
+ const file = new URL(searchPlace, dir);
87
92
  const found = await hasFile(file);
88
93
  if (found) {
89
- if (path.basename(file) !== 'package.json')
94
+ if (urlBasename(file) !== 'package.json')
90
95
  return file;
91
- if (await checkPackageJson(file))
96
+ if (await checkPackageJson(this.fs, file))
92
97
  return file;
93
98
  }
94
99
  }
95
100
  return undefined;
96
101
  }
97
102
  }
98
- async function checkPackageJson(filename) {
103
+ async function checkPackageJson(fs, filename) {
99
104
  try {
100
- const content = await readFile(filename, 'utf8');
101
- const pkg = JSON.parse(content);
105
+ const file = createTextFileResource(await fs.readFile(filename));
106
+ const pkg = JSON.parse(file.getText());
102
107
  return typeof pkg.cspell === 'object';
103
108
  }
104
109
  catch (e) {
105
110
  return false;
106
111
  }
107
112
  }
108
- async function isDirectory(path) {
109
- try {
110
- return (await stat(path)).isDirectory();
111
- }
112
- catch (e) {
113
- return false;
114
- }
115
- }
113
+ // async function isDirectory(virtualFs: VirtualFS, path: URL): Promise<boolean> {
114
+ // try {
115
+ // return (await virtualFs.fs.stat(path)).isDirectory();
116
+ // } catch (e) {
117
+ // return false;
118
+ // }
119
+ // }
116
120
  //# sourceMappingURL=configSearch.js.map
@@ -17,13 +17,16 @@ export function configToRawSettings(cfgFile) {
17
17
  };
18
18
  const source = {
19
19
  name: cfgFile.settings.name || filename,
20
- filename,
20
+ filename: cfgFile.virtual ? undefined : filename,
21
21
  };
22
22
  const rawSettings = { ...cfgFile.settings };
23
23
  rawSettings.import = normalizeImport(rawSettings.import);
24
24
  normalizeRawConfig(rawSettings);
25
25
  rawSettings.source = source;
26
- rawSettings.__importRef = fileRef;
26
+ // in virtual config files are ignored for the purposes of import history.
27
+ if (!cfgFile.virtual) {
28
+ rawSettings.__importRef = fileRef;
29
+ }
27
30
  const id = rawSettings.id || urlToSimpleId(url);
28
31
  const name = rawSettings.name || id;
29
32
  rawSettings.id = id;
@@ -35,7 +35,7 @@ type NormalizeOverrides = Pick<CSpellUserSettings, 'globRoot' | 'overrides'>;
35
35
  type NormalizeOverridesResult = Pick<CSpellUserSettings, 'overrides'>;
36
36
  export declare function normalizeOverrides(settings: NormalizeOverrides, pathToSettingsFile: URL): NormalizeOverridesResult;
37
37
  type NormalizeReporters = Pick<CSpellUserSettings, 'reporters'>;
38
- export declare function normalizeReporters(settings: NormalizeReporters, pathToSettingsFile: URL): NormalizeReporters;
38
+ export declare function normalizeReporters(settings: NormalizeReporters, pathToSettingsFile: URL): Promise<NormalizeReporters>;
39
39
  export declare function normalizeLanguageSettings(languageSettings: LanguageSetting[] | undefined): LanguageSetting[] | undefined;
40
40
  type NormalizeGitignoreRoot = Pick<CSpellUserSettings, 'gitignoreRoot'>;
41
41
  export declare function normalizeGitignoreRoot(settings: NormalizeGitignoreRoot, pathToSettingsFile: URL): NormalizeGitignoreRoot;
@@ -38,20 +38,20 @@ export function normalizeOverrides(settings, pathToSettingsFile) {
38
38
  });
39
39
  return overrides ? { overrides } : {};
40
40
  }
41
- export function normalizeReporters(settings, pathToSettingsFile) {
41
+ export async function normalizeReporters(settings, pathToSettingsFile) {
42
42
  if (settings.reporters === undefined)
43
43
  return {};
44
- function resolve(s) {
44
+ async function resolve(s) {
45
45
  if (s === 'default')
46
46
  return s;
47
- const r = resolveFile(s, pathToSettingsFile);
47
+ const r = await resolveFile(s, pathToSettingsFile);
48
48
  if (!r.found) {
49
49
  // console.warn('Not found: %o', { filename: s, relativeTo: pathToSettingsFile.href });
50
50
  throw new Error(`Not found: "${s}"`);
51
51
  }
52
52
  return r.filename;
53
53
  }
54
- function resolveReporter(s) {
54
+ async function resolveReporter(s) {
55
55
  if (typeof s === 'string') {
56
56
  return resolve(s);
57
57
  }
@@ -59,10 +59,10 @@ export function normalizeReporters(settings, pathToSettingsFile) {
59
59
  throw new Error('Invalid Reporter');
60
60
  // Preserve the shape of Reporter Setting while resolving the reporter file.
61
61
  const [r, ...rest] = s;
62
- return [resolve(r), ...rest];
62
+ return [await resolve(r), ...rest];
63
63
  }
64
64
  return {
65
- reporters: settings.reporters.map(resolveReporter),
65
+ reporters: await Promise.all(settings.reporters.map(resolveReporter)),
66
66
  };
67
67
  }
68
68
  export function normalizeLanguageSettings(languageSettings) {