]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
9e0c209e | 11 | use std::cell::Cell; |
85aaf69f | 12 | use std::env; |
c34b1796 AL |
13 | use std::ffi::OsString; |
14 | use std::io::prelude::*; | |
15 | use std::io; | |
16 | use std::path::PathBuf; | |
54a0048b | 17 | use std::panic::{self, AssertUnwindSafe}; |
c34b1796 | 18 | use std::process::Command; |
92a42be0 | 19 | use std::rc::Rc; |
1a4d82fc | 20 | use std::str; |
c34b1796 | 21 | use std::sync::{Arc, Mutex}; |
1a4d82fc | 22 | |
1a4d82fc | 23 | use testing; |
c34b1796 | 24 | use rustc_lint; |
7453a54e | 25 | use rustc::dep_graph::DepGraph; |
54a0048b | 26 | use rustc::hir::map as hir_map; |
1a4d82fc | 27 | use rustc::session::{self, config}; |
9e0c209e | 28 | use rustc::session::config::{OutputType, OutputTypes, Externs}; |
1a4d82fc | 29 | use rustc::session::search_paths::{SearchPaths, PathKind}; |
54a0048b | 30 | use rustc_back::dynamic_lib::DynamicLibrary; |
c34b1796 | 31 | use rustc_back::tempdir::TempDir; |
85aaf69f | 32 | use rustc_driver::{driver, Compilation}; |
3157f602 | 33 | use rustc_driver::driver::phase_2_configure_and_expand; |
92a42be0 | 34 | use rustc_metadata::cstore::CStore; |
3157f602 | 35 | use rustc_resolve::MakeGlobMap; |
85aaf69f | 36 | use syntax::codemap::CodeMap; |
9e0c209e | 37 | use syntax::feature_gate::UnstableFeatures; |
3157f602 XL |
38 | use errors; |
39 | use errors::emitter::ColorConfig; | |
1a4d82fc JJ |
40 | |
41 | use core; | |
42 | use clean; | |
43 | use clean::Clean; | |
44 | use fold::DocFolder; | |
45 | use html::markdown; | |
46 | use passes; | |
47 | use visit_ast::RustdocVisitor; | |
48 | ||
9346a6ac AL |
49 | #[derive(Clone, Default)] |
50 | pub struct TestOptions { | |
51 | pub no_crate_inject: bool, | |
52 | pub attrs: Vec<String>, | |
53 | } | |
54 | ||
1a4d82fc JJ |
55 | pub fn run(input: &str, |
56 | cfgs: Vec<String>, | |
57 | libs: SearchPaths, | |
5bcae85e | 58 | externs: Externs, |
1a4d82fc JJ |
59 | mut test_args: Vec<String>, |
60 | crate_name: Option<String>) | |
c34b1796 AL |
61 | -> isize { |
62 | let input_path = PathBuf::from(input); | |
1a4d82fc JJ |
63 | let input = config::Input::File(input_path.clone()); |
64 | ||
65 | let sessopts = config::Options { | |
c34b1796 AL |
66 | maybe_sysroot: Some(env::current_exe().unwrap().parent().unwrap() |
67 | .parent().unwrap().to_path_buf()), | |
1a4d82fc JJ |
68 | search_paths: libs.clone(), |
69 | crate_types: vec!(config::CrateTypeDylib), | |
70 | externs: externs.clone(), | |
9e0c209e | 71 | unstable_features: UnstableFeatures::from_environment(), |
1a4d82fc JJ |
72 | ..config::basic_options().clone() |
73 | }; | |
74 | ||
9cc50fc6 SL |
75 | let codemap = Rc::new(CodeMap::new()); |
76 | let diagnostic_handler = errors::Handler::with_tty_emitter(ColorConfig::Auto, | |
9cc50fc6 SL |
77 | true, |
78 | false, | |
5bcae85e | 79 | Some(codemap.clone())); |
1a4d82fc | 80 | |
a7813a04 XL |
81 | let dep_graph = DepGraph::new(false); |
82 | let _ignore = dep_graph.in_ignore(); | |
5bcae85e | 83 | let cstore = Rc::new(CStore::new(&dep_graph)); |
1a4d82fc | 84 | let sess = session::build_session_(sessopts, |
a7813a04 | 85 | &dep_graph, |
92a42be0 | 86 | Some(input_path.clone()), |
9cc50fc6 SL |
87 | diagnostic_handler, |
88 | codemap, | |
89 | cstore.clone()); | |
c34b1796 | 90 | rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess)); |
1a4d82fc | 91 | |
5bcae85e | 92 | let cfg = config::build_configuration(&sess, config::parse_cfgspecs(cfgs.clone())); |
54a0048b | 93 | let krate = panictry!(driver::phase_1_parse_input(&sess, cfg, &input)); |
3157f602 XL |
94 | let driver::ExpansionResult { defs, mut hir_forest, .. } = { |
95 | phase_2_configure_and_expand( | |
9e0c209e | 96 | &sess, &cstore, krate, None, "rustdoc-test", None, MakeGlobMap::No, |_| Ok(()) |
3157f602 XL |
97 | ).expect("phase_2_configure_and_expand aborted in rustdoc!") |
98 | }; | |
c34b1796 | 99 | |
3157f602 XL |
100 | let dep_graph = DepGraph::new(false); |
101 | let opts = scrape_test_config(hir_forest.krate()); | |
7453a54e | 102 | let _ignore = dep_graph.in_ignore(); |
3157f602 | 103 | let map = hir_map::map_crate(&mut hir_forest, defs); |
b039eaaf | 104 | |
1a4d82fc | 105 | let ctx = core::DocContext { |
b039eaaf SL |
106 | map: &map, |
107 | maybe_typed: core::NotTyped(&sess), | |
85aaf69f | 108 | input: input, |
9e0c209e SL |
109 | populated_all_crate_impls: Cell::new(false), |
110 | external_traits: Default::default(), | |
d9579d0f | 111 | deref_trait_did: Cell::new(None), |
9e0c209e | 112 | deref_mut_trait_did: Cell::new(None), |
a7813a04 XL |
113 | access_levels: Default::default(), |
114 | renderinfo: Default::default(), | |
9e0c209e SL |
115 | ty_substs: Default::default(), |
116 | lt_substs: Default::default(), | |
1a4d82fc JJ |
117 | }; |
118 | ||
a7813a04 | 119 | let mut v = RustdocVisitor::new(&ctx); |
b039eaaf | 120 | v.visit(ctx.map.krate()); |
1a4d82fc | 121 | let mut krate = v.clean(&ctx); |
54a0048b SL |
122 | if let Some(name) = crate_name { |
123 | krate.name = name; | |
1a4d82fc | 124 | } |
54a0048b SL |
125 | let krate = passes::collapse_docs(krate); |
126 | let krate = passes::unindent_comments(krate); | |
1a4d82fc JJ |
127 | |
128 | let mut collector = Collector::new(krate.name.to_string(), | |
92a42be0 | 129 | cfgs, |
1a4d82fc JJ |
130 | libs, |
131 | externs, | |
c34b1796 | 132 | false, |
9346a6ac | 133 | opts); |
1a4d82fc JJ |
134 | collector.fold_crate(krate); |
135 | ||
136 | test_args.insert(0, "rustdoctest".to_string()); | |
137 | ||
85aaf69f | 138 | testing::test_main(&test_args, |
1a4d82fc JJ |
139 | collector.tests.into_iter().collect()); |
140 | 0 | |
141 | } | |
142 | ||
c34b1796 | 143 | // Look for #![doc(test(no_crate_inject))], used by crates in the std facade |
54a0048b | 144 | fn scrape_test_config(krate: &::rustc::hir::Crate) -> TestOptions { |
b039eaaf | 145 | use syntax::print::pprust; |
c34b1796 | 146 | |
9346a6ac AL |
147 | let mut opts = TestOptions { |
148 | no_crate_inject: false, | |
149 | attrs: Vec::new(), | |
150 | }; | |
151 | ||
62682a34 SL |
152 | let attrs = krate.attrs.iter() |
153 | .filter(|a| a.check_name("doc")) | |
9346a6ac | 154 | .filter_map(|a| a.meta_item_list()) |
62682a34 | 155 | .flat_map(|l| l) |
9346a6ac AL |
156 | .filter(|a| a.check_name("test")) |
157 | .filter_map(|a| a.meta_item_list()) | |
62682a34 | 158 | .flat_map(|l| l); |
9346a6ac AL |
159 | for attr in attrs { |
160 | if attr.check_name("no_crate_inject") { | |
161 | opts.no_crate_inject = true; | |
162 | } | |
163 | if attr.check_name("attr") { | |
164 | if let Some(l) = attr.meta_item_list() { | |
165 | for item in l { | |
9e0c209e | 166 | opts.attrs.push(pprust::meta_list_item_to_string(item)); |
c34b1796 AL |
167 | } |
168 | } | |
169 | } | |
170 | } | |
171 | ||
9346a6ac | 172 | return opts; |
c34b1796 AL |
173 | } |
174 | ||
92a42be0 | 175 | fn runtest(test: &str, cratename: &str, cfgs: Vec<String>, libs: SearchPaths, |
5bcae85e | 176 | externs: Externs, |
c34b1796 | 177 | should_panic: bool, no_run: bool, as_test_harness: bool, |
3157f602 | 178 | compile_fail: bool, mut error_codes: Vec<String>, opts: &TestOptions) { |
1a4d82fc JJ |
179 | // the test harness wants its own `main` & top level functions, so |
180 | // never wrap the test in `fn main() { ... }` | |
9346a6ac | 181 | let test = maketest(test, Some(cratename), as_test_harness, opts); |
54a0048b SL |
182 | let input = config::Input::Str { |
183 | name: driver::anon_src(), | |
184 | input: test.to_owned(), | |
185 | }; | |
5bcae85e | 186 | let outputs = OutputTypes::new(&[(OutputType::Exe, None)]); |
1a4d82fc JJ |
187 | |
188 | let sessopts = config::Options { | |
c34b1796 AL |
189 | maybe_sysroot: Some(env::current_exe().unwrap().parent().unwrap() |
190 | .parent().unwrap().to_path_buf()), | |
1a4d82fc JJ |
191 | search_paths: libs, |
192 | crate_types: vec!(config::CrateTypeExecutable), | |
b039eaaf | 193 | output_types: outputs, |
1a4d82fc JJ |
194 | externs: externs, |
195 | cg: config::CodegenOptions { | |
196 | prefer_dynamic: true, | |
197 | .. config::basic_codegen_options() | |
198 | }, | |
199 | test: as_test_harness, | |
9e0c209e | 200 | unstable_features: UnstableFeatures::from_environment(), |
1a4d82fc JJ |
201 | ..config::basic_options().clone() |
202 | }; | |
203 | ||
204 | // Shuffle around a few input and output handles here. We're going to pass | |
205 | // an explicit handle into rustc to collect output messages, but we also | |
206 | // want to catch the error message that rustc prints when it fails. | |
207 | // | |
bd371182 | 208 | // We take our thread-local stderr (likely set by the test runner) and replace |
c34b1796 | 209 | // it with a sink that is also passed to rustc itself. When this function |
bd371182 | 210 | // returns the output of the sink is copied onto the output of our own thread. |
1a4d82fc | 211 | // |
62682a34 | 212 | // The basic idea is to not use a default Handler for rustc, and then also |
1a4d82fc | 213 | // not print things by default to the actual stderr. |
c34b1796 AL |
214 | struct Sink(Arc<Mutex<Vec<u8>>>); |
215 | impl Write for Sink { | |
216 | fn write(&mut self, data: &[u8]) -> io::Result<usize> { | |
217 | Write::write(&mut *self.0.lock().unwrap(), data) | |
218 | } | |
219 | fn flush(&mut self) -> io::Result<()> { Ok(()) } | |
220 | } | |
221 | struct Bomb(Arc<Mutex<Vec<u8>>>, Box<Write+Send>); | |
222 | impl Drop for Bomb { | |
223 | fn drop(&mut self) { | |
224 | let _ = self.1.write_all(&self.0.lock().unwrap()); | |
225 | } | |
226 | } | |
227 | let data = Arc::new(Mutex::new(Vec::new())); | |
9cc50fc6 SL |
228 | let codemap = Rc::new(CodeMap::new()); |
229 | let emitter = errors::emitter::EmitterWriter::new(box Sink(data.clone()), | |
5bcae85e | 230 | Some(codemap.clone())); |
c34b1796 | 231 | let old = io::set_panic(box Sink(data.clone())); |
3157f602 | 232 | let _bomb = Bomb(data.clone(), old.unwrap_or(box io::stdout())); |
1a4d82fc JJ |
233 | |
234 | // Compile the code | |
9cc50fc6 | 235 | let diagnostic_handler = errors::Handler::with_emitter(true, false, box emitter); |
1a4d82fc | 236 | |
a7813a04 | 237 | let dep_graph = DepGraph::new(false); |
5bcae85e | 238 | let cstore = Rc::new(CStore::new(&dep_graph)); |
1a4d82fc | 239 | let sess = session::build_session_(sessopts, |
a7813a04 | 240 | &dep_graph, |
85aaf69f | 241 | None, |
9cc50fc6 SL |
242 | diagnostic_handler, |
243 | codemap, | |
244 | cstore.clone()); | |
c34b1796 | 245 | rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess)); |
1a4d82fc | 246 | |
7453a54e | 247 | let outdir = Mutex::new(TempDir::new("rustdoctest").ok().expect("rustdoc needs a tempdir")); |
1a4d82fc | 248 | let libdir = sess.target_filesearch(PathKind::All).get_lib_path(); |
85aaf69f | 249 | let mut control = driver::CompileController::basic(); |
5bcae85e | 250 | let cfg = config::build_configuration(&sess, config::parse_cfgspecs(cfgs.clone())); |
7453a54e SL |
251 | let out = Some(outdir.lock().unwrap().path().to_path_buf()); |
252 | ||
85aaf69f SL |
253 | if no_run { |
254 | control.after_analysis.stop = Compilation::Stop; | |
255 | } | |
7453a54e | 256 | |
54a0048b SL |
257 | let res = panic::catch_unwind(AssertUnwindSafe(|| { |
258 | driver::compile_input(&sess, &cstore, cfg.clone(), | |
259 | &input, &out, | |
260 | &None, None, &control) | |
261 | })); | |
262 | ||
263 | match res { | |
7453a54e SL |
264 | Ok(r) => { |
265 | match r { | |
3157f602 XL |
266 | Err(count) => { |
267 | if count > 0 && compile_fail == false { | |
268 | sess.fatal("aborting due to previous error(s)") | |
269 | } else if count == 0 && compile_fail == true { | |
270 | panic!("test compiled while it wasn't supposed to") | |
271 | } | |
272 | if count > 0 && error_codes.len() > 0 { | |
273 | let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap(); | |
274 | error_codes.retain(|err| !out.contains(err)); | |
275 | } | |
7453a54e SL |
276 | } |
277 | Ok(()) if compile_fail => panic!("test compiled while it wasn't supposed to"), | |
278 | _ => {} | |
279 | } | |
280 | } | |
3157f602 XL |
281 | Err(_) => { |
282 | if compile_fail == false { | |
283 | panic!("couldn't compile the test"); | |
284 | } | |
285 | if error_codes.len() > 0 { | |
286 | let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap(); | |
287 | error_codes.retain(|err| !out.contains(err)); | |
288 | } | |
289 | } | |
290 | } | |
291 | ||
292 | if error_codes.len() > 0 { | |
293 | panic!("Some expected error codes were not found: {:?}", error_codes); | |
7453a54e | 294 | } |
1a4d82fc JJ |
295 | |
296 | if no_run { return } | |
297 | ||
298 | // Run the code! | |
299 | // | |
300 | // We're careful to prepend the *target* dylib search path to the child's | |
301 | // environment to ensure that the target loads the right libraries at | |
302 | // runtime. It would be a sad day if the *host* libraries were loaded as a | |
303 | // mistake. | |
7453a54e | 304 | let mut cmd = Command::new(&outdir.lock().unwrap().path().join("rust_out")); |
c34b1796 | 305 | let var = DynamicLibrary::envvar(); |
1a4d82fc | 306 | let newpath = { |
c34b1796 AL |
307 | let path = env::var_os(var).unwrap_or(OsString::new()); |
308 | let mut path = env::split_paths(&path).collect::<Vec<_>>(); | |
1a4d82fc | 309 | path.insert(0, libdir.clone()); |
62682a34 | 310 | env::join_paths(path).unwrap() |
1a4d82fc | 311 | }; |
c34b1796 | 312 | cmd.env(var, &newpath); |
1a4d82fc JJ |
313 | |
314 | match cmd.output() { | |
315 | Err(e) => panic!("couldn't run the test: {}{}", e, | |
c34b1796 | 316 | if e.kind() == io::ErrorKind::PermissionDenied { |
1a4d82fc JJ |
317 | " - maybe your tempdir is mounted with noexec?" |
318 | } else { "" }), | |
319 | Ok(out) => { | |
c34b1796 | 320 | if should_panic && out.status.success() { |
1a4d82fc | 321 | panic!("test executable succeeded when it should have failed"); |
c34b1796 AL |
322 | } else if !should_panic && !out.status.success() { |
323 | panic!("test executable failed:\n{}\n{}", | |
324 | str::from_utf8(&out.stdout).unwrap_or(""), | |
325 | str::from_utf8(&out.stderr).unwrap_or("")); | |
1a4d82fc JJ |
326 | } |
327 | } | |
328 | } | |
329 | } | |
330 | ||
9346a6ac AL |
331 | pub fn maketest(s: &str, cratename: Option<&str>, dont_insert_main: bool, |
332 | opts: &TestOptions) -> String { | |
c34b1796 AL |
333 | let (crate_attrs, everything_else) = partition_source(s); |
334 | ||
1a4d82fc | 335 | let mut prog = String::new(); |
c34b1796 AL |
336 | |
337 | // First push any outer attributes from the example, assuming they | |
338 | // are intended to be crate attributes. | |
339 | prog.push_str(&crate_attrs); | |
340 | ||
9346a6ac AL |
341 | // Next, any attributes for other aspects such as lints. |
342 | for attr in &opts.attrs { | |
343 | prog.push_str(&format!("#![{}]\n", attr)); | |
1a4d82fc JJ |
344 | } |
345 | ||
346 | // Don't inject `extern crate std` because it's already injected by the | |
347 | // compiler. | |
d9579d0f | 348 | if !s.contains("extern crate") && !opts.no_crate_inject && cratename != Some("std") { |
54a0048b SL |
349 | if let Some(cratename) = cratename { |
350 | if s.contains(cratename) { | |
351 | prog.push_str(&format!("extern crate {};\n", cratename)); | |
1a4d82fc | 352 | } |
1a4d82fc JJ |
353 | } |
354 | } | |
355 | if dont_insert_main || s.contains("fn main") { | |
c34b1796 | 356 | prog.push_str(&everything_else); |
1a4d82fc JJ |
357 | } else { |
358 | prog.push_str("fn main() {\n "); | |
a7813a04 | 359 | prog.push_str(&everything_else); |
9cc50fc6 | 360 | prog = prog.trim().into(); |
1a4d82fc JJ |
361 | prog.push_str("\n}"); |
362 | } | |
363 | ||
c34b1796 AL |
364 | info!("final test program: {}", prog); |
365 | ||
1a4d82fc JJ |
366 | return prog |
367 | } | |
368 | ||
c34b1796 | 369 | fn partition_source(s: &str) -> (String, String) { |
d9579d0f | 370 | use rustc_unicode::str::UnicodeStr; |
c34b1796 AL |
371 | |
372 | let mut after_header = false; | |
373 | let mut before = String::new(); | |
374 | let mut after = String::new(); | |
375 | ||
376 | for line in s.lines() { | |
377 | let trimline = line.trim(); | |
378 | let header = trimline.is_whitespace() || | |
379 | trimline.starts_with("#![feature"); | |
380 | if !header || after_header { | |
381 | after_header = true; | |
382 | after.push_str(line); | |
383 | after.push_str("\n"); | |
384 | } else { | |
385 | before.push_str(line); | |
386 | before.push_str("\n"); | |
387 | } | |
388 | } | |
389 | ||
390 | return (before, after); | |
391 | } | |
392 | ||
1a4d82fc JJ |
393 | pub struct Collector { |
394 | pub tests: Vec<testing::TestDescAndFn>, | |
395 | names: Vec<String>, | |
92a42be0 | 396 | cfgs: Vec<String>, |
1a4d82fc | 397 | libs: SearchPaths, |
5bcae85e | 398 | externs: Externs, |
c34b1796 | 399 | cnt: usize, |
1a4d82fc JJ |
400 | use_headers: bool, |
401 | current_header: Option<String>, | |
402 | cratename: String, | |
9346a6ac | 403 | opts: TestOptions, |
1a4d82fc JJ |
404 | } |
405 | ||
406 | impl Collector { | |
5bcae85e | 407 | pub fn new(cratename: String, cfgs: Vec<String>, libs: SearchPaths, externs: Externs, |
9346a6ac | 408 | use_headers: bool, opts: TestOptions) -> Collector { |
1a4d82fc JJ |
409 | Collector { |
410 | tests: Vec::new(), | |
411 | names: Vec::new(), | |
92a42be0 | 412 | cfgs: cfgs, |
1a4d82fc JJ |
413 | libs: libs, |
414 | externs: externs, | |
415 | cnt: 0, | |
416 | use_headers: use_headers, | |
417 | current_header: None, | |
418 | cratename: cratename, | |
9346a6ac | 419 | opts: opts, |
1a4d82fc JJ |
420 | } |
421 | } | |
422 | ||
423 | pub fn add_test(&mut self, test: String, | |
c34b1796 | 424 | should_panic: bool, no_run: bool, should_ignore: bool, |
3157f602 | 425 | as_test_harness: bool, compile_fail: bool, error_codes: Vec<String>) { |
1a4d82fc | 426 | let name = if self.use_headers { |
85aaf69f | 427 | let s = self.current_header.as_ref().map(|s| &**s).unwrap_or(""); |
1a4d82fc JJ |
428 | format!("{}_{}", s, self.cnt) |
429 | } else { | |
c1a9b12d | 430 | format!("{}_{}", self.names.join("::"), self.cnt) |
1a4d82fc JJ |
431 | }; |
432 | self.cnt += 1; | |
92a42be0 | 433 | let cfgs = self.cfgs.clone(); |
1a4d82fc JJ |
434 | let libs = self.libs.clone(); |
435 | let externs = self.externs.clone(); | |
436 | let cratename = self.cratename.to_string(); | |
9346a6ac | 437 | let opts = self.opts.clone(); |
1a4d82fc JJ |
438 | debug!("Creating test {}: {}", name, test); |
439 | self.tests.push(testing::TestDescAndFn { | |
440 | desc: testing::TestDesc { | |
441 | name: testing::DynTestName(name), | |
442 | ignore: should_ignore, | |
9346a6ac AL |
443 | // compiler failures are test failures |
444 | should_panic: testing::ShouldPanic::No, | |
1a4d82fc | 445 | }, |
54a0048b | 446 | testfn: testing::DynTestFn(box move|| { |
85aaf69f SL |
447 | runtest(&test, |
448 | &cratename, | |
92a42be0 | 449 | cfgs, |
1a4d82fc JJ |
450 | libs, |
451 | externs, | |
c34b1796 | 452 | should_panic, |
1a4d82fc | 453 | no_run, |
c34b1796 | 454 | as_test_harness, |
7453a54e | 455 | compile_fail, |
3157f602 | 456 | error_codes, |
9346a6ac | 457 | &opts); |
54a0048b | 458 | }) |
1a4d82fc JJ |
459 | }); |
460 | } | |
461 | ||
462 | pub fn register_header(&mut self, name: &str, level: u32) { | |
463 | if self.use_headers && level == 1 { | |
464 | // we use these headings as test names, so it's good if | |
465 | // they're valid identifiers. | |
466 | let name = name.chars().enumerate().map(|(i, c)| { | |
467 | if (i == 0 && c.is_xid_start()) || | |
468 | (i != 0 && c.is_xid_continue()) { | |
469 | c | |
470 | } else { | |
471 | '_' | |
472 | } | |
473 | }).collect::<String>(); | |
474 | ||
475 | // new header => reset count. | |
476 | self.cnt = 0; | |
477 | self.current_header = Some(name); | |
478 | } | |
479 | } | |
480 | } | |
481 | ||
482 | impl DocFolder for Collector { | |
483 | fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { | |
92a42be0 SL |
484 | let current_name = match item.name { |
485 | Some(ref name) if !name.is_empty() => Some(name.clone()), | |
486 | _ => typename_if_impl(&item) | |
1a4d82fc | 487 | }; |
92a42be0 | 488 | |
54a0048b | 489 | let pushed = current_name.map(|name| self.names.push(name)).is_some(); |
92a42be0 SL |
490 | |
491 | if let Some(doc) = item.doc_value() { | |
492 | self.cnt = 0; | |
493 | markdown::find_testable_code(doc, &mut *self); | |
1a4d82fc | 494 | } |
92a42be0 | 495 | |
1a4d82fc JJ |
496 | let ret = self.fold_item_recur(item); |
497 | if pushed { | |
498 | self.names.pop(); | |
499 | } | |
92a42be0 | 500 | |
1a4d82fc | 501 | return ret; |
92a42be0 SL |
502 | |
503 | // FIXME: it would be better to not have the escaped version in the first place | |
504 | fn unescape_for_testname(mut s: String) -> String { | |
505 | // for refs `&foo` | |
506 | if s.contains("&") { | |
507 | s = s.replace("&", "&"); | |
508 | ||
509 | // `::&'a mut Foo::` looks weird, let's make it `::<&'a mut Foo>`:: | |
510 | if let Some('&') = s.chars().nth(0) { | |
511 | s = format!("<{}>", s); | |
512 | } | |
513 | } | |
514 | ||
515 | // either `<..>` or `->` | |
516 | if s.contains(">") { | |
517 | s.replace(">", ">") | |
518 | .replace("<", "<") | |
519 | } else { | |
520 | s | |
521 | } | |
522 | } | |
523 | ||
524 | fn typename_if_impl(item: &clean::Item) -> Option<String> { | |
525 | if let clean::ItemEnum::ImplItem(ref impl_) = item.inner { | |
526 | let path = impl_.for_.to_string(); | |
527 | let unescaped_path = unescape_for_testname(path); | |
528 | Some(unescaped_path) | |
529 | } else { | |
530 | None | |
531 | } | |
532 | } | |
1a4d82fc JJ |
533 | } |
534 | } |