From a1c64c44eb198d53885747462b7aeb68084b0e0a Mon Sep 17 00:00:00 2001 From: kubus <38988733+xiboon@users.noreply.github.com> Date: Sun, 27 Oct 2024 20:55:01 +0100 Subject: [PATCH 01/10] Implement more efficient excluding algorithm --- src/index.ts | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index d6de95b..b8f3a69 100644 --- a/src/index.ts +++ b/src/index.ts @@ -75,7 +75,6 @@ function normalizePattern( properties.depthOffset = newCommonPath.length; properties.commonPath = newCommonPath; - properties.root = newCommonPath.length > 0 ? `${cwd}/${newCommonPath.join('/')}` : cwd; } @@ -108,32 +107,50 @@ function processPatterns( ignorePatterns.push(newPattern); } } - + const transformed: string[] = []; for (const pattern of patterns) { if (!pattern.startsWith('!') || pattern[1] === '(') { const newPattern = normalizePattern(pattern, expandDirectories, cwd, properties, false); matchPatterns.push(newPattern); + const split = newPattern.split('/'); //.filter(e => e !== ''); + // if (/*split.at(-1)?.includes('**') ||*/ split.length === 1) { + // transformed.push(split.join('/')); + // } else { + // transformed.push(split.slice(0, -1).join('/')); + // } + + if (split[split.length - 1]?.includes('**')) { + split[split.length - 2] = '**'; + split.pop(); + transformed.push(split.join('/')); + } else { + transformed.push(split.length > 1 ? split.slice(0, -1).join('/') : split.join('/')); + } + console.log(newPattern, split); } else if (pattern[1] !== '!' || pattern[2] === '(') { const newPattern = normalizePattern(pattern.slice(1), expandDirectories, cwd, properties, true); ignorePatterns.push(newPattern); } } - - return { match: matchPatterns, ignore: ignorePatterns }; + console.log(transformed, 'transformed'); + return { match: matchPatterns, ignore: ignorePatterns, transformed }; } // TODO: this is slow, find a better way to do this function getRelativePath(path: string, cwd: string, root: string) { return posix.relative(cwd, `${root}/${path}`); } - -function processPath(path: string, cwd: string, root: string, isDirectory: boolean, absolute?: boolean) { +let i = 0; +function processPath(path: string, cwd: string, root: string, isDirectory: boolean, absolute?: boolean, count = false) { const relativePath = absolute ? path.slice(root.length + 1) || '.' : path; if (root === cwd) { return isDirectory && relativePath !== '.' ? relativePath.slice(0, -1) : relativePath; } - + if (count) { + i++; + console.log(i); + } return getRelativePath(relativePath, cwd, root); } @@ -157,16 +174,28 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) { nocase: options.caseSensitiveMatch === false, ignore: processed.ignore }); + console.log(processed.transformed, 'Hi'); const exclude = picomatch(processed.ignore, { dot: options.dot, nocase: options.caseSensitiveMatch === false }); + const newExclude = picomatch('**/*', { + ignore: processed.transformed, + dot: !options.dot, + nocase: options.caseSensitiveMatch === false + }); const fdirOptions: Partial = { // use relative paths in the matcher - filters: [(p, isDirectory) => matcher(processPath(p, cwd, properties.root, isDirectory, options.absolute))], - exclude: (_, p) => exclude(processPath(p, cwd, properties.root, true, true)), + filters: [(p, isDirectory) => matcher(processPath(p, cwd, properties.root, isDirectory, options.absolute, true))], + exclude: (_, p) => { + const h = + exclude(processPath(p, cwd, properties.root, true, true, false)) || + newExclude(processPath(p, cwd, properties.root, true, true, false)); + console.log(h, processPath(p, cwd, properties.root, true, true, false)); + return h; + }, pathSeparator: '/', relativePaths: true, resolveSymlinks: true From 4055dd43108cf15f65bae128f0d2f3f89207e5d8 Mon Sep 17 00:00:00 2001 From: kubus <38988733+xiboon@users.noreply.github.com> Date: Sun, 27 Oct 2024 20:58:30 +0100 Subject: [PATCH 02/10] clean up code from debug logging --- src/index.ts | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/index.ts b/src/index.ts index b8f3a69..142d6d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -112,12 +112,7 @@ function processPatterns( if (!pattern.startsWith('!') || pattern[1] === '(') { const newPattern = normalizePattern(pattern, expandDirectories, cwd, properties, false); matchPatterns.push(newPattern); - const split = newPattern.split('/'); //.filter(e => e !== ''); - // if (/*split.at(-1)?.includes('**') ||*/ split.length === 1) { - // transformed.push(split.join('/')); - // } else { - // transformed.push(split.slice(0, -1).join('/')); - // } + const split = newPattern.split('/'); if (split[split.length - 1]?.includes('**')) { split[split.length - 2] = '**'; @@ -126,13 +121,11 @@ function processPatterns( } else { transformed.push(split.length > 1 ? split.slice(0, -1).join('/') : split.join('/')); } - console.log(newPattern, split); } else if (pattern[1] !== '!' || pattern[2] === '(') { const newPattern = normalizePattern(pattern.slice(1), expandDirectories, cwd, properties, true); ignorePatterns.push(newPattern); } } - console.log(transformed, 'transformed'); return { match: matchPatterns, ignore: ignorePatterns, transformed }; } @@ -140,17 +133,13 @@ function processPatterns( function getRelativePath(path: string, cwd: string, root: string) { return posix.relative(cwd, `${root}/${path}`); } -let i = 0; -function processPath(path: string, cwd: string, root: string, isDirectory: boolean, absolute?: boolean, count = false) { + +function processPath(path: string, cwd: string, root: string, isDirectory: boolean, absolute?: boolean) { const relativePath = absolute ? path.slice(root.length + 1) || '.' : path; if (root === cwd) { return isDirectory && relativePath !== '.' ? relativePath.slice(0, -1) : relativePath; } - if (count) { - i++; - console.log(i); - } return getRelativePath(relativePath, cwd, root); } @@ -174,7 +163,6 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) { nocase: options.caseSensitiveMatch === false, ignore: processed.ignore }); - console.log(processed.transformed, 'Hi'); const exclude = picomatch(processed.ignore, { dot: options.dot, @@ -188,14 +176,10 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) { const fdirOptions: Partial = { // use relative paths in the matcher - filters: [(p, isDirectory) => matcher(processPath(p, cwd, properties.root, isDirectory, options.absolute, true))], - exclude: (_, p) => { - const h = - exclude(processPath(p, cwd, properties.root, true, true, false)) || - newExclude(processPath(p, cwd, properties.root, true, true, false)); - console.log(h, processPath(p, cwd, properties.root, true, true, false)); - return h; - }, + filters: [(p, isDirectory) => matcher(processPath(p, cwd, properties.root, isDirectory, options.absolute))], + exclude: (_, p) => + exclude(processPath(p, cwd, properties.root, true, true)) || + newExclude(processPath(p, cwd, properties.root, true, true)), pathSeparator: '/', relativePaths: true, resolveSymlinks: true From 5d2be63651bdb2b10b23ec2d584fcc061fd4148a Mon Sep 17 00:00:00 2001 From: kubus <38988733+xiboon@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:09:46 +0100 Subject: [PATCH 03/10] fix new test case --- src/index.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 142d6d5..56e5533 100644 --- a/src/index.ts +++ b/src/index.ts @@ -113,19 +113,32 @@ function processPatterns( const newPattern = normalizePattern(pattern, expandDirectories, cwd, properties, false); matchPatterns.push(newPattern); const split = newPattern.split('/'); - + let current = ''; if (split[split.length - 1]?.includes('**')) { split[split.length - 2] = '**'; split.pop(); - transformed.push(split.join('/')); + current = split.join('/'); } else { - transformed.push(split.length > 1 ? split.slice(0, -1).join('/') : split.join('/')); + current = split.length > 1 ? split.slice(0, -1).join('/') : split.join('/'); + } + transformed.push(current); + + for (let i = split.length - 2; i > 0; i--) { + const part = split.slice(0, i); + if (part[part.length - 1] === '**') { + part.pop(); + if (part.length > 1) { + part.pop(); + } + } + transformed.push(part.join('/')); } } else if (pattern[1] !== '!' || pattern[2] === '(') { const newPattern = normalizePattern(pattern.slice(1), expandDirectories, cwd, properties, true); ignorePatterns.push(newPattern); } } + console.log({ transformed, patterns }); return { match: matchPatterns, ignore: ignorePatterns, transformed }; } From 5684f1b3ef31120e0ae05b3a31889b37c7d1547a Mon Sep 17 00:00:00 2001 From: Superchupu <53496941+SuperchupuDev@users.noreply.github.com> Date: Tue, 5 Nov 2024 00:09:13 +0100 Subject: [PATCH 04/10] small cleanup --- src/index.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 56e5533..28a341b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -75,6 +75,7 @@ function normalizePattern( properties.depthOffset = newCommonPath.length; properties.commonPath = newCommonPath; + properties.root = newCommonPath.length > 0 ? `${cwd}/${newCommonPath.join('/')}` : cwd; } @@ -107,6 +108,7 @@ function processPatterns( ignorePatterns.push(newPattern); } } + const transformed: string[] = []; for (const pattern of patterns) { if (!pattern.startsWith('!') || pattern[1] === '(') { @@ -138,7 +140,6 @@ function processPatterns( ignorePatterns.push(newPattern); } } - console.log({ transformed, patterns }); return { match: matchPatterns, ignore: ignorePatterns, transformed }; } @@ -153,6 +154,7 @@ function processPath(path: string, cwd: string, root: string, isDirectory: boole if (root === cwd) { return isDirectory && relativePath !== '.' ? relativePath.slice(0, -1) : relativePath; } + return getRelativePath(relativePath, cwd, root); } @@ -177,11 +179,12 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) { ignore: processed.ignore }); - const exclude = picomatch(processed.ignore, { + const ignore = picomatch(processed.ignore, { dot: options.dot, nocase: options.caseSensitiveMatch === false }); - const newExclude = picomatch('**/*', { + + const exclude = picomatch('**', { ignore: processed.transformed, dot: !options.dot, nocase: options.caseSensitiveMatch === false @@ -190,9 +193,10 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) { const fdirOptions: Partial = { // use relative paths in the matcher filters: [(p, isDirectory) => matcher(processPath(p, cwd, properties.root, isDirectory, options.absolute))], - exclude: (_, p) => - exclude(processPath(p, cwd, properties.root, true, true)) || - newExclude(processPath(p, cwd, properties.root, true, true)), + exclude: (_, p) => { + const relativePath = processPath(p, cwd, properties.root, true, true); + return ignore(relativePath) || exclude(relativePath); + }, pathSeparator: '/', relativePaths: true, resolveSymlinks: true From fc290ff82d279348d87981dddf5f7ddf845b08ad Mon Sep 17 00:00:00 2001 From: Superchupu <53496941+SuperchupuDev@users.noreply.github.com> Date: Tue, 5 Nov 2024 00:32:03 +0100 Subject: [PATCH 05/10] small optimization --- src/index.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index 28a341b..c5372e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -115,15 +115,13 @@ function processPatterns( const newPattern = normalizePattern(pattern, expandDirectories, cwd, properties, false); matchPatterns.push(newPattern); const split = newPattern.split('/'); - let current = ''; - if (split[split.length - 1]?.includes('**')) { + if (split[split.length - 1] === '**') { split[split.length - 2] = '**'; split.pop(); - current = split.join('/'); + transformed.push(split.join('/')); } else { - current = split.length > 1 ? split.slice(0, -1).join('/') : split.join('/'); + transformed.push(split.length > 1 ? split.slice(0, -1).join('/') : split.join('/')); } - transformed.push(current); for (let i = split.length - 2; i > 0; i--) { const part = split.slice(0, i); @@ -140,6 +138,7 @@ function processPatterns( ignorePatterns.push(newPattern); } } + return { match: matchPatterns, ignore: ignorePatterns, transformed }; } @@ -185,9 +184,9 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) { }); const exclude = picomatch('**', { - ignore: processed.transformed, - dot: !options.dot, - nocase: options.caseSensitiveMatch === false + dot: true, + nocase: options.caseSensitiveMatch === false, + ignore: processed.transformed }); const fdirOptions: Partial = { From 921412b4c1ff7299482af2c91bf5e4653eda5731 Mon Sep 17 00:00:00 2001 From: Superchupu <53496941+SuperchupuDev@users.noreply.github.com> Date: Tue, 5 Nov 2024 01:23:09 +0100 Subject: [PATCH 06/10] optimize `../` too this is huge actually --- src/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index c5372e7..1bc969c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -116,8 +116,10 @@ function processPatterns( matchPatterns.push(newPattern); const split = newPattern.split('/'); if (split[split.length - 1] === '**') { - split[split.length - 2] = '**'; - split.pop(); + if (split[split.length - 2] !== '..') { + split[split.length - 2] = '**'; + split.pop(); + } transformed.push(split.join('/')); } else { transformed.push(split.length > 1 ? split.slice(0, -1).join('/') : split.join('/')); @@ -183,7 +185,7 @@ function crawl(options: GlobOptions, cwd: string, sync: boolean) { nocase: options.caseSensitiveMatch === false }); - const exclude = picomatch('**', { + const exclude = picomatch('*(../)**', { dot: true, nocase: options.caseSensitiveMatch === false, ignore: processed.transformed From 8c684ec578820a9a7090db6c6a9fce7545aa46d7 Mon Sep 17 00:00:00 2001 From: Superchupu <53496941+SuperchupuDev@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:27:21 +0100 Subject: [PATCH 07/10] debug --- src/index.ts | 16 +++- src/old.ts | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 src/old.ts diff --git a/src/index.ts b/src/index.ts index 1bc969c..56c233a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ +import assert from 'node:assert/strict'; import path, { posix } from 'node:path'; import { type Options as FdirOptions, fdir } from 'fdir'; import picomatch from 'picomatch'; +import { globOld, globOldSync } from './old.ts'; import { isDynamicPattern } from './utils.ts'; export interface GlobOptions { @@ -256,7 +258,12 @@ export async function glob( : patternsOrOptions; const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/'); - return crawl(opts, cwd, false); + const files = await crawl(opts, cwd, false); + const files2 = await globOld(opts); + + assert.deepStrictEqual(files.sort(), files2.sort()); + + return files; } export function globSync(patterns: string | string[], options?: Omit): string[]; @@ -272,7 +279,12 @@ export function globSync(patternsOrOptions: string | string[] | GlobOptions, opt : patternsOrOptions; const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/'); - return crawl(opts, cwd, true); + const files = crawl(opts, cwd, true); + const files2 = globOldSync(opts); + + assert.deepStrictEqual(files.sort(), files2.sort()); + + return files; } export { convertPathToPattern, escapePath, isDynamicPattern } from './utils.ts'; diff --git a/src/old.ts b/src/old.ts new file mode 100644 index 0000000..2dc1e6f --- /dev/null +++ b/src/old.ts @@ -0,0 +1,247 @@ +import path, { posix } from 'node:path'; +import { type Options as FdirOptions, fdir } from 'fdir'; +import picomatch from 'picomatch'; +import { isDynamicPattern } from './utils.ts'; + +export interface GlobOptions { + absolute?: boolean; + cwd?: string; + patterns?: string | string[]; + ignore?: string | string[]; + dot?: boolean; + deep?: number; + followSymbolicLinks?: boolean; + caseSensitiveMatch?: boolean; + expandDirectories?: boolean; + onlyDirectories?: boolean; + onlyFiles?: boolean; +} + +interface InternalProperties { + root: string; + commonPath: string[] | null; + depthOffset: number; +} + +function normalizePattern( + pattern: string, + expandDirectories: boolean, + cwd: string, + properties: InternalProperties, + isIgnore: boolean +) { + let result: string = pattern; + if (pattern.endsWith('/')) { + result = pattern.slice(0, -1); + } + // using a directory as entry should match all files inside it + if (!result.endsWith('*') && expandDirectories) { + result += '/**'; + } + + if (path.isAbsolute(result.replace(/\\(?=[()[\]{}!*+?@|])/g, ''))) { + result = posix.relative(cwd, result); + } else { + result = posix.normalize(result); + } + + const parentDirectoryMatch = /^(\/?\.\.)+/.exec(result); + if (parentDirectoryMatch?.[0]) { + const potentialRoot = posix.join(cwd, parentDirectoryMatch[0]); + if (properties.root.length > potentialRoot.length) { + properties.root = potentialRoot; + properties.depthOffset = -(parentDirectoryMatch[0].length + 1) / 3; + } + } else if (!isIgnore && properties.depthOffset >= 0) { + const current = result.split('/'); + properties.commonPath ??= current; + + const newCommonPath = []; + + for (let i = 0; i < Math.min(properties.commonPath.length, current.length); i++) { + const part = current[i]; + + if (part === '**' && !current[i + 1]) { + newCommonPath.pop(); + break; + } + + if (part !== properties.commonPath[i] || isDynamicPattern(part) || i === current.length - 1) { + break; + } + + newCommonPath.push(part); + } + + properties.depthOffset = newCommonPath.length; + properties.commonPath = newCommonPath; + + properties.root = newCommonPath.length > 0 ? `${cwd}/${newCommonPath.join('/')}` : cwd; + } + + return result; +} + +function processPatterns( + { patterns, ignore = [], expandDirectories = true }: GlobOptions, + cwd: string, + properties: InternalProperties +) { + if (typeof patterns === 'string') { + patterns = [patterns]; + } else if (!patterns) { + // tinyglobby exclusive behavior, should be considered deprecated + patterns = ['**/*']; + } + + if (typeof ignore === 'string') { + ignore = [ignore]; + } + + const matchPatterns: string[] = []; + const ignorePatterns: string[] = []; + + for (const pattern of ignore) { + // don't handle negated patterns here for consistency with fast-glob + if (!pattern.startsWith('!') || pattern[1] === '(') { + const newPattern = normalizePattern(pattern, expandDirectories, cwd, properties, true); + ignorePatterns.push(newPattern); + } + } + + for (const pattern of patterns) { + if (!pattern.startsWith('!') || pattern[1] === '(') { + const newPattern = normalizePattern(pattern, expandDirectories, cwd, properties, false); + matchPatterns.push(newPattern); + } else if (pattern[1] !== '!' || pattern[2] === '(') { + const newPattern = normalizePattern(pattern.slice(1), expandDirectories, cwd, properties, true); + ignorePatterns.push(newPattern); + } + } + + return { match: matchPatterns, ignore: ignorePatterns }; +} + +// TODO: this is slow, find a better way to do this +function getRelativePath(path: string, cwd: string, root: string) { + return posix.relative(cwd, `${root}/${path}`); +} + +function processPath(path: string, cwd: string, root: string, isDirectory: boolean, absolute?: boolean) { + const relativePath = absolute ? path.slice(root.length + 1) || '.' : path; + + if (root === cwd) { + return isDirectory && relativePath !== '.' ? relativePath.slice(0, -1) : relativePath; + } + + return getRelativePath(relativePath, cwd, root); +} + +function crawl(options: GlobOptions, cwd: string, sync: false): Promise; +function crawl(options: GlobOptions, cwd: string, sync: true): string[]; +function crawl(options: GlobOptions, cwd: string, sync: boolean) { + if (Array.isArray(options.patterns) && options.patterns.length === 0) { + return sync ? [] : Promise.resolve([]); + } + + const properties = { + root: cwd, + commonPath: null, + depthOffset: 0 + }; + + const processed = processPatterns(options, cwd, properties); + + const matcher = picomatch(processed.match, { + dot: options.dot, + nocase: options.caseSensitiveMatch === false, + ignore: processed.ignore + }); + + const exclude = picomatch(processed.ignore, { + dot: options.dot, + nocase: options.caseSensitiveMatch === false + }); + + const fdirOptions: Partial = { + // use relative paths in the matcher + filters: [(p, isDirectory) => matcher(processPath(p, cwd, properties.root, isDirectory, options.absolute))], + exclude: (_, p) => exclude(processPath(p, cwd, properties.root, true, true)), + pathSeparator: '/', + relativePaths: true, + resolveSymlinks: true + }; + + if (options.deep) { + fdirOptions.maxDepth = Math.round(options.deep - properties.depthOffset); + } + + if (options.absolute) { + fdirOptions.relativePaths = false; + fdirOptions.resolvePaths = true; + fdirOptions.includeBasePath = true; + } + + if (options.followSymbolicLinks === false) { + fdirOptions.resolveSymlinks = false; + fdirOptions.excludeSymlinks = true; + } + + if (options.onlyDirectories) { + fdirOptions.excludeFiles = true; + fdirOptions.includeDirs = true; + } else if (options.onlyFiles === false) { + fdirOptions.includeDirs = true; + } + + // backslashes are removed so that inferred roots like `C:/New folder \\(1\\)` work + properties.root = properties.root.replace(/\\/g, ''); + const api = new fdir(fdirOptions).crawl(properties.root); + + if (cwd === properties.root || options.absolute) { + return sync ? api.sync() : api.withPromise(); + } + + return sync + ? api.sync().map(p => getRelativePath(p, cwd, properties.root) + (!p || p.endsWith('/') ? '/' : '')) + : api + .withPromise() + .then(paths => paths.map(p => getRelativePath(p, cwd, properties.root) + (!p || p.endsWith('/') ? '/' : ''))); +} + +export function globOld(patterns: string | string[], options?: Omit): Promise; +export function globOld(options: GlobOptions): Promise; +export async function globOld( + patternsOrOptions: string | string[] | GlobOptions, + options?: GlobOptions +): Promise { + if (patternsOrOptions && options?.patterns) { + throw new Error('Cannot pass patterns as both an argument and an option'); + } + + const opts = + Array.isArray(patternsOrOptions) || typeof patternsOrOptions === 'string' + ? { ...options, patterns: patternsOrOptions } + : patternsOrOptions; + const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/'); + + return crawl(opts, cwd, false); +} + +export function globOldSync(patterns: string | string[], options?: Omit): string[]; +export function globOldSync(options: GlobOptions): string[]; +export function globOldSync(patternsOrOptions: string | string[] | GlobOptions, options?: GlobOptions): string[] { + if (patternsOrOptions && options?.patterns) { + throw new Error('Cannot pass patterns as both an argument and an option'); + } + + const opts = + Array.isArray(patternsOrOptions) || typeof patternsOrOptions === 'string' + ? { ...options, patterns: patternsOrOptions } + : patternsOrOptions; + const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/'); + + return crawl(opts, cwd, true); +} + +export { convertPathToPattern, escapePath, isDynamicPattern } from './utils.ts'; From 20ce7663d45552f1c88c100b645aa2edec527197 Mon Sep 17 00:00:00 2001 From: Duc Nghiem-Xuan Date: Thu, 28 Nov 2024 12:06:22 +0900 Subject: [PATCH 08/10] Handle pattern with only double star --- src/index.ts | 2 +- test/index.test.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 56c233a..d1fd16e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -122,7 +122,7 @@ function processPatterns( split[split.length - 2] = '**'; split.pop(); } - transformed.push(split.join('/')); + transformed.push(split.length ? split.join('/') : '*'); } else { transformed.push(split.length > 1 ? split.slice(0, -1).join('/') : split.join('/')); } diff --git a/test/index.test.ts b/test/index.test.ts index bd4157c..2fc4fdb 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -26,6 +26,12 @@ const cwd = fixture.path; after(() => fixture.rm()); +test('only double star', async () => { + const files = await glob({ patterns: ['**'], cwd }); + assert.deepEqual(files.sort(), ['a/a.txt', 'a/b.txt', 'b/a.txt', 'b/b.txt']); +}); + + test('directory expansion', async () => { const files = await glob({ patterns: ['a'], cwd }); assert.deepEqual(files.sort(), ['a/a.txt', 'a/b.txt']); From a2f4b77618fc31e65dd2d0d685377e13ca3525b6 Mon Sep 17 00:00:00 2001 From: Superchupu <53496941+SuperchupuDev@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:41:37 +0100 Subject: [PATCH 09/10] format --- test/index.test.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/index.test.ts b/test/index.test.ts index 2fc4fdb..3e29650 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -26,12 +26,6 @@ const cwd = fixture.path; after(() => fixture.rm()); -test('only double star', async () => { - const files = await glob({ patterns: ['**'], cwd }); - assert.deepEqual(files.sort(), ['a/a.txt', 'a/b.txt', 'b/a.txt', 'b/b.txt']); -}); - - test('directory expansion', async () => { const files = await glob({ patterns: ['a'], cwd }); assert.deepEqual(files.sort(), ['a/a.txt', 'a/b.txt']); @@ -42,6 +36,11 @@ test('empty array matches nothing', async () => { assert.deepEqual(files.sort(), []); }); +test('only double star', async () => { + const files = await glob({ patterns: ['**'], cwd }); + assert.deepEqual(files.sort(), ['a/a.txt', 'a/b.txt', 'b/a.txt', 'b/b.txt']); +}); + test('no directory expansion if expandDirectories is set to false', async () => { const files = await glob({ patterns: ['a'], expandDirectories: false, cwd }); assert.deepEqual(files.sort(), []); From 479f99d91021a488986630c5fe1571c763bd19ae Mon Sep 17 00:00:00 2001 From: Superchupu <53496941+SuperchupuDev@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:13:15 +0100 Subject: [PATCH 10/10] Revert "debug" This reverts commit 8c684ec578820a9a7090db6c6a9fce7545aa46d7. --- src/index.ts | 16 +--- src/old.ts | 247 --------------------------------------------------- 2 files changed, 2 insertions(+), 261 deletions(-) delete mode 100644 src/old.ts diff --git a/src/index.ts b/src/index.ts index d1fd16e..97a3b88 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,6 @@ -import assert from 'node:assert/strict'; import path, { posix } from 'node:path'; import { type Options as FdirOptions, fdir } from 'fdir'; import picomatch from 'picomatch'; -import { globOld, globOldSync } from './old.ts'; import { isDynamicPattern } from './utils.ts'; export interface GlobOptions { @@ -258,12 +256,7 @@ export async function glob( : patternsOrOptions; const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/'); - const files = await crawl(opts, cwd, false); - const files2 = await globOld(opts); - - assert.deepStrictEqual(files.sort(), files2.sort()); - - return files; + return crawl(opts, cwd, false); } export function globSync(patterns: string | string[], options?: Omit): string[]; @@ -279,12 +272,7 @@ export function globSync(patternsOrOptions: string | string[] | GlobOptions, opt : patternsOrOptions; const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/'); - const files = crawl(opts, cwd, true); - const files2 = globOldSync(opts); - - assert.deepStrictEqual(files.sort(), files2.sort()); - - return files; + return crawl(opts, cwd, true); } export { convertPathToPattern, escapePath, isDynamicPattern } from './utils.ts'; diff --git a/src/old.ts b/src/old.ts deleted file mode 100644 index 2dc1e6f..0000000 --- a/src/old.ts +++ /dev/null @@ -1,247 +0,0 @@ -import path, { posix } from 'node:path'; -import { type Options as FdirOptions, fdir } from 'fdir'; -import picomatch from 'picomatch'; -import { isDynamicPattern } from './utils.ts'; - -export interface GlobOptions { - absolute?: boolean; - cwd?: string; - patterns?: string | string[]; - ignore?: string | string[]; - dot?: boolean; - deep?: number; - followSymbolicLinks?: boolean; - caseSensitiveMatch?: boolean; - expandDirectories?: boolean; - onlyDirectories?: boolean; - onlyFiles?: boolean; -} - -interface InternalProperties { - root: string; - commonPath: string[] | null; - depthOffset: number; -} - -function normalizePattern( - pattern: string, - expandDirectories: boolean, - cwd: string, - properties: InternalProperties, - isIgnore: boolean -) { - let result: string = pattern; - if (pattern.endsWith('/')) { - result = pattern.slice(0, -1); - } - // using a directory as entry should match all files inside it - if (!result.endsWith('*') && expandDirectories) { - result += '/**'; - } - - if (path.isAbsolute(result.replace(/\\(?=[()[\]{}!*+?@|])/g, ''))) { - result = posix.relative(cwd, result); - } else { - result = posix.normalize(result); - } - - const parentDirectoryMatch = /^(\/?\.\.)+/.exec(result); - if (parentDirectoryMatch?.[0]) { - const potentialRoot = posix.join(cwd, parentDirectoryMatch[0]); - if (properties.root.length > potentialRoot.length) { - properties.root = potentialRoot; - properties.depthOffset = -(parentDirectoryMatch[0].length + 1) / 3; - } - } else if (!isIgnore && properties.depthOffset >= 0) { - const current = result.split('/'); - properties.commonPath ??= current; - - const newCommonPath = []; - - for (let i = 0; i < Math.min(properties.commonPath.length, current.length); i++) { - const part = current[i]; - - if (part === '**' && !current[i + 1]) { - newCommonPath.pop(); - break; - } - - if (part !== properties.commonPath[i] || isDynamicPattern(part) || i === current.length - 1) { - break; - } - - newCommonPath.push(part); - } - - properties.depthOffset = newCommonPath.length; - properties.commonPath = newCommonPath; - - properties.root = newCommonPath.length > 0 ? `${cwd}/${newCommonPath.join('/')}` : cwd; - } - - return result; -} - -function processPatterns( - { patterns, ignore = [], expandDirectories = true }: GlobOptions, - cwd: string, - properties: InternalProperties -) { - if (typeof patterns === 'string') { - patterns = [patterns]; - } else if (!patterns) { - // tinyglobby exclusive behavior, should be considered deprecated - patterns = ['**/*']; - } - - if (typeof ignore === 'string') { - ignore = [ignore]; - } - - const matchPatterns: string[] = []; - const ignorePatterns: string[] = []; - - for (const pattern of ignore) { - // don't handle negated patterns here for consistency with fast-glob - if (!pattern.startsWith('!') || pattern[1] === '(') { - const newPattern = normalizePattern(pattern, expandDirectories, cwd, properties, true); - ignorePatterns.push(newPattern); - } - } - - for (const pattern of patterns) { - if (!pattern.startsWith('!') || pattern[1] === '(') { - const newPattern = normalizePattern(pattern, expandDirectories, cwd, properties, false); - matchPatterns.push(newPattern); - } else if (pattern[1] !== '!' || pattern[2] === '(') { - const newPattern = normalizePattern(pattern.slice(1), expandDirectories, cwd, properties, true); - ignorePatterns.push(newPattern); - } - } - - return { match: matchPatterns, ignore: ignorePatterns }; -} - -// TODO: this is slow, find a better way to do this -function getRelativePath(path: string, cwd: string, root: string) { - return posix.relative(cwd, `${root}/${path}`); -} - -function processPath(path: string, cwd: string, root: string, isDirectory: boolean, absolute?: boolean) { - const relativePath = absolute ? path.slice(root.length + 1) || '.' : path; - - if (root === cwd) { - return isDirectory && relativePath !== '.' ? relativePath.slice(0, -1) : relativePath; - } - - return getRelativePath(relativePath, cwd, root); -} - -function crawl(options: GlobOptions, cwd: string, sync: false): Promise; -function crawl(options: GlobOptions, cwd: string, sync: true): string[]; -function crawl(options: GlobOptions, cwd: string, sync: boolean) { - if (Array.isArray(options.patterns) && options.patterns.length === 0) { - return sync ? [] : Promise.resolve([]); - } - - const properties = { - root: cwd, - commonPath: null, - depthOffset: 0 - }; - - const processed = processPatterns(options, cwd, properties); - - const matcher = picomatch(processed.match, { - dot: options.dot, - nocase: options.caseSensitiveMatch === false, - ignore: processed.ignore - }); - - const exclude = picomatch(processed.ignore, { - dot: options.dot, - nocase: options.caseSensitiveMatch === false - }); - - const fdirOptions: Partial = { - // use relative paths in the matcher - filters: [(p, isDirectory) => matcher(processPath(p, cwd, properties.root, isDirectory, options.absolute))], - exclude: (_, p) => exclude(processPath(p, cwd, properties.root, true, true)), - pathSeparator: '/', - relativePaths: true, - resolveSymlinks: true - }; - - if (options.deep) { - fdirOptions.maxDepth = Math.round(options.deep - properties.depthOffset); - } - - if (options.absolute) { - fdirOptions.relativePaths = false; - fdirOptions.resolvePaths = true; - fdirOptions.includeBasePath = true; - } - - if (options.followSymbolicLinks === false) { - fdirOptions.resolveSymlinks = false; - fdirOptions.excludeSymlinks = true; - } - - if (options.onlyDirectories) { - fdirOptions.excludeFiles = true; - fdirOptions.includeDirs = true; - } else if (options.onlyFiles === false) { - fdirOptions.includeDirs = true; - } - - // backslashes are removed so that inferred roots like `C:/New folder \\(1\\)` work - properties.root = properties.root.replace(/\\/g, ''); - const api = new fdir(fdirOptions).crawl(properties.root); - - if (cwd === properties.root || options.absolute) { - return sync ? api.sync() : api.withPromise(); - } - - return sync - ? api.sync().map(p => getRelativePath(p, cwd, properties.root) + (!p || p.endsWith('/') ? '/' : '')) - : api - .withPromise() - .then(paths => paths.map(p => getRelativePath(p, cwd, properties.root) + (!p || p.endsWith('/') ? '/' : ''))); -} - -export function globOld(patterns: string | string[], options?: Omit): Promise; -export function globOld(options: GlobOptions): Promise; -export async function globOld( - patternsOrOptions: string | string[] | GlobOptions, - options?: GlobOptions -): Promise { - if (patternsOrOptions && options?.patterns) { - throw new Error('Cannot pass patterns as both an argument and an option'); - } - - const opts = - Array.isArray(patternsOrOptions) || typeof patternsOrOptions === 'string' - ? { ...options, patterns: patternsOrOptions } - : patternsOrOptions; - const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/'); - - return crawl(opts, cwd, false); -} - -export function globOldSync(patterns: string | string[], options?: Omit): string[]; -export function globOldSync(options: GlobOptions): string[]; -export function globOldSync(patternsOrOptions: string | string[] | GlobOptions, options?: GlobOptions): string[] { - if (patternsOrOptions && options?.patterns) { - throw new Error('Cannot pass patterns as both an argument and an option'); - } - - const opts = - Array.isArray(patternsOrOptions) || typeof patternsOrOptions === 'string' - ? { ...options, patterns: patternsOrOptions } - : patternsOrOptions; - const cwd = opts.cwd ? path.resolve(opts.cwd).replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/'); - - return crawl(opts, cwd, true); -} - -export { convertPathToPattern, escapePath, isDynamicPattern } from './utils.ts';