Skip to content

Commit a68f9a1

Browse files
committed
Intelligently convert C/C++ comments to Rust
With this change, we can correctly parse C++ block comments. ``` /** * Does a thing * * More documentation. This test does something * useful. */ ``` into ``` /// Does a thing /// /// More documentation. This test does something /// useful. ``` Fixes rust-lang#426.
1 parent 78e7546 commit a68f9a1

File tree

7 files changed

+127
-23
lines changed

7 files changed

+127
-23
lines changed

build.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod codegen {
1111
let dst = Path::new(&out_dir).join("codegen.rs");
1212

1313
quasi_codegen::expand(&src, &dst).unwrap();
14+
println!("cargo:rerun-if-changed=src/codegen/comment.rs");
1415
println!("cargo:rerun-if-changed=src/codegen/mod.rs");
1516
println!("cargo:rerun-if-changed=src/codegen/error.rs");
1617
println!("cargo:rerun-if-changed=src/codegen/helpers.rs");

src/codegen/comment.rs

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/// The type of a comment.
2+
#[derive(Debug, PartialEq, Eq)]
3+
enum Kind {
4+
/// A `///` comment, or something of the like.
5+
/// All lines in a comment should start with the same symbol.
6+
SingleLines,
7+
/// A `/**` comment, where each other line can start with `*` and the
8+
/// entire block ends with `*/`.
9+
MultiLine,
10+
}
11+
12+
/// Checks if a comment should be emitted as a Rust doc comment.
13+
pub fn is_doc(comment: &str) -> bool {
14+
self::kind(comment).is_some()
15+
}
16+
17+
/// Gets the kind of the doc comment, if it is one.
18+
fn kind(comment: &str) -> Option<Kind> {
19+
if comment.starts_with("/**") {
20+
Some(Kind::MultiLine)
21+
} else if comment.starts_with("///") {
22+
Some(Kind::SingleLines)
23+
} else {
24+
None
25+
}
26+
}
27+
28+
/// Preprocesses a C/C++ comment so that it is a valid Rust comment.
29+
pub fn preprocess(comment: &str) -> String {
30+
match self::kind(comment) {
31+
Some(Kind::SingleLines) => preprocess_single_lines(comment),
32+
Some(Kind::MultiLine) => preprocess_multi_line(comment),
33+
None => panic!("comment is not a doc comment"),
34+
}
35+
}
36+
37+
fn preprocess_single_lines(comment: &str) -> String {
38+
assert!(comment.starts_with("///"), "comment is not single line");
39+
// We don't need to amend the comment because each line already
40+
// starts with `///`.
41+
comment.to_owned()
42+
}
43+
44+
fn preprocess_multi_line(comment: &str) -> String {
45+
let comment = comment.trim_left_matches("/")
46+
.trim_right_matches("/")
47+
.trim_right_matches("*")
48+
.trim();
49+
50+
// Strip any potential `*` characters preceding each line.
51+
let mut lines: Vec<_> = comment.lines()
52+
.map(|line| line.trim().trim_left_matches('*').trim())
53+
.skip_while(|line| line.is_empty()) // Skip the first empty lines.
54+
.map(|line| format!("/// {}", line))
55+
.collect();
56+
57+
// Remove the trailing `*/`.
58+
let last_idx = lines.len() - 1;
59+
if lines[last_idx].is_empty() {
60+
lines.remove(last_idx);
61+
}
62+
63+
lines.join("\n")
64+
}
65+
66+
#[cfg(test)]
67+
mod test {
68+
use super::*;
69+
70+
#[test]
71+
fn ignores_non_doc_comments() {
72+
assert_eq!(kind("// hello"), None);
73+
}
74+
75+
#[test]
76+
fn picks_up_single_and_multi_line_doc_comments() {
77+
assert_eq!(kind("/// hello"), Some(Kind::SingleLines));
78+
assert_eq!(kind("/** world */"), Some(Kind::MultiLine));
79+
}
80+
81+
#[test]
82+
fn processes_single_lines_correctly() {
83+
assert_eq!(preprocess("/// hello"), "/// hello");
84+
}
85+
86+
#[test]
87+
fn processes_multi_lines_correctly() {
88+
assert_eq!(preprocess("/** hello \n * world \n * foo \n */"),
89+
"/// hello\n/// world\n/// foo");
90+
91+
assert_eq!(preprocess("/**\nhello\n*world\n*foo\n*/"),
92+
"/// hello\n/// world\n/// foo");
93+
}
94+
}

src/codegen/helpers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub mod attributes {
3030
aster::AstBuilder::new().attr().word("inline")
3131
}
3232

33-
pub fn doc(comment: &str) -> ast::Attribute {
33+
pub fn comment(comment: &str) -> ast::Attribute {
3434
aster::AstBuilder::new().attr().doc(comment)
3535
}
3636

src/codegen/mod.rs

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod error;
22
mod helpers;
3+
mod comment;
34
pub mod struct_layout;
45

56
use self::helpers::{BlobTyBuilder, attributes};
@@ -618,7 +619,10 @@ impl CodeGenerator for Type {
618619

619620
if ctx.options().generate_comments {
620621
if let Some(comment) = item.comment() {
621-
typedef = typedef.attr().doc(comment);
622+
if comment::is_doc(comment) {
623+
typedef = typedef.attr()
624+
.doc(&comment::preprocess(comment)[..]);
625+
}
622626
}
623627
}
624628

@@ -946,7 +950,9 @@ impl<'a> FieldCodegen<'a> for FieldData {
946950
let mut attrs = vec![];
947951
if ctx.options().generate_comments {
948952
if let Some(comment) = self.comment() {
949-
attrs.push(attributes::doc(comment));
953+
if comment::is_doc(comment) {
954+
attrs.push(attributes::comment(&comment::preprocess(comment)[..]));
955+
}
950956
}
951957
}
952958

@@ -1411,7 +1417,9 @@ impl CodeGenerator for CompInfo {
14111417
let mut needs_default_impl = false;
14121418
if ctx.options().generate_comments {
14131419
if let Some(comment) = item.comment() {
1414-
attributes.push(attributes::doc(comment));
1420+
if comment::is_doc(comment) {
1421+
attributes.push(attributes::comment(&comment::preprocess(comment)[..]));
1422+
}
14151423
}
14161424
}
14171425
if self.packed() {
@@ -2359,7 +2367,10 @@ impl CodeGenerator for Enum {
23592367

23602368
if ctx.options().generate_comments {
23612369
if let Some(comment) = item.comment() {
2362-
builder = builder.with_attr(attributes::doc(comment));
2370+
if comment::is_doc(comment) {
2371+
builder = builder.with_attr(
2372+
attributes::comment(&comment::preprocess(comment)[..]));
2373+
}
23632374
}
23642375
}
23652376

@@ -3061,7 +3072,9 @@ impl CodeGenerator for Function {
30613072

30623073
if ctx.options().generate_comments {
30633074
if let Some(comment) = item.comment() {
3064-
attributes.push(attributes::doc(comment));
3075+
if comment::is_doc(comment) {
3076+
attributes.push(attributes::comment(&comment::preprocess(comment)[..]));
3077+
}
30653078
}
30663079
}
30673080

tests/expectations/tests/accessors.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
#[derive(Debug, Default, Copy)]
99
pub struct SomeAccessors {
1010
pub mNoAccessor: ::std::os::raw::c_int,
11-
/** <div rustbindgen accessor></div> */
11+
/// <div rustbindgen accessor></div>
1212
pub mBothAccessors: ::std::os::raw::c_int,
13-
/** <div rustbindgen accessor="unsafe"></div> */
13+
/// <div rustbindgen accessor="unsafe"></div>
1414
pub mUnsafeAccessors: ::std::os::raw::c_int,
15-
/** <div rustbindgen accessor="immutable"></div> */
15+
/// <div rustbindgen accessor="immutable"></div>
1616
pub mImmutableAccessor: ::std::os::raw::c_int,
1717
}
1818
#[test]
@@ -68,7 +68,7 @@ impl SomeAccessors {
6868
&self.mImmutableAccessor
6969
}
7070
}
71-
/** <div rustbindgen accessor></div> */
71+
/// <div rustbindgen accessor></div>
7272
#[repr(C)]
7373
#[derive(Debug, Default, Copy)]
7474
pub struct AllAccessors {
@@ -114,7 +114,7 @@ impl AllAccessors {
114114
&mut self.mAlsoBothAccessors
115115
}
116116
}
117-
/** <div rustbindgen accessor="unsafe"></div> */
117+
/// <div rustbindgen accessor="unsafe"></div>
118118
#[repr(C)]
119119
#[derive(Debug, Default, Copy)]
120120
pub struct AllUnsafeAccessors {
@@ -162,16 +162,16 @@ impl AllUnsafeAccessors {
162162
&mut self.mAlsoBothAccessors
163163
}
164164
}
165-
/** <div rustbindgen accessor></div> */
165+
/// <div rustbindgen accessor></div>
166166
#[repr(C)]
167167
#[derive(Debug, Default, Copy)]
168168
pub struct ContradictAccessors {
169169
pub mBothAccessors: ::std::os::raw::c_int,
170-
/** <div rustbindgen accessor="false"></div> */
170+
/// <div rustbindgen accessor="false"></div>
171171
pub mNoAccessors: ::std::os::raw::c_int,
172-
/** <div rustbindgen accessor="unsafe"></div> */
172+
/// <div rustbindgen accessor="unsafe"></div>
173173
pub mUnsafeAccessors: ::std::os::raw::c_int,
174-
/** <div rustbindgen accessor="immutable"></div> */
174+
/// <div rustbindgen accessor="immutable"></div>
175175
pub mImmutableAccessor: ::std::os::raw::c_int,
176176
}
177177
#[test]
@@ -229,7 +229,7 @@ impl ContradictAccessors {
229229
&self.mImmutableAccessor
230230
}
231231
}
232-
/** <div rustbindgen accessor replaces="Replaced"></div> */
232+
/// <div rustbindgen accessor replaces="Replaced"></div>
233233
#[repr(C)]
234234
#[derive(Debug, Default, Copy)]
235235
pub struct Replaced {
@@ -258,7 +258,7 @@ impl Replaced {
258258
&mut self.mAccessor
259259
}
260260
}
261-
/** <div rustbindgen accessor></div> */
261+
/// <div rustbindgen accessor></div>
262262
#[repr(C)]
263263
#[derive(Debug, Default, Copy)]
264264
pub struct Wrapper {

tests/expectations/tests/annotation_hide.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
55

66

7-
/**
8-
* <div rustbindgen opaque></div>
9-
*/
7+
/// <div rustbindgen opaque></div>
108
#[repr(C)]
119
#[derive(Debug, Default, Copy)]
1210
pub struct D {

tests/expectations/tests/class_use_as.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
55

66

7-
/**
8-
* <div rustbindgen="true" replaces="whatever"></div>
9-
*/
7+
/// <div rustbindgen="true" replaces="whatever"></div>
108
#[repr(C)]
119
#[derive(Debug, Default, Copy)]
1210
pub struct whatever {

0 commit comments

Comments
 (0)