From ed07d6537dc1d10169b8529dfe3d13e9609f0162 Mon Sep 17 00:00:00 2001 From: Yarchik Date: Wed, 24 Jun 2026 02:25:10 +0100 Subject: [PATCH] fix: preserve blank line after inline block comment in stringify A blank line after an inline block comment (/* ... */) that follows a comma was silently dropped by stringify, while the identical case with a line comment (// ...) round-tripped correctly. The parse trees for both cases are identical (both carry the BlankLine token), so the defect is in stringify. In join(), the one && two branch did one + two.trim() + LF + gap, and two.trim() discarded the blank-line count carried by two. The line-comment case survived only because process_comments appends a trailing LF after a LineComment, which landed the missing break in one by coincidence. Emit the blank lines that two carries beyond those already supplied by the trailing LF + gap and the trailing line breaks of one. --- src/stringify.js | 36 ++++++++++++++++++++++++++++++++++-- test/stringify.test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/stringify.js b/src/stringify.js index 75a3c7e..8c2680a 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -62,6 +62,26 @@ const count_trailing_line_breaks = str => { return count } +const count_leading_line_breaks = str => { + let i = 0 + let count = 0 + + while (i < str.length) { + while (i < str.length && is_inline_whitespace(str[i])) { + i ++ + } + + if (i === str.length || str[i] !== LF) { + return count + } + + i ++ + count ++ + } + + return count +} + // display_block `boolean` whether the // WHOLE block of comments is always a block group const process_comments = (host, symbol_tag, deeper_gap, display_block) => { @@ -138,8 +158,20 @@ const join = (one, two, gap) => // Symbol.for('before') and Symbol.for('before:prop') // might both exist if user mannually add comments to the object // and make a mistake. - // SO, we are not to only trimRight but trim for both sides - ? one + two.trim() + LF + gap + // SO, we are not to only trimRight but trim for both sides. + // The trailing `LF + gap` plus the trailing line breaks of `one` + // already supply some line breaks, so only emit the blank lines that + // `two` carries beyond those, otherwise `two.trim()` would drop them. + ? one + + repeat_line_breaks( + Math.max( + count_leading_line_breaks(two) + - count_trailing_line_breaks(one) - 1, + 0 + ), + gap + ) + + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks( Math.max(1, count_trailing_line_breaks(one)), gap diff --git a/test/stringify.test.js b/test/stringify.test.js index 6d6c360..b7a9d8f 100644 --- a/test/stringify.test.js +++ b/test/stringify.test.js @@ -219,6 +219,36 @@ test('preserve blank lines after before comments', t => { t.is(output, content) }) +test('preserve blank line after inline block comment following a comma', t => { + const content = `{ + "a": 1, /* x */ + + "b": 2 +}` + + t.is(stringify(parse(content), null, 2), content) +}) + +test('preserve blank line after inline line comment following a comma', t => { + const content = `{ + "a": 1, // x + + "b": 2 +}` + + t.is(stringify(parse(content), null, 2), content) +}) + +test('preserve blank line after inline block comment in arrays', t => { + const content = `[ + 1, /* x */ + + 2 +]` + + t.is(stringify(parse(content), null, 2), content) +}) + test('render explicit BlankLine tokens between comments', t => { const comments = [ {