Skip to content

Commit e82761c

Browse files
committed
Modernize doc helper functions
1 parent 3d61c2c commit e82761c

File tree

3 files changed

+242
-51
lines changed

3 files changed

+242
-51
lines changed

compiler/rustc_ast/src/util/comments.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ pub fn beautify_doc_string(data: Symbol) -> String {
3131

3232
let data = data.as_str();
3333
if data.contains('\n') {
34-
let lines = data.lines().map(|s| s.to_string()).collect::<Vec<String>>();
35-
let lines = vertical_trim(lines);
36-
let lines = horizontal_trim(lines);
37-
lines.join("\n")
34+
let lines = data.lines().collect::<Vec<&str>>();
35+
let lines = vertical_trim(&lines);
36+
match horizontal_trim(lines) {
37+
Some(lines) => lines.join("\n"),
38+
None => lines.join("\n"),
39+
}
3840
} else {
3941
data.to_string()
4042
}

compiler/rustc_ast/src/util/comments/block_comment.rs

+90-47
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,106 @@
22
* Block comment helpers.
33
*/
44

5-
/// remove whitespace-only lines from the start/end of lines
6-
pub fn vertical_trim(lines: Vec<String>) -> Vec<String> {
7-
let mut i = 0;
8-
let mut j = lines.len();
9-
// first line of all-stars should be omitted
10-
if !lines.is_empty() && lines[0].chars().all(|c| c == '*') {
11-
i += 1;
5+
#[cfg(test)]
6+
mod tests;
7+
8+
/********************************************************
9+
* Skip lines based on the following rules:
10+
*
11+
* * Skip first line of all stars ("*").
12+
* * Skip consecutive empty lines top-bottom.
13+
* * Skip consecutive empty lines bottom-top.
14+
* * Skip last line contains pattern "^ ?\**$" in regex.
15+
*******************************************************/
16+
pub fn vertical_trim<'arr, 'row: 'arr>(lines: &'arr [&'row str]) -> &'arr [&'row str] {
17+
let mut region = lines;
18+
if let [first, tail @ ..] = region {
19+
// Skip first line of all-stars.
20+
if first.bytes().all(|c| c == b'*') {
21+
region = tail;
22+
}
1223
}
1324

14-
while i < j && lines[i].trim().is_empty() {
15-
i += 1;
25+
// Skip consecutive empty lines.
26+
loop {
27+
match region {
28+
[first, tail @ ..] if first.trim().is_empty() => region = tail,
29+
_ => break,
30+
}
1631
}
17-
// like the first, a last line of all stars should be omitted
18-
if j > i && lines[j - 1].chars().skip(1).all(|c| c == '*') {
19-
j -= 1;
32+
33+
// Skip last line contains pattern "^ ?*\**" in regex.
34+
if let [head @ .., last] = region {
35+
let s = match last.as_bytes() {
36+
[b' ', tail @ ..] => tail,
37+
all => all,
38+
};
39+
if s.iter().all(|&c| c == b'*') {
40+
region = head;
41+
}
2042
}
2143

22-
while j > i && lines[j - 1].trim().is_empty() {
23-
j -= 1;
44+
// Skip consecutive empty lines from last line backward.
45+
loop {
46+
match region {
47+
[head @ .., last] if last.trim().is_empty() => region = head,
48+
_ => break,
49+
}
2450
}
2551

26-
lines[i..j].to_vec()
52+
region
2753
}
2854

29-
/// remove a "[ \t]*\*" block from each line, if possible
30-
pub fn horizontal_trim(lines: Vec<String>) -> Vec<String> {
31-
let mut i = usize::MAX;
32-
let mut can_trim = true;
33-
let mut first = true;
55+
/// Trim all "\s*\*" prefix from comment: all or nothing.
56+
///
57+
/// For example,
58+
/// ```text
59+
/// * one two three four five ...
60+
/// * one two three four five ...
61+
/// * one two three four five ...
62+
/// ```
63+
/// will be trimmed to
64+
/// ```text
65+
/// one two three four five ...
66+
/// one two three four five ...
67+
/// one two three four five ...
68+
/// ```
69+
pub fn horizontal_trim<'arr, 'row: 'arr>(lines: &'arr [&'row str]) -> Option<Vec<&'row str>> {
70+
let prefix = match lines {
71+
[first, ..] => get_prefix(first)?,
72+
_ => return None,
73+
};
3474

35-
for line in &lines {
36-
for (j, c) in line.chars().enumerate() {
37-
if j > i || !"* \t".contains(c) {
38-
can_trim = false;
39-
break;
40-
}
41-
if c == '*' {
42-
if first {
43-
i = j;
44-
first = false;
45-
} else if i != j {
46-
can_trim = false;
47-
}
48-
break;
49-
}
50-
}
51-
if i >= line.len() {
52-
can_trim = false;
53-
}
54-
if !can_trim {
55-
break;
56-
}
75+
if lines.iter().any(|l| !l.starts_with(prefix)) {
76+
return None;
5777
}
5878

59-
if can_trim {
60-
lines.iter().map(|line| (&line[i + 1..line.len()]).to_string()).collect()
61-
} else {
62-
lines
63-
}
79+
let lines = lines
80+
.iter()
81+
// SAFETY: All lines have been checked if it starts with prefix
82+
.map(|l| unsafe { l.get_unchecked(prefix.len()..) })
83+
.collect();
84+
Some(lines)
85+
}
86+
87+
/// Get the prefix with pattern "\s*\*" of input `s`.
88+
fn get_prefix(s: &str) -> Option<&str> {
89+
let mut bytes = s.as_bytes();
90+
let dst: *const u8 = loop {
91+
match bytes {
92+
[b' ' | b'\t', end @ ..] => bytes = end,
93+
[b'*', end @ ..] => break end.as_ptr(),
94+
_ => return None,
95+
}
96+
};
97+
let prefix = unsafe {
98+
// SAFETY: Two invariants are followed.
99+
// * length of `prefix` is the diff of two pointer from the same str `s`.
100+
// * lifetime of `prefix` is the same as argument `s`.
101+
let src: *const u8 = s.as_ptr();
102+
let len = dst as usize - src as usize;
103+
let slice = std::slice::from_raw_parts(src, len);
104+
std::str::from_utf8_unchecked(slice)
105+
};
106+
Some(prefix)
64107
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
use super::*;
2+
3+
// If vertical_trim trim first and last line.
4+
#[test]
5+
fn trim_vertically_first_or_line() {
6+
// Accepted cases
7+
8+
let inp = &["*********************************", "* This is a module to do foo job."];
9+
let out = &["* This is a module to do foo job."];
10+
assert_eq!(vertical_trim(inp), out);
11+
12+
let inp = &["* This is a module to do foo job.", "*********************************"];
13+
let out = &["* This is a module to do foo job."];
14+
assert_eq!(vertical_trim(inp), out);
15+
16+
let inp = &[
17+
"*********************************",
18+
"* This is a module to do foo job.",
19+
"*********************************",
20+
];
21+
let out = &["* This is a module to do foo job."];
22+
assert_eq!(vertical_trim(inp), out);
23+
24+
let inp = &[
25+
"***********************",
26+
"* This is a module to do foo job.",
27+
"*********************************",
28+
];
29+
let out = &["* This is a module to do foo job."];
30+
assert_eq!(vertical_trim(inp), out);
31+
32+
let inp = &[
33+
"**************************",
34+
" * one two three four five six seven",
35+
" ****************",
36+
];
37+
let out = &[" * one two three four five six seven"];
38+
assert_eq!(vertical_trim(inp), out);
39+
40+
let inp = &["", " * one two three four five", " "];
41+
let out = &[" * one two three four five"];
42+
assert_eq!(vertical_trim(inp), out);
43+
44+
// Non-accepted cases
45+
46+
let inp = &["\t *********************** \t", "* This is a module to do foo job."];
47+
let out = &["\t *********************** \t", "* This is a module to do foo job."];
48+
assert_eq!(vertical_trim(inp), out);
49+
50+
// More than one space indentation.
51+
let inp = &[
52+
"******************************",
53+
" * This is a module to do foo job.",
54+
" **************",
55+
];
56+
let out = &[" * This is a module to do foo job.", " **************"];
57+
assert_eq!(vertical_trim(inp), out);
58+
}
59+
60+
// Trim consecutive empty lines. Break if meet a non-empty line.
61+
#[test]
62+
fn trim_vertically_empty_lines_forward() {
63+
let inp = &[" ", " \t \t ", " * One two three four five six seven eight nine ten."];
64+
let out = &[" * One two three four five six seven eight nine ten."];
65+
assert_eq!(vertical_trim(inp), out);
66+
67+
let inp = &[
68+
" ",
69+
" * One two three four five six seven eight nine ten.",
70+
" \t \t ",
71+
" * One two three four five six seven eight nine ten.",
72+
];
73+
let out = &[
74+
" * One two three four five six seven eight nine ten.",
75+
" \t \t ",
76+
" * One two three four five six seven eight nine ten.",
77+
];
78+
assert_eq!(vertical_trim(inp), out);
79+
}
80+
81+
// Trim consecutive empty lines bottom-top. Break if meet a non-empty line.
82+
#[test]
83+
fn trim_vertically_empty_lines_backward() {
84+
let inp = &[" * One two three four five six seven eight nine ten.", " ", " \t \t "];
85+
let out = &[" * One two three four five six seven eight nine ten."];
86+
assert_eq!(vertical_trim(inp), out);
87+
88+
let inp = &[
89+
" * One two three four five six seven eight nine ten.",
90+
" ",
91+
" * One two three four five six seven eight nine ten.",
92+
" \t \t ",
93+
];
94+
let out = &[
95+
" * One two three four five six seven eight nine ten.",
96+
" ",
97+
" * One two three four five six seven eight nine ten.",
98+
];
99+
assert_eq!(vertical_trim(inp), out);
100+
}
101+
102+
// Test for any panic from wrong indexing.
103+
#[test]
104+
fn trim_vertically_empty() {
105+
let inp = &[""];
106+
let out: &[&str] = &[];
107+
assert_eq!(vertical_trim(inp), out);
108+
109+
let inp: &[&str] = &[];
110+
let out: &[&str] = &[];
111+
assert_eq!(vertical_trim(inp), out);
112+
}
113+
114+
#[test]
115+
fn trim_horizontally() {
116+
let inp = &[
117+
" \t\t * one two three",
118+
" \t\t * four fix six seven *",
119+
" \t\t * forty two ",
120+
" \t\t ** sixty nine",
121+
];
122+
let out: &[&str] = &[" one two three", " four fix six seven *", " forty two ", "* sixty nine"];
123+
assert_eq!(horizontal_trim(inp).as_deref(), Some(out));
124+
125+
// Test that we handle empty collection and collection with one item.
126+
assert_eq!(horizontal_trim(&[]).as_deref(), None);
127+
assert_eq!(horizontal_trim(&[""]).as_deref(), None);
128+
129+
// Non-accepted: "\t" will not equal to " "
130+
131+
let inp = &[
132+
" \t * one two three",
133+
" * four fix six seven *",
134+
" \t * forty two ",
135+
" \t ** sixty nine",
136+
];
137+
assert_eq!(horizontal_trim(inp).as_deref(), None);
138+
}
139+
140+
#[test]
141+
fn test_get_prefix() {
142+
assert_eq!(get_prefix(" \t **"), Some(" \t *"));
143+
assert_eq!(get_prefix("*"), Some("*"));
144+
assert_eq!(get_prefix(" \t ^*"), None);
145+
assert_eq!(get_prefix(" "), None);
146+
}

0 commit comments

Comments
 (0)