From 5ceb4fcc65c44bd3e655d39fca175f2d91f60994 Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 1 Oct 2026 09:45:54 +0800 Subject: [PATCH 1/2] refactor(config): shallowly merge format config layers --- packages/rstack/src/fmt/cli.ts | 2 +- packages/rstack/src/fmt/config.ts | 14 +++++--- packages/rstack/tests/fmt/config.test.ts | 45 ++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/rstack/src/fmt/cli.ts b/packages/rstack/src/fmt/cli.ts index f4c96402..ec47edf2 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 5e4c543d..54d735b7 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 eb5a9b28..fa3bc5ba 100644 --- a/packages/rstack/tests/fmt/config.test.ts +++ b/packages/rstack/tests/fmt/config.test.ts @@ -3,10 +3,55 @@ 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.rootPath).toBe(rootPath); + expect(config.baseOptions).toEqual({ + singleQuote: true, + semi: false, + plugins: ['project-plugin'], + pluginOptions: { project: true }, + }); + expect(config.ignorePatterns).toEqual(['generated/**']); + expect( + createOptionsResolver(config)(path.join(rootPath, 'index.ts')), + ).toEqual({ + ...config.baseOptions, + semi: true, + }); +}); + test('reuses base options when no override matches', () => { const config = normalizeFmtConfig( { From e38c6336b5b2ff3fe2c9428d03cae1f575d99c51 Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 1 Oct 2026 17:04:51 +0800 Subject: [PATCH 2/2] test(config): assert the complete merged format config --- packages/rstack/tests/fmt/config.test.ts | 28 +++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/rstack/tests/fmt/config.test.ts b/packages/rstack/tests/fmt/config.test.ts index fa3bc5ba..978f1350 100644 --- a/packages/rstack/tests/fmt/config.test.ts +++ b/packages/rstack/tests/fmt/config.test.ts @@ -36,19 +36,21 @@ test('shallowly merges fmt layers before normalizing options and overrides', asy cwd: path.dirname(rootPath), }); - expect(config.rootPath).toBe(rootPath); - expect(config.baseOptions).toEqual({ - singleQuote: true, - semi: false, - plugins: ['project-plugin'], - pluginOptions: { project: true }, - }); - expect(config.ignorePatterns).toEqual(['generated/**']); - expect( - createOptionsResolver(config)(path.join(rootPath, 'index.ts')), - ).toEqual({ - ...config.baseOptions, - semi: true, + 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 }, + }, + ], }); });