From 89e0f740193997f86f8b96f90481152829118374 Mon Sep 17 00:00:00 2001 From: neverland Date: Wed, 12 Aug 2026 21:58:19 +0800 Subject: [PATCH] refactor(fmt): centralize per-file resolution --- packages/rstack/src/fmt/discovery.ts | 42 ++----------- packages/rstack/src/fmt/fileResolver.ts | 28 +++++++++ packages/rstack/src/fmt/lsp/server.ts | 20 ++----- packages/rstack/src/fmt/stdin.ts | 12 +--- packages/rstack/tests/fmt/discovery.test.ts | 54 ----------------- .../rstack/tests/fmt/fileResolver.test.ts | 59 +++++++++++++++++++ 6 files changed, 97 insertions(+), 118 deletions(-) create mode 100644 packages/rstack/src/fmt/fileResolver.ts create mode 100644 packages/rstack/tests/fmt/fileResolver.test.ts diff --git a/packages/rstack/src/fmt/discovery.ts b/packages/rstack/src/fmt/discovery.ts index 6eca1d83..cf1c3472 100644 --- a/packages/rstack/src/fmt/discovery.ts +++ b/packages/rstack/src/fmt/discovery.ts @@ -1,38 +1,9 @@ import path from 'node:path'; -import { createOptionsResolver, type FmtOptionsResolver } from './config.ts'; import { discoverFmtPaths } from './discoverPaths.ts'; +import { createFmtFileResolver } from './fileResolver.ts'; import { createIgnoreMatcher } from './ignore.ts'; -import type { FmtPluginResolver } from './plugins.ts'; import type { DiscoverFmtFilesOptions, FmtFileRequest } from './types.ts'; -const createFileRequest = ( - filePath: string, - resolveOptions: FmtOptionsResolver, -): FmtFileRequest => ({ - path: filePath, - options: resolveOptions(filePath), -}); - -/** Imports the plugin chunk on first use and shares the resolver across calls. */ -const createLazyPluginResolver = (rootPath: string): (() => Promise) => { - let resolver: Promise | undefined; - - return () => - (resolver ??= import( - /* rspackChunkName: 'fmtPlugins' */ - './plugins.ts' - ).then(({ createPluginResolver }) => createPluginResolver(rootPath))); -}; - -/** Resolves the plugin specifiers of a request whose options configure plugins. */ -const resolveFileRequestPlugins = async ( - file: FmtFileRequest, - getPluginResolver: () => Promise, -): Promise => - file.options.plugins?.length - ? { ...file, options: (await getPluginResolver())(file.options) } - : file; - const createDirMatcher = (dirPath: string): ((filePath: string) => boolean) => { const prefix = dirPath.endsWith(path.sep) ? dirPath : `${dirPath}${path.sep}`; return (filePath) => filePath === dirPath || filePath.startsWith(prefix); @@ -63,14 +34,9 @@ const discoverFmtFiles = async ({ return []; } - const resolveOptions = createOptionsResolver(config); - const getPluginResolver = createLazyPluginResolver(config.rootPath); + const resolveFile = createFmtFileResolver(config); - return Promise.all( - filePaths.map((filePath) => - resolveFileRequestPlugins(createFileRequest(filePath, resolveOptions), getPluginResolver), - ), - ); + return Promise.all(filePaths.map((filePath) => resolveFile(filePath))); }; -export { createFileRequest, createLazyPluginResolver, discoverFmtFiles, resolveFileRequestPlugins }; +export { discoverFmtFiles }; diff --git a/packages/rstack/src/fmt/fileResolver.ts b/packages/rstack/src/fmt/fileResolver.ts new file mode 100644 index 00000000..d6cc0abf --- /dev/null +++ b/packages/rstack/src/fmt/fileResolver.ts @@ -0,0 +1,28 @@ +import { createOptionsResolver } from './config.ts'; +import type { FmtPluginResolver } from './plugins.ts'; +import type { FmtFileRequest, ResolvedFmtConfig } from './types.ts'; + +type FmtFileResolver = (filePath: string) => Promise; + +/** Applies per-file overrides and resolves configured plugin specifiers. */ +const createFmtFileResolver = (config: ResolvedFmtConfig): FmtFileResolver => { + const resolveOptions = createOptionsResolver(config); + let pluginResolver: Promise | undefined; + + return async (filePath) => { + let options = resolveOptions(filePath); + + if (options.plugins?.length) { + pluginResolver ??= import( + /* rspackChunkName: 'fmtPlugins' */ + './plugins.ts' + ).then(({ createPluginResolver }) => createPluginResolver(config.rootPath)); + options = (await pluginResolver)(options); + } + + return { path: filePath, options }; + }; +}; + +export { createFmtFileResolver }; +export type { FmtFileResolver }; diff --git a/packages/rstack/src/fmt/lsp/server.ts b/packages/rstack/src/fmt/lsp/server.ts index d1e1348c..7dfddf68 100644 --- a/packages/rstack/src/fmt/lsp/server.ts +++ b/packages/rstack/src/fmt/lsp/server.ts @@ -9,15 +9,9 @@ import { type InitializeParams, type TextEdit, } from 'vscode-languageserver/node'; -import { createOptionsResolver, type FmtOptionsResolver } from '../config.ts'; -import { - createFileRequest, - createLazyPluginResolver, - resolveFileRequestPlugins, -} from '../discovery.ts'; +import { createFmtFileResolver, type FmtFileResolver } from '../fileResolver.ts'; import { formatFmtSource } from '../format.ts'; import { createIgnoreMatcher, type IgnorePredicate } from '../ignore.ts'; -import type { FmtPluginResolver } from '../plugins.ts'; import type { ResolvedFmtConfig } from '../types.ts'; import { computeMinimalTextEdit } from './minimalEdit.ts'; @@ -35,9 +29,7 @@ type FmtLspSessionOptions = RunFmtLspOptions & { root: string }; interface FmtLspSession { isIgnored: IgnorePredicate; - resolveOptions: FmtOptionsResolver; - /** Resolves plugin specifiers through the file system; cached per session. */ - getPluginResolver: () => Promise; + resolveFile: FmtFileResolver; } const toFilePath = (uri: string): string | undefined => { @@ -122,8 +114,7 @@ const createFmtLspSession = async ({ return { isIgnored, - resolveOptions: createOptionsResolver(config), - getPluginResolver: createLazyPluginResolver(config.rootPath), + resolveFile: createFmtFileResolver(config), }; }; @@ -137,10 +128,7 @@ const formatDocumentSource = async ( return undefined; } - const file = await resolveFileRequestPlugins( - createFileRequest(filePath, session.resolveOptions), - session.getPluginResolver, - ); + const file = await session.resolveFile(filePath); const result = await formatFmtSource(file, () => source); return result.status === 'formatted' ? result.formatted : undefined; diff --git a/packages/rstack/src/fmt/stdin.ts b/packages/rstack/src/fmt/stdin.ts index 90062709..6501ba73 100644 --- a/packages/rstack/src/fmt/stdin.ts +++ b/packages/rstack/src/fmt/stdin.ts @@ -1,10 +1,5 @@ import { resolve } from 'node:path'; -import { createOptionsResolver } from './config.ts'; -import { - createFileRequest, - createLazyPluginResolver, - resolveFileRequestPlugins, -} from './discovery.ts'; +import { createFmtFileResolver } from './fileResolver.ts'; import { formatFmtSource } from './format.ts'; import { createIgnoreMatcher } from './ignore.ts'; import type { ResolvedFmtConfig } from './types.ts'; @@ -83,10 +78,7 @@ const runFmtStdin = async ({ return; } - const file = await resolveFileRequestPlugins( - createFileRequest(absolutePath, createOptionsResolver(config)), - createLazyPluginResolver(config.rootPath), - ); + const file = await createFmtFileResolver(config)(absolutePath); const result = await formatFmtSource(file, () => source); if (result.status === 'unsupported') { diff --git a/packages/rstack/tests/fmt/discovery.test.ts b/packages/rstack/tests/fmt/discovery.test.ts index 171fbaae..4dfd1937 100644 --- a/packages/rstack/tests/fmt/discovery.test.ts +++ b/packages/rstack/tests/fmt/discovery.test.ts @@ -1,6 +1,5 @@ import { mkdirSync } from 'node:fs'; import path from 'node:path'; -import { pathToFileURL } from 'node:url'; import { expect, test } from 'rstack/test'; import { normalizeFmtConfig } from '../../src/fmt/config.ts'; import { discoverFmtFiles } from '../../src/fmt/discovery.ts'; @@ -128,56 +127,3 @@ test('defers parser inference to workers and preserves an explicit parser', asyn }); }); }); - -test('resolves plugins after applying matching overrides', async () => { - await withTempProject(async (rootPath) => { - const pluginEntry = writeProjectFile( - rootPath, - 'node_modules/prettier-plugin-fixture/index.mjs', - `export default { - languages: [ - { name: 'Fixture JSON', parsers: ['json'], extensions: ['.fixture'] }, - { name: 'Fixture TypeScript', parsers: ['babel'], extensions: ['.ts'] }, - ], -}; -`, - ); - writeProjectFile( - rootPath, - 'node_modules/prettier-plugin-fixture/package.json', - JSON.stringify({ name: 'prettier-plugin-fixture', exports: './index.mjs' }), - ); - writeProjectFile(rootPath, 'example.fixture'); - writeProjectFile(rootPath, 'example.ts'); - const config = { - overrides: [ - { - files: '*.fixture', - options: { plugins: ['prettier-plugin-fixture'] }, - }, - { - files: '*.ts', - options: { plugins: ['prettier-plugin-fixture'] }, - }, - { - files: '*.md', - options: { plugins: ['missing-plugin'] }, - }, - ], - }; - - const files = await discover(rootPath, ['example.fixture', 'example.ts'], config); - - expect(files).toHaveLength(2); - expect(files[0]).toMatchObject({ - options: { - plugins: [pathToFileURL(pluginEntry).href], - }, - }); - expect(files[1]).toMatchObject({ - options: { - plugins: [pathToFileURL(pluginEntry).href], - }, - }); - }); -}); diff --git a/packages/rstack/tests/fmt/fileResolver.test.ts b/packages/rstack/tests/fmt/fileResolver.test.ts new file mode 100644 index 00000000..98e1e486 --- /dev/null +++ b/packages/rstack/tests/fmt/fileResolver.test.ts @@ -0,0 +1,59 @@ +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { expect, test } from 'rstack/test'; +import { normalizeFmtConfig } from '../../src/fmt/config.ts'; +import { createFmtFileResolver } from '../../src/fmt/fileResolver.ts'; +import { withTempProject, writeProjectFile } from './helpers.ts'; + +test('applies matching overrides before resolving plugins', async () => { + await withTempProject(async (rootPath) => { + const pluginEntry = writeProjectFile( + rootPath, + 'node_modules/prettier-plugin-fixture/index.mjs', + `export default { + languages: [ + { name: 'Fixture JSON', parsers: ['json'], extensions: ['.fixture'] }, + { name: 'Fixture TypeScript', parsers: ['babel'], extensions: ['.ts'] }, + ], +}; +`, + ); + writeProjectFile( + rootPath, + 'node_modules/prettier-plugin-fixture/package.json', + JSON.stringify({ name: 'prettier-plugin-fixture', exports: './index.mjs' }), + ); + const config = normalizeFmtConfig( + { + overrides: [ + { + files: '*.{fixture,ts}', + options: { plugins: ['prettier-plugin-fixture'] }, + }, + { + files: '*.md', + options: { plugins: ['missing-plugin'] }, + }, + ], + }, + rootPath, + ); + const resolveFile = createFmtFileResolver(config); + + const files = await Promise.all([ + resolveFile(path.join(rootPath, 'example.fixture')), + resolveFile(path.join(rootPath, 'example.ts')), + ]); + + expect(files).toEqual([ + { + path: path.join(rootPath, 'example.fixture'), + options: { plugins: [pathToFileURL(pluginEntry).href] }, + }, + { + path: path.join(rootPath, 'example.ts'), + options: { plugins: [pathToFileURL(pluginEntry).href] }, + }, + ]); + }); +});