Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add --verbose flag #415

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 67 additions & 25 deletions markdownlint.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of keeping this, I think it can be removed and recreated if it is needed. It looks like it is empty exactly when the strings array has no elements?

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}: ✔`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect failing files to end with a little 'x'?

});
lintResultString = lintResultStrings.join('\n');
}
}

if (options.output) {
Expand All @@ -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);
}
}
}
}
}

Expand All @@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me feel like it's one setting with three values?

}

if (options.verbose && !options.output) {
console.log(pkg.name, 'version', pkg.version);
}

function tryResolvePath(filepath) {
try {
if (path.basename(filepath) === filepath && path.extname(filepath) === '') {
Expand Down Expand Up @@ -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(' '));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be all on the same line? That is harder to scan visually.

}

function lintAndPrint(stdin, files) {
files = files || [];
Expand Down Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a colon above, let's use one here also.

}

const originalText = fs.readFileSync(file, fsOptions);
const fixedText = markdownlintRuleHelpers.applyFixes(originalText, fixes);
if (originalText !== fixedText) {
Expand Down
76 changes: 76 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test for verbose with broken file (no fix).

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 => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could argue that verbose should always mean "verbose to console". If so, these two test cases would not be different. Just like I don't like how quiet and verbose overlap, disabling verbose for output seems not obvious?

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);
}
});