Skip to content

Commit 56d3bc7

Browse files
committed
Handle /** and ~~~ in DOC_MARKDOWN
1 parent 3cd177d commit 56d3bc7

File tree

2 files changed

+156
-20
lines changed

2 files changed

+156
-20
lines changed

clippy_lints/src/doc.rs

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,25 +50,50 @@ impl EarlyLintPass for Doc {
5050
}
5151
}
5252

53+
/// Cleanup documentation decoration (`///` and such).
54+
///
55+
/// We can't use `syntax::attr::AttributeMethods::with_desugared_doc` or
56+
/// `syntax::parse::lexer::comments::strip_doc_comment_decoration` because we need to keep track of
57+
/// the span but this function is inspired from the later.
58+
#[allow(cast_possible_truncation)]
59+
pub fn strip_doc_comment_decoration((comment, span): (&str, Span)) -> Vec<(&str, Span)> {
60+
// one-line comments lose their prefix
61+
const ONELINERS: &'static [&'static str] = &["///!", "///", "//!", "//"];
62+
for prefix in ONELINERS {
63+
if comment.starts_with(*prefix) {
64+
return vec![(
65+
&comment[prefix.len()..],
66+
Span { lo: span.lo + BytePos(prefix.len() as u32), ..span }
67+
)];
68+
}
69+
}
70+
71+
if comment.starts_with("/*") {
72+
return comment[3..comment.len() - 2].lines().map(|line| {
73+
let offset = line.as_ptr() as usize - comment.as_ptr() as usize;
74+
debug_assert_eq!(offset as u32 as usize, offset);
75+
76+
(
77+
line,
78+
Span {
79+
lo: span.lo + BytePos(offset as u32),
80+
..span
81+
}
82+
)
83+
}).collect();
84+
}
85+
86+
panic!("not a doc-comment: {}", comment);
87+
}
88+
5389
pub fn check_attrs<'a>(cx: &EarlyContext, valid_idents: &[String], attrs: &'a [ast::Attribute]) {
5490
let mut docs = vec![];
5591

56-
let mut in_multiline = false;
5792
for attr in attrs {
5893
if attr.node.is_sugared_doc {
5994
if let ast::MetaItemKind::NameValue(_, ref doc) = attr.node.value.node {
6095
if let ast::LitKind::Str(ref doc, _) = doc.node {
61-
// doc comments start with `///` or `//!`
62-
let real_doc = &doc[3..];
63-
let mut span = attr.span;
64-
span.lo = span.lo + BytePos(3);
65-
66-
// check for multiline code blocks
67-
if real_doc.trim_left().starts_with("```") {
68-
in_multiline = !in_multiline;
69-
} else if !in_multiline {
70-
docs.push((real_doc, span));
71-
}
96+
docs.extend_from_slice(&strip_doc_comment_decoration((doc, attr.span)));
7297
}
7398
}
7499
}
@@ -135,11 +160,11 @@ fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(&str, Span)])
135160
}
136161

137162
#[allow(while_let_on_iterator)] // borrowck complains about for
138-
fn jump_to(&mut self, n: char) -> Result<(), ()> {
139-
while let Some((_, c)) = self.next() {
163+
fn jump_to(&mut self, n: char) -> Result<bool, ()> {
164+
while let Some((new_line, c)) = self.next() {
140165
if c == n {
141166
self.advance_begin();
142-
return Ok(());
167+
return Ok(new_line);
143168
}
144169
}
145170

@@ -217,6 +242,54 @@ fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(&str, Span)])
217242
pos: 0,
218243
};
219244

245+
/// Check for fanced code block.
246+
macro_rules! check_block {
247+
($parser:expr, $c:tt, $new_line:expr) => {{
248+
check_block!($parser, $c, $c, $new_line)
249+
}};
250+
251+
($parser:expr, $c:pat, $c_expr:expr, $new_line:expr) => {{
252+
fn check_block(parser: &mut Parser, new_line: bool) -> Result<bool, ()> {
253+
if new_line {
254+
let mut lookup_parser = parser.clone();
255+
if let (Some((false, $c)), Some((false, $c))) = (lookup_parser.next(), lookup_parser.next()) {
256+
*parser = lookup_parser;
257+
// 3 or more ` or ~ open a code block to be closed with the same number of ` or ~
258+
let mut open_count = 3;
259+
while let Some((false, $c)) = parser.next() {
260+
open_count += 1;
261+
}
262+
263+
loop {
264+
loop {
265+
if try!(parser.jump_to($c_expr)) {
266+
break;
267+
}
268+
}
269+
270+
lookup_parser = parser.clone();
271+
if let (Some((false, $c)), Some((false, $c))) = (lookup_parser.next(), lookup_parser.next()) {
272+
let mut close_count = 3;
273+
while let Some((false, $c)) = lookup_parser.next() {
274+
close_count += 1;
275+
}
276+
277+
if close_count == open_count {
278+
*parser = lookup_parser;
279+
return Ok(true);
280+
}
281+
}
282+
}
283+
}
284+
}
285+
286+
Ok(false)
287+
}
288+
289+
check_block(&mut $parser, $new_line)
290+
}};
291+
}
292+
220293
loop {
221294
match parser.next() {
222295
Some((new_line, c)) => {
@@ -225,7 +298,20 @@ fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(&str, Span)])
225298
parser.next_line();
226299
}
227300
'`' => {
228-
try!(parser.jump_to('`'));
301+
if try!(check_block!(parser, '`', new_line)) {
302+
continue;
303+
}
304+
305+
try!(parser.jump_to('`')); // not a code block, just inline code
306+
}
307+
'~' => {
308+
if try!(check_block!(parser, '~', new_line)) {
309+
continue;
310+
}
311+
312+
// ~ does not introduce inline code, but two of them introduce
313+
// strikethrough. Too bad for the consistency but we don't care about
314+
// strikethrough.
229315
}
230316
'[' => {
231317
// Check for a reference definition `[foo]:` at the beginning of a line
@@ -249,8 +335,12 @@ fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(&str, Span)])
249335
parser.link = false;
250336

251337
match parser.peek() {
252-
Some('(') => try!(parser.jump_to(')')),
253-
Some('[') => try!(parser.jump_to(']')),
338+
Some('(') => {
339+
try!(parser.jump_to(')'));
340+
}
341+
Some('[') => {
342+
try!(parser.jump_to(']'));
343+
}
254344
Some(_) => continue,
255345
None => return Err(()),
256346
}

tests/compile-fail/doc.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
/// which should be reported only once despite being __doubly bad__.
1515
/// Here be ::is::a::global:path.
1616
//~^ ERROR: you should put `is::a::global:path` between ticks
17+
/// That's not code ~NotInCodeBlock~.
18+
//~^ ERROR: you should put `NotInCodeBlock` between ticks
1719
/// be_sure_we_got_to_the_end_of_it
1820
//~^ ERROR: you should put `be_sure_we_got_to_the_end_of_it` between ticks
1921
fn foo_bar() {
@@ -24,9 +26,14 @@ fn foo_bar() {
2426
/// foo_bar FOO_BAR
2527
/// _foo bar_
2628
/// ```
29+
///
30+
/// ~~~rust
31+
/// foo_bar FOO_BAR
32+
/// _foo bar_
33+
/// ~~~
2734
/// be_sure_we_got_to_the_end_of_it
2835
//~^ ERROR: you should put `be_sure_we_got_to_the_end_of_it` between ticks
29-
fn multiline_ticks() {
36+
fn multiline_codeblock() {
3037
}
3138

3239
/// This _is a test for
@@ -106,7 +113,7 @@ fn test_unicode() {
106113
//~^ ERROR: you should put `be_sure_we_got_to_the_end_of_it` between ticks
107114
fn main() {
108115
foo_bar();
109-
multiline_ticks();
116+
multiline_codeblock();
110117
test_emphasis();
111118
test_units();
112119
}
@@ -151,3 +158,42 @@ fn issue883() {
151158
/// bar](https://doc.rust-lang.org/stable/std/iter/trait.IteratorFooBar.html)
152159
fn multiline() {
153160
}
161+
162+
/** E.g. serialization of an empty list: FooBar
163+
```
164+
That's in a code block: `PackedNode`
165+
```
166+
167+
And BarQuz too.
168+
be_sure_we_got_to_the_end_of_it
169+
*/
170+
//~^^^^^^^^ ERROR: you should put `FooBar` between ticks
171+
//~^^^^ ERROR: you should put `BarQuz` between ticks
172+
//~^^^^ ERROR: you should put `be_sure_we_got_to_the_end_of_it` between ticks
173+
fn issue1073() {
174+
}
175+
176+
/** E.g. serialization of an empty list: FooBar
177+
```
178+
That's in a code block: PackedNode
179+
```
180+
181+
And BarQuz too.
182+
be_sure_we_got_to_the_end_of_it
183+
*/
184+
//~^^^^^^^^ ERROR: you should put `FooBar` between ticks
185+
//~^^^^ ERROR: you should put `BarQuz` between ticks
186+
//~^^^^ ERROR: you should put `be_sure_we_got_to_the_end_of_it` between ticks
187+
fn issue1073_alt() {
188+
}
189+
190+
/// Test more than three quotes:
191+
/// ````
192+
/// DoNotWarn
193+
/// ```
194+
/// StillDont
195+
/// ````
196+
/// be_sure_we_got_to_the_end_of_it
197+
//~^ ERROR: you should put `be_sure_we_got_to_the_end_of_it` between ticks
198+
fn four_quotes() {
199+
}

0 commit comments

Comments
 (0)