Auto merge of #54861 - rep-nop:find_main_in_doctest, r=estebank

rustdoc: Replaces fn main search and extern crate search with proper parsing during doctests.

Fixes #21299.
Fixes #33731.

Let me know if there's any additional changes you'd like made!
This commit is contained in:
bors 2018-11-04 01:43:40 +00:00
commit 6d69fe7a2f
5 changed files with 201 additions and 26 deletions

View file

@ -378,7 +378,7 @@ pub fn make_test(s: &str,
dont_insert_main: bool,
opts: &TestOptions)
-> (String, usize) {
let (crate_attrs, everything_else) = partition_source(s);
let (crate_attrs, everything_else, crates) = partition_source(s);
let everything_else = everything_else.trim();
let mut line_offset = 0;
let mut prog = String::new();
@ -402,10 +402,84 @@ pub fn make_test(s: &str,
// are intended to be crate attributes.
prog.push_str(&crate_attrs);
// Uses libsyntax to parse the doctest and find if there's a main fn and the extern
// crate already is included.
let (already_has_main, already_has_extern_crate) = crate::syntax::with_globals(|| {
use crate::syntax::{ast, parse::{self, ParseSess}, source_map::FilePathMapping};
use crate::syntax_pos::FileName;
use errors::emitter::EmitterWriter;
use errors::Handler;
let filename = FileName::Anon;
let source = crates + &everything_else;
// any errors in parsing should also appear when the doctest is compiled for real, so just
// send all the errors that libsyntax emits directly into a Sink instead of stderr
let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let emitter = EmitterWriter::new(box io::sink(), None, false, false);
let handler = Handler::with_emitter(false, false, box emitter);
let sess = ParseSess::with_span_handler(handler, cm);
debug!("about to parse: \n{}", source);
let mut found_main = false;
let mut found_extern_crate = cratename.is_none();
let mut parser = match parse::maybe_new_parser_from_source_str(&sess, filename, source) {
Ok(p) => p,
Err(errs) => {
for mut err in errs {
err.cancel();
}
return (found_main, found_extern_crate);
}
};
loop {
match parser.parse_item() {
Ok(Some(item)) => {
if !found_main {
if let ast::ItemKind::Fn(..) = item.node {
if item.ident.as_str() == "main" {
found_main = true;
}
}
}
if !found_extern_crate {
if let ast::ItemKind::ExternCrate(original) = item.node {
// This code will never be reached if `cratename` is none because
// `found_extern_crate` is initialized to `true` if it is none.
let cratename = cratename.unwrap();
match original {
Some(name) => found_extern_crate = name.as_str() == cratename,
None => found_extern_crate = item.ident.as_str() == cratename,
}
}
}
if found_main && found_extern_crate {
break;
}
}
Ok(None) => break,
Err(mut e) => {
e.cancel();
break;
}
}
}
(found_main, found_extern_crate)
});
// Don't inject `extern crate std` because it's already injected by the
// compiler.
if !s.contains("extern crate") && !opts.no_crate_inject && cratename != Some("std") {
if !already_has_extern_crate && !opts.no_crate_inject && cratename != Some("std") {
if let Some(cratename) = cratename {
// Make sure its actually used if not included.
if s.contains(cratename) {
prog.push_str(&format!("extern crate {};\n", cratename));
line_offset += 1;
@ -413,19 +487,6 @@ pub fn make_test(s: &str,
}
}
// FIXME (#21299): prefer libsyntax or some other actual parser over this
// best-effort ad hoc approach
let already_has_main = s.lines()
.map(|line| {
let comment = line.find("//");
if let Some(comment_begins) = comment {
&line[0..comment_begins]
} else {
line
}
})
.any(|code| code.contains("fn main"));
if dont_insert_main || already_has_main {
prog.push_str(everything_else);
} else {
@ -441,9 +502,10 @@ pub fn make_test(s: &str,
}
// FIXME(aburka): use a real parser to deal with multiline attributes
fn partition_source(s: &str) -> (String, String) {
fn partition_source(s: &str) -> (String, String, String) {
let mut after_header = false;
let mut before = String::new();
let mut crates = String::new();
let mut after = String::new();
for line in s.lines() {
@ -457,12 +519,17 @@ fn partition_source(s: &str) -> (String, String) {
after.push_str(line);
after.push_str("\n");
} else {
if trimline.starts_with("#[macro_use] extern crate")
|| trimline.starts_with("extern crate") {
crates.push_str(line);
crates.push_str("\n");
}
before.push_str(line);
before.push_str("\n");
}
}
(before, after)
(before, after, crates)
}
pub trait Tester {
@ -1014,4 +1081,38 @@ assert_eq!(2+2, 4);
let output = make_test(input, None, false, &opts);
assert_eq!(output, (expected, 1));
}
#[test]
fn make_test_issues_21299_33731() {
let opts = TestOptions::default();
let input =
"// fn main
assert_eq!(2+2, 4);";
let expected =
"#![allow(unused)]
fn main() {
// fn main
assert_eq!(2+2, 4);
}".to_string();
let output = make_test(input, None, false, &opts);
assert_eq!(output, (expected, 2));
let input =
"extern crate hella_qwop;
assert_eq!(asdf::foo, 4);";
let expected =
"#![allow(unused)]
extern crate hella_qwop;
extern crate asdf;
fn main() {
assert_eq!(asdf::foo, 4);
}".to_string();
let output = make_test(input, Some("asdf"), false, &opts);
assert_eq!(output, (expected, 3));
}
}