Skip to content

Commit

Permalink
fix: expose ConfigLoader API (#5032)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S committed Dec 4, 2023
1 parent b0ac8d9 commit e839990
Show file tree
Hide file tree
Showing 13 changed files with 146 additions and 30 deletions.
47 changes: 45 additions & 2 deletions packages/cspell-lib/api/api.d.ts
Expand Up @@ -3,9 +3,11 @@ import { Glob, CSpellSettingsWithSourceTrace, TextOffset, TextDocumentOffset, Ad
export * from '@cspell/cspell-types';
import { WeightMap } from 'cspell-trie-lib';
export { CompoundWordsMethod } from 'cspell-trie-lib';
import { CSpellConfigFile } from 'cspell-config-lib';
import { CSpellIO } from 'cspell-io';
export { asyncIterableToArray, readFileText as readFile, readFileTextSync as readFileSync, writeToFile, writeToFileIterable, writeToFileIterableP } from 'cspell-io';
import { SuggestOptions, SuggestionResult, CachingDictionary, SpellingDictionaryCollection } from 'cspell-dictionary';
export { SpellingDictionary, SpellingDictionaryCollection, SuggestOptions, SuggestionCollector, SuggestionResult, createSpellingDictionary, createCollection as createSpellingDictionaryCollection } from 'cspell-dictionary';
export { asyncIterableToArray, readFileText as readFile, readFileTextSync as readFileSync, writeToFile, writeToFileIterable, writeToFileIterableP } from 'cspell-io';

type ExclusionFunction = (fileUri: string) => boolean;
type FileExclusionFunction = (file: string) => boolean;
Expand Down Expand Up @@ -412,7 +414,47 @@ type CSpellSettingsI = CSpellSettingsInternal;

declare const sectionCSpell = "cSpell";
declare const defaultFileName = "cspell.json";
interface IConfigLoader {
readSettingsAsync(filename: string | URL, relativeTo?: string | URL, pnpSettings?: PnPSettingsOptional): Promise<CSpellSettingsI>;
/**
* Read a cspell configuration file.
* @param filenameOrURL - URL, relative path, absolute path, or package name.
* @param relativeTo - optional URL, defaults to `pathToFileURL('./')`
*/
readConfigFile(filenameOrURL: string | URL, relativeTo?: string | URL): Promise<CSpellConfigFile | Error>;
searchForConfigFileLocation(searchFrom: URL | string | undefined): Promise<URL | undefined>;
searchForConfigFile(searchFrom: URL | string | undefined): Promise<CSpellConfigFile | undefined>;
/**
* This is an alias for `searchForConfigFile` and `mergeConfigFileWithImports`.
* @param searchFrom the directory / file URL to start searching from.
* @param pnpSettings - related to Using Yarn PNP.
* @returns the resulting settings
*/
searchForConfig(searchFrom: URL | string | undefined, pnpSettings?: PnPSettingsOptional): Promise<CSpellSettingsI | undefined>;
getGlobalSettingsAsync(): Promise<CSpellSettingsI>;
/**
* The loader caches configuration files for performance. This method clears the cache.
*/
clearCachedSettingsFiles(): void;
/**
* Resolve imports and merge.
* @param cfgFile - configuration file.
* @param pnpSettings - optional settings related to Using Yarn PNP.
*/
mergeConfigFileWithImports(cfgFile: CSpellConfigFile, pnpSettings?: PnPSettingsOptional | undefined): Promise<CSpellSettingsI>;
/**
* Create an in memory CSpellConfigFile.
* @param filename - URL to the file. Used to resolve imports.
* @param settings - settings to use.
*/
createCSpellConfigFile(filename: URL | string, settings: CSpellUserSettings): CSpellConfigFile;
/**
* Unsubscribe from any events and dispose of any resources including caches.
*/
dispose(): void;
}
declare function loadPnP(pnpSettings: PnPSettingsOptional, searchFrom: URL): Promise<LoaderResult>;
declare function createConfigLoader(cspellIO?: CSpellIO): IConfigLoader;

declare const defaultConfigFilenames: readonly string[];

Expand Down Expand Up @@ -441,6 +483,7 @@ declare function getGlobalSettings(): CSpellSettingsI;
*/
declare function getGlobalSettingsAsync(): Promise<CSpellSettingsI>;
declare function getCachedFileSize(): number;
declare function getDefaultConfigLoader(): IConfigLoader;
declare function readRawSettings(filename: string | URL, relativeTo?: string | URL): Promise<CSpellSettingsWST>;

declare function extractImportErrors(settings: CSpellSettingsWST): ImportFileRefWithError[];
Expand Down Expand Up @@ -957,4 +1000,4 @@ interface PerfTimer {
type TimeNowFn = () => number;
declare function createPerfTimer(name: string, onEnd?: (elapsed: number, name: string) => void, timeNowFn?: TimeNowFn): PerfTimer;

export { type CheckTextInfo, type ConfigurationDependencies, type CreateTextDocumentParams, type DetermineFinalDocumentSettingsResult, type Document, DocumentValidator, type DocumentValidatorOptions, ENV_CSPELL_GLOB_ROOT, type ExcludeFilesGlobMap, type ExclusionFunction, exclusionHelper_d as ExclusionHelper, type FeatureFlag, FeatureFlags, ImportError, type ImportFileRefWithError$1 as ImportFileRefWithError, IncludeExcludeFlag, type IncludeExcludeOptions, index_link_d as Link, type Logger, type PerfTimer, type SpellCheckFileOptions, type SpellCheckFileResult, SpellingDictionaryLoadError, type SuggestedWord, SuggestionError, type SuggestionOptions, type SuggestionsForWordResult, text_d as Text, type TextDocument, type TextDocumentLine, type TextDocumentRef, type TextInfoItem, type TraceOptions, type TraceResult, UnknownFeatureFlagError, type ValidationIssue, calcOverrideSettings, checkFilenameMatchesGlob, checkText, checkTextDocument, clearCachedFiles, clearCaches, combineTextAndLanguageSettings, combineTextAndLanguageSettings as constructSettingsForText, createPerfTimer, createTextDocument, currentSettingsFileVersion, defaultConfigFilenames, defaultFileName, defaultFileName as defaultSettingsFilename, determineFinalDocumentSettings, extractDependencies, extractImportErrors, fileToDocument, fileToTextDocument, finalizeSettings, getCachedFileSize, getDefaultBundledSettingsAsync, getDefaultSettings, getDictionary, getGlobalSettings, getGlobalSettingsAsync, getLanguagesForBasename as getLanguageIdsForBaseFilename, getLanguagesForExt, getLogger, getSources, getSystemFeatureFlags, isBinaryFile, isSpellingDictionaryLoadError, loadConfig, loadPnP, mergeInDocSettings, mergeSettings, readRawSettings, readSettings, readSettingsFiles, refreshDictionaryCache, resolveFile, searchForConfig, sectionCSpell, setLogger, shouldCheckDocument, spellCheckDocument, spellCheckFile, suggestionsForWord, suggestionsForWords, traceWords, traceWordsAsync, updateTextDocument, validateText };
export { type CheckTextInfo, type ConfigurationDependencies, type CreateTextDocumentParams, type DetermineFinalDocumentSettingsResult, type Document, DocumentValidator, type DocumentValidatorOptions, ENV_CSPELL_GLOB_ROOT, type ExcludeFilesGlobMap, type ExclusionFunction, exclusionHelper_d as ExclusionHelper, type FeatureFlag, FeatureFlags, ImportError, type ImportFileRefWithError$1 as ImportFileRefWithError, IncludeExcludeFlag, type IncludeExcludeOptions, index_link_d as Link, type Logger, type PerfTimer, type SpellCheckFileOptions, type SpellCheckFileResult, SpellingDictionaryLoadError, type SuggestedWord, SuggestionError, type SuggestionOptions, type SuggestionsForWordResult, text_d as Text, type TextDocument, type TextDocumentLine, type TextDocumentRef, type TextInfoItem, type TraceOptions, type TraceResult, UnknownFeatureFlagError, type ValidationIssue, calcOverrideSettings, checkFilenameMatchesGlob, checkText, checkTextDocument, clearCachedFiles, clearCaches, combineTextAndLanguageSettings, combineTextAndLanguageSettings as constructSettingsForText, createConfigLoader, createPerfTimer, createTextDocument, currentSettingsFileVersion, defaultConfigFilenames, defaultFileName, defaultFileName as defaultSettingsFilename, determineFinalDocumentSettings, extractDependencies, extractImportErrors, fileToDocument, fileToTextDocument, finalizeSettings, getCachedFileSize, getDefaultBundledSettingsAsync, getDefaultConfigLoader, getDefaultSettings, getDictionary, getGlobalSettings, getGlobalSettingsAsync, getLanguagesForBasename as getLanguageIdsForBaseFilename, getLanguagesForExt, getLogger, getSources, getSystemFeatureFlags, isBinaryFile, isSpellingDictionaryLoadError, loadConfig, loadPnP, mergeInDocSettings, mergeSettings, readRawSettings, readSettings, readSettingsFiles, refreshDictionaryCache, resolveFile, searchForConfig, sectionCSpell, setLogger, shouldCheckDocument, spellCheckDocument, spellCheckFile, suggestionsForWord, suggestionsForWords, traceWords, traceWordsAsync, updateTextDocument, validateText };
2 changes: 2 additions & 0 deletions packages/cspell-lib/src/lib-cjs/pkg-info.cts
@@ -1 +1,3 @@
// import { join } from 'path';
// export const srcDirectory = join(__dirname, '/');
export const srcDirectory = __dirname;
Expand Up @@ -35,7 +35,7 @@ import { readSettingsFiles } from './readSettingsFiles.js';

const { validateRawConfigVersion } = __configLoader_testing__;

const rootCspellLib = pathPackageRoot;
const rootCspellLib = path.join(pathPackageRoot, '.');
const root = pathRepoRoot;
const samplesDir = pathPackageSamples;
const samplesSrc = path.join(samplesDir, 'src');
Expand Down
Expand Up @@ -5,7 +5,7 @@ import { createReaderWriter, CSpellConfigFileInMemory } from 'cspell-config-lib'
import type { CSpellIO } from 'cspell-io';
import { getDefaultCSpellIO } from 'cspell-io';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { fileURLToPath, pathToFileURL } from 'url';

import { onClearCache } from '../../../events/index.js';
import { createCSpellSettingsInternal as csi } from '../../../Models/CSpellSettingsInternalDef.js';
Expand Down Expand Up @@ -78,7 +78,66 @@ interface CacheMergeConfigFileWithImports {
result: Promise<CSpellSettingsI>;
}

export class ConfigLoader {
export interface IConfigLoader {
readSettingsAsync(
filename: string | URL,
relativeTo?: string | URL,
pnpSettings?: PnPSettingsOptional,
): Promise<CSpellSettingsI>;

/**
* Read a cspell configuration file.
* @param filenameOrURL - URL, relative path, absolute path, or package name.
* @param relativeTo - optional URL, defaults to `pathToFileURL('./')`
*/
readConfigFile(filenameOrURL: string | URL, relativeTo?: string | URL): Promise<CSpellConfigFile | Error>;

searchForConfigFileLocation(searchFrom: URL | string | undefined): Promise<URL | undefined>;

searchForConfigFile(searchFrom: URL | string | undefined): Promise<CSpellConfigFile | undefined>;

/**
* This is an alias for `searchForConfigFile` and `mergeConfigFileWithImports`.
* @param searchFrom the directory / file URL to start searching from.
* @param pnpSettings - related to Using Yarn PNP.
* @returns the resulting settings
*/
searchForConfig(
searchFrom: URL | string | undefined,
pnpSettings?: PnPSettingsOptional,
): Promise<CSpellSettingsI | undefined>;

getGlobalSettingsAsync(): Promise<CSpellSettingsI>;

/**
* The loader caches configuration files for performance. This method clears the cache.
*/
clearCachedSettingsFiles(): void;

/**
* Resolve imports and merge.
* @param cfgFile - configuration file.
* @param pnpSettings - optional settings related to Using Yarn PNP.
*/
mergeConfigFileWithImports(
cfgFile: CSpellConfigFile,
pnpSettings?: PnPSettingsOptional | undefined,
): Promise<CSpellSettingsI>;

/**
* Create an in memory CSpellConfigFile.
* @param filename - URL to the file. Used to resolve imports.
* @param settings - settings to use.
*/
createCSpellConfigFile(filename: URL | string, settings: CSpellUserSettings): CSpellConfigFile;

/**
* Unsubscribe from any events and dispose of any resources including caches.
*/
dispose(): void;
}

export class ConfigLoader implements IConfigLoader {
public onReady: Promise<void>;

/**
Expand All @@ -87,10 +146,7 @@ export class ConfigLoader {
*/
protected constructor(readonly cspellIO: CSpellIO) {
this.cspellConfigFileReaderWriter = createReaderWriter(undefined, undefined, createIO(cspellIO));
this.onReady = this.getGlobalSettingsAsync().then(
() => undefined,
(e) => logError(e),
);
this.onReady = this.prefetchGlobalSettingsAsync();
this.subscribeToEvents();
}

Expand All @@ -113,7 +169,8 @@ export class ConfigLoader {
relativeTo?: string | URL,
pnpSettings?: PnPSettingsOptional,
): Promise<CSpellSettingsI> {
const ref = resolveFilename(filename, relativeTo || process.cwd());
await this.onReady;
const ref = resolveFilename(filename, relativeTo || pathToFileURL('./'));
const entry = this.importSettings(ref, pnpSettings || defaultPnPSettings, []);
return entry.onReady;
}
Expand All @@ -122,7 +179,7 @@ export class ConfigLoader {
filenameOrURL: string | URL,
relativeTo?: string | URL,
): Promise<CSpellConfigFile | Error> {
const ref = resolveFilename(filenameOrURL.toString(), relativeTo || process.cwd());
const ref = resolveFilename(filenameOrURL.toString(), relativeTo || pathToFileURL('./'));
const url = this.cspellIO.toFileURL(ref.filename);
const href = url.href;
if (ref.error) return new ImportError(`Failed to read config file: "${ref.filename}"`, ref.error);
Expand Down Expand Up @@ -172,7 +229,7 @@ export class ConfigLoader {
}

public getGlobalSettings(): CSpellSettingsI {
assert(this.globalSettings);
assert(this.globalSettings, 'Global settings not loaded');
return this.globalSettings;
}

Expand All @@ -194,6 +251,15 @@ export class ConfigLoader {
this.cachedPendingConfigFile.clear();
this.cspellConfigFileReaderWriter.clearCachedFiles();
this.cachedMergedConfig = new WeakMap<CSpellConfigFile, CacheMergeConfigFileWithImports>();
this.prefetchGlobalSettingsAsync();
}

protected prefetchGlobalSettingsAsync(): Promise<void> {
this.onReady = this.getGlobalSettingsAsync().then(
() => undefined,
(e) => logError(e),
);
return this.onReady;
}

protected importSettings(
Expand Down Expand Up @@ -495,7 +561,7 @@ function createConfigLoaderInternal(cspellIO?: CSpellIO) {
return new ConfigLoaderInternal(cspellIO ?? getDefaultCSpellIO());
}

export function createConfigLoader(cspellIO?: CSpellIO): ConfigLoader {
export function createConfigLoader(cspellIO?: CSpellIO): IConfigLoader {
return createConfigLoaderInternal(cspellIO);
}

Expand Down
Expand Up @@ -2,7 +2,7 @@ import type { CSpellConfigFile } from 'cspell-config-lib';

import { toError } from '../../../util/errors.js';
import { toFileUrl } from '../../../util/url.js';
import type { ConfigLoader } from './configLoader.js';
import type { IConfigLoader } from './configLoader.js';
import { getDefaultConfigLoaderInternal } from './configLoader.js';
import { configErrorToRawSettings, configToRawSettings } from './configToRawSettings.js';
import type { PnPSettingsOptional } from './PnPSettings.js';
Expand Down Expand Up @@ -66,7 +66,7 @@ export function clearCachedSettingsFiles(): void {
return gcl().clearCachedSettingsFiles();
}

export function getDefaultConfigLoader(): ConfigLoader {
export function getDefaultConfigLoader(): IConfigLoader {
return getDefaultConfigLoaderInternal();
}
function cachedFiles() {
Expand Down
Expand Up @@ -10,6 +10,7 @@ export { defaultConfigFilenames } from './configLocations.js';
export {
clearCachedSettingsFiles,
getCachedFileSize,
getDefaultConfigLoader,
getGlobalSettings,
getGlobalSettingsAsync,
loadConfig,
Expand Down
Expand Up @@ -33,7 +33,6 @@ export async function readSettings(
pnpSettings?: PnPSettingsOptional,
): Promise<CSpellSettingsI> {
const loader = getDefaultConfigLoader();
await loader.onReady;
const relativeTo =
typeof relativeToOrPnP === 'string' || relativeToOrPnP instanceof URL ? relativeToOrPnP : undefined;
const pnp = pnpSettings
Expand Down
2 changes: 2 additions & 0 deletions packages/cspell-lib/src/lib/Settings/index.ts
Expand Up @@ -3,10 +3,12 @@ export { checkFilenameMatchesGlob } from './checkFilenameMatchesGlob.js';
export { currentSettingsFileVersion, ENV_CSPELL_GLOB_ROOT } from './constants.js';
export {
clearCachedSettingsFiles,
createConfigLoader,
defaultConfigFilenames,
defaultFileName,
extractImportErrors,
getCachedFileSize,
getDefaultConfigLoader,
getGlobalSettings,
getGlobalSettingsAsync,
loadConfig,
Expand Down
2 changes: 2 additions & 0 deletions packages/cspell-lib/src/lib/__snapshots__/index.test.ts.snap
Expand Up @@ -151,6 +151,7 @@ exports[`Validate the cspell API > Verify API exports 1`] = `
"clearCaches": [Function],
"combineTextAndLanguageSettings": [Function],
"constructSettingsForText": [Function],
"createConfigLoader": [Function],
"createPerfTimer": [Function],
"createSpellingDictionary": [Function],
"createSpellingDictionaryCollection": [Function],
Expand Down Expand Up @@ -218,6 +219,7 @@ exports[`Validate the cspell API > Verify API exports 1`] = `
"finalizeSettings": [Function],
"getCachedFileSize": [Function],
"getDefaultBundledSettingsAsync": [Function],
"getDefaultConfigLoader": [Function],
"getDefaultSettings": [Function],
"getDictionary": [Function],
"getGlobalSettings": [Function],
Expand Down
2 changes: 2 additions & 0 deletions packages/cspell-lib/src/lib/index.ts
Expand Up @@ -18,6 +18,7 @@ export {
calcOverrideSettings,
checkFilenameMatchesGlob,
type ConfigurationDependencies,
createConfigLoader,
currentSettingsFileVersion,
defaultConfigFilenames,
defaultFileName,
Expand All @@ -27,6 +28,7 @@ export {
finalizeSettings,
getCachedFileSize,
getDefaultBundledSettingsAsync,
getDefaultConfigLoader,
getDefaultSettings,
getGlobalSettings,
getGlobalSettingsAsync,
Expand Down
2 changes: 1 addition & 1 deletion packages/cspell-lib/src/lib/util/url.test.ts
Expand Up @@ -103,7 +103,7 @@ describe('url', () => {
describe('cwdURL', () => {
test('should return the URL for the current working directory', () => {
const result = cwdURL();
expect(result.href).toBe(pathToFileURL(process.cwd() + '/').href);
expect(result.href).toBe(pathToFileURL('./').href);
});
});

Expand Down
5 changes: 2 additions & 3 deletions packages/cspell-lib/src/lib/util/url.ts
Expand Up @@ -19,8 +19,7 @@ export function toFilePathOrHref(url: URL | string): string {
* @returns URL for the source directory
*/
export function getSourceDirectoryUrl(): URL {
const base = pathToFileURL(srcDirectory);
const srcDirectoryURL = new URL(base.pathname + '/', base);
const srcDirectoryURL = pathToFileURL(path.join(srcDirectory, '/'));
return srcDirectoryURL;
}

Expand All @@ -34,7 +33,7 @@ export function relativeTo(path: string, relativeTo?: URL | string): URL {
}

export function cwdURL(): URL {
return pathToFileURL(process.cwd() + '/');
return pathToFileURL('./');
}

export function resolveFileWithURL(file: string | URL, relativeToURL: URL): URL {
Expand Down
20 changes: 10 additions & 10 deletions packages/cspell-lib/src/test-util/test.locations.cts
@@ -1,14 +1,14 @@
import * as path from 'path';
import { pathToFileURL } from 'url';

export const pathPackageRoot = path.join(__dirname, '../..');
export const pathRepoRoot = path.join(pathPackageRoot, '../..');
export const pathPackageSamples = path.join(pathPackageRoot, 'samples');
export const pathPackageFixtures = path.join(pathPackageRoot, 'fixtures');
export const pathRepoTestFixtures = path.join(pathRepoRoot, 'test-fixtures');
export const pathPackageRoot = path.join(__dirname, '../../');
export const pathRepoRoot = path.join(pathPackageRoot, '../../');
export const pathPackageSamples = path.join(pathPackageRoot, 'samples/');
export const pathPackageFixtures = path.join(pathPackageRoot, 'fixtures/');
export const pathRepoTestFixtures = path.join(pathRepoRoot, 'test-fixtures/');

export const pathPackageRootURL = pathToFileURL(pathPackageRoot + '/');
export const pathRepoRootURL = pathToFileURL(pathRepoRoot + '/');
export const pathPackageSamplesURL = pathToFileURL(pathPackageSamples + '/');
export const pathPackageFixturesURL = pathToFileURL(pathPackageFixtures + '/');
export const pathRepoTestFixturesURL = pathToFileURL(pathRepoTestFixtures + '/');
export const pathPackageRootURL = pathToFileURL(pathPackageRoot);
export const pathRepoRootURL = pathToFileURL(pathRepoRoot);
export const pathPackageSamplesURL = pathToFileURL(pathPackageSamples);
export const pathPackageFixturesURL = pathToFileURL(pathPackageFixtures);
export const pathRepoTestFixturesURL = pathToFileURL(pathRepoTestFixtures);

0 comments on commit e839990

Please sign in to comment.