Skip to content

Commit

Permalink
Add findUpMultiple() method (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
vast committed Oct 7, 2021
1 parent d3a3cfc commit 8f80ac7
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 15 deletions.
Empty file added fixture/foo/bar/qux.js
Empty file.
100 changes: 100 additions & 0 deletions index.d.ts
Expand Up @@ -115,6 +115,106 @@ console.log(findUpSync(directory => {
*/
export function findUpSync(matcher: (directory: string) => Match, options?: Options): string | undefined;

/**
Find files or directories by walking up parent directories.
@param name - The name of the file or directory to find. Can be multiple.
@returns All paths found (by respecting the order of `name`s) or an empty array if none could be found.
@example
```
// /
// └── Users
// └── sindresorhus
// ├── unicorn.png
// └── foo
// ├── unicorn.png
// └── bar
// ├── baz
// └── example.js
// example.js
import {findUpMultiple} from 'find-up';
console.log(await findUpMultiple('unicorn.png'));
//=> ['/Users/sindresorhus/foo/unicorn.png', '/Users/sindresorhus/unicorn.png']
console.log(await findUpMultiple(['rainbow.png', 'unicorn.png']));
//=> ['/Users/sindresorhus/foo/unicorn.png', '/Users/sindresorhus/unicorn.png']
```
*/
export function findUpMultiple(name: string | readonly string[], options?: Options): Promise<string[]>;

/**
Find files or directories by walking up parent directories.
@param matcher - Called for each directory in the search. Return a path or `findUpStop` to stop the search.
@returns All paths found or an empty array if none could be found.
@example
```
import path from 'node:path';
import {findUpMultiple, pathExists} from 'find-up';
console.log(await findUpMultiple(async directory => {
const hasUnicorns = await pathExists(path.join(directory, 'unicorn.png'));
return hasUnicorns && directory;
}, {type: 'directory'}));
//=> ['/Users/sindresorhus/foo', '/Users/sindresorhus']
```
*/
export function findUpMultiple(matcher: (directory: string) => (Match | Promise<Match>), options?: Options): Promise<string[]>;

/**
Synchronously find files or directories by walking up parent directories.
@param name - The name of the file or directory to find. Can be multiple.
@returns All paths found (by respecting the order of `name`s) or an empty array if none could be found.
@example
```
// /
// └── Users
// └── sindresorhus
// ├── unicorn.png
// └── foo
// ├── unicorn.png
// └── bar
// ├── baz
// └── example.js
// example.js
import {findUpMultipleSync} from 'find-up';
console.log(findUpMultipleSync('unicorn.png'));
//=> ['/Users/sindresorhus/foo/unicorn.png', '/Users/sindresorhus/unicorn.png']
console.log(findUpMultipleSync(['rainbow.png', 'unicorn.png']));
//=> ['/Users/sindresorhus/foo/unicorn.png', '/Users/sindresorhus/unicorn.png']
```
*/
export function findUpMultipleSync(name: string | readonly string[], options?: Options): string[];

/**
Synchronously find files or directories by walking up parent directories.
@param matcher - Called for each directory in the search. Return a path or `findUpStop` to stop the search.
@returns All paths found or an empty array if none could be found.
@example
```
import path from 'node:path';
import {findUpMultipleSync, pathExistsSync} from 'find-up';
console.log(findUpMultipleSync(directory => {
const hasUnicorns = pathExistsSync(path.join(directory, 'unicorn.png'));
return hasUnicorns && directory;
}, {type: 'directory'}));
//=> ['/Users/sindresorhus/foo', '/Users/sindresorhus']
```
*/
export function findUpMultipleSync(matcher: (directory: string) => Match, options?: Options): string[];

/**
Check if a path exists.
Expand Down
38 changes: 28 additions & 10 deletions index.js
Expand Up @@ -3,10 +3,11 @@ import {locatePath, locatePathSync} from 'locate-path';

export const findUpStop = Symbol('findUpStop');

export async function findUp(name, options = {}) {
export async function findUpMultiple(name, options = {}) {
let directory = path.resolve(options.cwd || '');
const {root} = path.parse(directory);
const stopAt = path.resolve(directory, options.stopAt || root);
const limit = options.limit || Number.POSITIVE_INFINITY;
const paths = [name].flat();

const runMatcher = async locateOptions => {
Expand All @@ -22,31 +23,35 @@ export async function findUp(name, options = {}) {
return foundPath;
};

const matches = [];
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-await-in-loop
const foundPath = await runMatcher({...options, cwd: directory});

if (foundPath === findUpStop) {
return;
break;
}

if (foundPath) {
return path.resolve(directory, foundPath);
matches.push(path.resolve(directory, foundPath));
}

if (directory === stopAt) {
return;
if (directory === stopAt || matches.length >= limit) {
break;
}

directory = path.dirname(directory);
}

return matches;
}

export function findUpSync(name, options = {}) {
export function findUpMultipleSync(name, options = {}) {
let directory = path.resolve(options.cwd || '');
const {root} = path.parse(directory);
const stopAt = options.stopAt || root;
const limit = options.limit || Number.POSITIVE_INFINITY;
const paths = [name].flat();

const runMatcher = locateOptions => {
Expand All @@ -62,24 +67,37 @@ export function findUpSync(name, options = {}) {
return foundPath;
};

const matches = [];
// eslint-disable-next-line no-constant-condition
while (true) {
const foundPath = runMatcher({...options, cwd: directory});

if (foundPath === findUpStop) {
return;
break;
}

if (foundPath) {
return path.resolve(directory, foundPath);
matches.push(path.resolve(directory, foundPath));
}

if (directory === stopAt) {
return;
if (directory === stopAt || matches.length >= limit) {
break;
}

directory = path.dirname(directory);
}

return matches;
}

export async function findUp(name, options = {}) {
const matches = await findUpMultiple(name, {...options, limit: 1});
return matches[0];
}

export function findUpSync(name, options = {}) {
const matches = findUpMultipleSync(name, {...options, limit: 1});
return matches[0];
}

export {
Expand Down
82 changes: 81 additions & 1 deletion index.test-d.ts
@@ -1,5 +1,5 @@
import {expectType, expectError} from 'tsd';
import {findUp, findUpSync, findUpStop, pathExists, pathExistsSync} from './index.js';
import {findUp, findUpSync, findUpMultiple, findUpMultipleSync, findUpStop, pathExists, pathExistsSync} from './index.js';

expectType<Promise<string | undefined>>(findUp('unicorn.png'));
expectType<Promise<string | undefined>>(findUp('unicorn.png', {cwd: ''}));
Expand Down Expand Up @@ -52,6 +52,57 @@ expectType<Promise<string | undefined>>(findUp(async (): Promise<typeof findUpSt
expectType<Promise<string | undefined>>(findUp(async (): Promise<typeof findUpStop> => findUpStop, {type: 'directory'}));
expectType<Promise<string | undefined>>(findUp(async (): Promise<typeof findUpStop> => findUpStop, {stopAt: 'foo'}));

expectType<Promise<string[]>>(findUpMultiple('unicorn.png'));
expectType<Promise<string[]>>(findUpMultiple('unicorn.png', {cwd: ''}));
expectType<Promise<string[]>>(findUpMultiple(['rainbow.png', 'unicorn.png']));
expectType<Promise<string[]>>(findUpMultiple(['rainbow.png', 'unicorn.png'], {cwd: ''}));
expectType<Promise<string[]>>(findUpMultiple(['rainbow.png', 'unicorn.png'], {allowSymlinks: true}));
expectType<Promise<string[]>>(findUpMultiple(['rainbow.png', 'unicorn.png'], {allowSymlinks: false}));
expectType<Promise<string[]>>(findUpMultiple(['rainbow.png', 'unicorn.png'], {type: 'file'}));
expectType<Promise<string[]>>(findUpMultiple(['rainbow.png', 'unicorn.png'], {type: 'directory'}));
expectType<Promise<string[]>>(findUpMultiple(['rainbow.png', 'unicorn.png'], {stopAt: 'foo'}));
expectError(findUpMultiple(['rainbow.png', 'unicorn.png'], {concurrency: 1}));

expectType<Promise<string[]>>(findUpMultiple(() => 'unicorn.png'));
expectType<Promise<string[]>>(findUpMultiple(() => 'unicorn.png', {cwd: ''}));
expectType<Promise<string[]>>(findUpMultiple(() => 'unicorn.png', {allowSymlinks: true}));
expectType<Promise<string[]>>(findUpMultiple(() => 'unicorn.png', {allowSymlinks: false}));
expectType<Promise<string[]>>(findUpMultiple(() => 'unicorn.png', {type: 'file'}));
expectType<Promise<string[]>>(findUpMultiple(() => 'unicorn.png', {type: 'directory'}));
expectType<Promise<string[]>>(findUpMultiple(() => 'unicorn.png', {stopAt: 'foo'}));
expectType<Promise<string[]>>(findUpMultiple(() => undefined));
expectType<Promise<string[]>>(findUpMultiple(() => undefined, {cwd: ''}));
expectType<Promise<string[]>>(findUpMultiple(() => undefined, {allowSymlinks: true}));
expectType<Promise<string[]>>(findUpMultiple(() => undefined, {allowSymlinks: false}));
expectType<Promise<string[]>>(findUpMultiple(() => undefined, {type: 'file'}));
expectType<Promise<string[]>>(findUpMultiple(() => undefined, {type: 'directory'}));
expectType<Promise<string[]>>(findUpMultiple(() => undefined, {stopAt: 'foo'}));
expectType<Promise<string[]>>(findUpMultiple((): typeof findUpStop => findUpStop));
expectType<Promise<string[]>>(findUpMultiple((): typeof findUpStop => findUpStop, {cwd: ''}));
expectType<Promise<string[]>>(findUpMultiple((): typeof findUpStop => findUpStop, {stopAt: 'foo'}));
expectType<Promise<string[]>>(findUpMultiple(async () => 'unicorn.png'));
expectType<Promise<string[]>>(findUpMultiple(async () => 'unicorn.png', {cwd: ''}));
expectType<Promise<string[]>>(findUpMultiple(async () => 'unicorn.png', {allowSymlinks: true}));
expectType<Promise<string[]>>(findUpMultiple(async () => 'unicorn.png', {allowSymlinks: false}));
expectType<Promise<string[]>>(findUpMultiple(async () => 'unicorn.png', {type: 'file'}));
expectType<Promise<string[]>>(findUpMultiple(async () => 'unicorn.png', {type: 'directory'}));
expectType<Promise<string[]>>(findUpMultiple(async () => 'unicorn.png', {stopAt: 'foo'}));
expectType<Promise<string[]>>(findUpMultiple(async () => undefined));
expectType<Promise<string[]>>(findUpMultiple(async () => undefined, {cwd: ''}));
expectType<Promise<string[]>>(findUpMultiple(async () => undefined, {allowSymlinks: true}));
expectType<Promise<string[]>>(findUpMultiple(async () => undefined, {allowSymlinks: false}));
expectType<Promise<string[]>>(findUpMultiple(async () => undefined, {type: 'file'}));
expectType<Promise<string[]>>(findUpMultiple(async () => undefined, {type: 'directory'}));
expectType<Promise<string[]>>(findUpMultiple(async () => undefined, {stopAt: 'foo'}));

expectType<Promise<string[]>>(findUpMultiple(async (): Promise<typeof findUpStop> => findUpStop));
expectType<Promise<string[]>>(findUpMultiple(async (): Promise<typeof findUpStop> => findUpStop, {cwd: ''}));
expectType<Promise<string[]>>(findUpMultiple(async (): Promise<typeof findUpStop> => findUpStop, {allowSymlinks: true}));
expectType<Promise<string[]>>(findUpMultiple(async (): Promise<typeof findUpStop> => findUpStop, {allowSymlinks: false}));
expectType<Promise<string[]>>(findUpMultiple(async (): Promise<typeof findUpStop> => findUpStop, {type: 'file'}));
expectType<Promise<string[]>>(findUpMultiple(async (): Promise<typeof findUpStop> => findUpStop, {type: 'directory'}));
expectType<Promise<string[]>>(findUpMultiple(async (): Promise<typeof findUpStop> => findUpStop, {stopAt: 'foo'}));

expectType<string | undefined>(findUpSync('unicorn.png'));
expectType<string | undefined>(findUpSync('unicorn.png', {cwd: ''}));
expectType<string | undefined>(findUpSync(['rainbow.png', 'unicorn.png']));
Expand Down Expand Up @@ -82,5 +133,34 @@ expectType<string | undefined>(findUpSync((): typeof findUpStop => findUpStop, {
expectType<string | undefined>(findUpSync((): typeof findUpStop => findUpStop, {type: 'directory'}));
expectType<string | undefined>(findUpSync((): typeof findUpStop => findUpStop, {stopAt: 'foo'}));

expectType<string[]>(findUpMultipleSync('unicorn.png'));
expectType<string[]>(findUpMultipleSync('unicorn.png', {cwd: ''}));
expectType<string[]>(findUpMultipleSync(['rainbow.png', 'unicorn.png']));
expectType<string[]>(findUpMultipleSync(['rainbow.png', 'unicorn.png'], {cwd: ''}));
expectType<string[]>(findUpMultipleSync(['rainbow.png', 'unicorn.png'], {allowSymlinks: true}));
expectType<string[]>(findUpMultipleSync(['rainbow.png', 'unicorn.png'], {allowSymlinks: false}));
expectType<string[]>(findUpMultipleSync(['rainbow.png', 'unicorn.png'], {type: 'file'}));
expectType<string[]>(findUpMultipleSync(['rainbow.png', 'unicorn.png'], {type: 'directory'}));
expectType<string[]>(findUpMultipleSync(['rainbow.png', 'unicorn.png'], {stopAt: 'foo'}));
expectType<string[]>(findUpMultipleSync(() => 'unicorn.png'));
expectType<string[]>(findUpMultipleSync(() => 'unicorn.png', {cwd: ''}));
expectType<string[]>(findUpMultipleSync(() => 'unicorn.png', {allowSymlinks: true}));
expectType<string[]>(findUpMultipleSync(() => 'unicorn.png', {allowSymlinks: false}));
expectType<string[]>(findUpMultipleSync(() => 'unicorn.png', {type: 'file'}));
expectType<string[]>(findUpMultipleSync(() => 'unicorn.png', {type: 'directory'}));
expectType<string[]>(findUpMultipleSync(() => 'unicorn.png', {stopAt: 'foo'}));
expectType<string[]>(findUpMultipleSync(() => undefined));
expectType<string[]>(findUpMultipleSync(() => undefined, {cwd: ''}));
expectType<string[]>(findUpMultipleSync(() => undefined, {allowSymlinks: true}));
expectType<string[]>(findUpMultipleSync(() => undefined, {allowSymlinks: false}));
expectType<string[]>(findUpMultipleSync(() => undefined, {type: 'file'}));
expectType<string[]>(findUpMultipleSync(() => undefined, {type: 'directory'}));
expectType<string[]>(findUpMultipleSync(() => undefined, {stopAt: 'foo'}));
expectType<string[]>(findUpMultipleSync((): typeof findUpStop => findUpStop));
expectType<string[]>(findUpMultipleSync((): typeof findUpStop => findUpStop, {cwd: ''}));
expectType<string[]>(findUpMultipleSync((): typeof findUpStop => findUpStop, {type: 'file'}));
expectType<string[]>(findUpMultipleSync((): typeof findUpStop => findUpStop, {type: 'directory'}));
expectType<string[]>(findUpMultipleSync((): typeof findUpStop => findUpStop, {stopAt: 'foo'}));

expectType<Promise<boolean>>(pathExists('unicorn.png'));
expectType<boolean>(pathExistsSync('unicorn.png'));
18 changes: 18 additions & 0 deletions readme.md
Expand Up @@ -51,6 +51,15 @@ Returns a `Promise` for either the path or `undefined` if it couldn't be found.

Returns a `Promise` for either the first path found (by respecting the order of the array) or `undefined` if none could be found.

### findUpMultiple(name, options?)
### findUpMultiple(matcher, options?)

Returns a `Promise` for either an array of paths or an empty array if none could be found.

### findUpMultiple([...name], options?)

Returns a `Promise` for either an array of the first paths found (by respecting the order of the array) or an empty array if none could be found.

### findUpSync(name, options?)
### findUpSync(matcher, options?)

Expand All @@ -60,6 +69,15 @@ Returns a path or `undefined` if it couldn't be found.

Returns the first path found (by respecting the order of the array) or `undefined` if none could be found.

### findUpMultipleSync(name, options?)
### findUpMultipleSync(matcher, options?)

Returns an array of paths or an empty array if none could be found.

### findUpMultipleSync([...name], options?)

Returns an array of the first paths found (by respecting the order of the array) or an empty array if none could be found.

#### name

Type: `string`
Expand Down

0 comments on commit 8f80ac7

Please sign in to comment.