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