Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/cspell/src/__snapshots__/app.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10551,8 +10551,8 @@ exports[`Validate cli > app help [ 'init', '--help' ] 1`] = `
" --dictionary <dictionary> Enable a dictionary. Can be used multiple times.",
" --no-comments Do not add comments to the config file.",
" --remove-comments Remove all comments from the config file.",
" --schema Add or update the schema reference. This is the",
" default for a new file.",
" --schema Add or update the schema reference. Default for a",
" config with no settings.",
" --no-schema Do not add the schema reference to the config file.",
" --stdout Write the configuration to stdout instead of a",
" file.",
Expand Down
58 changes: 57 additions & 1 deletion packages/cspell/src/commandInit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { run } from './app.mjs';
const oldSchema = 'https://example.com/old.schema.json';
const yamlSchemaComment = `# yaml-language-server: $schema=${cspellConfigFileSchema}`;

describe('cspell init $schema', () => {
describe('cspell init', () => {
let tempDir: string;

beforeEach(async () => {
Expand Down Expand Up @@ -84,4 +84,60 @@ describe('cspell init $schema', () => {
const content = await init('cspell.config.yaml', ...args);
expect(content.includes(yamlSchemaComment)).toBe(expected);
});

async function initConfig(fileName: string, ...args: string[]): Promise<string> {
const filePath = path.join(tempDir, fileName);
await run(new Command(), [...process.argv.slice(0, 2), 'init', '--config', filePath, ...args]);
return fs.readFile(filePath, 'utf8');
}

const newFileSettings = { $schema: cspellConfigFileSchema, version: '0.2', language: 'en' };

test.each`
fileName
${'cspell.json'}
${'cspell.jsonc'}
`('new $fileName with --config', async ({ fileName }) => {
expect(JSON.parse(await initConfig(fileName, '--no-comments'))).toEqual(newFileSettings);
});

test('new JSON file with --output and the default format', async () => {
const filePath = path.join(tempDir, 'cspell.json');
await run(new Command(), [...process.argv.slice(0, 2), 'init', '--output', filePath]);
expect(JSON.parse(await fs.readFile(filePath, 'utf8'))).toEqual(newFileSettings);
});

test('existing JSON file only changes what was asked for', async () => {
await writeConfig('cspell.json', '{ "words": ["one"] }\n');
expect(JSON.parse(await initConfig('cspell.json'))).toEqual({ words: ['one'] });
expect(JSON.parse(await initConfig('cspell.json', '--locale', 'en-GB'))).toEqual({
words: ['one'],
language: 'en-GB',
});
});

test('existing YAML file is unchanged', async () => {
const content = 'words:\n - one\n';
await writeConfig('cspell.config.yaml', content);
expect(await initConfig('cspell.config.yaml')).toBe(content);
});

test('existing file with no settings is initialized', async () => {
await writeConfig('cspell.json', '{}\n');
expect(JSON.parse(await initConfig('cspell.json'))).toEqual(newFileSettings);
});

test('package.json without a cspell section gets one', async () => {
await writeConfig('package.json', '{ "name": "sample" }\n');
expect(JSON.parse(await initConfig('package.json'))).toEqual({
name: 'sample',
cspell: { version: '0.2', language: 'en' },
});
});

test('package.json with a cspell section is unchanged', async () => {
const packageJson = { name: 'sample', cspell: { words: ['one'] } };
await writeConfig('package.json', JSON.stringify(packageJson));
expect(JSON.parse(await initConfig('package.json'))).toEqual(packageJson);
});
});
2 changes: 1 addition & 1 deletion packages/cspell/src/commandInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function commandInit(prog: Command): Command {
.option('--no-comments', 'Do not add comments to the config file.')
.addOption(crOpt('--remove-comments', 'Remove all comments from the config file.').implies({ comments: false }))
.addOption(
crOpt('--schema', 'Add or update the schema reference. This is the default for a new file.').default(
crOpt('--schema', 'Add or update the schema reference. Default for a config with no settings.').default(
undefined,
),
)
Expand Down
2 changes: 1 addition & 1 deletion packages/cspell/src/config/adjustConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { isCfgArrayNode, MutableCSpellConfigFile } from 'cspell-config-lib';

import { toError } from '../util/errors.js';

export async function fileExists(url: URL): Promise<boolean> {
async function fileExists(url: URL): Promise<boolean> {
if (url.protocol !== 'file:') {
return false; // Only check file URLs
}
Expand Down
42 changes: 16 additions & 26 deletions packages/cspell/src/config/configInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import type { CSpellConfigFile, CSpellConfigFileReaderWriter } from 'cspell-conf
import { createReaderWriter, cspellConfigFileSchema } from 'cspell-config-lib';

import { console } from '../console.js';
import { addDictionariesToConfigFile, addImportsToConfigFile, fileExists } from './adjustConfig.js';
import { addDictionariesToConfigFile, addImportsToConfigFile } from './adjustConfig.js';
import { applyValuesToConfigFile } from './config.js';
import type { CommentConfig } from './constants.js';
import { defaultConfig } from './constants.js';
import type { InitOptions } from './options.js';

Expand All @@ -24,9 +25,8 @@ const defaultConfigYaml = `
export async function configInit(options: InitOptions): Promise<void> {
const rw = createReaderWriter();
const url = determineFileNameURL(options);
const schema = options.schema ?? !(await fileExists(url));
const configFile = await createConfigFile(rw, url, options);
await applyOptionsToConfigFile(configFile, { ...options, schema });
const configFile = await createConfigFile(rw, url);
await applyOptionsToConfigFile(configFile, options);
await fs.mkdir(new URL('./', configFile.url), { recursive: true });

if (options.stdout) {
Expand All @@ -38,6 +38,7 @@ export async function configInit(options: InitOptions): Promise<void> {

async function applyOptionsToConfigFile(configFile: CSpellConfigFile, options: InitOptions): Promise<CSpellConfigFile> {
const settings: CSpellSettings = {};
const hasSettings = Object.keys(configFile.settings).length > 0;

const addComments =
options.comments ||
Expand All @@ -47,15 +48,16 @@ async function applyOptionsToConfigFile(configFile: CSpellConfigFile, options: I
configFile.removeAllComments();
}

if (options.schema) {
if (options.schema ?? !hasSettings) {
configFile.setSchema(schemaRef);
}

if (options.locale) {
settings.language = options.locale;
}

applyValuesToConfigFile(configFile, settings, defaultConfig, addComments);
const defaults = hasSettings ? withoutDefaultValues(defaultConfig) : defaultConfig;
applyValuesToConfigFile(configFile, settings, defaults, addComments);

if (options.import) {
await addImportsToConfigFile(
Expand All @@ -76,6 +78,10 @@ async function applyOptionsToConfigFile(configFile: CSpellConfigFile, options: I
return configFile;
}

function withoutDefaultValues(config: CommentConfig): CommentConfig {
return Object.fromEntries(Object.entries(config).map(([key, entry]) => [key, { ...entry, value: undefined }]));
}

function determineFileNameURL(options: InitOptions): URL {
if (options.config) {
return toFileURL(options.config);
Expand Down Expand Up @@ -110,32 +116,16 @@ function determineDefaultFileName(options: InitOptions): string {
throw new Error(`Unsupported format: ${options.format}`);
}

function getDefaultContent(options: InitOptions): string {
switch (options.format) {
case undefined:
case 'yaml': {
return defaultConfigYaml;
}
case 'json':
case 'jsonc': {
return defaultConfigJson;
}
default: {
throw new Error(`Unsupported format: ${options.format}`);
}
}
function getDefaultContent(url: URL): string {
return /\.jsonc?$/.test(url.pathname) ? defaultConfigJson : defaultConfigYaml;
}

async function createConfigFile(
rw: CSpellConfigFileReaderWriter,
url: URL,
options: InitOptions,
): Promise<CSpellConfigFile> {
async function createConfigFile(rw: CSpellConfigFileReaderWriter, url: URL): Promise<CSpellConfigFile> {
if (url.pathname.endsWith('package.json')) {
return rw.readConfig(url);
}

const content = await fs.readFile(url, 'utf8').catch(() => getDefaultContent(options));
const content = await fs.readFile(url, 'utf8').catch(() => getDefaultContent(url));

return rw.parse({ url, content });
}
2 changes: 1 addition & 1 deletion packages/cspell/src/config/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface BaseConfigOptions {
* Whether to add the schema reference to the configuration file.
* - `true` - the schema reference will be added / updated.
* - `false` - no schema reference will be added.
* - `undefined` - the schema reference will be added only when creating a new file.
* - `undefined` - the schema reference will be added only if the configuration has no settings yet.
*/
schema?: boolean;
/**
Expand Down
Loading