diff --git a/README.md b/README.md index c4b47c2..d3f518e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ filter only the errors introduced in the commit range (and nothing more). ```sh # This will lint the last commit - difflint lint --commit-range HEAD^..HEAD + difflint lint HEAD^..HEAD ``` ## Examples @@ -42,12 +42,18 @@ filter only the errors introduced in the commit range (and nothing more). 1. Lint the last 3 commits: ```sh - difflint lint --commit-range HEAD~3..HEAD + difflint lint HEAD~3..HEAD ``` -2. Lint all commits from a build in Travis: +2. Lint local changes that are not yet commited (similar to what [lint-staged](https://github.com/okonet/lint-staged) do): + + ```sh + difflint lint HEAD + ``` + +3. Lint all commits from a build in [Travis](https://travis-ci.org): ```sh # This environment variable will be available in any Travis build - difflint lint --commit-range $TRAVIS_COMMIT_RANGE + difflint lint $TRAVIS_COMMIT_RANGE ``` diff --git a/src/difflint.js b/src/difflint.js new file mode 100644 index 0000000..7bd413c --- /dev/null +++ b/src/difflint.js @@ -0,0 +1,109 @@ +import Promise from 'bluebird' +import exec from 'execa' +import path from 'path' +import { CLIEngine } from 'eslint' +import { + T, + assoc, + cond, + curry, + curryN, + endsWith, + evolve, + equals, + filter, + find, + map, + objOf, + pipe, + pipeP, + pluck, + prop, + propEq, + split, + sum, + tap, +} from 'ramda' +import { getChangedLinesFromDiff } from './lib/git' + +const linter = new CLIEngine() +const formatter = linter.getFormatter() + +const getChangedFiles = pipeP( + commitRange => exec('git', ['diff', commitRange, '--name-only']), + prop('stdout'), + split('\n'), + filter(endsWith('.js')), + map(path.resolve) +) + +const getDiff = curry((commitRange, filename) => + exec('git', ['diff', commitRange, filename]) + .then(prop('stdout'))) + +const getChangedFileLineMap = curry((commitRange, filePath) => pipeP( + getDiff(commitRange), + getChangedLinesFromDiff, + objOf('changedLines'), + assoc('filePath', filePath) +)(filePath)) + +const lintChangedLines = pipe( + map(prop('filePath')), + linter.executeOnFiles.bind(linter) +) + +const filterLinterMessages = changedFileLineMap => (linterOutput) => { + const filterMessagesByFile = (result) => { + const fileLineMap = find(propEq('filePath', result.filePath), changedFileLineMap) + const changedLines = prop('changedLines', fileLineMap) + + const filterMessages = evolve({ + messages: filter(message => changedLines.includes(message.line)), + }) + + return filterMessages(result) + } + + return pipe( + prop('results'), + map(filterMessagesByFile), + objOf('results') + )(linterOutput) +} + +const applyLinter = changedFileLineMap => pipe( + lintChangedLines, + filterLinterMessages(changedFileLineMap) +)(changedFileLineMap) + +const logResults = pipe( + prop('results'), + formatter, + console.log +) + +const getErrorCountFromReport = pipe( + prop('results'), + pluck('errorCount'), + sum +) + +const exitProcess = curryN(2, n => process.exit(n)) + +const reportResults = pipe( + tap(logResults), + getErrorCountFromReport, + cond([ + [equals(0), exitProcess(0)], + [T, exitProcess(1)], + ]) +) + +const run = commitRange => Promise.resolve(commitRange) + .then(getChangedFiles) + .map(getChangedFileLineMap(commitRange)) + .then(applyLinter) + .then(reportResults) + +export default run diff --git a/src/index.js b/src/index.js index 7c46963..955cf24 100644 --- a/src/index.js +++ b/src/index.js @@ -1,91 +1,8 @@ -import Promise from 'bluebird' -import exec from 'execa' import program from 'commander' -import path from 'path' -import { CLIEngine } from 'eslint' -import { - assoc, - curry, - endsWith, - evolve, - filter, - find, - map, - objOf, - pipe, - pipeP, - prop, - propEq, - split, -} from 'ramda' -import { getChangedLinesFromDiff } from './lib/git' - -const linter = new CLIEngine() -const formatter = linter.getFormatter() - -const getChangedFiles = pipeP( - commitRange => exec('git', ['diff', commitRange, '--name-only']), - prop('stdout'), - split('\n'), - filter(endsWith('.js')), - map(path.resolve) -) - -const getDiff = curry((commitRange, filename) => - exec('git', ['diff', commitRange, filename]) - .then(prop('stdout'))) - -const getChangedFileLineMap = curry((commitRange, filePath) => pipeP( - getDiff(commitRange), - getChangedLinesFromDiff, - objOf('changedLines'), - assoc('filePath', filePath) -)(filePath)) - - -const lintChangedLines = pipe( - map(prop('filePath')), - linter.executeOnFiles.bind(linter) -) - -const filterLinterMessages = changedFileLineMap => (linterOutput) => { - const filterMessagesByFile = (result) => { - const fileLineMap = find(propEq('filePath', result.filePath), changedFileLineMap) - const changedLines = prop('changedLines', fileLineMap) - - const filterMessages = evolve({ - messages: filter(message => changedLines.includes(message.line)), - }) - - return filterMessages(result) - } - - return pipe( - prop('results'), - map(filterMessagesByFile), - objOf('results') - )(linterOutput) -} - -const applyLinter = changedFileLineMap => pipe( - lintChangedLines, - filterLinterMessages(changedFileLineMap) -)(changedFileLineMap) - -const reportResults = pipe( - prop('results'), - formatter -) - -const run = commitRange => Promise.resolve(commitRange) - .then(getChangedFiles) - .map(getChangedFileLineMap(commitRange)) - .then(applyLinter) - .then(reportResults) - .tap(console.log) +import run from './difflint' program - .command('lint ') + .command('lint ') .action(run) program.parse(process.argv)