Skip to content

Commit

Permalink
fix: Support passing the config in memory to cspell (#6358)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S authored Oct 13, 2024
1 parent e43c9a2 commit e7c0b5a
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 23 deletions.
3 changes: 3 additions & 0 deletions cspell.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
"autoAttachChildProcesses": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"program": "${workspaceRoot:cspell-monorepo}/node_modules/vitest/vitest.mjs",
"env": {
"NODE_OPTIONS": "--enable-source-maps"
},
"args": [
"run",
"--testTimeout=600000",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { CSpellSettings } from '@cspell/cspell-types';
import { describe, expect, test, vi } from 'vitest';

import { CSpellConfigFile } from './CSpellConfigFile.js';
import { CSpellConfigFileJavaScript } from './CSpellConfigFile/index.js';
import { CSpellConfigFileInMemory, CSpellConfigFileJavaScript } from './CSpellConfigFile/index.js';
import { CSpellConfigFileReaderWriterImpl } from './CSpellConfigFileReaderWriter.js';
import type { IO } from './IO.js';
import { defaultLoaders } from './loaders/index.js';
Expand All @@ -16,8 +16,8 @@ const oc = <T>(obj: T) => expect.objectContaining(obj);

describe('CSpellConfigFileReaderWriter', () => {
test.each`
uri | content | expected
${'file:///package.json'} | ${json({ name: 'name' })} | ${oc({ url: new URL('file:///package.json'), settings: {} })}
uri | content | expected
${'file:///package.json'} | ${json({ name: 'name', cspell: { words: ['one'] } })} | ${oc({ url: new URL('file:///package.json'), settings: { words: ['one'] } })}
`('readConfig', async ({ uri, content, expected }) => {
const io: IO = {
readFile: vi.fn((url) => Promise.resolve({ url, content })),
Expand Down Expand Up @@ -136,6 +136,34 @@ describe('CSpellConfigFileReaderWriter', () => {

await expect(rw.readConfig(uri)).resolves.toEqual(expected);
});

test.each`
url | content
${'file:///cspell.json'} | ${json({ name: 'name', words: ['one'] })}
`('toCSpellConfigFile $url', async ({ url, content }) => {
const io: IO = {
readFile: vi.fn((url) => Promise.resolve({ url, content })),
writeFile: vi.fn(),
};
url = new URL(url);
const settings = JSON.parse(content) as CSpellSettings;
const rw = new CSpellConfigFileReaderWriterImpl(io, defaultDeserializers, defaultLoaders);
const config = await rw.readConfig(url);
expect(config).toBeInstanceOf(CSpellConfigFile);
expect(config.url).toEqual(url);
expect(config.settings).toEqual(settings);

expect(rw.toCSpellConfigFile(config)).toBe(config);

const config2 = rw.toCSpellConfigFile({ url, settings });
expect(config2).toBeInstanceOf(CSpellConfigFile);
expect(config2).not.toEqual(config);

// At the moment, we do not try to associate the settings with the right loader.
expect(config2).toBeInstanceOf(CSpellConfigFileInMemory);

expect(config2.settings).toEqual(settings);
});
});

class Cfg extends CSpellConfigFile {
Expand Down
11 changes: 10 additions & 1 deletion packages/cspell-config-lib/src/CSpellConfigFileReaderWriter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { extname } from 'node:path/posix';

import type { CSpellConfigFile, ICSpellConfigFile } from './CSpellConfigFile.js';
import type { ICSpellConfigFile } from './CSpellConfigFile.js';
import { CSpellConfigFile } from './CSpellConfigFile.js';
import { CSpellConfigFileInMemory } from './CSpellConfigFile/index.js';
import type { FileLoaderMiddleware } from './FileLoader.js';
import type { IO } from './IO.js';
import { getDeserializer, getLoader, getSerializer } from './middlewareHelper.js';
Expand All @@ -17,6 +19,7 @@ export interface CSpellConfigFileReaderWriter {
clearCachedFiles(): void;
setUntrustedExtensions(ext: readonly string[]): this;
setTrustedUrls(urls: readonly (URL | string)[]): this;
toCSpellConfigFile(configFile: ICSpellConfigFile): CSpellConfigFile;
/**
* Untrusted extensions are extensions that are not trusted to be loaded from a file system.
* Extension are case insensitive and should include the leading dot.
Expand Down Expand Up @@ -68,6 +71,12 @@ export class CSpellConfigFileReaderWriterImpl implements CSpellConfigFileReaderW
return loader({ url: toURL(uri), context: { deserialize: this.getDeserializer(), io: this.io } });
}

toCSpellConfigFile(configFile: ICSpellConfigFile): CSpellConfigFile {
return configFile instanceof CSpellConfigFile
? configFile
: new CSpellConfigFileInMemory(configFile.url, configFile.settings);
}

getDeserializer(): DeserializerNext {
return getDeserializer(this.middleware);
}
Expand Down
1 change: 1 addition & 0 deletions packages/cspell-config-lib/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { createReaderWriter } from './createReaderWriter.js';
export type { ICSpellConfigFile } from './CSpellConfigFile.js';
export { CSpellConfigFile } from './CSpellConfigFile.js';
export {
CSpellConfigFileInMemory,
Expand Down
12 changes: 10 additions & 2 deletions packages/cspell-lib/api/api.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

import type { CSpellUserSettings, ImportFileRef, Source } from '@cspell/cspell-types';
import type { CSpellConfigFile, CSpellConfigFileReaderWriter, IO, TextFile } from 'cspell-config-lib';
import { createReaderWriter, CSpellConfigFileInMemory } from 'cspell-config-lib';
import { CSpellConfigFile, CSpellConfigFileReaderWriter, ICSpellConfigFile, IO, TextFile } from 'cspell-config-lib';
import { createReaderWriter } from 'cspell-config-lib';
import { isUrlLike, toFileURL } from 'cspell-io';
import { URI, Utils as UriUtils } from 'vscode-uri';

Expand Down Expand Up @@ -154,6 +154,13 @@ export interface IConfigLoader {
*/
createCSpellConfigFile(filename: URL | string, settings: CSpellUserSettings): CSpellConfigFile;

/**
* Convert a ICSpellConfigFile into a CSpellConfigFile.
* If cfg is a CSpellConfigFile, it is returned as is.
* @param cfg - configuration file to convert.
*/
toCSpellConfigFile(cfg: ICSpellConfigFile): CSpellConfigFile;

/**
* Unsubscribe from any events and dispose of any resources including caches.
*/
Expand Down Expand Up @@ -204,7 +211,7 @@ export class ConfigLoader implements IConfigLoader {
protected cachedConfigFiles = new Map<string, CSpellConfigFile>();
protected cachedPendingConfigFile = new AutoResolveCache<string, Promise<CSpellConfigFile | Error>>();
protected cachedMergedConfig = new WeakMap<CSpellConfigFile, CacheMergeConfigFileWithImports>();
protected cachedCSpellConfigFileInMemory = new WeakMap<CSpellUserSettings, Map<string, CSpellConfigFileInMemory>>();
protected cachedCSpellConfigFileInMemory = new WeakMap<CSpellUserSettings, Map<string, CSpellConfigFile>>();
protected globalSettings: CSpellSettingsI | undefined;
protected cspellConfigFileReaderWriter: CSpellConfigFileReaderWriter;
protected configSearch: ConfigSearch;
Expand Down Expand Up @@ -435,10 +442,11 @@ export class ConfigLoader implements IConfigLoader {
}

public mergeConfigFileWithImports(
cfgFile: CSpellConfigFile,
cfg: CSpellConfigFile | ICSpellConfigFile,
pnpSettings: PnPSettingsOptional | undefined,
referencedBy?: string[] | undefined,
): Promise<CSpellSettingsI> {
const cfgFile = this.toCSpellConfigFile(cfg);
const cached = this.cachedMergedConfig.get(cfgFile);
if (cached && cached.pnpSettings === pnpSettings && cached.referencedBy === referencedBy) {
return cached.result;
Expand Down Expand Up @@ -540,8 +548,19 @@ export class ConfigLoader implements IConfigLoader {
}

createCSpellConfigFile(filename: URL | string, settings: CSpellUserSettings): CSpellConfigFile {
const map = autoResolveWeak(this.cachedCSpellConfigFileInMemory, settings, () => new Map());
return autoResolve(map, filename, () => new CSpellConfigFileInMemory(toFileURL(filename), settings));
const map = autoResolveWeak(
this.cachedCSpellConfigFileInMemory,
settings,
() => new Map<string, CSpellConfigFile>(),
);
return autoResolve(map, filename, () =>
this.cspellConfigFileReaderWriter.toCSpellConfigFile({ url: toFileURL(filename), settings }),
);
}

toCSpellConfigFile(cfg: ICSpellConfigFile): CSpellConfigFile {
if (cfg instanceof CSpellConfigFile) return cfg;
return this.createCSpellConfigFile(cfg.url, cfg.settings);
}

dispose() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { describe, expect, test } from 'vitest';

import { getDefaultConfigLoader } from './defaultConfigLoader.js';
import { getDefaultConfigLoader, resolveConfigFileImports } from './defaultConfigLoader.js';

describe('defaultConfigLoader', () => {
test('getDefaultConfigLoader', () => {
expect(getDefaultConfigLoader()).toEqual(expect.any(Object));
});

test('resolveConfigFileImports', async () => {
const configFile = {
url: new URL('cspell.json', import.meta.url),
settings: {
words: ['one'],
},
};
expect(await resolveConfigFileImports(configFile)).toEqual(expect.objectContaining({ words: ['one'] }));
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CSpellSettings } from '@cspell/cspell-types';
import type { CSpellConfigFile } from 'cspell-config-lib';
import type { CSpellConfigFile, ICSpellConfigFile } from 'cspell-config-lib';

import { toError } from '../../../util/errors.js';
import { toFileUrl } from '../../../util/url.js';
Expand Down Expand Up @@ -56,6 +56,12 @@ export async function readConfigFile(filename: string | URL, relativeTo?: string
return result;
}

export async function resolveConfigFileImports(
configFile: CSpellConfigFile | ICSpellConfigFile,
): Promise<CSpellSettingsI> {
return gcl().mergeConfigFileWithImports(configFile, configFile.settings);
}

/**
* Might throw if the settings have not yet been loaded.
* @deprecated use {@link getGlobalSettingsAsync} instead.
Expand Down Expand Up @@ -83,9 +89,11 @@ export function clearCachedSettingsFiles(): void {
export function getDefaultConfigLoader(): IConfigLoader {
return getDefaultConfigLoaderInternal();
}

function cachedFiles() {
return gcl()._cachedFiles;
}

export async function readRawSettings(filename: string | URL, relativeTo?: string | URL): Promise<CSpellSettingsWST> {
try {
const cfg = await readConfigFile(filename, relativeTo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export {
getGlobalSettings,
getGlobalSettingsAsync,
loadConfig,
readConfigFile,
readRawSettings,
resolveConfigFileImports,
resolveSettingsImports,
searchForConfig,
} from './defaultConfigLoader.js';
Expand Down
2 changes: 2 additions & 0 deletions packages/cspell-lib/src/lib/Settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ export {
getGlobalSettingsAsync,
loadConfig,
loadPnP,
readConfigFile,
readRawSettings,
readSettings,
readSettingsFiles,
resolveConfigFileImports,
resolveSettingsImports,
searchForConfig,
sectionCSpell,
Expand Down
2 changes: 2 additions & 0 deletions packages/cspell-lib/src/lib/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,14 @@ exports[`Validate the cspell API > Verify API exports 1`] = `
"loadPnP": [Function],
"mergeInDocSettings": [Function],
"mergeSettings": [Function],
"readConfigFile": [Function],
"readFile": [Function],
"readFileSync": [Function],
"readRawSettings": [Function],
"readSettings": [Function],
"readSettingsFiles": [Function],
"refreshDictionaryCache": [Function],
"resolveConfigFileImports": [Function],
"resolveFile": [Function],
"searchForConfig": [Function],
"sectionCSpell": "cSpell",
Expand Down
2 changes: 2 additions & 0 deletions packages/cspell-lib/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ export {
loadPnP,
mergeInDocSettings,
mergeSettings,
readConfigFile,
readRawSettings,
readSettings,
readSettingsFiles,
resolveConfigFileImports,
searchForConfig,
sectionCSpell,
} from './Settings/index.js';
Expand Down
4 changes: 2 additions & 2 deletions packages/cspell/src/app/lint/LintRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as path from 'node:path';

import type { Issue } from '@cspell/cspell-types';

import type { LinterCliOptions, LinterOptions } from '../options.js';
import type { CSpellConfigFile, LinterCliOptions, LinterOptions } from '../options.js';
import type { GlobSrcInfo } from '../util/glob.js';
import { calcExcludeGlobInfo } from '../util/glob.js';
import type { FinalizedReporter } from '../util/reporters.js';
Expand All @@ -18,7 +18,7 @@ export class LintRequest {
readonly uniqueFilter: (issue: Issue) => boolean;
readonly locale: string;

readonly configFile: string | undefined;
readonly configFile: string | CSpellConfigFile | undefined;
readonly excludes: GlobSrcInfo[];
readonly root: string;
readonly showContext: number;
Expand Down
Loading

0 comments on commit e7c0b5a

Please sign in to comment.