-
Notifications
You must be signed in to change notification settings - Fork 103
feat: add support for --stdin with --fix option #554
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
base: master
Are you sure you want to change the base?
Changes from all commits
f8b12da
96ef796
a66ffaa
111fe3a
dc12b4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,6 +60,15 @@ const configParsers = [jsoncParse, tomlParse, yamlParse]; | |
| const fsOptions = {encoding: 'utf8'}; | ||
| const processCwd = process.cwd(); | ||
|
|
||
| function writeOutputFile(path, content) { | ||
| try { | ||
| fs.writeFileSync(path, content, fsOptions); | ||
| } catch (error) { | ||
| console.warn('Cannot write to output file ' + path + ': ' + error.message); | ||
| process.exitCode = exitCodes.failedToWriteOutputFile; | ||
| } | ||
| } | ||
|
|
||
| function readConfiguration(userConfigFile) { | ||
| // Load from well-known config files | ||
| let config = rc('markdownlint', {}); | ||
|
|
@@ -186,12 +195,7 @@ function printResult(lintResult) { | |
|
|
||
| if (options.output) { | ||
| lintResultString = lintResultString.length > 0 ? lintResultString + os.EOL : lintResultString; | ||
| try { | ||
| fs.writeFileSync(options.output, lintResultString); | ||
| } catch (error) { | ||
| console.warn('Cannot write to output file ' + options.output + ': ' + error.message); | ||
| process.exitCode = exitCodes.failedToWriteOutputFile; | ||
| } | ||
| writeOutputFile(options.output, lintResultString); | ||
| } else if (lintResultString && !options.quiet) { | ||
| console.error(lintResultString); | ||
| } | ||
|
|
@@ -208,7 +212,7 @@ program | |
| .option('-c, --config <configFile>', 'configuration file (JSON, JSONC, JS, YAML, or TOML)') | ||
| .option('--configPointer <pointer>', 'JSON Pointer to object within configuration file', '') | ||
| .option('-d, --dot', 'include files/folders with a dot (for example `.github`)') | ||
| .option('-f, --fix', 'fix basic errors (does not work with STDIN)') | ||
| .option('-f, --fix', 'fix basic errors') | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. Please update README.md with the same change. |
||
| .option('-i, --ignore <file|directory|glob>', 'file(s) to ignore/exclude', concatArray, []) | ||
| .option('-j, --json', 'write issues in json format') | ||
| .option('-o, --output <outputFile>', 'write issues to file (no console)') | ||
|
|
@@ -311,6 +315,45 @@ function lintAndPrint(stdin, files) { | |
|
|
||
| if (options.fix) { | ||
| const fixOptions = {...lintOptions}; | ||
| if (stdin) { | ||
| // Handle fix for stdin | ||
| const fixResult = lint(fixOptions); | ||
| const fixes = fixResult.stdin.filter(error => error.fixInfo); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to filter here, I think - pass the full list and it will ignore what it cannot do. |
||
| let outputText = stdin; | ||
| outputText = applyFixes(stdin, fixes); | ||
|
|
||
| if (options.output) { | ||
| writeOutputFile(options.output, outputText); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The README says this file contains the issues, not the file content - I do not think this should be used for both purposes? |
||
| // Check for remaining errors after fix | ||
| const checkOptions = {...lintOptions}; | ||
| checkOptions.strings = {stdin: outputText}; | ||
| const checkResult = lint(checkOptions); | ||
| if (Object.keys(checkResult).some(file => checkResult[file].length > 0)) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is only one input just like line 321, so it can be used directly here. |
||
| printResult(checkResult); | ||
| } | ||
|
|
||
| return; // Exit early when output is specified | ||
| } | ||
|
|
||
| // Update stdin with fixed content for subsequent linting | ||
| stdin = outputText; | ||
| lintOptions.strings.stdin = outputText; | ||
|
|
||
| // Check for remaining errors after fix | ||
| const remainingErrors = lint(lintOptions); | ||
| const hasErrors = Object.keys(remainingErrors).some(file => remainingErrors[file].length > 0); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as above, only one key will be present. |
||
|
|
||
| process.stdout.write(outputText); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please move this line above the previous lines setting remainingErrors/hasErrors. |
||
|
|
||
| if (hasErrors) { | ||
| // Print remaining errors to stderr | ||
| printResult(remainingErrors); | ||
| } | ||
|
|
||
| return; // Exit after handling stdin with fix | ||
| } | ||
|
|
||
| // Handle fix for files | ||
| for (const file of files) { | ||
| fixOptions.files = [file]; | ||
| const fixResult = lint(fixOptions); | ||
|
|
@@ -332,7 +375,7 @@ function lintAndPrint(stdin, files) { | |
| try { | ||
| if (files.length > 0 && !options.stdin) { | ||
| lintAndPrint(null, diff); | ||
| } else if (files.length === 0 && options.stdin && !options.fix) { | ||
| } else if (files.length === 0 && options.stdin) { | ||
| import('node:stream/consumers').then(module => module.text(process.stdin)).then(lintAndPrint); | ||
| } else { | ||
| program.help(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -411,6 +411,76 @@ test('--output with invalid path fails', async t => { | |
| } | ||
| }); | ||
|
|
||
| test('--stdin --fix with fixable content outputs fixed content', async t => { | ||
|
DavidAnson marked this conversation as resolved.
|
||
| const stdin = {string: ['# Heading', '', 'Text with trailing spaces ', '', '- List item', ''].join('\n')}; | ||
| const result = await spawn('../markdownlint.js', ['--stdin', '--fix'], {stdin}); | ||
| const expected = ['# Heading', '', 'Text with trailing spaces', '', '- List item'].join('\n'); | ||
| t.is(result.stdout, expected); | ||
| t.is(result.stderr, ''); | ||
| t.is(result.exitCode, 0); | ||
| }); | ||
|
|
||
| test('--stdin --fix with valid content outputs unchanged content', async t => { | ||
| const stdin = {string: ['# Heading', '', 'Text without issues', '', '- Clean list item'].join('\n')}; | ||
| const result = await spawn('../markdownlint.js', ['--stdin', '--fix'], {stdin}); | ||
| t.is(result.stdout, stdin.string); | ||
| t.is(result.stderr, ''); | ||
| t.is(result.exitCode, 0); | ||
| }); | ||
|
|
||
| test('--stdin --fix with empty input outputs empty content', async t => { | ||
| const stdin = {string: ''}; | ||
| const result = await spawn('../markdownlint.js', ['--stdin', '--fix'], {stdin}); | ||
| t.is(result.stdout, ''); | ||
| t.is(result.stderr, ''); | ||
| t.is(result.exitCode, 0); | ||
| }); | ||
|
|
||
| test('--stdin --fix --output writes fixed content to file', async t => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above for why I think this is testing the wrong behavior. |
||
| const stdin = {string: ['# Heading', '', 'Text with trailing spaces ', ''].join('\n')}; | ||
| const output = '../outputStdinFix.md'; | ||
| const result = await spawn('../markdownlint.js', ['--stdin', '--fix', '--output', output], {stdin}); | ||
| t.is(result.stdout, ''); | ||
| t.is(result.stderr, ''); | ||
| t.is(result.exitCode, 0); | ||
| const fileContent = fs.readFileSync(output, 'utf8'); | ||
| const expected = ['# Heading', '', 'Text with trailing spaces', ''].join('\n'); | ||
| t.is(fileContent, expected); | ||
| fs.unlinkSync(output); | ||
| }); | ||
|
|
||
| test('--stdin --fix with unfixable errors still outputs original content', async t => { | ||
| // MD041 first-line-heading is not automatically fixable | ||
| const stdin = {string: ['## Not a first level heading', '', 'Text content'].join('\n')}; | ||
| try { | ||
| await spawn('../markdownlint.js', ['--stdin', '--fix', '--config', 'test-config.json'], {stdin}); | ||
| t.fail(); | ||
| } catch (error) { | ||
| t.is(error.stdout, stdin.string); | ||
| t.is(error.stderr.match(errorPattern).length, 1); | ||
| t.true(error.stderr.includes('stdin:1')); | ||
| t.true(error.stderr.includes('MD041')); | ||
| t.is(error.exitCode, 1); | ||
| } | ||
| }); | ||
|
|
||
| test('--stdin --fix with mixed fixable and unfixable errors', async t => { | ||
| // MD041 (first-line-heading) is not fixable, trailing spaces should be fixable | ||
| const stdin = {string: ['## Not a first level heading ', '', 'Text content '].join('\n')}; | ||
| const expected = ['## Not a first level heading', '', 'Text content'].join('\n'); | ||
| try { | ||
| await spawn('../markdownlint.js', ['--stdin', '--fix'], {stdin}); | ||
| t.fail(); | ||
| } catch (error) { | ||
| t.is(error.stdout, expected); | ||
| t.is(error.stderr.match(errorPattern).length, 1); | ||
| t.true(error.stderr.includes('stdin:1')); | ||
| t.true(error.stderr.includes('MD041')); | ||
| t.false(error.stderr.includes('MD009')); // Trailing spaces should be fixed | ||
| t.is(error.exitCode, 1); | ||
| } | ||
| }); | ||
|
|
||
| test('configuration file can be YAML', async t => { | ||
| const result = await spawn('../markdownlint.js', ['--config', 'md043-config.yaml', 'md043-config.md']); | ||
| t.is(result.stdout, ''); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.