Skip to content

Commit 111062a

Browse files
committed
Update implementation of --format to stream/pipeline directly to stdout and avoid console.log appending an extra newline, update tests to detect this problem (fixes #715).
1 parent a45987c commit 111062a

File tree

3 files changed

+41
-35
lines changed

3 files changed

+41
-35
lines changed

markdownlint-cli2.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,11 @@ export const main = async (/** @type {Parameters} */ params) => {
10671067
logMessage(`Summary: ${results.length} error(s)`);
10681068
}
10691069
if (formattingContext.formatting) {
1070-
console.log(formattingContext.formatted);
1070+
const { pipeline } = await import("node:stream/promises");
1071+
await pipeline(
1072+
String(formattingContext.formatted),
1073+
process.stdout
1074+
);
10711075
} else {
10721076
const outputFormatters =
10731077
(optionsOverride && optionsOverride.outputFormatters) ||

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"eslint-plugin-jsdoc": "61.3.0",
9696
"eslint-plugin-n": "17.23.1",
9797
"eslint-plugin-unicorn": "62.0.0",
98+
"execa": "9.6.0",
9899
"markdown-it-emoji": "3.0.0",
99100
"markdown-it-for-inline": "2.0.1",
100101
"markdownlint-cli2-formatter-codequality": "0.0.7",
@@ -105,7 +106,6 @@
105106
"markdownlint-cli2-formatter-summarize": "0.0.8",
106107
"markdownlint-cli2-formatter-template": "0.0.4",
107108
"markdownlint-rule-extended-ascii": "0.2.1",
108-
"nano-spawn": "2.0.0",
109109
"npm-run-all": "4.1.5",
110110
"terminal-link": "5.0.0"
111111
},

test/markdownlint-cli2-test-exec.mjs

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import fs from "node:fs/promises";
44
import path from "node:path";
55
import test from "ava";
6-
import spawn from "nano-spawn";
6+
import { execa } from "execa";
77
import testCases from "./markdownlint-cli2-test-cases.mjs";
88
import { __dirname } from "./esm-helpers.mjs";
99

@@ -12,7 +12,7 @@ const repositoryPath = (/** @type {string} */ name) => path.join(__dirname(impor
1212

1313
const invoke = (/** @type {string} */ directory, /** @type {string[]} */ args, /** @type {boolean | undefined} */ noImport, /** @type {Record<string, string> | undefined} */ env, /** @type {string | undefined} */ script) => async () => {
1414
await fs.access(directory);
15-
return spawn(
15+
return execa(
1616
"node",
1717
[
1818
repositoryPath(script || "markdownlint-cli2-bin.mjs"),
@@ -43,15 +43,17 @@ testCases({
4343

4444
// eslint-disable-next-line unicorn/no-useless-undefined
4545
const invokeStdin = (/** @type {string[]} */ args, /** @type {string} */ stdin, /** @type {string | undefined} */ cwd = undefined) => (
46-
spawn(
46+
execa(
4747
"node",
4848
[
4949
repositoryPath("markdownlint-cli2-bin.mjs"),
5050
...args
5151
],
5252
{
53+
"all": true,
5354
"cwd": cwd || __dirname(import.meta),
54-
"stdin": { "string": stdin }
55+
"input": stdin,
56+
"stripFinalNewline": false
5557
}
5658
)
5759
);
@@ -90,7 +92,7 @@ test("- parameter with invalid input from stdin", (t) => {
9092
then(() => t.fail()).
9193
catch((error) => {
9294
t.is(error.exitCode, 1);
93-
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/mu, ""));
95+
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/msu, ""));
9496
});
9597
});
9698

@@ -103,7 +105,7 @@ test("- parameter with invalid input from stdin and --fix reports existing issue
103105
then(() => t.fail()).
104106
catch((error) => {
105107
t.is(error.exitCode, 1);
106-
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/mu, ""));
108+
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/msu, ""));
107109
});
108110
});
109111

@@ -116,7 +118,7 @@ test("- parameter multiple times with invalid input", (t) => {
116118
then(() => t.fail()).
117119
catch((error) => {
118120
t.is(error.exitCode, 1);
119-
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/mu, ""));
121+
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/msu, ""));
120122
});
121123
});
122124

@@ -139,7 +141,7 @@ test("- parameter with invalid input combined with valid globs", (t) => {
139141
then(() => t.fail()).
140142
catch((error) => {
141143
t.is(error.exitCode, 1);
142-
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/mu, ""));
144+
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/msu, ""));
143145
});
144146
});
145147

