From 5b4dd326e65b6f9e2b6c7d004110706ca159a1c1 Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 30 Jul 2026 17:36:54 +0800 Subject: [PATCH] feat(fmt): resolve formatter overrides --- packages/rstack/package.json | 2 + packages/rstack/src/fmt/config.ts | 47 ++++++++++++++++++++- pnpm-lock.yaml | 69 +++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 2 + 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/packages/rstack/package.json b/packages/rstack/package.json index 7a0b31c..d71e512 100644 --- a/packages/rstack/package.json +++ b/packages/rstack/package.json @@ -69,8 +69,10 @@ "@rstackjs/test-utils": "catalog:", "@rstest/adapter-rsbuild": "catalog:", "@rstest/adapter-rslib": "catalog:", + "@types/micromatch": "catalog:", "@types/node": "catalog:", "lint-staged": "catalog:", + "micromatch": "catalog:", "rslog": "catalog:", "typescript": "catalog:" }, diff --git a/packages/rstack/src/fmt/config.ts b/packages/rstack/src/fmt/config.ts index 93a2ca3..d27ea93 100644 --- a/packages/rstack/src/fmt/config.ts +++ b/packages/rstack/src/fmt/config.ts @@ -1,4 +1,6 @@ -import { dirname } from 'node:path'; +import { dirname, relative } from 'node:path'; +import micromatch from 'micromatch'; +import type { Options as PrettierOptions } from 'prettier'; import type { FmtConfig, FmtConfigDefinition, ResolvedFmtConfig } from './types.ts'; type ResolveFmtConfigOptions = { @@ -19,6 +21,47 @@ const normalizeFmtConfig = (config: FmtConfig | undefined, rootPath: string): Re }; }; +const pathMatchesGlobs = ( + filePath: string, + patterns: string | string[], + excludedPatterns?: string | string[], +): boolean => { + const patternList = Array.isArray(patterns) ? patterns : [patterns]; + const withSlashes = patternList.filter((pattern) => pattern.includes('/')); + const withoutSlashes = patternList.filter((pattern) => !pattern.includes('/')); + + return ( + micromatch.isMatch(filePath, withoutSlashes, { + ignore: excludedPatterns, + basename: true, + dot: true, + }) || + micromatch.isMatch(filePath, withSlashes, { + ignore: excludedPatterns, + basename: false, + dot: true, + }) + ); +}; + +/** Applies matching overrides to the shared formatter options. */ +const resolveFmtOptions = (filePath: string, config: ResolvedFmtConfig): PrettierOptions => { + if (config.overrides.length === 0) { + return config.baseOptions; + } + + const options = { ...config.baseOptions }; + const relativeFilePath = relative(config.rootPath, filePath); + + for (const override of config.overrides) { + if (pathMatchesGlobs(relativeFilePath, override.files, override.excludeFiles)) { + Object.assign(options, override.options); + } + } + + return options; +}; + /** Resolves a formatter config definition and its project root. */ const resolveFmtConfig = async ({ definition, @@ -31,4 +74,4 @@ const resolveFmtConfig = async ({ return normalizeFmtConfig(config, rootPath); }; -export { normalizeFmtConfig, resolveFmtConfig }; +export { normalizeFmtConfig, resolveFmtConfig, resolveFmtOptions }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f35e17..086c3c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,6 +58,9 @@ catalogs: '@testing-library/react': specifier: ^16.3.2 version: 16.3.2 + '@types/micromatch': + specifier: ^4.0.10 + version: 4.0.10 '@types/node': specifier: ^24.13.3 version: 24.13.3 @@ -79,6 +82,9 @@ catalogs: lint-staged: specifier: ^17.2.0 version: 17.2.0 + micromatch: + specifier: 4.0.8 + version: 4.0.8 oxfmt: specifier: ^0.60.0 version: 0.60.0 @@ -328,12 +334,18 @@ importers: '@rstest/adapter-rslib': specifier: 'catalog:' version: 0.11.4(@rslib/core@1.0.0-beta.1)(@rstest/core@0.11.4)(typescript@7.0.2) + '@types/micromatch': + specifier: 'catalog:' + version: 4.0.10 '@types/node': specifier: 'catalog:' version: 24.13.3 lint-staged: specifier: 'catalog:' version: 17.2.0 + micromatch: + specifier: 'catalog:' + version: 4.0.8 rslog: specifier: 'catalog:' version: 2.3.0 @@ -1038,6 +1050,9 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/braces@3.0.5': + resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1062,6 +1077,9 @@ packages: '@types/mdx@2.0.14': resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + '@types/micromatch@4.0.10': + resolution: {integrity: sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -1258,6 +1276,10 @@ packages: body-scroll-lock@4.0.0-beta.0: resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + buffer-image-size@0.6.4: resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==} engines: {node: '>=4.0'} @@ -1392,6 +1414,10 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + flexsearch@0.8.212: resolution: {integrity: sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw==} @@ -1487,6 +1513,10 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -1725,6 +1755,10 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1771,6 +1805,10 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} @@ -2136,6 +2174,10 @@ packages: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} @@ -2809,6 +2851,8 @@ snapshots: '@types/aria-query@5.0.4': {} + '@types/braces@3.0.5': {} + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -2836,6 +2880,10 @@ snapshots: '@types/mdx@2.0.14': {} + '@types/micromatch@4.0.10': + dependencies: + '@types/braces': 3.0.5 + '@types/ms@2.1.0': {} '@types/node@24.13.3': @@ -2953,6 +3001,10 @@ snapshots: body-scroll-lock@4.0.0-beta.0: {} + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + buffer-image-size@0.6.4: dependencies: '@types/node': 24.13.3 @@ -3076,6 +3128,10 @@ snapshots: extend@3.0.2: {} + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + flexsearch@0.8.212: {} get-east-asian-width@1.6.0: {} @@ -3248,6 +3304,8 @@ snapshots: is-hexadecimal@2.0.1: {} + is-number@7.0.0: {} + is-plain-obj@4.1.0: {} js-tokens@4.0.0: {} @@ -3759,6 +3817,11 @@ snapshots: transitivePeerDependencies: - supports-color + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + min-indent@1.0.1: {} ms@2.1.3: {} @@ -3818,6 +3881,8 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.2: {} + picomatch@4.0.4: {} picomatch@4.0.5: {} @@ -4186,6 +4251,10 @@ snapshots: tinypool@2.1.0: {} + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + toggle-selection@1.0.6: {} trim-lines@3.0.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a017793..33c9d28 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -28,6 +28,7 @@ catalog: '@testing-library/dom': '^10.4.1' '@testing-library/jest-dom': '^7.0.0' '@testing-library/react': '^16.3.2' + '@types/micromatch': '^4.0.10' '@types/node': '^24.13.3' '@types/react': '^19.2.17' '@types/react-dom': '^19.2.3' @@ -36,6 +37,7 @@ catalog: 'happy-dom': '^20.11.1' 'heading-case': '^1.1.4' 'lint-staged': '^17.2.0' + 'micromatch': '4.0.8' 'oxfmt': '^0.60.0' prettier: '3.9.6' 'react': '^19.2.8'