From 57db227a5fd798f3cf233f6c8f259a42df63149c Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 30 Jul 2026 19:30:16 +0800 Subject: [PATCH] feat(fmt): add ignore patterns matcher --- packages/rstack/THIRD_PARTY_NOTICES.md | 28 +++++++++++++ packages/rstack/package.json | 1 + packages/rstack/src/fmt/ignore.ts | 12 ++++++ packages/rstack/tests/fmt/ignore.test.ts | 52 ++++++++++++++++++++++++ pnpm-lock.yaml | 24 +++++++++++ pnpm-workspace.yaml | 1 + 6 files changed, 118 insertions(+) create mode 100644 packages/rstack/src/fmt/ignore.ts create mode 100644 packages/rstack/tests/fmt/ignore.test.ts diff --git a/packages/rstack/THIRD_PARTY_NOTICES.md b/packages/rstack/THIRD_PARTY_NOTICES.md index e083d50..67033d5 100644 --- a/packages/rstack/THIRD_PARTY_NOTICES.md +++ b/packages/rstack/THIRD_PARTY_NOTICES.md @@ -2,6 +2,34 @@ This package includes software developed by third parties. +## fast-ignore + +This package includes bundled code from [fast-ignore](https://github.com/fabiospampinato/fast-ignore). + +License: MIT + +The MIT License (MIT) + +Copyright (c) 2023-present Fabio Spampinato + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + ## fresh-import This package includes bundled code from [fresh-import](https://github.com/sapphi-red/fresh-import). diff --git a/packages/rstack/package.json b/packages/rstack/package.json index 6ff9ace..3ec3e36 100644 --- a/packages/rstack/package.json +++ b/packages/rstack/package.json @@ -67,6 +67,7 @@ "@rstest/adapter-rslib": "catalog:", "@types/micromatch": "catalog:", "@types/node": "catalog:", + "fast-ignore": "catalog:", "lint-staged": "catalog:", "micromatch": "catalog:", "rslog": "catalog:", diff --git a/packages/rstack/src/fmt/ignore.ts b/packages/rstack/src/fmt/ignore.ts new file mode 100644 index 0000000..09b33bc --- /dev/null +++ b/packages/rstack/src/fmt/ignore.ts @@ -0,0 +1,12 @@ +import { relative } from 'node:path'; +import fastIgnore from 'fast-ignore'; +import type { ResolvedFmtConfig } from './types.ts'; + +/** Creates a reusable matcher for config-level ignore patterns. */ +const createFmtIgnoreMatcher = (config: ResolvedFmtConfig): ((filePath: string) => boolean) => { + const matches = fastIgnore(config.ignorePatterns.join('\n')); + + return (filePath) => matches(relative(config.rootPath, filePath)); +}; + +export { createFmtIgnoreMatcher }; diff --git a/packages/rstack/tests/fmt/ignore.test.ts b/packages/rstack/tests/fmt/ignore.test.ts new file mode 100644 index 0000000..0bfc14b --- /dev/null +++ b/packages/rstack/tests/fmt/ignore.test.ts @@ -0,0 +1,52 @@ +import path from 'node:path'; +import { expect, test } from 'rstack/test'; +import { normalizeFmtConfig } from '../../src/fmt/config.ts'; +import { createFmtIgnoreMatcher } from '../../src/fmt/ignore.ts'; + +const rootPath = path.join(import.meta.dirname, 'project'); + +const createMatcher = (ignorePatterns: string[]) => + createFmtIgnoreMatcher(normalizeFmtConfig({ ignorePatterns }, rootPath)); + +test('matches gitignore patterns relative to the config root', () => { + const isIgnored = createMatcher(['dist/', '*.snap', '/root.js', '# comment', '\\#generated.js']); + + expect(isIgnored(path.join(rootPath, 'dist/index.js'))).toBe(true); + expect(isIgnored(path.join(rootPath, 'src/data.snap'))).toBe(true); + expect(isIgnored(path.join(rootPath, 'root.js'))).toBe(true); + expect(isIgnored(path.join(rootPath, 'nested/root.js'))).toBe(false); + expect(isIgnored(path.join(rootPath, '#generated.js'))).toBe(true); + expect(isIgnored(path.join(rootPath, 'src/index.js'))).toBe(false); +}); + +test('applies negated patterns in declaration order', () => { + const isIgnored = createMatcher(['*.js', '!src/keep.js']); + const isIgnoredAgain = createMatcher(['*.js', '!src/keep.js', 'src/keep.js']); + const isReincluded = createMatcher(['dist', '!dist']); + const filePath = path.join(rootPath, 'src/keep.js'); + + expect(isIgnored(filePath)).toBe(false); + expect(isIgnored(path.join(rootPath, 'src/drop.js'))).toBe(true); + expect(isIgnoredAgain(filePath)).toBe(true); + expect(isReincluded(path.join(rootPath, 'dist'))).toBe(false); +}); + +test('does not let explicit files bypass ignore patterns', () => { + const isIgnored = createMatcher(['generated/']); + const explicitFilePath = path.join(rootPath, 'generated/output.js'); + + expect(isIgnored(explicitFilePath)).toBe(true); +}); + +test('matches parent directory patterns without validation', () => { + const isIgnored = createMatcher(['../shared/*.js']); + + expect(isIgnored(path.join(rootPath, '../shared/index.js'))).toBe(true); + expect(isIgnored(path.join(rootPath, 'shared/index.js'))).toBe(false); +}); + +test('does not ignore files when no patterns are configured', () => { + const isIgnored = createMatcher([]); + + expect(isIgnored(path.join(rootPath, 'src/index.js'))).toBe(false); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14048bd..6476582 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,6 +73,9 @@ catalogs: cspell-ban-words: specifier: ^0.0.4 version: 0.0.4 + fast-ignore: + specifier: 2.0.0 + version: 2.0.0 happy-dom: specifier: ^20.11.1 version: 20.11.1 @@ -343,6 +346,9 @@ importers: '@types/node': specifier: 'catalog:' version: 24.13.3 + fast-ignore: + specifier: 'catalog:' + version: 2.0.0 lint-staged: specifier: 'catalog:' version: 17.2.0 @@ -1303,6 +1309,9 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-ignore@2.0.0: + resolution: {integrity: sha512-41OOPBgTDyVjF2oytGXvmqm56a38znzWLTQhvD3gp3FPCs37+j7qYjWDnL2WEA0P/Z80W53MKAXNJxc0JT8Tpw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1326,6 +1335,9 @@ packages: git-hooks-list@4.2.1: resolution: {integrity: sha512-WNvqJjOxxs/8ZP9+DWdwWJ7cDsd60NHf39XnD82pDVrKO5q7xfPqpkK6hwEAmBa/ZSEE4IOoR75EzbbIuwGlMw==} + grammex@3.1.13: + resolution: {integrity: sha512-LnPnhOBLEJEVKS8WFDVaA397L9Kq55Q9oSITJiVLHVdhAclfUkWzQv74KhvZHKL2Q09Pb1XdsrOsZ4LfTFFTEg==} + happy-dom@20.11.1: resolution: {integrity: sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg==} engines: {node: '>=20.0.0'} @@ -2050,6 +2062,9 @@ packages: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} + string-escape-regex@1.0.1: + resolution: {integrity: sha512-cdSXOHSJ32K/T2dbj9t7rJwonujaOkaINpa1zsXT+PNFIv1zuPjtr0tXanCvUhN2bIu2IB0z/C7ksl+Qsy44nA==} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -2984,6 +2999,11 @@ snapshots: extend@3.0.2: {} + fast-ignore@2.0.0: + dependencies: + grammex: 3.1.13 + string-escape-regex: 1.0.1 + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 @@ -2998,6 +3018,8 @@ snapshots: git-hooks-list@4.2.1: {} + grammex@3.1.13: {} + happy-dom@20.11.1: dependencies: '@types/node': 24.13.3 @@ -4078,6 +4100,8 @@ snapshots: string-argv@0.3.2: {} + string-escape-regex@1.0.1: {} + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1421ae9..57c78d5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -34,6 +34,7 @@ catalog: '@types/react-dom': '^19.2.3' '@shikijs/transformers': '^4.3.1' 'cspell-ban-words': '^0.0.4' + 'fast-ignore': '2.0.0' 'happy-dom': '^20.11.1' 'heading-case': '^1.1.4' 'lint-staged': '^17.2.0'