Skip to content

Commit

Permalink
feat(ignore): add support for eslint-ignore (#24)
Browse files Browse the repository at this point in the history
Closes #16

BREAKING CHANGE: files matched by an `.eslintignore` will no longer be formatted by default. Use `--no-eslint-ignore` to disable this
  • Loading branch information
Kent C. Dodds committed Feb 21, 2017
1 parent 31e9e92 commit 7caaf17
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Expand Up @@ -2,4 +2,4 @@ node_modules
.nyc_output
coverage
dist
cli-test/fixtures
cli-test/fixtures
12 changes: 11 additions & 1 deletion README.md
Expand Up @@ -83,9 +83,13 @@ Options:
--version Show version number [boolean]
--write Edit the file in-place (beware!) [boolean] [default: false]
--stdin Read input via stdin [boolean] [default: false]
--eslint-ignore Only format matching files even if they are not ignored by
.eslintignore. (can use --no-eslint-ignore to disable this)
[boolean] [default: true]
--eslintPath The path to the eslint module to use
[default: "/Users/kdodds/Developer/prettier-eslint-cli/node_modules/eslint"]
[default: "<path-to-root>/node_modules/eslint"]
--prettierPath The path to the prettier module to use
[default: "<path-to-root>/node_modules/prettier"]
--ignore pattern(s) you wish to ignore (can be used multiple times and
includes **/node_modules/** automatically)
--log-level, -l The log level to use
Expand Down Expand Up @@ -136,6 +140,12 @@ Forwarded as the `prettierPath` option to `prettier-eslint`

Forwarded as `logLevel` option to `prettier-eslint`

#### --no-eslint-ignore

Disables application of `.eslintignore` to the files resolved from the glob. By
default, `prettier-eslint-cli` will exclude files if they are matched by a
`.eslintignore`. Add this flag to disable this behavior.

> Note: You can also set the `LOG_LEVEL` environment variable to control logging in `prettier-eslint`
## Related
Expand Down
5 changes: 5 additions & 0 deletions __mocks__/find-up.js
@@ -0,0 +1,5 @@
module.exports = {sync}

function sync(filename) {
return `/${filename}`
}
10 changes: 9 additions & 1 deletion __mocks__/fs.js
Expand Up @@ -5,4 +5,12 @@ const writeFile = jest.fn((filePath, contents, callback) => {
callback(null)
})

module.exports = {readFile, writeFile}
const readFileSync = jest.fn(filePath => {
if (filePath.indexOf('eslintignore')) {
return '*ignored*\n**/ignored/**\n'
} else {
throw new Error('readFileSync mock does nto yet handle ', filePath)
}
})

module.exports = {readFile, writeFile, readFileSync}
10 changes: 10 additions & 0 deletions __mocks__/glob.js
Expand Up @@ -42,6 +42,12 @@ module.exports = jest.fn(function mockGlob(globString, options, callback) {
fredProject('ignored3.js'),
fredProject('applied4.js'),
])
} else if (globString.includes('no-eslint-ignore')) {
callback(null, [
barneyProject('no-ignore/1.js'),
barneyProject('no-ignore/2.js'),
barneyProject('no-ignore/3.js'),
])
} else {
throw new Error(
`Your test globString: "${globString}"` +
Expand All @@ -53,3 +59,7 @@ module.exports = jest.fn(function mockGlob(globString, options, callback) {
function fredProject(path) {
return `/Users/fredFlintstone/Developer/top-secret/footless-carriage/${path}`
}

function barneyProject(path) {
return `/Users/barneyRubble/Developer/top-secret/tesla/${path}`
}
4 changes: 2 additions & 2 deletions cli-test/tests/__snapshots__/index.js.snap
@@ -1,4 +1,4 @@
exports[`file contents: prettier-eslint cli-test/fixtures/example*.js --write 1`] = `
exports[`file contents: prettier-eslint cli-test/fixtures/example*.js --write --no-eslint-ignore 1`] = `
Object {
"example1Result": "const {example1} = baz.bar
",
Expand All @@ -14,7 +14,7 @@ exports[`stdout: --version 1`] = `
"
`;

exports[`stdout: prettier-eslint cli-test/fixtures/example*.js --write 1`] = `
exports[`stdout: prettier-eslint cli-test/fixtures/example*.js --write --no-eslint-ignore 1`] = `
"success formatting 2 files with prettier-eslint
"
`;
8 changes: 5 additions & 3 deletions cli-test/tests/index.js
Expand Up @@ -27,7 +27,7 @@ test('help outputs usage information and flags', async () => {
expect(stdout).toContain('Options:\n')
// just a sanity check.
// If it's ever longer than 2000 then we've probably got a problem...
if (stdout.length > 1600) {
if (stdout.length > 2000) {
console.error(stdout)
throw new Error(
'We probably have a problem. The --help output is probably too long...',
Expand All @@ -38,7 +38,9 @@ test('help outputs usage information and flags', async () => {
test('formats files and outputs to stdout', async () => {
// can't just do the testOutput function here because
// the output is in an undeterministic order
const stdout = await runPrettierESLintCLI('cli-test/fixtures/stdout*.js')
const stdout = await runPrettierESLintCLI(
'cli-test/fixtures/stdout*.js --no-eslint-ignore',
)
expect(stdout).toContain(
stripIndent(
`
Expand Down Expand Up @@ -71,7 +73,7 @@ test('accepts stdin of code', async () => {
expect(stdout).toEqual('console.log(window.baz, typeof [])\n\n')
})

const writeCommand = 'cli-test/fixtures/example*.js --write'
const writeCommand = 'cli-test/fixtures/example*.js --write --no-eslint-ignore'

test(`prettier-eslint ${writeCommand}`, async () => {
// because we're using --write,
Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -26,6 +26,8 @@
"find-up": "^2.1.0",
"get-stdin": "^5.0.1",
"glob": "^7.1.1",
"ignore": "^3.2.4",
"lodash.memoize": "^4.1.2",
"messageformat": "^1.0.2",
"prettier-eslint": "^4.0.4",
"rxjs": "^5.1.1",
Expand Down
62 changes: 59 additions & 3 deletions src/format-files.js
@@ -1,15 +1,24 @@
/* eslint no-console:0 */
import path from 'path'
import fs from 'fs'
import glob from 'glob'
import Rx from 'rxjs/Rx'
import format from 'prettier-eslint'
import chalk from 'chalk'
import getStdin from 'get-stdin'
import nodeIgnore from 'ignore'
import findUp from 'find-up'
import memoize from 'lodash.memoize'
import * as messages from './messages'

const LINE_SEPERATOR_REGEX = /(\r|\n|\r\n)/
const rxGlob = Rx.Observable.bindNodeCallback(glob)
const rxReadFile = Rx.Observable.bindNodeCallback(fs.readFile)
const rxWriteFile = Rx.Observable.bindNodeCallback(fs.writeFile)
const findUpSyncMemoized = memoize(findUpSync, function resolver(...args) {
return args.join('::')
})
const getIsIgnoredMemoized = memoize(getIsIgnored)

export default formatFilesFromArgv

Expand All @@ -22,6 +31,7 @@ async function formatFilesFromArgv(
eslintPath,
prettierPath,
ignore: ignoreGlobs = [],
eslintIgnore: applyEslintIgnore = true,
},
) {
const prettierESLintOptions = {logLevel, eslintPath, prettierPath}
Expand All @@ -34,6 +44,7 @@ async function formatFilesFromArgv(
[...ignoreGlobs], // make a copy to avoid manipulation
cliOptions,
prettierESLintOptions,
applyEslintIgnore,
)
}
}
Expand All @@ -59,6 +70,7 @@ async function formatFilesFromGlobs(
ignoreGlobs,
cliOptions,
prettierESLintOptions,
applyEslintIgnore,
) {
const concurrentGlobs = 3
const concurrentFormats = 10
Expand All @@ -68,7 +80,11 @@ async function formatFilesFromGlobs(
const unchanged = []
Rx.Observable
.from(fileGlobs)
.mergeMap(getFilesFromGlob.bind(null, ignoreGlobs), null, concurrentGlobs)
.mergeMap(
getFilesFromGlob.bind(null, ignoreGlobs, applyEslintIgnore),
null,
concurrentGlobs,
)
.concatAll()
.distinct()
.mergeMap(filePathToFormatted, null, concurrentFormats)
Expand Down Expand Up @@ -131,14 +147,20 @@ async function formatFilesFromGlobs(
})
}

