From 35e4606f919b2b4cb97da00d3d2a254033bce77d Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Thu, 9 Oct 2025 04:31:07 +0800 Subject: [PATCH 1/3] Fix panic on blockquote --- src/comment.rs | 20 +++++++++++++++----- tests/source/issue-6660.rs | 4 ++++ tests/target/issue-6660.rs | 4 ++++ 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 tests/source/issue-6660.rs create mode 100644 tests/target/issue-6660.rs diff --git a/src/comment.rs b/src/comment.rs index 709031dda44..8ca4183a7d9 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -491,7 +491,7 @@ impl ItemizedBlock { let mut line_start = " ".repeat(indent); // Markdown blockquote start with a "> " - if line.trim_start().starts_with('>') { + if line.trim_start().starts_with("> ") { // remove the original +2 indent because there might be multiple nested block quotes // and it's easier to reason about the final indent by just taking the length // of the new line_start. We update the indent because it effects the max width @@ -499,6 +499,7 @@ impl ItemizedBlock { line_start = itemized_block_quote_start(line, line_start, 2); indent = line_start.len(); } + Some(ItemizedBlock { lines: vec![line[indent..].to_string()], indent, @@ -551,10 +552,18 @@ impl ItemizedBlock { /// The original line_start likely contains indentation (whitespaces), which we'd like to /// replace with '> ' characters. fn itemized_block_quote_start(line: &str, mut line_start: String, remove_indent: usize) -> String { - let quote_level = line - .chars() - .take_while(|c| !c.is_alphanumeric()) - .fold(0, |acc, c| if c == '>' { acc + 1 } else { acc }); + let mut quote_level = 0; + let mut chars = line.trim_start().chars().peekable(); + + while chars.peek() == Some(&'>') { + chars.next(); + if chars.peek() == Some(&' ') { + chars.next(); + quote_level += 1; + } else { + break; + } + } for _ in 0..remove_indent { line_start.pop(); @@ -563,6 +572,7 @@ fn itemized_block_quote_start(line: &str, mut line_start: String, remove_indent: for _ in 0..quote_level { line_start.push_str("> ") } + line_start } diff --git a/tests/source/issue-6660.rs b/tests/source/issue-6660.rs new file mode 100644 index 00000000000..bff4e3602ab --- /dev/null +++ b/tests/source/issue-6660.rs @@ -0,0 +1,4 @@ +// rustfmt-wrap_comments: true + +// > >= >> >>= >>> >>>= +fn block_quote() {} diff --git a/tests/target/issue-6660.rs b/tests/target/issue-6660.rs new file mode 100644 index 00000000000..bff4e3602ab --- /dev/null +++ b/tests/target/issue-6660.rs @@ -0,0 +1,4 @@ +// rustfmt-wrap_comments: true + +// > >= >> >>= >>> >>>= +fn block_quote() {} From 75a3461fc4f3f045c0e430e69a89822042dc2981 Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:01:27 +0800 Subject: [PATCH 2/3] add more test cases --- tests/source/issue-6660.rs | 6 ++++++ tests/target/issue-6660.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/tests/source/issue-6660.rs b/tests/source/issue-6660.rs index bff4e3602ab..6b1224f2e01 100644 --- a/tests/source/issue-6660.rs +++ b/tests/source/issue-6660.rs @@ -1,4 +1,10 @@ // rustfmt-wrap_comments: true +// >>text +// >> text +// > >text +// > > text +// ? ? text // > >= >> >>= >>> >>>= +// > > = >> >>= >>> >>>= fn block_quote() {} diff --git a/tests/target/issue-6660.rs b/tests/target/issue-6660.rs index bff4e3602ab..233ece23563 100644 --- a/tests/target/issue-6660.rs +++ b/tests/target/issue-6660.rs @@ -1,4 +1,10 @@ // rustfmt-wrap_comments: true +// >>text +// >> text +// > >text +// > > text +// ? ? text // > >= >> >>= >>> >>>= +// > > = >> >>= >>> >>>= fn block_quote() {} From 06f67c2d08372be7549df15c44505a313c118e2f Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:54:24 +0800 Subject: [PATCH 3/3] u --- src/comment.rs | 21 ++++++++++----------- tests/source/issue-6660.rs | 10 ---------- tests/source/itemized-blocks/no_wrap.rs | 9 +++++++++ tests/source/itemized-blocks/wrap.rs | 9 +++++++++ tests/target/issue-6660.rs | 10 ---------- tests/target/itemized-blocks/no_wrap.rs | 9 +++++++++ tests/target/itemized-blocks/wrap.rs | 23 +++++++++++++++++++++++ 7 files changed, 60 insertions(+), 31 deletions(-) delete mode 100644 tests/source/issue-6660.rs delete mode 100644 tests/target/issue-6660.rs diff --git a/src/comment.rs b/src/comment.rs index 8ca4183a7d9..d9751d8730e 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -462,9 +462,9 @@ impl ItemizedBlock { fn get_marker_length(trimmed: &str) -> Option { // https://spec.commonmark.org/0.30/#bullet-list-marker or // https://spec.commonmark.org/0.30/#block-quote-marker - let itemized_start = ["* ", "- ", "> ", "+ "]; - if itemized_start.iter().any(|s| trimmed.starts_with(s)) { - return Some(2); // All items in `itemized_start` have length 2. + let itemized_start = ["* ", "- ", ">", "+ "]; + if let Some(s) = itemized_start.iter().find(|&&s| trimmed.starts_with(s)) { + return Some(s.len()); } // https://spec.commonmark.org/0.30/#ordered-list-marker, where at most 2 digits are @@ -491,12 +491,12 @@ impl ItemizedBlock { let mut line_start = " ".repeat(indent); // Markdown blockquote start with a "> " - if line.trim_start().starts_with("> ") { - // remove the original +2 indent because there might be multiple nested block quotes + if line.trim_start().starts_with('>') { + // remove the original +1 indent because there might be multiple nested block quotes // and it's easier to reason about the final indent by just taking the length // of the new line_start. We update the indent because it effects the max width // of each formatted line. - line_start = itemized_block_quote_start(line, line_start, 2); + line_start = itemized_block_quote_start(line, line_start, 1); indent = line_start.len(); } @@ -557,11 +557,10 @@ fn itemized_block_quote_start(line: &str, mut line_start: String, remove_indent: while chars.peek() == Some(&'>') { chars.next(); - if chars.peek() == Some(&' ') { + quote_level += 1; + // Skip all spaces after '>' + while chars.peek() == Some(&' ') { chars.next(); - quote_level += 1; - } else { - break; } } @@ -570,7 +569,7 @@ fn itemized_block_quote_start(line: &str, mut line_start: String, remove_indent: } for _ in 0..quote_level { - line_start.push_str("> ") + line_start.push_str("> "); } line_start diff --git a/tests/source/issue-6660.rs b/tests/source/issue-6660.rs deleted file mode 100644 index 6b1224f2e01..00000000000 --- a/tests/source/issue-6660.rs +++ /dev/null @@ -1,10 +0,0 @@ -// rustfmt-wrap_comments: true - -// >>text -// >> text -// > >text -// > > text -// ? ? text -// > >= >> >>= >>> >>>= -// > > = >> >>= >>> >>>= -fn block_quote() {} diff --git a/tests/source/itemized-blocks/no_wrap.rs b/tests/source/itemized-blocks/no_wrap.rs index e5699e76684..4dcb2efdf50 100644 --- a/tests/source/itemized-blocks/no_wrap.rs +++ b/tests/source/itemized-blocks/no_wrap.rs @@ -79,3 +79,12 @@ fn func2() {} /// * `tag` is a tag that identifies the message type /// * `msg` is the (serialized) message fn func3() {} + +/// >>This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// >> This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > >This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > > This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > > This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > >= >> >>= >>> >>>= This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > > = >> >>= >>> >>>= This is a blockquote comment that is very long and should be wrapped according to max_width setting +fn func4() {} diff --git a/tests/source/itemized-blocks/wrap.rs b/tests/source/itemized-blocks/wrap.rs index 768461a43f9..98b5c9a15b3 100644 --- a/tests/source/itemized-blocks/wrap.rs +++ b/tests/source/itemized-blocks/wrap.rs @@ -87,3 +87,12 @@ fn func2() {} /// * `tag` is a tag that identifies the message type /// * `msg` is the (serialized) message fn func3() {} + +/// >>This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// >> This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > >This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > > This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > > This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > >= >> >>= >>> >>>= This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > > = >> >>= >>> >>>= This is a blockquote comment that is very long and should be wrapped according to max_width setting +fn func4() {} diff --git a/tests/target/issue-6660.rs b/tests/target/issue-6660.rs deleted file mode 100644 index 233ece23563..00000000000 --- a/tests/target/issue-6660.rs +++ /dev/null @@ -1,10 +0,0 @@ -// rustfmt-wrap_comments: true - -// >>text -// >> text -// > >text -// > > text -// ? ? text -// > >= >> >>= >>> >>>= -// > > = >> >>= >>> >>>= -fn block_quote() {} diff --git a/tests/target/itemized-blocks/no_wrap.rs b/tests/target/itemized-blocks/no_wrap.rs index 86818b44745..09508f6ae5b 100644 --- a/tests/target/itemized-blocks/no_wrap.rs +++ b/tests/target/itemized-blocks/no_wrap.rs @@ -79,3 +79,12 @@ fn func2() {} /// * `tag` is a tag that identifies the message type /// * `msg` is the (serialized) message fn func3() {} + +/// >>This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// >> This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > >This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > > This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > > This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > >= >> >>= >>> >>>= This is a blockquote comment that is very long and should be wrapped according to max_width setting +/// > > = >> >>= >>> >>>= This is a blockquote comment that is very long and should be wrapped according to max_width setting +fn func4() {} diff --git a/tests/target/itemized-blocks/wrap.rs b/tests/target/itemized-blocks/wrap.rs index 4826590ea59..8cdeed83fd4 100644 --- a/tests/target/itemized-blocks/wrap.rs +++ b/tests/target/itemized-blocks/wrap.rs @@ -147,3 +147,26 @@ fn func2() {} /// type /// * `msg` is the (serialized) message fn func3() {} + +/// >>This is a blockquote comment that is very +/// > > long and should be wrapped according to +/// > > max_width setting +/// >> This is a blockquote comment that is very +/// > > long and should be wrapped according to +/// > > max_width setting +/// > >This is a blockquote comment that is very +/// > > long and should be wrapped according to +/// > > max_width setting +/// > > This is a blockquote comment that is +/// > > very long and should be wrapped according +/// > > to max_width setting +/// > > This is a blockquote comment that is +/// > > very long and should be wrapped according +/// > > to max_width setting +/// > >=>> >>= >>> >>>= This is a blockquote +/// > > comment that is very long and should be +/// > > wrapped according to max_width setting +/// > > = >> >>= >>> >>>= This is a blockquote +/// > > comment that is very long and should be +/// > > wrapped according to max_width setting +fn func4() {}