Skip to content

Commit 423f63a

Browse files
committed
Fix doc attribute parsing to properly handle block comments
The existing parsing logic is flawed, as it concatenates consecutive lines, e.g. this: /** This is the first paragraph. */ ...becomes this: /** * # Heading This is the first paragraph. */ Lists are also effected.
1 parent ac824ec commit 423f63a

12 files changed

+316
-38
lines changed

src/bindgen/utilities.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -425,13 +425,38 @@ impl SynAttributeHelpers for [syn::Attribute] {
425425
}
426426

427427
fn split_doc_attr(input: &str) -> Vec<String> {
428+
if !input.contains('\n') {
429+
// This is a special case for single-line doc comments, which normally already contain a leading space
430+
// if it is desired.
431+
return vec![input.to_owned()];
432+
}
433+
434+
// Calculate the common leading whitespace across all non-empty lines, so we can trim it from all lines while
435+
// preserving relative indentation. This is important for items nested (esp. in modules) where the doc comment
436+
// is usually indented to the same level as the item, leaving whitespace at the beginning of each line.
437+
// We want to trim that, but preserve relative indentation.
438+
// Note: we assume you aren't using mixed tabs and spaces, but that is probably safe to assume for rust code
439+
// which is usually indented with spaces.
440+
let common_indent = input
441+
.trim()
442+
.lines()
443+
.filter(|line| !line.trim().is_empty())
444+
.map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
445+
.min()
446+
.unwrap_or(0);
447+
428448
input
429-
// Convert two newline (indicate "new paragraph") into two line break.
430-
.replace("\n\n", " \n \n")
431-
// Convert newline after two spaces (indicate "line break") into line break.
432-
.split(" \n")
433-
// Convert single newline (indicate hard-wrapped) into space.
434-
.map(|s| s.replace('\n', " "))
435-
.map(|s| s.trim_end().to_string())
449+
// Trim leading and trailing whitespace (first and last lines are usually empty)
450+
.trim()
451+
.lines()
452+
// Add a leading space to non-empty lines to prevent misinterpreting leading symbols and
453+
// mirror the behaviour of single-line doc comments, which already have a leading space.
454+
.map(|s| {
455+
if s.trim().is_empty() {
456+
String::new()
457+
} else {
458+
format!(" {}", s.chars().skip(common_indent).collect::<String>())
459+
}
460+
})
436461
.collect()
437462
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
root;
3+
block_function;
4+
FOO;
5+
};

tests/expectations/documentation_attr.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
*like this one with a new line character at its end
99
*and this one as well. So they are in the same paragraph
1010
*
11-
*Line ends with one new line should not break
11+
*We treat empty doc comments as empty lines, so they break to the next paragraph.
1212
*
13-
*Line ends with two spaces and a new line
14-
*should break to next line
13+
* Newlines are preserved with leading spaces added
14+
* to prettify and avoid misinterpreting leading symbols.
15+
*like headings and lists.
1516
*
16-
*Line ends with two new lines
17+
* Line ends with two new lines
1718
*
18-
*Should break to next paragraph
19+
* Should break to next paragraph
1920
*/
2021
void root(void);

