• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
preprocess(comment: &str, indent: usize) -> String15 pub fn preprocess(comment: &str, indent: usize) -> String {
16     match self::kind(comment) {
17         Some(Kind::SingleLines) => preprocess_single_lines(comment, indent),
18         Some(Kind::MultiLine) => preprocess_multi_line(comment, indent),
19         None => comment.to_owned(),
20     }
21 }
22 
23 /// Gets the kind of the doc comment, if it is one.
kind(comment: &str) -> Option<Kind>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 
make_indent(indent: usize) -> String34 fn make_indent(indent: usize) -> String {
35     const RUST_INDENTATION: usize = 4;
36     " ".repeat(indent * RUST_INDENTATION)
37 }
38 
39 /// Preprocesses multiple single line comments.
40 ///
41 /// Handles lines starting with both `//` and `///`.
preprocess_single_lines(comment: &str, indent: usize) -> String42 fn preprocess_single_lines(comment: &str, indent: usize) -> String {
43     debug_assert!(comment.starts_with("//"), "comment is not single line");
44 
45     let indent = make_indent(indent);
46     let mut is_first = true;
47     let lines: Vec<_> = comment
48         .lines()
49         .map(|l| l.trim().trim_start_matches('/'))
50         .map(|l| {
51             let indent = if is_first { "" } else { &*indent };
52             is_first = false;
53             format!("{}///{}", indent, l)
54         })
55         .collect();
56     lines.join("\n")
57 }
58 
preprocess_multi_line(comment: &str, indent: usize) -> String59 fn preprocess_multi_line(comment: &str, indent: usize) -> String {
60     let comment = comment
61         .trim_start_matches('/')
62         .trim_end_matches('/')
63         .trim_end_matches('*');
64 
65     let indent = make_indent(indent);
66     // Strip any potential `*` characters preceding each line.
67     let mut is_first = true;
68     let mut lines: Vec<_> = comment
69         .lines()
70         .map(|line| line.trim().trim_start_matches('*').trim_start_matches('!'))
71         .skip_while(|line| line.trim().is_empty()) // Skip the first empty lines.
72         .map(|line| {
73             let indent = if is_first { "" } else { &*indent };
74             is_first = false;
75             format!("{}///{}", indent, line)
76         })
77         .collect();
78 
79     // Remove the trailing line corresponding to the `*/`.
80     if lines
81         .last()
82         .map_or(false, |l| l.trim().is_empty() || l.trim() == "///")
83     {
84         lines.pop();
85     }
86 
87     lines.join("\n")
88 }
89 
90 #[cfg(test)]
91 mod test {
92     use super::*;
93 
94     #[test]
picks_up_single_and_multi_line_doc_comments()95     fn picks_up_single_and_multi_line_doc_comments() {
96         assert_eq!(kind("/// hello"), Some(Kind::SingleLines));
97         assert_eq!(kind("/** world */"), Some(Kind::MultiLine));
98     }
99 
100     #[test]
processes_single_lines_correctly()101     fn processes_single_lines_correctly() {
102         assert_eq!(preprocess("/// hello", 0), "/// hello");
103         assert_eq!(preprocess("// hello", 0), "/// hello");
104         assert_eq!(preprocess("//    hello", 0), "///    hello");
105     }
106 
107     #[test]
processes_multi_lines_correctly()108     fn processes_multi_lines_correctly() {
109         assert_eq!(
110             preprocess("/** hello \n * world \n * foo \n */", 0),
111             "/// hello\n/// world\n/// foo"
112         );
113 
114         assert_eq!(
115             preprocess("/**\nhello\n*world\n*foo\n*/", 0),
116             "///hello\n///world\n///foo"
117         );
118     }
119 }
120