diff --git a/packages/rstack/src/fmt/cli.ts b/packages/rstack/src/fmt/cli.ts index f4c9640..ec47edf 100644 --- a/packages/rstack/src/fmt/cli.ts +++ b/packages/rstack/src/fmt/cli.ts @@ -253,7 +253,7 @@ const loadFmtConfig = async ( loadedConfig ?? (await loadRstackConfig({ cwd })); return resolveFmtConfig({ - definition: configs.fmt, + layers: [configs], configFilePath: filePath, cwd, }); diff --git a/packages/rstack/src/fmt/config.ts b/packages/rstack/src/fmt/config.ts index 5e4c543..54d735b 100644 --- a/packages/rstack/src/fmt/config.ts +++ b/packages/rstack/src/fmt/config.ts @@ -1,15 +1,16 @@ import { dirname } from 'node:path'; import micromatch from 'micromatch'; +import type { Configs } from '../config.ts'; +import { resolveConfigLayers } from '../configLayers.ts'; import { createRelativePathResolver } from './pathHelpers.ts'; import type { FmtConfig, - FmtConfigDefinition, ResolvedFmtConfig, ResolvedFmtOptions, } from './types.ts'; type ResolveFmtConfigOptions = { - definition: FmtConfigDefinition | undefined; + layers: readonly Configs[]; configFilePath: string | null; cwd: string; }; @@ -150,14 +151,17 @@ const createOptionsResolver = ( }; }; -/** Resolves a formatter config definition and its project root. */ +/** Resolves formatter config layers and their project root. */ const resolveFmtConfig = async ({ - definition, + layers, configFilePath, cwd, }: ResolveFmtConfigOptions): Promise => { + const configs = await resolveConfigLayers(layers, 'fmt'); const config = - typeof definition === 'function' ? await definition() : definition; + configs.length > 1 + ? (Object.assign({}, ...configs) as FmtConfig) + : configs[0]; const rootPath = configFilePath ? dirname(configFilePath) : cwd; return normalizeFmtConfig(config, rootPath); diff --git a/packages/rstack/tests/fmt/config.test.ts b/packages/rstack/tests/fmt/config.test.ts index eb5a9b2..978f135 100644 --- a/packages/rstack/tests/fmt/config.test.ts +++ b/packages/rstack/tests/fmt/config.test.ts @@ -3,10 +3,57 @@ import { expect, test } from 'rstack/test'; import { createOptionsResolver, normalizeFmtConfig, + resolveFmtConfig, } from '../../src/fmt/config.ts'; const rootPath = path.join(import.meta.dirname, 'project'); +test('shallowly merges fmt layers before normalizing options and overrides', async () => { + const config = await resolveFmtConfig({ + layers: [ + { + fmt: { + singleQuote: true, + semi: true, + plugins: ['shared-plugin'], + ignorePatterns: ['dist/**'], + overrides: [{ files: '*.ts', options: { tabWidth: 8 } }], + pluginOptions: { shared: true, project: false }, + }, + }, + { + fmt: () => + Promise.resolve({ + semi: false, + plugins: ['project-plugin'], + ignorePatterns: ['generated/**'], + overrides: [{ files: '*.ts', options: { semi: true } }], + pluginOptions: { project: true }, + }), + }, + ], + configFilePath: path.join(rootPath, 'rstack.config.ts'), + cwd: path.dirname(rootPath), + }); + + expect(config).toEqual({ + rootPath, + baseOptions: { + singleQuote: true, + semi: false, + plugins: ['project-plugin'], + pluginOptions: { project: true }, + }, + ignorePatterns: ['generated/**'], + overrides: [ + { + matches: expect.any(Function) as unknown, + options: { semi: true }, + }, + ], + }); +}); + test('reuses base options when no override matches', () => { const config = normalizeFmtConfig( {