diff --git a/markdownlint.js b/markdownlint.js index 708a3b78e..79dfb5282 100755 --- a/markdownlint.js +++ b/markdownlint.js @@ -116,48 +116,64 @@ function prepareFileList(files, fileExtensions, previousResults) { } function printResult(lintResult) { - const results = Object.keys(lintResult).flatMap(file => - lintResult[file].map(result => { - if (options.json) { + const results = Object.keys(lintResult) + .flatMap(file => { + if (lintResult[file].length > 0) { + return lintResult[file].map(result => { + if (options.json) { + return { + fileName: file, + ...result + }; + } + + return { + file: file, + lineNumber: result.lineNumber, + column: (result.errorRange && result.errorRange[0]) || 0, + names: result.ruleNames.join('/'), + description: result.ruleDescription + (result.errorDetail ? ' [' + result.errorDetail + ']' : '') + (result.errorContext ? ' [Context: "' + result.errorContext + '"]' : '') + }; + }); + } + + if (options.verbose && !options.json) { return { - fileName: file, - ...result + file: file }; } - return { - file: file, - lineNumber: result.lineNumber, - column: (result.errorRange && result.errorRange[0]) || 0, - names: result.ruleNames.join('/'), - description: result.ruleDescription + (result.errorDetail ? ' [' + result.errorDetail + ']' : '') + (result.errorContext ? ' [Context: "' + result.errorContext + '"]' : '') - }; + return null; }) - ); + .filter(Boolean); let lintResultString = ''; + let lintResultStrings = []; if (results.length > 0) { if (options.json) { + // Note: process.exit(1) will end abruptly, interrupting asynchronous IO + // streams (e.g., when the output is being piped). Just set the exit code + // and let the program terminate normally. + // @see {@link https://nodejs.org/dist/latest-v8.x/docs/api/process.html#process_process_exit_code} + // @see {@link https://github.com/igorshubovych/markdownlint-cli/pull/29#issuecomment-343535291} + process.exitCode = exitCodes.lintFindings; results.sort((a, b) => a.fileName.localeCompare(b.fileName) || a.lineNumber - b.lineNumber || a.ruleDescription.localeCompare(b.ruleDescription)); lintResultString = JSON.stringify(results, null, 2); } else { results.sort((a, b) => a.file.localeCompare(b.file) || a.lineNumber - b.lineNumber || a.names.localeCompare(b.names) || a.description.localeCompare(b.description)); - lintResultString = results - .map(result => { + lintResultStrings = results.map(result => { + if (result.lineNumber) { + process.exitCode = exitCodes.lintFindings; const {file, lineNumber, column, names, description} = result; const columnText = column ? `:${column}` : ''; return `${file}:${lineNumber}${columnText} ${names} ${description}`; - }) - .join('\n'); - } + } - // Note: process.exit(1) will end abruptly, interrupting asynchronous IO - // streams (e.g., when the output is being piped). Just set the exit code - // and let the program terminate normally. - // @see {@link https://nodejs.org/dist/latest-v8.x/docs/api/process.html#process_process_exit_code} - // @see {@link https://github.com/igorshubovych/markdownlint-cli/pull/29#issuecomment-343535291} - process.exitCode = exitCodes.lintFindings; + return `${result.file}: ✔`; + }); + lintResultString = lintResultStrings.join('\n'); + } } if (options.output) { @@ -169,7 +185,17 @@ function printResult(lintResult) { process.exitCode = exitCodes.failedToWriteOutputFile; } } else if (lintResultString && !options.quiet) { - console.error(lintResultString); + if (options.json) { + console.error(lintResultString); + } else { + for (const line of lintResultStrings) { + if (line.endsWith(': ✔')) { + console.log(line); + } else { + console.error(line); + } + } + } } } @@ -192,11 +218,20 @@ program .option('-q, --quiet', 'do not write issues to STDOUT') .option('-r, --rules [file|directory|glob|package]', 'include custom rule files', concatArray, []) .option('-s, --stdin', 'read from STDIN (does not work with files)') + .option('-v, --verbose', 'verbose mode') .option('--enable [rules...]', 'Enable certain rules, e.g. --enable MD013 MD041 --') .option('--disable [rules...]', 'Disable certain rules, e.g. --disable MD013 MD041 --'); program.parse(process.argv); +if (options.quiet && options.verbose) { + options.verbose = false; +} + +if (options.verbose && !options.output) { + console.log(pkg.name, 'version', pkg.version); +} + function tryResolvePath(filepath) { try { if (path.basename(filepath) === filepath && path.extname(filepath) === '') { @@ -256,6 +291,9 @@ const files = prepareFileList(program.args, ['md', 'markdown']).filter(value => const ignores = prepareFileList(options.ignore, ['md', 'markdown'], files); const customRules = loadCustomRules(options.rules); const diff = files.filter(file => !ignores.some(ignore => ignore.absolute === file.absolute)).map(paths => paths.original); +if (options.verbose && !options.stdin) { + console.log('files to check:', diff.join(' ')); +} function lintAndPrint(stdin, files) { files = files || []; @@ -299,6 +337,10 @@ function lintAndPrint(stdin, files) { const fixResult = markdownlint.sync(fixOptions); const fixes = fixResult[file].filter(error => error.fixInfo); if (fixes.length > 0) { + if (options.verbose && !options.output) { + console.log('fixing', file); + } + const originalText = fs.readFileSync(file, fsOptions); const fixedText = markdownlintRuleHelpers.applyFixes(originalText, fixes); if (originalText !== fixedText) { diff --git a/test/test.js b/test/test.js index fc36d6eab..e7405ceda 100644 --- a/test/test.js +++ b/test/test.js @@ -824,3 +824,79 @@ test('configuration can be .cjs in the ESM (module) workspace', async t => { t.is(result.stderr, ''); t.is(result.exitCode, 0); }); + +test('--verbose flag for correct file', async t => { + const result = await execa('../markdownlint.js', ['--verbose', 'correct.md'], {stripFinalNewline: false}); + t.true(result.stdout.includes('files to check: correct.md')); + t.true(result.stdout.includes('correct.md: ✔')); + t.is(result.stderr, ''); + t.is(result.exitCode, 0); +}); + +test('--verbose flag with --fix for correct file', async t => { + const result = await execa('../markdownlint.js', ['--fix', '--verbose', 'correct.md'], {stripFinalNewline: false}); + t.true(result.stdout.includes('files to check: correct.md')); + t.true(result.stdout.includes('correct.md: ✔')); + t.true(!result.stdout.includes('fixing correct.md')); + t.is(result.stderr, ''); + t.is(result.exitCode, 0); +}); + +test('--verbose flag with --fix for incorrect file', async t => { + const fixFileD = 'incorrect.d.mdf'; + try { + fs.copyFileSync('incorrect.md', fixFileD); + await execa('../markdownlint.js', ['--fix', '--verbose', '--config', 'test-config.json', path.resolve(fixFileD)], {stripFinalNewline: false}); + t.fail(); + } catch (error) { + t.true(error.stdout.includes('files to check: ' + path.resolve(fixFileD))); + t.true(error.stdout.includes('fixing ' + path.resolve(fixFileD))); + t.is(error.stderr.match(errorPattern).length, 2); + t.is(error.exitCode, 1); + fs.unlinkSync(fixFileD); + } +}); + +test('--quiet overrides --verbose', async t => { + try { + await execa('../markdownlint.js', ['--quiet', '--verbose', '--config', 'test-config.json', 'incorrect.md'], {stripFinalNewline: false}); + t.fail(); + } catch (error) { + t.is(error.stdout, ''); + t.is(error.stderr, ''); + t.is(error.exitCode, 1); + } +}); + +test('--stdin and --verbose with valid input logs stdin as source', async t => { + const input = ['# Heading', '', 'Text', ''].join('\n'); + const result = await execa('../markdownlint.js', ['--verbose', '--stdin'], {input, stripFinalNewline: false}); + t.true(result.stdout.includes('stdin: ✔')); + t.true(!result.stdout.includes('files to check:')); + t.is(result.stderr, ''); + t.is(result.exitCode, 0); +}); + +test('--output and --verbose with valid input logs nothing to console', async t => { + const input = ['# Heading', '', 'Text', ''].join('\n'); + const output = '../outputG.txt'; + const result = await execa('../markdownlint.js', ['--stdin', '--verbose', '--output', output], {input, stripFinalNewline: false}); + t.is(result.stdout, ''); + t.is(result.stderr, ''); + t.is(result.exitCode, 0); + fs.unlinkSync(output); +}); + +test('--output and --verbose with invalid input logs nothing to console', async t => { + const input = ['Heading', '', 'Text ', ''].join('\n'); + const output = '../outputH.txt'; + try { + await execa('../markdownlint.js', ['--stdin', '--verbose', '--output', output], {input, stripFinalNewline: false}); + t.fail(); + } catch (error) { + t.is(error.stdout, ''); + t.is(error.stderr, ''); + t.is(error.exitCode, 1); + fs.unlinkSync(output); + } +});