function getFilesFromGlob(ignoreGlobs, fileGlob) {
function getFilesFromGlob(ignoreGlobs, applyEslintIgnore, fileGlob) {
const globOptions = {ignore: ignoreGlobs}
if (!fileGlob.includes('node_modules')) {
// basically, we're going to protect you from doing something
// not smart unless you explicitly include it in your glob
globOptions.ignore.push('**/node_modules/**')
}
return rxGlob(fileGlob, globOptions)
return rxGlob(fileGlob, globOptions).map(filePaths => {
return filePaths.filter(filePath => {
return applyEslintIgnore ?
!isFilePathMatchedByEslintignore(filePath) :
true
})
})
}

function formatFile(filePath, prettierESLintOptions, cliOptions) {
Expand Down Expand Up @@ -169,6 +191,40 @@ function formatFile(filePath, prettierESLintOptions, cliOptions) {
})
}

function getNearestEslintignorePath(filePath) {
const {dir} = path.parse(filePath)
return findUpSyncMemoized('.eslintignore', dir)
}

function isFilePathMatchedByEslintignore(filePath) {
const eslintignorePath = getNearestEslintignorePath(filePath)
if (!eslintignorePath) {
return false
}

const eslintignoreDir = path.parse(eslintignorePath).dir
const filePathRelativeToEslintignoreDir = path.relative(
eslintignoreDir,
filePath,
)
const isIgnored = getIsIgnoredMemoized(eslintignorePath)
return isIgnored(filePathRelativeToEslintignoreDir)
}