@@ -152,7 +154,7 @@ test("- parameter with invalid input combined with invalid glob", (t) => {
152154
then(() => t.fail()).
153155
catch((error) => {
154156
t.is(error.exitCode, 1);
155-
t.is("", error.stderr.replace(/^\.\.\/LICENSE:1 error MD041\/.*$[\n\r]+^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/mu, ""));
157+
t.is("", error.stderr.replace(/^\.\.\/LICENSE:1 error MD041\/.*$[\n\r]+^stdin:1:3 error MD019\/.*$[\n\r]+^stdin:3:4 error MD047\/.*$/msu, ""));
156158
});
157159
});
158160

@@ -166,7 +168,7 @@ test("- parameter uses base directory configuration", (t) => {
166168
then(() => t.fail()).
167169
catch((error) => {
168170
t.is(error.exitCode, 1);
169-
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$/mu, ""));
171+
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$/msu, ""));
170172
});
171173
});
172174

@@ -179,7 +181,7 @@ test("- parameter with --config behaves correctly", (t) => {
179181
then(() => t.fail()).
180182
catch((error) => {
181183
t.is(error.exitCode, 1);
182-
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$/mu, ""));
184+
t.is("", error.stderr.replace(/^stdin:1:3 error MD019\/.*$/msu, ""));
183185
});
184186
});
185187

@@ -210,7 +212,7 @@ test("--format of empty input produces same output", (t) => {
210212
[ "--format" ],
211213
""
212214
).
213-
then((result) => t.is(result.output, "")).
215+
then((result) => t.is(result.all, "")).
214216
catch(() => t.fail());
215217
});
216218

@@ -220,7 +222,7 @@ test("--format of input with no issues produces same output", (t) => {
220222
[ "--format" ],
221223
inputWithNoIssues
222224
).
223-
then((result) => t.is(result.output, inputWithNoIssues)).
225+
then((result) => t.is(result.all, inputWithNoIssues)).
224226
catch(() => t.fail());
225227
});
226228

@@ -230,7 +232,7 @@ test("--format of input with all fixable issues produces output with no issues",
230232
[ "--format" ],
231233
inputWithFixableIssues
232234
).
233-
then((result) => t.is(result.output, inputWithNoIssues)).
235+
then((result) => t.is(result.all, inputWithNoIssues)).
234236
catch(() => t.fail());
235237
});
236238

@@ -240,7 +242,7 @@ test("--format of input with no fixable issues produces same output", (t) => {
240242
[ "--format" ],
241243
inputWithUnfixableIssues
242244
).
243-
then((result) => t.is(result.output, inputWithUnfixableIssues)).
245+
then((result) => t.is(result.all, inputWithUnfixableIssues)).
244246
catch(() => t.fail());
245247
});
246248

@@ -250,7 +252,7 @@ test("--format of input with some fixable issues produces output with unfixable
250252
[ "--format" ],
251253
inputWithSomeFixableIssues
252254
).
253-
then((result) => t.is(result.output, inputWithUnfixableIssues)).
255+
then((result) => t.is(result.all, inputWithUnfixableIssues)).
254256
catch(() => t.fail());
255257
});
256258

@@ -260,7 +262,7 @@ test("--format with globs behaves the same", (t) => {
260262
[ "--format", path.join(__dirname(import.meta), "no-config", "viewme.md") ],
261263
inputWithSomeFixableIssues
262264
).
263-
then((result) => t.is(result.output, inputWithUnfixableIssues)).
265+
then((result) => t.is(result.all, inputWithUnfixableIssues)).
264266
catch(() => t.fail());
265267
});
266268

@@ -270,7 +272,7 @@ test("--format with --fix behaves the same", (t) => {
270272
[ "--format", "--fix" ],
271273
inputWithSomeFixableIssues
272274
).
273-
then((result) => t.is(result.output, inputWithUnfixableIssues)).
275+
then((result) => t.is(result.all, inputWithUnfixableIssues)).
274276
catch(() => t.fail());
275277
});
276278

@@ -280,7 +282,7 @@ test("--format with - behaves the same", (t) => {
280282
[ "--format", "-" ],
281283
inputWithSomeFixableIssues
282284
).
283-
then((result) => t.is(result.output, inputWithUnfixableIssues)).
285+
then((result) => t.is(result.all, inputWithUnfixableIssues)).
284286
catch(() => t.fail());
285287
});
286288

@@ -291,7 +293,7 @@ test("--format uses base directory configuration", (t) => {
291293
inputWithSomeFixableIssues,
292294
path.join(__dirname(import.meta), "stdin")
293295
).
294-
then((result) => t.is(result.output, inputWithUnfixableIssues.slice(0, -1))).
296+
then((result) => t.is(result.all, inputWithUnfixableIssues.slice(0, -1))).
295297
catch(() => t.fail());
296298
});
297299

