diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index fd4640c2158..af1fbaa0f7d 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -66,8 +66,7 @@ pub fn check_attrs<'a>(cx: &EarlyContext, valid_idents: &[String], attrs: &'a [a // check for multiline code blocks if real_doc.trim_left().starts_with("```") { in_multiline = !in_multiline; - } - if !in_multiline { + } else if !in_multiline { docs.push((real_doc, span)); } } @@ -75,13 +74,13 @@ pub fn check_attrs<'a>(cx: &EarlyContext, valid_idents: &[String], attrs: &'a [a } } - for (doc, span) in docs { - let _ = check_doc(cx, valid_idents, doc, span); + if !docs.is_empty() { + let _ = check_doc(cx, valid_idents, &docs); } } #[allow(while_let_loop)] // #362 -pub fn check_doc(cx: &EarlyContext, valid_idents: &[String], doc: &str, span: Span) -> Result<(), ()> { +pub fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(&str, Span)]) -> Result<(), ()> { // In markdown, `_` can be used to emphasize something, or, is a raw `_` depending on context. // There really is no markdown specification that would disambiguate this properly. This is // what GitHub and Rustdoc do: @@ -103,12 +102,22 @@ pub fn check_doc(cx: &EarlyContext, valid_idents: &[String], doc: &str, span: Sp } #[derive(Clone, Debug)] + /// This type is used to iterate through the documentation characters, keeping the span at the + /// same time. struct Parser<'a> { - link: bool, - line: &'a str, - span: Span, + /// First byte of the current potential match current_word_begin: usize, + /// List of lines and their associated span + docs: &'a[(&'a str, Span)], + /// Index of the current line we are parsing + line: usize, + /// Whether we are in a link + link: bool, + /// Whether we are at the beginning of a line new_line: bool, + /// Whether we were to the end of a line last time `next` was called + reset: bool, + /// The position of the current character within the current line pos: usize, } @@ -117,19 +126,31 @@ pub fn check_doc(cx: &EarlyContext, valid_idents: &[String], doc: &str, span: Sp self.current_word_begin = self.pos; } - fn peek(&self) -> Option { - self.line[self.pos..].chars().next() + fn line(&self) -> (&'a str, Span) { + self.docs[self.line] } + fn peek(&self) -> Option { + self.line().0[self.pos..].chars().next() + } + + #[allow(while_let_on_iterator)] // borrowck complains about for fn jump_to(&mut self, n: char) -> Result<(), ()> { - while let Some(c) = self.next() { + while let Some((_, c)) = self.next() { if c == n { self.advance_begin(); return Ok(()); } } - return Err(()); + Err(()) + } + + fn next_line(&mut self) { + self.pos = 0; + self.current_word_begin = 0; + self.line += 1; + self.new_line = true; } fn put_back(&mut self, c: char) { @@ -144,46 +165,64 @@ pub fn check_doc(cx: &EarlyContext, valid_idents: &[String], doc: &str, span: Sp debug_assert_eq!(end as u32 as usize, end); debug_assert_eq!(begin as u32 as usize, begin); - let mut span = self.span; + let (doc, mut span) = self.line(); span.hi = span.lo + BytePos(end as u32); span.lo = span.lo + BytePos(begin as u32); - (&self.line[begin..end], span) + (&doc[begin..end], span) } } impl<'a> Iterator for Parser<'a> { - type Item = char; + type Item = (bool, char); - fn next(&mut self) -> Option { - let mut chars = self.line[self.pos..].chars(); - let c = chars.next(); + fn next(&mut self) -> Option<(bool, char)> { + while self.line < self.docs.len() { + if self.reset { + self.line += 1; + self.reset = false; + self.pos = 0; + self.current_word_begin = 0; + } - if let Some(c) = c { - self.pos += c.len_utf8(); - } else { - // TODO: new line + let mut chars = self.line().0[self.pos..].chars(); + let c = chars.next(); + + if let Some(c) = c { + self.pos += c.len_utf8(); + let new_line = self.new_line; + self.new_line = c == '\n' || (self.new_line && c.is_whitespace()); + return Some((new_line, c)); + } else if self.line == self.docs.len() - 1 { + return None; + } else { + self.new_line = true; + self.reset = true; + self.pos += 1; + return Some((true, '\n')); + } } - c + None } } let mut parser = Parser { - link: false, - line: doc, - span: span, current_word_begin: 0, + docs: docs, + line: 0, + link: false, new_line: true, + reset: false, pos: 0, }; loop { match parser.next() { - Some(c) => { + Some((new_line, c)) => { match c { '#' if new_line => { // don’t warn on titles - try!(parser.jump_to('\n')); + parser.next_line(); } '`' => { try!(parser.jump_to('`')); @@ -191,11 +230,12 @@ pub fn check_doc(cx: &EarlyContext, valid_idents: &[String], doc: &str, span: Sp '[' => { // Check for a reference definition `[foo]:` at the beginning of a line let mut link = true; - if parser.new_line { + + if new_line { let mut lookup_parser = parser.clone(); - if let Some(_) = lookup_parser.find(|&c| c == ']') { - if let Some(':') = lookup_parser.next() { - try!(lookup_parser.jump_to(')')); + if let Some(_) = lookup_parser.find(|&(_, c)| c == ']') { + if let Some((_, ':')) = lookup_parser.next() { + lookup_parser.next_line(); parser = lookup_parser; link = false; } @@ -219,7 +259,7 @@ pub fn check_doc(cx: &EarlyContext, valid_idents: &[String], doc: &str, span: Sp parser.advance_begin(); } _ => { - if let Some(c) = parser.find(|&c| !is_path_char(c)) { + if let Some((_, c)) = parser.find(|&(_, c)| !is_path_char(c)) { parser.put_back(c); } @@ -229,7 +269,6 @@ pub fn check_doc(cx: &EarlyContext, valid_idents: &[String], doc: &str, span: Sp } } - parser.new_line = c == '\n' || (parser.new_line && c.is_whitespace()); } None => break, } diff --git a/tests/compile-fail/doc.rs b/tests/compile-fail/doc.rs index f5c1e4768e9..d3b1c037f47 100755 --- a/tests/compile-fail/doc.rs +++ b/tests/compile-fail/doc.rs @@ -68,18 +68,18 @@ fn test_units() { //~^ ERROR: you should put `foo_ℝ` between ticks /// foo_💣 /// foo_❤️ -/// [ßdummy textß][foo_ß] -/// [ℝdummy textℝ][foo_ℝ] -/// [💣dummy tex💣t][foo_💣] -/// [❤️dummy text❤️][foo_❤️] -/// [ßdummy textß](foo_ß) -/// [ℝdummy textℝ](foo_ℝ) -/// [💣dummy tex💣t](foo_💣) -/// [❤️dummy text❤️](foo_❤️) -/// [foo_ß]: dummy text -/// [foo_ℝ]: dummy text -/// [foo_💣]: dummy text -/// [foo_❤️]: dummy text +/// [ßdummy textß][foo_1ß] +/// [ℝdummy textℝ][foo_2ℝ] +/// [💣dummy tex💣t][foo3_💣] +/// [❤️dummy text❤️][foo_4❤️] +/// [ßdummy textß](foo_5ß) +/// [ℝdummy textℝ](foo_6ℝ) +/// [💣dummy tex💣t](fo7o_💣) +/// [❤️dummy text❤️](foo_8❤️) +/// [foo1_ß]: dummy text +/// [foo2_ℝ]: dummy text +/// [foo3_💣]: dummy text +/// [foo4_❤️]: dummy text /// be_sure_we_got_to_the_end_of_it //~^ ERROR: you should put `be_sure_we_got_to_the_end_of_it` between ticks fn test_unicode() { @@ -146,5 +146,7 @@ fn issue883() { /// `foo_bar /// baz_quz` +/// [foo +/// bar](https://doc.rust-lang.org/stable/std/iter/trait.IteratorFooBar.html) fn multiline() { }