Skip to content

Commit

Permalink
Add ignoreFiles option (#228)
Browse files Browse the repository at this point in the history
Co-authored-by: Justin Ridgewell <justin@ridgewell.name>
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
3 people committed Jan 25, 2022
1 parent bd30cfb commit 895d6ec
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 24 deletions.
1 change: 1 addition & 0 deletions fixtures/ignore-files/.eslintignore
@@ -0,0 +1 @@
ignored-by-eslint.js
1 change: 1 addition & 0 deletions fixtures/ignore-files/.prettierignore
@@ -0,0 +1 @@
ignored-by-prettier.js
20 changes: 13 additions & 7 deletions gitignore.js → ignore.js
Expand Up @@ -6,21 +6,24 @@ import gitIgnore from 'ignore';
import slash from 'slash';
import {toPath, isNegativePattern} from './utilities.js';

const gitignoreGlobOptions = {
const ignoreFilesGlobOptions = {
ignore: [
'**/node_modules',
'**/flow-typed',
'**/coverage',
'**/.git',
],
absolute: true,
dot: true,
};

export const GITIGNORE_FILES_PATTERN = '**/.gitignore';

const applyBaseToPattern = (pattern, base) => isNegativePattern(pattern)
? '!' + path.posix.join(base, pattern.slice(1))
: path.posix.join(base, pattern);

const parseGitIgnoreFile = (file, cwd) => {
const parseIgnoreFile = (file, cwd) => {
const base = slash(path.relative(cwd, path.dirname(file.filePath)));

return file.content
Expand All @@ -43,7 +46,7 @@ const toRelativePath = (fileOrDirectory, cwd) => {
};

const getIsIgnoredPredicate = (files, cwd) => {
const patterns = files.flatMap(file => parseGitIgnoreFile(file, cwd));
const patterns = files.flatMap(file => parseIgnoreFile(file, cwd));
const ignores = gitIgnore().add(patterns);

return fileOrDirectory => {
Expand All @@ -57,10 +60,10 @@ const normalizeOptions = (options = {}) => ({
cwd: toPath(options.cwd) || process.cwd(),
});

export const isGitIgnored = async options => {
export const isIgnoredByIgnoreFiles = async (patterns, options) => {
const {cwd} = normalizeOptions(options);

const paths = await fastGlob('**/.gitignore', {cwd, ...gitignoreGlobOptions});
const paths = await fastGlob(patterns, {cwd, ...ignoreFilesGlobOptions});

const files = await Promise.all(
paths.map(async filePath => ({
Expand All @@ -72,10 +75,10 @@ export const isGitIgnored = async options => {
return getIsIgnoredPredicate(files, cwd);
};

export const isGitIgnoredSync = options => {
export const isIgnoredByIgnoreFilesSync = (patterns, options) => {
const {cwd} = normalizeOptions(options);

const paths = fastGlob.sync('**/.gitignore', {cwd, ...gitignoreGlobOptions});
const paths = fastGlob.sync(patterns, {cwd, ...ignoreFilesGlobOptions});

const files = paths.map(filePath => ({
filePath,
Expand All @@ -84,3 +87,6 @@ export const isGitIgnoredSync = options => {

return getIsIgnoredPredicate(files, cwd);
};

export const isGitIgnored = options => isIgnoredByIgnoreFiles(GITIGNORE_FILES_PATTERN, options);
export const isGitIgnoredSync = options => isIgnoredByIgnoreFilesSync(GITIGNORE_FILES_PATTERN, options);
9 changes: 9 additions & 0 deletions index.d.ts
Expand Up @@ -47,6 +47,15 @@ export interface Options extends FastGlobOptionsWithoutCwd {
*/
readonly gitignore?: boolean;

/**
Glob patterns to look for ignore files, which are then used to ignore globbed files.
This is a more generic form of the `gitignore` option, allowing you to find ignore files with a [compatible syntax](http://git-scm.com/docs/gitignore). For instance, this works with Babel's `.babelignore`, Prettier's `.prettierignore`, or ESLint's `.eslintignore` files.
@default undefined
*/
readonly ignoreFiles?: string | string[];

/**
The current working directory in which to search.
Expand Down
37 changes: 29 additions & 8 deletions index.js
Expand Up @@ -2,7 +2,11 @@ import fs from 'node:fs';
import merge2 from 'merge2';
import fastGlob from 'fast-glob';
import dirGlob from 'dir-glob';
import {isGitIgnored, isGitIgnoredSync} from './gitignore.js';
import {
GITIGNORE_FILES_PATTERN,
isIgnoredByIgnoreFiles,
isIgnoredByIgnoreFilesSync,
} from './ignore.js';
import {FilterStream, toPath, isNegativePattern} from './utilities.js';

const assertPatternsInput = patterns => {
Expand Down Expand Up @@ -50,13 +54,30 @@ const normalizeOptions = (options = {}) => {
const normalizeArguments = fn => async (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
const normalizeArgumentsSync = fn => (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));

const getFilter = async options => createFilterFunction(
options.gitignore && await isGitIgnored({cwd: options.cwd}),
);
const getIgnoreFilesPatterns = options => {
const {ignoreFiles, gitignore} = options;

const getFilterSync = options => createFilterFunction(
options.gitignore && isGitIgnoredSync({cwd: options.cwd}),
);
const patterns = ignoreFiles ? toPatternsArray(ignoreFiles) : [];
if (gitignore) {
patterns.push(GITIGNORE_FILES_PATTERN);
}

return patterns;
};

const getFilter = async options => {
const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
return createFilterFunction(
ignoreFilesPatterns.length > 0 && await isIgnoredByIgnoreFiles(ignoreFilesPatterns, {cwd: options.cwd}),
);
};

const getFilterSync = options => {
const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
return createFilterFunction(
ignoreFilesPatterns.length > 0 && isIgnoredByIgnoreFilesSync(ignoreFilesPatterns, {cwd: options.cwd}),
);
};

const createFilterFunction = isIgnored => {
const seen = new Set();
Expand Down Expand Up @@ -201,4 +222,4 @@ export const generateGlobTasksSync = normalizeArgumentsSync(generateTasksSync);
export {
isGitIgnored,
isGitIgnoredSync,
} from './gitignore.js';
} from './ignore.js';
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -22,7 +22,7 @@
"files": [
"index.js",
"index.d.ts",
"gitignore.js",
"ignore.js",
"utilities.js"
],
"keywords": [
Expand Down
11 changes: 10 additions & 1 deletion readme.md
Expand Up @@ -10,7 +10,7 @@ Based on [`fast-glob`](https://github.com/mrmlnc/fast-glob) but adds a bunch of
- Multiple patterns
- Negated patterns: `['foo*', '!foobar']`
- Expands directories: `foo``foo/**/*`
- Supports `.gitignore`
- Supports `.gitignore` and similar ignore config files
- Supports `URL` as `cwd`

## Install
Expand Down Expand Up @@ -88,6 +88,15 @@ Default: `false`

Respect ignore patterns in `.gitignore` files that apply to the globbed files.

##### ignoreFiles

Type: `string | string[]`\
Default: `undefined`

Glob patterns to look for ignore files, which are then used to ignore globbed files.

This is a more generic form of the `gitignore` option, allowing you to find ignore files with a [compatible syntax](http://git-scm.com/docs/gitignore). For instance, this works with Babel's `.babelignore`, Prettier's `.prettierignore`, or ESLint's `.eslintignore` files.

### globbySync(patterns, options?)

Returns `string[]` of matching paths.
Expand Down
15 changes: 15 additions & 0 deletions tests/globby.js
Expand Up @@ -241,6 +241,21 @@ test('gitignore option and objectMode option', async t => {
t.truthy(result[0].path);
});

test('respects ignoreFiles string option', async t => {
const actual = await runGlobby(t, '*', {gitignore: false, ignoreFiles: '.gitignore', onlyFiles: false});
t.false(actual.includes('node_modules'));
});

test('respects ignoreFiles array option', async t => {
const actual = await runGlobby(t, '*', {gitignore: false, ignoreFiles: ['.gitignore'], onlyFiles: false});
t.false(actual.includes('node_modules'));
});

test('glob dot files', async t => {
const actual = await runGlobby(t, '*', {gitignore: false, ignoreFiles: '*gitignore', onlyFiles: false});
t.false(actual.includes('node_modules'));
});

test('`{extension: false}` and `expandDirectories.extensions` option', async t => {
for (const temporaryDirectory of getPathValues(temporary)) {
t.deepEqual(
Expand Down
83 changes: 76 additions & 7 deletions tests/gitignore.js → tests/ignore.js
@@ -1,13 +1,18 @@
import path from 'node:path';
import test from 'ava';
import slash from 'slash';
import {isGitIgnored, isGitIgnoredSync} from '../gitignore.js';
import {
isIgnoredByIgnoreFiles,
isIgnoredByIgnoreFilesSync,
isGitIgnored,
isGitIgnoredSync,
} from '../ignore.js';
import {
PROJECT_ROOT,
getPathValues,
} from './utilities.js';

test('gitignore', async t => {
test('ignore', async t => {
for (const cwd of getPathValues(path.join(PROJECT_ROOT, 'fixtures/gitignore'))) {
// eslint-disable-next-line no-await-in-loop
const isIgnored = await isGitIgnored({cwd});
Expand All @@ -17,7 +22,7 @@ test('gitignore', async t => {
}
});

test('gitignore - mixed path styles', async t => {
test('ignore - mixed path styles', async t => {
const directory = path.join(PROJECT_ROOT, 'fixtures/gitignore');
for (const cwd of getPathValues(directory)) {
// eslint-disable-next-line no-await-in-loop
Expand All @@ -26,7 +31,7 @@ test('gitignore - mixed path styles', async t => {
}
});

test('gitignore - os paths', async t => {
test('ignore - os paths', async t => {
const directory = path.join(PROJECT_ROOT, 'fixtures/gitignore');
for (const cwd of getPathValues(directory)) {
// eslint-disable-next-line no-await-in-loop
Expand All @@ -35,7 +40,7 @@ test('gitignore - os paths', async t => {
}
});

test('gitignore - sync', t => {
test('ignore - sync', t => {
for (const cwd of getPathValues(path.join(PROJECT_ROOT, 'fixtures/gitignore'))) {
const isIgnored = isGitIgnoredSync({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
Expand All @@ -44,7 +49,7 @@ test('gitignore - sync', t => {
}
});

test('negative gitignore', async t => {
test('negative ignore', async t => {
for (const cwd of getPathValues(path.join(PROJECT_ROOT, 'fixtures/negative'))) {
// eslint-disable-next-line no-await-in-loop
const isIgnored = await isGitIgnored({cwd});
Expand All @@ -54,7 +59,7 @@ test('negative gitignore', async t => {
}
});

test('negative gitignore - sync', t => {
test('negative ignore - sync', t => {
for (const cwd of getPathValues(path.join(PROJECT_ROOT, 'fixtures/negative'))) {
const isIgnored = isGitIgnoredSync({cwd});
const actual = ['foo.js', 'bar.js'].filter(file => !isIgnored(file));
Expand Down Expand Up @@ -111,3 +116,67 @@ test('check file', async t => {
t.false(isIgnored(file));
}
});

test('custom ignore files - sync', t => {
const cwd = path.join(PROJECT_ROOT, 'fixtures/ignore-files');
const files = [
'ignored-by-eslint.js',
'ignored-by-prettier.js',
'not-ignored.js',
];

const isEslintIgnored = isIgnoredByIgnoreFilesSync('.eslintignore', {cwd});
const isPrettierIgnored = isIgnoredByIgnoreFilesSync('.prettierignore', {cwd});
const isEslintOrPrettierIgnored = isIgnoredByIgnoreFilesSync('.{prettier,eslint}ignore', {cwd});
t.deepEqual(
files.filter(file => isEslintIgnored(file)),
[
'ignored-by-eslint.js',
],
);
t.deepEqual(
files.filter(file => isPrettierIgnored(file)),
[
'ignored-by-prettier.js',
],
);
t.deepEqual(
files.filter(file => isEslintOrPrettierIgnored(file)),
[
'ignored-by-eslint.js',
'ignored-by-prettier.js',
],
);
});

test('custom ignore files - async', async t => {
const cwd = path.join(PROJECT_ROOT, 'fixtures/ignore-files');
const files = [
'ignored-by-eslint.js',
'ignored-by-prettier.js',
'not-ignored.js',
];

const isEslintIgnored = await isIgnoredByIgnoreFiles('.eslintignore', {cwd});
const isPrettierIgnored = await isIgnoredByIgnoreFiles('.prettierignore', {cwd});
const isEslintOrPrettierIgnored = await isIgnoredByIgnoreFiles('.{prettier,eslint}ignore', {cwd});
t.deepEqual(
files.filter(file => isEslintIgnored(file)),
[
'ignored-by-eslint.js',
],
);
t.deepEqual(
files.filter(file => isPrettierIgnored(file)),
[
'ignored-by-prettier.js',
],
);
t.deepEqual(
files.filter(file => isEslintOrPrettierIgnored(file)),
[
'ignored-by-eslint.js',
'ignored-by-prettier.js',
],
);
});

0 comments on commit 895d6ec

Please sign in to comment.