function logError(...args) {
console.error('prettier-eslint-cli error:', ...args)
}

function findUpSync(filename, cwd) {
return findUp.sync('.eslintignore', {cwd})
}

function getIsIgnored(filename) {
const ignoreLines = fs
.readFileSync(filename, 'utf8')
.split(LINE_SEPERATOR_REGEX)
.filter(line => Boolean(line.trim()))
const instance = nodeIgnore()
instance.add(ignoreLines)
return instance.ignores.bind(instance)
}
46 changes: 46 additions & 0 deletions src/format-files.test.js
@@ -1,5 +1,6 @@
/* eslint no-console:0 */
import fsMock from 'fs'
import findUpMock from 'find-up'
import formatMock from 'prettier-eslint'
import globMock from 'glob'
import mockGetStdin from 'get-stdin'
Expand Down Expand Up @@ -127,3 +128,48 @@ test('allows you to specify an ignore glob', async () => {
const callback = expect.any(Function)
expect(globMock).toHaveBeenCalledWith(fileGlob, globOptions, callback)
})

test('wont modify a file if it is eslint ignored', async () => {
await formatFiles({_: ['src/**/ignored*.js'], write: true})
expect(fsMock.readFile).toHaveBeenCalledTimes(1)
expect(fsMock.writeFile).toHaveBeenCalledTimes(1)
expect(fsMock.readFile).toHaveBeenCalledWith(
expect.stringMatching(/applied/),
'utf8',
expect.any(Function),
)
expect(fsMock.writeFile).toHaveBeenCalledWith(
expect.stringMatching(/applied/),
expect.stringMatching(/MOCK_OUTPUT.*?applied/),
expect.any(Function),
)
const ignoredOutput = expect.stringMatching(/success.*1.*file/)
expect(console.log).toHaveBeenCalledWith(ignoredOutput)
})

test('will modify a file if it is eslint ignored with noIgnore', async () => {
await formatFiles({
_: ['src/**/ignored*.js'],
write: true,
eslintIgnore: false,
})
expect(fsMock.readFile).toHaveBeenCalledTimes(4)
expect(fsMock.writeFile).toHaveBeenCalledTimes(4)
const ignoredOutput = expect.stringMatching(/success.*4.*files/)
expect(console.log).toHaveBeenCalledWith(ignoredOutput)
})

test('will not blow up if an .eslintignore cannot be found', async () => {
const originalSync = findUpMock.sync
findUpMock.sync = () => null
try {
await formatFiles({
_: ['src/**/no-eslint-ignore/*.js'],
write: true,
})
} catch (error) {
throw error
} finally {
findUpMock.sync = originalSync
}
})
11 changes: 11 additions & 0 deletions src/parser.js
Expand Up @@ -16,13 +16,23 @@ const parser = yargs
type: 'boolean',
},
stdin: {default: false, describe: 'Read input via stdin', type: 'boolean'},
'eslint-ignore': {
default: true,
type: 'boolean',
describe: oneLine`
Only format matching files even if
they are not ignored by .eslintignore.
(can use --no-eslint-ignore to disable this)
`,
},
eslintPath: {
default: getPathInHostNodeModules('eslint'),
describe: 'The path to the eslint module to use',
coerce: coercePath,
},
prettierPath: {
describe: 'The path to the prettier module to use',
default: getPathInHostNodeModules('prettier'),
coerce: coercePath,
},
ignore: {
Expand Down Expand Up @@ -54,6 +64,7 @@ const parser = yargs
// describe: 'Path to the prettier config to use',
// },,
})
.strict()

export default parser

Expand Down
6 changes: 5 additions & 1 deletion yarn.lock
Expand Up @@ -2492,7 +2492,7 @@ ieee754@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"

ignore@^3.2.0:
ignore@^3.2.0, ignore@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.4.tgz#4055e03596729a8fabe45a43c100ad5ed815c4e8"

Expand Down Expand Up @@ -3334,6 +3334,10 @@ lodash.map@^4.5.1:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"

lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"

lodash.pad@^4.1.0:
version "4.5.1"
resolved "https://registry.yarnpkg.com/lodash.pad/-/lodash.pad-4.5.1.tgz#4330949a833a7c8da22cc20f6a26c4d59debba70"
Expand Down

0 comments on commit 7caaf17

Please sign in to comment.