Repository: rust-lang/rustfmt (rust)

Issue:
wrap_comments can break up type links
Similar to #5250, I have a problem when addressing types. They get split over multiple lines and are not valid links anymore in rust doc.

```
/// [IntermediateBucketResult](crate::aggregation::intermediate_agg_result::IntermediateBucketResult) 
```

This is converted to:
```
/// [IntermediateBucketResult](crate::aggregation::intermediate_agg_result::
/// IntermediateBucketResult) 
```


Pull request description:
Fixes #5260
Fixes #5260 by checking if it is part of a type '::'
Add possibility to run single test e.g. `TEST_FILE=5250 cargo test system` to run test files containing 5250


Patch:
diff --git a/src/comment.rs b/src/comment.rs
index f9d8a0fa..eb195b1f 100644
--- a/src/comment.rs
+++ b/src/comment.rs
@@ -796,7 +796,7 @@ impl<'a> CommentRewrite<'a> {
         // 1) wrap_comments = true is configured
         // 2) The comment is not the start of a markdown header doc comment
         // 3) The comment width exceeds the shape's width
-        // 4) No URLS were found in the commnet
+        // 4) No URLS were found in the comment
         let should_wrap_comment = self.fmt.config.wrap_comments()
             && !is_markdown_header_doc_comment
             && unicode_str_width(line) > self.fmt.shape.width
diff --git a/src/parse/session.rs b/src/parse/session.rs
index 7571e6d0..55050571 100644
--- a/src/parse/session.rs
+++ b/src/parse/session.rs
@@ -152,7 +152,7 @@ impl ParseSess {
     /// * `relative` - If Some(symbol), the symbol name is a directory relative to the dir_path.
     ///   If relative is Some, resolve the submodle at {dir_path}/{symbol}/{id}.rs
     ///   or {dir_path}/{symbol}/{id}/mod.rs. if None, resolve the module at {dir_path}/{id}.rs.
-    /// *  `dir_path` - Module resolution will occur relative to this direcotry.
+    /// *  `dir_path` - Module resolution will occur relative to this directory.
     pub(crate) fn default_submod_path(
         &self,
         id: symbol::Ident,
diff --git a/src/string.rs b/src/string.rs
index b65aa5b3..78b72a50 100644
--- a/src/string.rs
+++ b/src/string.rs
@@ -315,20 +315,21 @@ fn break_string(max_width: usize, trim_end: bool, line_end: &str, input: &[&str]
         // Found a whitespace and what is on its left side is big enough.
         Some(index) if index >= MIN_STRING => break_at(index),
         // No whitespace found, try looking for a punctuation instead
-        _ => match input[0..max_width_index_in_input]
-            .iter()
-            .rposition(|grapheme| is_punctuation(grapheme))
+        _ => match (0..max_width_index_in_input)
+            .rev()
+            .skip_while(|pos| !is_valid_linebreak(input, *pos))
+            .next()
         {
             // Found a punctuation and what is on its left side is big enough.
             Some(index) if index >= MIN_STRING => break_at(index),
             // Either no boundary character was found to the left of `input[max_chars]`, or the line
             // got too small. We try searching for a boundary character to the right.
-            _ => match input[max_width_index_in_input..]
-                .iter()
-                .position(|grapheme| is_whitespace(grapheme) || is_punctuation(grapheme))
+            _ => match (max_width_index_in_input..input.len())
+                .skip_while(|pos| !is_valid_linebreak(input, *pos))
+                .next()
             {
                 // A boundary was found after the line limit
-                Some(index) => break_at(max_width_index_in_input + index),
+                Some(index) => break_at(index),
                 // No boundary to the right, the input cannot be broken
                 None => SnippetState::EndOfInput(input.concat()),
             },
@@ -336,6 +337,23 @@ fn break_string(max_width: usize, trim_end: bool, line_end: &str, input: &[&str]
     }
 }
 
+fn is_valid_linebreak(input: &[&str], pos: usize) -> bool {
+    let is_whitespace = is_whitespace(input[pos]);
+    if is_whitespace {
+        return true;
+    }
+    let is_punctuation = is_punctuation(input[pos]);
+    if is_punctuation && !is_part_of_type(input, pos) {
+        return true;
+    }
+    false
+}
+
+fn is_part_of_type(input: &[&str], pos: usize) -> bool {
+    input.get(pos..=pos + 1) == Some(&[":", ":"])
+        || input.get(pos.saturating_sub(1)..=pos) == Some(&[":", ":"])
+}
+
 fn is_new_line(grapheme: &str) -> bool {
     let bytes = grapheme.as_bytes();
     bytes.starts_with(b"\n") || bytes.starts_with(b"\r\n")
@@ -369,6 +387,19 @@ mod test {
         rewrite_string("eq_", &fmt, 2);
     }
 
+    #[test]
+    fn line_break_at_valid_points_test() {
+        let string = "[TheName](Dont::break::my::type::That::would::be::very::nice) break here";
+        let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
+        assert_eq!(
+            break_string(20, false, "", &graphemes[..]),
+            SnippetState::LineEnd(
+                "[TheName](Dont::break::my::type::That::would::be::very::nice) ".to_string(),
+                62
+            )
+        );
+    }
+
     #[test]
     fn should_break_on_whitespace() {
         let string = "Placerat felis. Mauris porta ante sagittis purus.";


Test patch:
diff --git a/tests/rustfmt/main.rs b/tests/rustfmt/main.rs
index 450051d2..4c6d5272 100644
--- a/tests/rustfmt/main.rs
+++ b/tests/rustfmt/main.rs
@@ -143,7 +143,7 @@ fn mod_resolution_error_relative_module_not_found() {
     let args = ["tests/mod-resolver/module-not-found/relative_module/lib.rs"];
     let (_stdout, stderr) = rustfmt(&args);
     // The file `./a.rs` and directory `./a` both exist.
-    // Module resolution fails becuase we're unable to find `./a/b.rs`
+    // Module resolution fails because we're unable to find `./a/b.rs`
     #[cfg(not(windows))]
     assert!(stderr.contains("a/b.rs does not exist"));
     #[cfg(windows)]
diff --git a/tests/source/issue-5260.rs b/tests/source/issue-5260.rs
new file mode 100644
index 00000000..c0606817
--- /dev/null
+++ b/tests/source/issue-5260.rs
@@ -0,0 +1,14 @@
+// rustfmt-wrap_comments: true
+
+/// [MyType](VeryLongPathToMyType::NoLineBreak::Here::Okay::ThatWouldBeNice::Thanks)
+fn documented_with_longtype() {
+    // # We're using a long type link, rustfmt should not break line
+    // on the type when `wrap_comments = true`
+}
+
+/// VeryLongPathToMyType::JustMyType::But::VeryVery::Long::NoLineBreak::Here::Okay::ThatWouldBeNice::Thanks
+fn documented_with_verylongtype() {
+    // # We're using a long type link, rustfmt should not break line
+    // on the type when `wrap_comments = true`
+}
+
diff --git a/tests/target/issue-5260.rs b/tests/target/issue-5260.rs
new file mode 100644
index 00000000..171f6fa5
--- /dev/null
+++ b/tests/target/issue-5260.rs
@@ -0,0 +1,13 @@
+// rustfmt-wrap_comments: true
+
+/// [MyType](VeryLongPathToMyType::NoLineBreak::Here::Okay::ThatWouldBeNice::Thanks)
+fn documented_with_longtype() {
+    // # We're using a long type link, rustfmt should not break line
+    // on the type when `wrap_comments = true`
+}
+
+/// VeryLongPathToMyType::JustMyType::But::VeryVery::Long::NoLineBreak::Here::Okay::ThatWouldBeNice::Thanks
+fn documented_with_verylongtype() {
+    // # We're using a long type link, rustfmt should not break line
+    // on the type when `wrap_comments = true`
+}
diff --git a/tests/target/issue_4573.rs b/tests/target/issue_4573.rs
new file mode 100644
index 00000000..82cfe4f5
--- /dev/null
+++ b/tests/target/issue_4573.rs
@@ -0,0 +1,245 @@
+// rustmft-version:Two
+// rustmft-use_small_heuristics:Max
+// rustmft-merge_derives:false
+// These are the same rustfmt configuration options that are used
+// in the comiler as of ce39461ca75a and 8eb7c58dbb7b
+// These are commits in https://github.com/rust-lang/rust
+
+#![no_std] // inner attribute comment
+// inner attribute comment
+#![no_implicit_prelude]
+// post inner attribute comment
+
+#[cfg(not(miri))] // inline comment
+#[no_link]
+extern crate foo;
+
+// before attributes
+#[no_link]
+// between attributes
+#[cfg(not(miri))] // inline comment
+extern crate foo as bar;
+
+#[cfg(not(miri))] // inline comment
+// between attribute and use
+use foo;
+
+#[cfg(not(miri))] // inline comment
+use foo;
+
+/* pre attributre */
+#[cfg(not(miri))]
+use foo::bar;
+
+#[cfg(not(miri))] // inline comment
+use foo::bar as FooBar;
+
+#[cfg(not(miri))] // inline comment
+#[allow(unused)]
+#[deprecated(
+    since = "5.2",  // inline inner comment
+    note = "FOO was rarely used. Users should instead use BAR"
+)]
+#[allow(unused)]
+static FOO: i32 = 42;
+
+#[used]
+#[export_name = "FOO"]
+#[cfg(not(miri))] // inline comment
+#[deprecated(
+    since = "5.2",
+    note = "FOO was rarely used. Users should instead use BAR"
+)]
+static FOO: i32 = 42;
+
+#[cfg(not(miri))] // inline comment
+#[export_name = "FOO"]
+static BAR: &'static str = "bar";
+
+#[cfg(not(miri))] // inline comment
+const BAR: i32 = 42;
+
+#[cfg(not(miri))] // inline comment
+#[no_mangle]
+#[link_section = ".example_section"]
+fn foo(bar: usize) {
+    #[cfg(not(miri))] // inline comment
+    println!("hello world!");
+}
+
+#[cfg(not(miri))] // inline comment
+mod foo {}
+
+#[cfg(not(miri))] // inline comment
+extern "C" {
+    fn my_c_function(x: i32) -> bool;
+}
+
+#[cfg(not(miri))] // inline comment
+#[link(name = "CoreFoundation", kind = "framework")]
+extern "C" {
+
+    #[link_name = "actual_symbol_name"] // inline comment
+    // between attribute and function
+    fn my_c_function(x: i32) -> bool;
+}
+
+#[cfg(not(miri))] // inline comment
+pub extern "C" fn callable_from_c(x: i32) -> bool {
+    x % 3 == 0
+}
+
+#[cfg(not(miri))] // inline comment
+/* between attribute block comment */
+#[no_mangle]
+/* between attribute and type */
+type Foo = Bar<u8>;
+
+#[no_mangle]
+#[cfg(not(miri))] // inline comment
+#[non_exhaustive] // inline comment
+enum Foo {
+    Bar,
+    Baz,
+}
+
+#[no_mangle]
+#[cfg(not(miri))] /* inline comment */
+struct Foo<A> {
+    x: A,
+}
+
+#[cfg(not(miri))] // inline comment
+union Foo<A, B> {
+    x: A,
+    y: B,
+}
+
+#[cfg(not(miri))] // inline comment
+trait Foo {}
+
+#[cfg(not(miri))] // inline comment
+trait Foo = Bar + Quux;
+
+#[cfg(not(miri))] // inline comment
+impl Foo {}
+
+#[cfg(not(miri))] // inline comment
+macro_rules! bar {
+    (3) => {};
+}
+
+mod nested {
+    #[cfg(not(miri))] // inline comment
+    // between attribute and use
+    use foo;
+
+    #[cfg(not(miri))] // inline comment
+    use foo;
+
+    #[cfg(not(miri))] // inline comment
+    use foo::bar;
+
+    #[cfg(not(miri))] // inline comment
+    use foo::bar as FooBar;
+
+    #[cfg(not(miri))] // inline comment
+    static FOO: i32 = 42;
+
+    #[cfg(not(miri))] // inline comment
+    static FOO: i32 = 42;
+
+    #[cfg(not(miri))] // inline comment
+    static FOO: &'static str = "bar";
+
+    #[cfg(not(miri))] // inline comment
+    const FOO: i32 = 42;
+
+    #[cfg(not(miri))] // inline comment
+    fn foo(bar: usize) {
+        #[cfg(not(miri))] // inline comment
+        println!("hello world!");
+    }
+
+    #[cfg(not(miri))] // inline comment
+    mod foo {}
+
+    #[cfg(not(miri))] // inline comment
+    mod foo {}
+
+    #[cfg(not(miri))] // inline comment
+    extern "C" {
+        fn my_c_function(x: i32) -> bool;
+    }
+
+    #[cfg(not(miri))] // inline comment
+    #[link(name = "CoreFoundation", kind = "framework")]
+    extern "C" {
+
+        #[link_name = "actual_symbol_name"] // inline comment
+        // between attribute and function
+        fn my_c_function(x: i32) -> bool;
+    }
+
+    #[cfg(not(miri))] // inline comment
+    pub extern "C" fn callable_from_c(x: i32) -> bool {
+        x % 3 == 0
+    }
+
+    #[cfg(not(miri))] // inline comment
+    type Foo = Bar<u8>;
+
+    #[cfg(not(miri))] // inline comment
+    #[non_exhaustive] // inline comment
+    enum Foo {
+        // comment
+        #[attribute_1]
+        #[attribute_2] // comment
+        // comment!
+        Bar,
+        /* comment */
+        #[attribute_1]
+        #[attribute_2] /* comment */
+        #[attribute_3]
+        #[attribute_4]
+        /* comment! */
+        Baz,
+    }
+
+    #[cfg(not(miri))] // inline comment
+    struct Foo<A> {
+        x: A,
+    }
+
+    #[cfg(not(miri))] // inline comment
+    union Foo<A, B> {
+        #[attribute_1]
+        #[attribute_2] /* comment */
+        #[attribute_3]
+        #[attribute_4] // comment
+        x: A,
+        y: B,
+    }
+
+    #[cfg(not(miri))] // inline comment
+    #[allow(missing_docs)]
+    trait Foo {
+        #[must_use] /* comment
+                     * that wrappes to
+                     * the next line */
+        fn bar() {}
+    }
+
+    #[allow(missing_docs)]
+    #[cfg(not(miri))] // inline comment
+    trait Foo = Bar + Quux;
+
+    #[allow(missing_docs)]
+    #[cfg(not(miri))] // inline comment
+    impl Foo {}
+
+    #[cfg(not(miri))] // inline comment
+    macro_rules! bar {
+        (3) => {};
+    }
+}