tests/expectations/documentation_attr.compat.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ extern "C" {
1212
*like this one with a new line character at its end
1313
*and this one as well. So they are in the same paragraph
1414
*
15-
*Line ends with one new line should not break
15+
*We treat empty doc comments as empty lines, so they break to the next paragraph.
1616
*
17-
*Line ends with two spaces and a new line
18-
*should break to next line
17+
* Newlines are preserved with leading spaces added
18+
* to prettify and avoid misinterpreting leading symbols.
19+
*like headings and lists.
1920
*
20-
*Line ends with two new lines
21+
* Line ends with two new lines
2122
*
22-
*Should break to next paragraph
23+
* Should break to next paragraph
2324
*/
2425
void root(void);
2526

tests/expectations/documentation_attr.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ extern "C" {
1010
///like this one with a new line character at its end
1111
///and this one as well. So they are in the same paragraph
1212
///
13-
///Line ends with one new line should not break
13+
///We treat empty doc comments as empty lines, so they break to the next paragraph.
1414
///
15-
///Line ends with two spaces and a new line
16-
///should break to next line
15+
/// Newlines are preserved with leading spaces added
16+
/// to prettify and avoid misinterpreting leading symbols.
17+
///like headings and lists.
1718
///
18-
///Line ends with two new lines
19+
/// Line ends with two new lines
1920
///
20-
///Should break to next paragraph
21+
/// Should break to next paragraph
2122
void root();
2223

2324
} // extern "C"

tests/expectations/documentation_attr.pyx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ cdef extern from *:
1010
#like this one with a new line character at its end
1111
#and this one as well. So they are in the same paragraph
1212
#
13-
#Line ends with one new line should not break
13+
#We treat empty doc comments as empty lines, so they break to the next paragraph.
1414
#
15-
#Line ends with two spaces and a new line
16-
#should break to next line
15+
# Newlines are preserved with leading spaces added
16+
# to prettify and avoid misinterpreting leading symbols.
17+
#like headings and lists.
1718
#
18-
#Line ends with two new lines
19+
# Line ends with two new lines
1920
#
20-
#Should break to next paragraph
21+
# Should break to next paragraph
2122
void root();
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <stdarg.h>
2+
#include <stdbool.h>
3+
#include <stdint.h>
4+
#include <stdlib.h>
5+
6+
/**
7+
* Some docs.
8+
*/
9+
extern const uint32_t FOO;
10+
11+
/**
12+
* The root of all evil.
13+
*
14+
* But at least it contains some more documentation as someone would expect
15+
* from a simple test case like this.
16+
*
17+
* # Hint
18+
* Always ensure that everything is properly documented, even if you feel lazy.
19+
* **Sometimes** it is also helpful to include some markdown formatting.
20+
*
21+
* ////////////////////////////////////////////////////////////////////////////
22+
*
23+
* Attention:
24+
*
25+
* This is an indentation test.
26+
* The indentation should be preserved in the generated documentation.
27+
*
28+
* ...and here is my shopping list to check that we do not mess with line breaks and indentation:
29+
* - Bread
30+
* - Brown
31+
* - White
32+
* - Milk
33+
* - Eggs
34+
*/
35+
void root(void);
36+
37+
/**
38+
* In this block, we're testing indentation handling.
39+
* Since all of these lines are equally indented, we want to discard the common leading whitespace,
40+
* but preserve the relative indentation and line breaks.
41+
*
42+
* Including between paragraphs,
43+
*
44+
* - And
45+
* - within
46+
* - Lists
47+
*/
48+
void block_function(void);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include <stdarg.h>
2+
#include <stdbool.h>
3+
#include <stdint.h>
4+
#include <stdlib.h>
5+
6+
#ifdef __cplusplus
7+
extern "C" {
8+
#endif // __cplusplus
9+
10+
/**
11+
* Some docs.
12+
*/
13+
extern const uint32_t FOO;
14+
15+
/**
16+
* The root of all evil.
17+
*
18+
* But at least it contains some more documentation as someone would expect
19+
* from a simple test case like this.
20+
*
21+
* # Hint
22+
* Always ensure that everything is properly documented, even if you feel lazy.
23+
* **Sometimes** it is also helpful to include some markdown formatting.
24+
*
25+
* ////////////////////////////////////////////////////////////////////////////
26+
*
27+
* Attention:
28+
*
29+
* This is an indentation test.
30+
* The indentation should be preserved in the generated documentation.
31+
*
32+
* ...and here is my shopping list to check that we do not mess with line breaks and indentation:
33+
* - Bread
34+
* - Brown
35+
* - White
36+
* - Milk
37+
* - Eggs
38+
*/
39+
void root(void);
40+
41+
/**
42+
* In this block, we're testing indentation handling.
43+
* Since all of these lines are equally indented, we want to discard the common leading whitespace,
44+
* but preserve the relative indentation and line breaks.
45+
*
46+
* Including between paragraphs,
47+
*
48+
* - And
49+
* - within
50+
* - Lists
51+
*/
52+
void block_function(void);
53+
54+
#ifdef __cplusplus
55+
} // extern "C"
56+
#endif // __cplusplus
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include <cstdarg>
2+
#include <cstdint>
3+
#include <cstdlib>
4+
#include <ostream>
5+
#include <new>
6+
7+
extern "C" {
8+
9+
/// Some docs.
10+
extern const uint32_t FOO;
11+
12+
/// The root of all evil.
13+
///
14+
/// But at least it contains some more documentation as someone would expect
15+
/// from a simple test case like this.
16+
///
17+
/// # Hint
18+
/// Always ensure that everything is properly documented, even if you feel lazy.
19+
/// **Sometimes** it is also helpful to include some markdown formatting.
20+
///
21+
/// ////////////////////////////////////////////////////////////////////////////
22+
///
23+
/// Attention:
24+
///
25+
/// This is an indentation test.
26+
/// The indentation should be preserved in the generated documentation.
27+
///
28+
/// ...and here is my shopping list to check that we do not mess with line breaks and indentation:
29+
/// - Bread
30+
/// - Brown
31+
/// - White
32+
/// - Milk
33+
/// - Eggs
34+
void root();
35+
36+
/// In this block, we're testing indentation handling.
37+
/// Since all of these lines are equally indented, we want to discard the common leading whitespace,
38+
/// but preserve the relative indentation and line breaks.
39+
///
40+
/// Including between paragraphs,
41+
///
42+
/// - And
43+
/// - within
44+
/// - Lists
45+
void block_function();
46+
47+
} // extern "C"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t
2+
from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t
3+
cdef extern from *:
4+
ctypedef bint bool
5+
ctypedef struct va_list
6+
7+
cdef extern from *:
8+
9+
# Some docs.
10+
extern const uint32_t FOO;
11+
12+
# The root of all evil.
13+
#
14+
# But at least it contains some more documentation as someone would expect
15+
# from a simple test case like this.
16+
#
17+
# # Hint
18+
# Always ensure that everything is properly documented, even if you feel lazy.
19+
# **Sometimes** it is also helpful to include some markdown formatting.
20+
#
21+
# ////////////////////////////////////////////////////////////////////////////
22+
#
23+
# Attention:
24+
#
25+
# This is an indentation test.
26+
# The indentation should be preserved in the generated documentation.
27+
#
28+
# ...and here is my shopping list to check that we do not mess with line breaks and indentation:
29+
# - Bread
30+
# - Brown
31+
# - White
32+
# - Milk
33+
# - Eggs
34+
void root();
35+
36+
# In this block, we're testing indentation handling.
37+
# Since all of these lines are equally indented, we want to discard the common leading whitespace,
38+
# but preserve the relative indentation and line breaks.
39+
#
40+
# Including between paragraphs,
41+
#
42+
# - And
43+
# - within
44+
# - Lists
45+
void block_function();

0 commit comments

Comments
 (0)