@@ -302,7 +304,7 @@ test("--format with base directory configuration ignores globs", (t) => {
302304
inputWithSomeFixableIssues,
303305
path.join(__dirname(import.meta), "globs")
304306
).
305-
then((result) => t.is(result.output, inputWithUnfixableIssues)).
307+
then((result) => t.is(result.all, inputWithUnfixableIssues)).
306308
catch(() => t.fail());
307309
});
308310

@@ -312,7 +314,7 @@ test("--format with --config behaves correctly", (t) => {
312314
[ "--format", "--config", path.join(__dirname(import.meta), "stdin", ".markdownlint.jsonc") ],
313315
inputWithSomeFixableIssues
314316
).
315-
then((result) => t.is(result.output, inputWithUnfixableIssues.slice(0, -1))).
317+
then((result) => t.is(result.all, inputWithUnfixableIssues.slice(0, -1))).
316318
catch(() => t.fail());
317319
});
318320

@@ -326,65 +328,65 @@ test("exit codes", (t) => {
326328
invokeStdin(
327329
[ "-" ],
328330
inputWithNoIssues
329-
).then((result) => t.notRegex(result.output, /MD\d{3}/su)),
331+
).then((result) => t.notRegex(result.all, /MD\d{3}/su)),
330332
invokeStdin(
331333
[ "-" ],
332334
inputWithFixableIssues
333335
).catch((error) => {
334336
t.is(error.exitCode, 1);
335-
t.regex(error.output, /MD019.*MD047/su);
337+
t.regex(error.all, /MD019.*MD047/su);
336338
}),
337339
invokeStdin(
338340
[ "-" ],
339341
`${inputWithFixableIssues} <!-- markdownlint-configure-file { "default": false } -->`
340-
).then((result) => t.notRegex(result.output, /MD\d{3}/su)),
342+
).then((result) => t.notRegex(result.all, /MD\d{3}/su)),
341343
invokeStdin(
342344
[ "-" ],
343345
`${inputWithFixableIssues} <!-- markdownlint-configure-file { "default": "warning" } -->`
344-
).then((result) => t.regex(result.output, /MD019.*MD047/su)),
346+
).then((result) => t.regex(result.all, /MD019.*MD047/su)),
345347
invokeStdin(
346348
[ "-" ],
347349
`${inputWithFixableIssues} <!-- markdownlint-configure-file { "default": "error" } -->`
348350
).catch((error) => {
349351
t.is(error.exitCode, 1);
350-
t.regex(error.output, /MD019.*MD047/su);
352+
t.regex(error.all, /MD019.*MD047/su);
351353
}),
352354
invokeStdin(
353355
[ "-" ],
354356
`${inputWithFixableIssues} <!-- markdownlint-configure-file { "MD019": false } -->`
355357
).catch((error) => {
356358
t.is(error.exitCode, 1);
357-
t.regex(error.output, /MD047/su);
359+
t.regex(error.all, /MD047/su);
358360
}),
359361
invokeStdin(
360362
[ "-" ],
361363
`${inputWithFixableIssues} <!-- markdownlint-configure-file { "MD019": "warning" } -->`
362364
).catch((error) => {
363365
t.is(error.exitCode, 1);
364-
t.regex(error.output, /MD019.*MD047/su);
366+
t.regex(error.all, /MD019.*MD047/su);
365367
}),
366368
invokeStdin(
367369
[ "-" ],
368370
`${inputWithFixableIssues} <!-- markdownlint-configure-file { "MD047": false } -->`
369371
).catch((error) => {
370372
t.is(error.exitCode, 1);
371-
t.regex(error.output, /MD019/su);
373+
t.regex(error.all, /MD019/su);
372374
}),
373375
invokeStdin(
374376
[ "-" ],
375377
`${inputWithFixableIssues} <!-- markdownlint-configure-file { "MD047": "warning" } -->`
376378
).catch((error) => {
377379
t.is(error.exitCode, 1);
378-
t.regex(error.output, /MD019.*MD047/su);
380+
t.regex(error.all, /MD019.*MD047/su);
379381
}),
380382
invokeStdin(
381383
[ "-" ],
382384
`${inputWithFixableIssues} <!-- markdownlint-configure-file { "MD019": false, "MD047": false } -->`
383-
).then((result) => t.notRegex(result.output, /MD\d{3}/su)),
385+
).then((result) => t.notRegex(result.all, /MD\d{3}/su)),
384386
invokeStdin(
385387
[ "-" ],
386388
`${inputWithFixableIssues} <!-- markdownlint-configure-file {"MD019":"warning","MD047":"warning"} -->`
387-
).then((result) => t.regex(result.output, /MD019.*MD047/su))
389+
).then((result) => t.regex(result.all, /MD019.*MD047/su))
388390
]).
389391
then(() => t.pass()).
390392
catch(() => t.fail());

0 commit comments

Comments
 (0)