Skip to content

Commit

Permalink
feat: add --fail-fast cli option
Browse files Browse the repository at this point in the history
Related to #2294
  • Loading branch information
Jason3S committed Jan 26, 2022
1 parent 26dd25a commit 645a9a4
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 29 deletions.
2 changes: 1 addition & 1 deletion cspell.schema.json
Expand Up @@ -1077,7 +1077,7 @@
},
"failFast": {
"default": false,
"description": "Exit with non-zero code as soon as an issue/error encountered (useful for CI or git hooks)",
"description": "Exit with non-zero code as soon as an issue/error is encountered (useful for CI or git hooks)",
"type": "boolean"
},
"features": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cspell-types/cspell.schema.json
Expand Up @@ -1077,7 +1077,7 @@
},
"failFast": {
"default": false,
"description": "Exit with non-zero code as soon as an issue/error encountered (useful for CI or git hooks)",
"description": "Exit with non-zero code as soon as an issue/error is encountered (useful for CI or git hooks)",
"type": "boolean"
},
"features": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cspell-types/src/CSpellSettingsDef.ts
Expand Up @@ -267,7 +267,7 @@ export interface CommandLineSettings {
*/
cache?: CacheSettings;
/**
* Exit with non-zero code as soon as an issue/error encountered (useful for CI or git hooks)
* Exit with non-zero code as soon as an issue/error is encountered (useful for CI or git hooks)
* @default false
*/
failFast?: boolean;
Expand Down
37 changes: 37 additions & 0 deletions packages/cspell/src/__snapshots__/app.test.ts.snap
@@ -1,5 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Validate cli app --fail-fast no option Expect Error: [Function CheckFailed] 1`] = `Array []`;

exports[`Validate cli app --fail-fast no option Expect Error: [Function CheckFailed] 2`] = `
"error 1/2 ./samples/fail-fast/first-fail.txt 0.00ms X
log ./samples/fail-fast/first-fail.txt:1:1 - Unknown word (iamtypo)
error 2/2 ./samples/fail-fast/second-fail.txt 0.00ms X
log ./samples/fail-fast/second-fail.txt:1:1 - Unknown word (iamtypotoo)
error CSpell: Files checked: 2, Issues found: 2 in 2 files"
`;

exports[`Validate cli app --fail-fast with config Expect Error: [Function CheckFailed] 1`] = `Array []`;

exports[`Validate cli app --fail-fast with config Expect Error: [Function CheckFailed] 2`] = `
"error 1/2 ./samples/fail-fast/first-fail.txt 0.00ms X
log ./samples/fail-fast/first-fail.txt:1:1 - Unknown word (iamtypo)
error CSpell: Files checked: 1, Issues found: 1 in 1 files"
`;

exports[`Validate cli app --fail-fast with option Expect Error: [Function CheckFailed] 1`] = `Array []`;

exports[`Validate cli app --fail-fast with option Expect Error: [Function CheckFailed] 2`] = `
"error 1/2 ./samples/fail-fast/first-fail.txt 0.00ms X
log ./samples/fail-fast/first-fail.txt:1:1 - Unknown word (iamtypo)
error CSpell: Files checked: 1, Issues found: 1 in 1 files"
`;

exports[`Validate cli app --help Expect Error: outputHelp 1`] = `
Array [
"Usage: cspell [options] [command]",
Expand Down Expand Up @@ -27,6 +53,16 @@ Array [

exports[`Validate cli app --help Expect Error: outputHelp 2`] = `""`;

exports[`Validate cli app --no-fail-fast with config Expect Error: [Function CheckFailed] 1`] = `Array []`;

exports[`Validate cli app --no-fail-fast with config Expect Error: [Function CheckFailed] 2`] = `
"error 1/2 ./samples/fail-fast/first-fail.txt 0.00ms X
log ./samples/fail-fast/first-fail.txt:1:1 - Unknown word (iamtypo)
error 2/2 ./samples/fail-fast/second-fail.txt 0.00ms X
log ./samples/fail-fast/second-fail.txt:1:1 - Unknown word (iamtypotoo)
error CSpell: Files checked: 2, Issues found: 2 in 2 files"
`;

exports[`Validate cli app LICENSE Expect Error: undefined 1`] = `Array []`;

exports[`Validate cli app LICENSE Expect Error: undefined 2`] = `
Expand Down Expand Up @@ -318,6 +354,7 @@ Array [
" --no-progress Turn off progress messages",
" --no-summary Turn off summary message in console.",
" -s, --silent Silent mode, suppress error messages.",
" --fail-fast Exit after first file with an issue or error.",
" -r, --root <root folder> Root directory, defaults to current directory.",
" --relative Issues are displayed relative to root.",
" --show-context Show the surrounding text around an issue.",
Expand Down
54 changes: 30 additions & 24 deletions packages/cspell/src/app.test.ts
Expand Up @@ -119,31 +119,37 @@ describe('Validate cli', () => {
chalk.level = colorLevel;
});

const failFastConfig = pathSamples('fail-fast/fail-fast-cspell.json');

test.each`
msg | testArgs | errorCheck | eError | eLog | eInfo
${'with errors and excludes'} | ${['-r', 'samples', '*', '-e', 'Dutch.txt', '-c', 'samples/.cspell.json']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'no-args'} | ${[]} | ${'outputHelp'} | ${false} | ${false} | ${false}
${'--help'} | ${['--help']} | ${'outputHelp'} | ${false} | ${false} | ${false}
${'current_file'} | ${[__filename]} | ${undefined} | ${true} | ${false} | ${false}
${'with spelling errors Dutch.txt'} | ${[pathSamples('Dutch.txt')]} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'with spelling errors Dutch.txt words only'} | ${[pathSamples('Dutch.txt'), '--wordsOnly']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'with spelling errors Dutch.txt --legacy'} | ${[pathSamples('Dutch.txt'), '--legacy']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'with spelling errors --silent Dutch.txt'} | ${['--silent', pathSamples('Dutch.txt')]} | ${app.CheckFailed} | ${false} | ${false} | ${false}
${'current_file languageId'} | ${[__filename, '--languageId=typescript']} | ${undefined} | ${true} | ${false} | ${false}
${'check help'} | ${['check', '--help']} | ${'outputHelp'} | ${false} | ${false} | ${false}
${'check LICENSE'} | ${['check', pathRoot('LICENSE')]} | ${undefined} | ${false} | ${true} | ${false}
${'check missing'} | ${['check', pathRoot('missing-file.txt')]} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'check with spelling errors'} | ${['check', pathSamples('Dutch.txt')]} | ${app.CheckFailed} | ${false} | ${true} | ${false}
${'LICENSE'} | ${[pathRoot('LICENSE')]} | ${undefined} | ${true} | ${false} | ${false}
${'samples/Dutch.txt'} | ${[pathSamples('Dutch.txt')]} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'with forbidden words'} | ${[pathSamples('src/sample-with-forbidden-words.md')]} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'current_file --verbose'} | ${['--verbose', __filename]} | ${undefined} | ${true} | ${false} | ${true}
${'bad config'} | ${['-c', __filename, __filename]} | ${app.CheckFailed} | ${true} | ${false} | ${false}
${'not found error by default'} | ${['*.not']} | ${app.CheckFailed} | ${true} | ${false} | ${false}
${'must find with error'} | ${['*.not', '--must-find-files']} | ${app.CheckFailed} | ${true} | ${false} | ${false}
${'must find force no error'} | ${['*.not', '--no-must-find-files']} | ${undefined} | ${true} | ${false} | ${false}
${'cspell-bad.json'} | ${['-c', pathSamples('cspell-bad.json'), __filename]} | ${undefined} | ${true} | ${false} | ${false}
${'cspell-import-missing.json'} | ${['-c', pathSamples('linked/cspell-import-missing.json'), __filename]} | ${app.CheckFailed} | ${true} | ${false} | ${false}
msg | testArgs | errorCheck | eError | eLog | eInfo
${'with errors and excludes'} | ${['-r', 'samples', '*', '-e', 'Dutch.txt', '-c', 'samples/.cspell.json']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'no-args'} | ${[]} | ${'outputHelp'} | ${false} | ${false} | ${false}
${'--help'} | ${['--help']} | ${'outputHelp'} | ${false} | ${false} | ${false}
${'current_file'} | ${[__filename]} | ${undefined} | ${true} | ${false} | ${false}
${'with spelling errors Dutch.txt'} | ${[pathSamples('Dutch.txt')]} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'with spelling errors Dutch.txt words only'} | ${[pathSamples('Dutch.txt'), '--wordsOnly']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'with spelling errors Dutch.txt --legacy'} | ${[pathSamples('Dutch.txt'), '--legacy']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'with spelling errors --silent Dutch.txt'} | ${['--silent', pathSamples('Dutch.txt')]} | ${app.CheckFailed} | ${false} | ${false} | ${false}
${'current_file languageId'} | ${[__filename, '--languageId=typescript']} | ${undefined} | ${true} | ${false} | ${false}
${'check help'} | ${['check', '--help']} | ${'outputHelp'} | ${false} | ${false} | ${false}
${'check LICENSE'} | ${['check', pathRoot('LICENSE')]} | ${undefined} | ${false} | ${true} | ${false}
${'check missing'} | ${['check', pathRoot('missing-file.txt')]} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'check with spelling errors'} | ${['check', pathSamples('Dutch.txt')]} | ${app.CheckFailed} | ${false} | ${true} | ${false}
${'LICENSE'} | ${[pathRoot('LICENSE')]} | ${undefined} | ${true} | ${false} | ${false}
${'samples/Dutch.txt'} | ${[pathSamples('Dutch.txt')]} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'with forbidden words'} | ${[pathSamples('src/sample-with-forbidden-words.md')]} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'current_file --verbose'} | ${['--verbose', __filename]} | ${undefined} | ${true} | ${false} | ${true}
${'bad config'} | ${['-c', __filename, __filename]} | ${app.CheckFailed} | ${true} | ${false} | ${false}
${'not found error by default'} | ${['*.not']} | ${app.CheckFailed} | ${true} | ${false} | ${false}
${'must find with error'} | ${['*.not', '--must-find-files']} | ${app.CheckFailed} | ${true} | ${false} | ${false}
${'must find force no error'} | ${['*.not', '--no-must-find-files']} | ${undefined} | ${true} | ${false} | ${false}
${'cspell-bad.json'} | ${['-c', pathSamples('cspell-bad.json'), __filename]} | ${undefined} | ${true} | ${false} | ${false}
${'cspell-import-missing.json'} | ${['-c', pathSamples('linked/cspell-import-missing.json'), __filename]} | ${app.CheckFailed} | ${true} | ${false} | ${false}
${'--fail-fast no option'} | ${['--root', pathSamples('fail-fast'), '*.txt']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'--fail-fast with option'} | ${['--root', pathSamples('fail-fast'), '--fail-fast', '*.txt']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'--fail-fast with config'} | ${['--root', pathSamples('fail-fast'), '-c', failFastConfig, '*.txt']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
${'--no-fail-fast with config'} | ${['--root', pathSamples('fail-fast'), '--no-fail-fast', '-c', failFastConfig, '*.txt']} | ${app.CheckFailed} | ${true} | ${true} | ${false}
`('app $msg Expect Error: $errorCheck', async ({ testArgs, errorCheck, eError, eLog, eInfo }: TestCase) => {
const commander = getCommander();
const args = argv(...testArgs);
Expand Down
2 changes: 2 additions & 0 deletions packages/cspell/src/commandLint.ts
Expand Up @@ -66,6 +66,8 @@ export function commandLint(prog: Command): Command {
.option('--no-progress', 'Turn off progress messages')
.option('--no-summary', 'Turn off summary message in console.')
.option('-s, --silent', 'Silent mode, suppress error messages.')
.option('--fail-fast', 'Exit after first file with an issue or error.')
.addOption(new CommanderOption('--no-fail-fast', 'Process all files even if there is an error.').hideHelp())
.option('-r, --root <root folder>', 'Root directory, defaults to current directory.')
.option('--relative', 'Issues are displayed relative to root.')
.option('--show-context', 'Show the surrounding text around an issue.')
Expand Down
4 changes: 3 additions & 1 deletion packages/cspell/src/lint/lint.test.ts
Expand Up @@ -29,7 +29,9 @@ describe('Linter Validation Tests', () => {
files | options | expectedRunResult | expectedReport
${[]} | ${{ root: latexSamples }} | ${oc({ errors: 0, files: 4 })} | ${oc({ errorCount: 0, errors: [], issues: [oc({ text: 'Tufte' })] })}
${['*.txt']} | ${{ root: failFastSamples }} | ${oc({ errors: 0, files: 2 })} | ${oc({ errorCount: 0, errors: [], issues: oc({ length: 2 }) })}
${['*.txt']} | ${{ root: failFastSamples, config: j(samples, 'fail-fast', 'fail-fast-cspell.json') }} | ${oc({ errors: 0, files: 1 })} | ${oc({ errorCount: 0, errors: [], issues: oc({ length: 1 }) })}
${['*.txt']} | ${{ root: failFastSamples, config: j(samples, 'fail-fast/fail-fast-cspell.json') }} | ${oc({ errors: 0, files: 1 })} | ${oc({ errorCount: 0, errors: [], issues: oc({ length: 1 }) })}
${['*.txt']} | ${{ root: failFastSamples, failFast: true }} | ${oc({ errors: 0, files: 1 })} | ${oc({ errorCount: 0, errors: [], issues: oc({ length: 1 }) })}
${['*.txt']} | ${{ root: failFastSamples, failFast: false, config: j(samples, 'fail-fast/fail-fast-cspell.json') }} | ${oc({ errors: 0, files: 2 })} | ${oc({ errorCount: 0, errors: [], issues: oc({ length: 2 }) })}
${['**/ebook.tex']} | ${{ root: latexSamples }} | ${oc({ errors: 0, files: 1 })} | ${oc({ errorCount: 0, errors: [], issues: [] })}
${['**/ebook.tex']} | ${{ root: latexSamples, gitignore: true }} | ${oc({ errors: 0, files: 1 })} | ${oc({ errorCount: 0, errors: [], issues: [] })}
${['**/hidden.md']} | ${{ root: hiddenSamples }} | ${oc({ errors: 0, files: 0 })} | ${oc({ errorCount: 0, errors: [], issues: [] })}
Expand Down
3 changes: 2 additions & 1 deletion packages/cspell/src/lint/lint.ts
Expand Up @@ -128,6 +128,7 @@ export async function runLint(cfg: LintRequest): Promise<RunResult> {
const fileCount = files.length;
const status: RunResult = runResult();
const cache = createCache(cacheSettings);
const failFast = cfg.options.failFast ?? configInfo.config.failFast ?? false;

const emitProgress = (filename: string, fileNum: number, result: FileResult) =>
reporter.progress({
Expand Down Expand Up @@ -160,7 +161,7 @@ export async function runLint(cfg: LintRequest): Promise<RunResult> {
status.filesWithIssues.add(filename);
status.issues += result.issues.length;
status.errors += result.errors;
if (configInfo.config.failFast === true) {
if (failFast) {
return status;
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/cspell/src/options.ts
Expand Up @@ -62,6 +62,11 @@ export interface LinterOptions extends BaseOptions, CacheOptions {
* Files must be found and processed otherwise it is considered an error.
*/
mustFindFiles?: boolean;

/**
* Stop processing and exit if an issue or error is found.
*/
failFast?: boolean;
}

export interface TraceOptions extends BaseOptions {
Expand Down

0 comments on commit 645a9a4

Please sign in to comment.