diff --git a/options-manager.js b/options-manager.js index 8695c7b5..b6d242d7 100644 --- a/options-manager.js +++ b/options-manager.js @@ -7,6 +7,8 @@ const deepAssign = require('deep-assign'); const multimatch = require('multimatch'); const resolveFrom = require('resolve-from'); const pathExists = require('path-exists'); +const parseGitignore = require('parse-gitignore'); +const globby = require('globby'); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -203,11 +205,29 @@ function groupConfigs(paths, baseOptions, overrides) { return arr; } +function getIgnores(opts) { + opts.ignores = DEFAULT_IGNORE.concat(opts.ignores || []); + const gitignores = globby.sync('**/.gitignore', {ignore: opts.ignores, cwd: opts.cwd || process.cwd()}); + const ignores = gitignores + .map(pathToGitignore => { + const patterns = parseGitignore(pathToGitignore); + const base = path.dirname(pathToGitignore); + + return patterns.map(file => path.join(base, file)); + }) + .reduce((a, b) => a.concat(b), []); + + opts.ignores = opts.ignores.concat(ignores); + + return opts; +} + function preprocess(opts) { opts = mergeWithPkgConf(opts); opts = normalizeOpts(opts); - opts.ignores = DEFAULT_IGNORE.concat(opts.ignores || []); + opts = getIgnores(opts); opts.extensions = DEFAULT_EXTENSION.concat(opts.extensions || []); + return opts; } @@ -221,3 +241,4 @@ exports.mergeApplicableOverrides = mergeApplicableOverrides; exports.groupConfigs = groupConfigs; exports.preprocess = preprocess; exports.emptyOptions = emptyOptions; +exports.getIgnores = getIgnores; diff --git a/package.json b/package.json index ceea5f6b..d4f46a7e 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "has-flag": "^2.0.0", "meow": "^3.4.2", "multimatch": "^2.1.0", + "parse-gitignore": "^0.3.1", "path-exists": "^3.0.0", "pkg-conf": "^2.0.0", "resolve-cwd": "^1.0.0", diff --git a/readme.md b/readme.md index 50e9767d..eced4653 100644 --- a/readme.md +++ b/readme.md @@ -161,7 +161,7 @@ Additional global variables your code accesses during execution. Type: `Array` -Some [paths](https://github.com/sindresorhus/xo/blob/master/options-manager.js) are ignored by default. Additional ignores can be added here. +Some [paths](https://github.com/sindresorhus/xo/blob/master/options-manager.js) are ignored by default, including paths in .gitignore. Additional ignores can be added here. ### space diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..c542df16 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +# This is a test .gitignore. +foo/ diff --git a/test/bar/.gitignore b/test/bar/.gitignore new file mode 100644 index 00000000..066381ba --- /dev/null +++ b/test/bar/.gitignore @@ -0,0 +1 @@ +foo.js \ No newline at end of file diff --git a/test/bar/foobar/.gitignore b/test/bar/foobar/.gitignore new file mode 100644 index 00000000..b80cafad --- /dev/null +++ b/test/bar/foobar/.gitignore @@ -0,0 +1 @@ +bar.js \ No newline at end of file diff --git a/test/cli.js b/test/cli.js index 48e1c497..0df64b72 100644 --- a/test/cli.js +++ b/test/cli.js @@ -31,6 +31,17 @@ test.failing('ignores fixture', async t => { t.throws(execa('../../../cli.js', ['--no-local'], {cwd})); }); +test('ignore files in .gitignore', async t => { + const cwd = path.join(__dirname, 'fixtures/gitignore'); + + try { + await execa('../../../cli.js', ['--no-local'], {cwd}); + } catch (err) { + t.is(err.stdout.indexOf('foo.js'), -1); + t.true(err.stdout.indexOf('bar.js') !== -1); + } +}); + test('supports being extended with a shareable config', async () => { const cwd = path.join(__dirname, 'fixtures/project'); await execa('../../../cli.js', ['--no-local'], {cwd}); diff --git a/test/fixtures/gitignore/index.js b/test/fixtures/gitignore/index.js new file mode 100644 index 00000000..0a9af888 --- /dev/null +++ b/test/fixtures/gitignore/index.js @@ -0,0 +1,5 @@ +'use strict' + +module.exports = function (foo) { + return foo + 'bar' +} diff --git a/test/fixtures/gitignore/test/.gitignore b/test/fixtures/gitignore/test/.gitignore new file mode 100644 index 00000000..066381ba --- /dev/null +++ b/test/fixtures/gitignore/test/.gitignore @@ -0,0 +1 @@ +foo.js \ No newline at end of file diff --git a/test/fixtures/gitignore/test/bar.js b/test/fixtures/gitignore/test/bar.js new file mode 100644 index 00000000..11c61b8a --- /dev/null +++ b/test/fixtures/gitignore/test/bar.js @@ -0,0 +1,6 @@ +import test from 'ava' +import fn from '../' + +test(t => { + t.is(fn('foo'), fn('foobar')) +}) diff --git a/test/fixtures/gitignore/test/foo.js b/test/fixtures/gitignore/test/foo.js new file mode 100644 index 00000000..bc98824b --- /dev/null +++ b/test/fixtures/gitignore/test/foo.js @@ -0,0 +1,6 @@ +import test from 'ava' +import fn from '../' + +test(t => { + t.is(fn('foo'), fn('foobar')) +}) diff --git a/test/options-manager.js b/test/options-manager.js index 3fff1a72..7f6b5cf4 100644 --- a/test/options-manager.js +++ b/test/options-manager.js @@ -142,6 +142,24 @@ test('groupConfigs', t => { })); }); +test('gitignore', t => { + const result = manager.getIgnores({}); + t.not(result.ignores.indexOf(path.join('foo', '**')), -1); + t.not(result.ignores.indexOf(path.join('bar', 'foo.js')), -1); +}); + +test('ignore ignored .gitignore', t => { + const opts = { + ignores: [ + '**/foobar/**' + ] + }; + + const result = manager.getIgnores(opts); + + t.is(result.ignores.indexOf(path.join('bar', 'foobar', 'bar.js')), -1); +}); + test('mergeWithPkgConf: use child if closest', t => { const cwd = path.resolve('fixtures', 'nested', 'child'); const result = manager.mergeWithPkgConf({cwd});