Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add findUpMultiple() method #56

Merged
merged 12 commits into from
Oct 7, 2021
Empty file added fixture/foo/bar/qux.js
Empty file.
100 changes: 100 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading