| 1 | //! Utilities for manipulating C/C++ comments. |
| 2 | |
| 3 | /// The type of a comment. |
| 4 | #[derive (Debug, PartialEq, Eq)] |
| 5 | enum Kind { |
| 6 | /// A `///` comment, or something of the like. |
| 7 | /// All lines in a comment should start with the same symbol. |
| 8 | SingleLines, |
| 9 | /// A `/**` comment, where each other line can start with `*` and the |
| 10 | /// entire block ends with `*/`. |
| 11 | MultiLine, |
| 12 | } |
| 13 | |
| 14 | /// Preprocesses a C/C++ comment so that it is a valid Rust comment. |
| 15 | pub(crate) fn preprocess(comment: &str) -> String { |
| 16 | match self::kind(comment) { |
| 17 | Some(Kind::SingleLines) => preprocess_single_lines(comment), |
| 18 | Some(Kind::MultiLine) => preprocess_multi_line(comment), |
| 19 | None => comment.to_owned(), |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | /// Gets the kind of the doc comment, if it is one. |
| 24 | fn kind(comment: &str) -> Option<Kind> { |
| 25 | if comment.starts_with("/*" ) { |
| 26 | Some(Kind::MultiLine) |
| 27 | } else if comment.starts_with("//" ) { |
| 28 | Some(Kind::SingleLines) |
| 29 | } else { |
| 30 | None |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | /// Preprocesses multiple single line comments. |
| 35 | /// |
| 36 | /// Handles lines starting with both `//` and `///`. |
| 37 | fn preprocess_single_lines(comment: &str) -> String { |
| 38 | debug_assert!(comment.starts_with("//" ), "comment is not single line" ); |
| 39 | |
| 40 | let lines: Vec<_> = commentimpl Iterator |
| 41 | .lines() |
| 42 | .map(|l: &str| l.trim().trim_start_matches('/' )) |
| 43 | .collect(); |
| 44 | lines.join(sep:" \n" ) |
| 45 | } |
| 46 | |
| 47 | fn preprocess_multi_line(comment: &str) -> String { |
| 48 | let comment: &str = comment&str |
| 49 | .trim_start_matches('/' ) |
| 50 | .trim_end_matches('/' ) |
| 51 | .trim_end_matches('*' ); |
| 52 | |
| 53 | // Strip any potential `*` characters preceding each line. |
| 54 | let mut lines: Vec<_> = commentimpl Iterator |
| 55 | .lines() |
| 56 | .map(|line: &str| line.trim().trim_start_matches('*' ).trim_start_matches('!' )) |
| 57 | .skip_while(|line: &&str| line.trim().is_empty()) // Skip the first empty lines. |
| 58 | .collect(); |
| 59 | |
| 60 | // Remove the trailing line corresponding to the `*/`. |
| 61 | if lines.last().map_or(default:false, |l: &&str| l.trim().is_empty()) { |
| 62 | lines.pop(); |
| 63 | } |
| 64 | |
| 65 | lines.join(sep:" \n" ) |
| 66 | } |
| 67 | |
| 68 | #[cfg (test)] |
| 69 | mod test { |
| 70 | use super::*; |
| 71 | |
| 72 | #[test ] |
| 73 | fn picks_up_single_and_multi_line_doc_comments() { |
| 74 | assert_eq!(kind("/// hello" ), Some(Kind::SingleLines)); |
| 75 | assert_eq!(kind("/** world */" ), Some(Kind::MultiLine)); |
| 76 | } |
| 77 | |
| 78 | #[test ] |
| 79 | fn processes_single_lines_correctly() { |
| 80 | assert_eq!(preprocess("///" ), "" ); |
| 81 | assert_eq!(preprocess("/// hello" ), " hello" ); |
| 82 | assert_eq!(preprocess("// hello" ), " hello" ); |
| 83 | assert_eq!(preprocess("// hello" ), " hello" ); |
| 84 | } |
| 85 | |
| 86 | #[test ] |
| 87 | fn processes_multi_lines_correctly() { |
| 88 | assert_eq!(preprocess("/**/" ), "" ); |
| 89 | |
| 90 | assert_eq!( |
| 91 | preprocess("/** hello \n * world \n * foo \n */" ), |
| 92 | " hello \n world \n foo" |
| 93 | ); |
| 94 | |
| 95 | assert_eq!( |
| 96 | preprocess("/** \nhello \n*world \n*foo \n*/" ), |
| 97 | "hello \nworld \nfoo" |
| 98 | ); |
| 99 | } |
| 100 | } |
| 101 | |