diff --git a/package.json b/package.json index b363a09..474d3ed 100644 --- a/package.json +++ b/package.json @@ -45,12 +45,11 @@ }, "dependencies": { "fdir": "^6.4.3", - "picomatch": "^4.0.2" + "unmatch": "^1.0.1" }, "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/node": "^22.13.4", - "@types/picomatch": "^3.0.2", "fs-fixture": "^2.7.0", "tsup": "^8.3.6", "typescript": "^5.7.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1e0597..31311f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,9 @@ importers: fdir: specifier: ^6.4.3 version: 6.4.3(picomatch@4.0.2) - picomatch: - specifier: ^4.0.2 - version: 4.0.2 + unmatch: + specifier: ^1.0.1 + version: 1.0.1 devDependencies: '@biomejs/biome': specifier: ^1.9.4 @@ -21,9 +21,6 @@ importers: '@types/node': specifier: ^22.13.4 version: 22.13.4 - '@types/picomatch': - specifier: ^3.0.2 - version: 3.0.2 fs-fixture: specifier: ^2.7.0 version: 2.7.0 @@ -366,9 +363,6 @@ packages: '@types/node@22.13.4': resolution: {integrity: sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==} - '@types/picomatch@3.0.2': - resolution: {integrity: sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -669,6 +663,10 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + unmatch@1.0.1: + resolution: {integrity: sha512-Fe6Mi4RUci7pappunyfLbU0m+vq4xNkgjXXMmMzv58F3hQs3N4fg1oiAeGE7P6O8y6g+ck3RpbMn5CiLTHHysQ==} + engines: {node: '>=12'} + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -892,8 +890,6 @@ snapshots: dependencies: undici-types: 6.20.0 - '@types/picomatch@3.0.2': {} - ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -1183,6 +1179,8 @@ snapshots: undici-types@6.20.0: {} + unmatch@1.0.1: {} + webidl-conversions@4.0.2: {} whatwg-url@7.1.0: diff --git a/src/index.ts b/src/index.ts index 9def92f..4bbcfa4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import path, { posix } from 'node:path'; import { type Options as FdirOptions, fdir } from 'fdir'; -import picomatch from 'picomatch'; +import match from 'unmatch'; import { escapePath, getPartialMatcher, isDynamicPattern, log, splitPattern } from './utils.ts'; const PARENT_DIRECTORY = /^(\/?\.\.)+/; @@ -106,14 +106,16 @@ function processPatterns( const matchPatterns: string[] = []; const ignorePatterns: string[] = []; + const unignorePatterns: string[] = []; for (const pattern of ignore) { if (!pattern) { continue; } - // don't handle negated patterns here for consistency with fast-glob if (pattern[0] !== '!' || pattern[1] === '(') { ignorePatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, true)); + } else { + unignorePatterns.push(pattern.slice(1)); } } @@ -128,7 +130,7 @@ function processPatterns( } } - return { match: matchPatterns, ignore: ignorePatterns }; + return { match: matchPatterns, ignore: ignorePatterns, unignore: unignorePatterns }; } // TODO: this is slow, find a better way to do this @@ -182,13 +184,16 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) { log('internal processing patterns:', processed); } - const matcher = picomatch(processed.match, { + const unignoreMatcher = processed.unignore.length === 0 ? undefined : match(processed.unignore); + + const matcher = match(processed.match, { dot: options.dot, nocase, - ignore: processed.ignore + ignore: processed.ignore, + onIgnore: unignoreMatcher ? result => unignoreMatcher(result.output) && match.constants.UNIGNORE : undefined }); - const ignore = picomatch(processed.ignore, { + const ignore = match(processed.ignore, { dot: options.dot, nocase }); diff --git a/src/utils.ts b/src/utils.ts index 275789b..e3683b7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import picomatch, { type Matcher } from 'picomatch'; +import match, { type Matcher } from 'unmatch'; // #region PARTIAL MATCHER export interface PartialMatcherOptions { @@ -18,7 +18,7 @@ export function getPartialMatcher(patterns: string[], options?: PartialMatcherOp const partsCount = parts.length; const partRegexes = Array(partsCount); for (let j = 0; j < partsCount; j++) { - partRegexes[j] = picomatch.makeRe(parts[j], options); + partRegexes[j] = match.makeRe(parts[j], options); } regexes[i] = partRegexes; } @@ -72,7 +72,7 @@ const splitPatternOptions = { parts: true }; // if a pattern has no slashes outside glob symbols, results.parts is [] export function splitPattern(path: string): string[] { - const result = picomatch.scan(path, splitPatternOptions); + const result = match.scan(path, splitPatternOptions); return result.parts?.length ? result.parts : [path]; } // #endregion @@ -129,7 +129,7 @@ export function isDynamicPattern(pattern: string, options?: { caseSensitiveMatch return true; } - const scan = picomatch.scan(pattern); + const scan = match.scan(pattern); return scan.isGlob || scan.negated; } // #endregion diff --git a/test/index.test.ts b/test/index.test.ts index f91125b..9f9d4e9 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -352,11 +352,9 @@ test('negative absolute patterns in options', async () => { assert.deepEqual(files2.sort(), ['a/b.txt', 'b/b.txt']); }); -// can't easily make them properly work right now -// but at least it's consistent with fast-glob this way test('negative patterns in ignore are ignored', async () => { const files = await glob({ patterns: ['**/*'], ignore: ['**/b.txt', '!a/b.txt'], cwd }); - assert.deepEqual(files.sort(), ['a/a.txt', 'b/a.txt']); + assert.deepEqual(files.sort(), ['a/a.txt', 'a/b.txt', 'b/a.txt']); const files2 = await glob({ patterns: ['**/*', '!**/b.txt', '!!a/b.txt'], cwd }); assert.deepEqual(files2.sort(), ['a/a.txt', 'b/a.txt']);