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 | |