From 12c5f8cb75b0320f4ba9557417e5e554ffe2001b Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Thu, 24 Nov 2016 01:40:52 +0200 Subject: [PATCH] rustdoc: use libsyntax ast::Attribute instead of "cleaning" them. --- src/librustdoc/clean/inline.rs | 8 +- src/librustdoc/clean/mod.rs | 171 +++++++++++--------- src/librustdoc/html/render.rs | 103 ++++++------ src/librustdoc/lib.rs | 172 +++++++++++---------- src/librustdoc/passes/collapse_docs.rs | 37 ++--- src/librustdoc/passes/strip_hidden.rs | 4 +- src/librustdoc/passes/unindent_comments.rs | 25 ++- src/librustdoc/visit_ast.rs | 13 +- src/librustdoc/visit_lib.rs | 7 +- 9 files changed, 272 insertions(+), 268 deletions(-) diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 185f897c1ba..e7575468640 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -135,8 +135,8 @@ fn try_inline_def<'a, 'tcx>(cx: &DocContext, tcx: TyCtxt<'a, 'tcx, 'tcx>, } pub fn load_attrs<'a, 'tcx>(cx: &DocContext, tcx: TyCtxt<'a, 'tcx, 'tcx>, - did: DefId) -> Vec { - tcx.get_attrs(did).iter().map(|a| a.clean(cx)).collect() + did: DefId) -> clean::Attributes { + tcx.get_attrs(did).clean(cx) } /// Record an external fully qualified name in the external_paths cache. @@ -377,7 +377,7 @@ pub fn build_impl<'a, 'tcx>(cx: &DocContext, default, ), source: clean::Span::empty(), - attrs: vec![], + attrs: clean::Attributes::default(), visibility: None, stability: tcx.lookup_stability(item.def_id).clean(cx), deprecation: tcx.lookup_deprecation(item.def_id).clean(cx), @@ -424,7 +424,7 @@ pub fn build_impl<'a, 'tcx>(cx: &DocContext, name: Some(item.name.clean(cx)), inner: clean::TypedefItem(typedef, true), source: clean::Span::empty(), - attrs: vec![], + attrs: clean::Attributes::default(), visibility: None, stability: tcx.lookup_stability(item.def_id).clean(cx), deprecation: tcx.lookup_deprecation(item.def_id).clean(cx), diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 2cc1882ce3e..18cb9630f49 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -14,7 +14,6 @@ pub use self::Type::*; pub use self::Mutability::*; pub use self::ItemEnum::*; -pub use self::Attribute::*; pub use self::TyParamBound::*; pub use self::SelfTy::*; pub use self::FunctionRetTy::*; @@ -25,7 +24,6 @@ use syntax::ast; use syntax::attr; use syntax::codemap::Spanned; use syntax::ptr::P; -use syntax::print::pprust as syntax_pprust; use syntax::symbol::keywords; use syntax_pos::{self, DUMMY_SP, Pos}; @@ -44,6 +42,7 @@ use rustc::hir; use std::path::PathBuf; use std::rc::Rc; +use std::slice; use std::sync::Arc; use std::u32; use std::env::current_dir; @@ -227,7 +226,7 @@ impl<'a, 'tcx> Clean for visit_ast::RustdocVisitor<'a, 'tcx> { #[derive(Clone, RustcEncodable, RustcDecodable, Debug)] pub struct ExternalCrate { pub name: String, - pub attrs: Vec, + pub attrs: Attributes, pub primitives: Vec, } @@ -258,7 +257,7 @@ pub struct Item { pub source: Span, /// Not everything has a name. E.g., impls pub name: Option, - pub attrs: Vec, + pub attrs: Attributes, pub inner: ItemEnum, pub visibility: Option, pub def_id: DefId, @@ -270,7 +269,7 @@ impl Item { /// Finds the `doc` attribute as a NameValue and returns the corresponding /// value found. pub fn doc_value<'a>(&'a self) -> Option<&'a str> { - self.attrs.value("doc") + self.attrs.doc_value() } pub fn is_crate(&self) -> bool { match self.inner { @@ -459,86 +458,104 @@ impl Clean for doctree::Module { } } -pub trait Attributes { - fn has_word(&self, &str) -> bool; - fn value<'a>(&'a self, &str) -> Option<&'a str>; - fn list<'a>(&'a self, &str) -> &'a [Attribute]; +pub struct ListAttributesIter<'a> { + attrs: slice::Iter<'a, ast::Attribute>, + current_list: slice::Iter<'a, ast::NestedMetaItem>, + name: &'a str } -impl Attributes for [Attribute] { - /// Returns whether the attribute list contains a specific `Word` - fn has_word(&self, word: &str) -> bool { - for attr in self { - if let Word(ref w) = *attr { - if word == *w { - return true; - } - } - } - false - } +impl<'a> Iterator for ListAttributesIter<'a> { + type Item = &'a ast::NestedMetaItem; - /// Finds an attribute as NameValue and returns the corresponding value found. - fn value<'a>(&'a self, name: &str) -> Option<&'a str> { - for attr in self { - if let NameValue(ref x, ref v) = *attr { - if name == *x { - return Some(v); + fn next(&mut self) -> Option { + if let Some(nested) = self.current_list.next() { + return Some(nested); + } + + for attr in &mut self.attrs { + if let Some(ref list) = attr.meta_item_list() { + if attr.check_name(self.name) { + self.current_list = list.iter(); + if let Some(nested) = self.current_list.next() { + return Some(nested); + } } } } + None } +} +pub trait AttributesExt { /// Finds an attribute as List and returns the list of attributes nested inside. - fn list<'a>(&'a self, name: &str) -> &'a [Attribute] { - for attr in self { - if let List(ref x, ref list) = *attr { - if name == *x { - return &list[..]; + fn lists<'a>(&'a self, &'a str) -> ListAttributesIter<'a>; +} + +impl AttributesExt for [ast::Attribute] { + fn lists<'a>(&'a self, name: &'a str) -> ListAttributesIter<'a> { + ListAttributesIter { + attrs: self.iter(), + current_list: [].iter(), + name: name + } + } +} + +pub trait NestedAttributesExt { + /// Returns whether the attribute list contains a specific `Word` + fn has_word(self, &str) -> bool; +} + +impl<'a, I: IntoIterator> NestedAttributesExt for I { + fn has_word(self, word: &str) -> bool { + self.into_iter().any(|attr| attr.is_word() && attr.check_name(word)) + } +} + +#[derive(Clone, RustcEncodable, RustcDecodable, PartialEq, Debug, Default)] +pub struct Attributes { + pub doc_strings: Vec, + pub other_attrs: Vec +} + +impl Attributes { + pub fn from_ast(attrs: &[ast::Attribute]) -> Attributes { + let mut doc_strings = vec![]; + let other_attrs = attrs.iter().filter_map(|attr| { + attr.with_desugared_doc(|attr| { + if let Some(value) = attr.value_str() { + if attr.check_name("doc") { + doc_strings.push(value.to_string()); + return None; + } } - } - } - &[] - } -} -/// This is a flattened version of the AST's Attribute + MetaItem. -#[derive(Clone, RustcEncodable, RustcDecodable, PartialEq, Debug)] -pub enum Attribute { - Word(String), - List(String, Vec), - NameValue(String, String), - Literal(String), -} - -impl Clean for ast::NestedMetaItem { - fn clean(&self, cx: &DocContext) -> Attribute { - if let Some(mi) = self.meta_item() { - mi.clean(cx) - } else { // must be a literal - let lit = self.literal().unwrap(); - Literal(syntax_pprust::lit_to_string(lit)) + Some(attr.clone()) + }) + }).collect(); + Attributes { + doc_strings: doc_strings, + other_attrs: other_attrs } } -} -impl Clean for ast::MetaItem { - fn clean(&self, cx: &DocContext) -> Attribute { - if self.is_word() { - Word(self.name().to_string()) - } else if let Some(v) = self.value_str() { - NameValue(self.name().to_string(), v.to_string()) - } else { // must be a list - let l = self.meta_item_list().unwrap(); - List(self.name().to_string(), l.clean(cx)) - } + /// Finds the `doc` attribute as a NameValue and returns the corresponding + /// value found. + pub fn doc_value<'a>(&'a self) -> Option<&'a str> { + self.doc_strings.first().map(|s| &s[..]) } } -impl Clean for ast::Attribute { - fn clean(&self, cx: &DocContext) -> Attribute { - self.with_desugared_doc(|a| a.meta().clean(cx)) +impl AttributesExt for Attributes { + fn lists<'a>(&'a self, name: &'a str) -> ListAttributesIter<'a> { + self.other_attrs.lists(name) + } +} + +impl Clean for [ast::Attribute] { + fn clean(&self, _cx: &DocContext) -> Attributes { + Attributes::from_ast(self) } } @@ -1048,7 +1065,7 @@ impl Clean for hir::MethodSig { }, output: self.decl.output.clean(cx), variadic: false, - attrs: Vec::new() + attrs: Attributes::default() }; Method { generics: self.generics.clean(cx), @@ -1076,7 +1093,7 @@ impl Clean for hir::MethodSig { }, output: self.decl.output.clean(cx), variadic: false, - attrs: Vec::new() + attrs: Attributes::default() }; TyMethod { unsafety: self.unsafety.clone(), @@ -1122,7 +1139,7 @@ pub struct FnDecl { pub inputs: Arguments, pub output: FunctionRetTy, pub variadic: bool, - pub attrs: Vec, + pub attrs: Attributes, } impl FnDecl { @@ -1148,7 +1165,7 @@ impl Clean for hir::FnDecl { }, output: self.output.clean(cx), variadic: self.variadic, - attrs: Vec::new() + attrs: Attributes::default() } } } @@ -1163,7 +1180,7 @@ impl<'a, 'tcx> Clean for (DefId, &'a ty::PolyFnSig<'tcx>) { }.peekable(); FnDecl { output: Return(sig.0.output.clean(cx)), - attrs: Vec::new(), + attrs: Attributes::default(), variadic: sig.0.variadic, inputs: Arguments { values: sig.0.inputs.iter().map(|t| { @@ -1616,11 +1633,11 @@ impl PrimitiveType { } } - fn find(attrs: &[Attribute]) -> Option { - for attr in attrs.list("doc") { - if let NameValue(ref k, ref v) = *attr { - if "primitive" == *k { - if let ret@Some(..) = PrimitiveType::from_str(v) { + fn find(attrs: &Attributes) -> Option { + for attr in attrs.lists("doc") { + if let Some(v) = attr.value_str() { + if attr.check_name("primitive") { + if let ret@Some(..) = PrimitiveType::from_str(&v.as_str()) { return ret; } } diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 31fbcb5059f..757db81c440 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -53,7 +53,7 @@ use std::sync::Arc; use externalfiles::ExternalHtml; use serialize::json::{ToJson, Json, as_json}; -use syntax::abi; +use syntax::{abi, ast}; use syntax::feature_gate::UnstableFeatures; use rustc::hir::def_id::{CrateNum, CRATE_DEF_INDEX, DefId, LOCAL_CRATE}; use rustc::middle::privacy::AccessLevels; @@ -62,7 +62,7 @@ use rustc::hir; use rustc::util::nodemap::{FxHashMap, FxHashSet}; use rustc_data_structures::flock; -use clean::{self, Attributes, GetDefId, SelfTy, Mutability}; +use clean::{self, AttributesExt, GetDefId, SelfTy, Mutability}; use doctree; use fold::DocFolder; use html::escape::Escape; @@ -453,30 +453,26 @@ pub fn run(mut krate: clean::Crate, // Crawl the crate attributes looking for attributes which control how we're // going to emit HTML - if let Some(attrs) = krate.module.as_ref().map(|m| m.attrs.list("doc")) { - for attr in attrs { - match *attr { - clean::NameValue(ref x, ref s) - if "html_favicon_url" == *x => { + if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) { + for attr in attrs.lists("doc") { + let name = attr.name().map(|s| s.as_str()); + match (name.as_ref().map(|s| &s[..]), attr.value_str()) { + (Some("html_favicon_url"), Some(s)) => { scx.layout.favicon = s.to_string(); } - clean::NameValue(ref x, ref s) - if "html_logo_url" == *x => { + (Some("html_logo_url"), Some(s)) => { scx.layout.logo = s.to_string(); } - clean::NameValue(ref x, ref s) - if "html_playground_url" == *x => { + (Some("html_playground_url"), Some(s)) => { markdown::PLAYGROUND.with(|slot| { let name = krate.name.clone(); - *slot.borrow_mut() = Some((Some(name), s.clone())); + *slot.borrow_mut() = Some((Some(name), s.to_string())); }); } - clean::NameValue(ref x, ref s) - if "issue_tracker_base_url" == *x => { + (Some("issue_tracker_base_url"), Some(s)) => { scx.issue_tracker_base_url = Some(s.to_string()); } - clean::Word(ref x) - if "html_no_source" == *x => { + (Some("html_no_source"), None) if attr.is_word() => { scx.include_sources = false; } _ => {} @@ -860,13 +856,16 @@ fn extern_location(e: &clean::ExternalCrate, dst: &Path) -> ExternalLocation { // Failing that, see if there's an attribute specifying where to find this // external crate - e.attrs.list("doc").value("html_root_url").map(|url| { - let mut url = url.to_owned(); + e.attrs.lists("doc") + .filter(|a| a.check_name("html_root_url")) + .filter_map(|a| a.value_str()) + .map(|url| { + let mut url = url.to_string(); if !url.ends_with("/") { url.push('/') } Remote(url) - }).unwrap_or(Unknown) // Well, at least we tried. + }).next().unwrap_or(Unknown) // Well, at least we tried. } impl<'a> DocFolder for SourceCollector<'a> { @@ -2511,49 +2510,47 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, Ok(()) } -fn attribute_without_value(s: &str) -> bool { - ["must_use", "no_mangle", "unsafe_destructor_blind_to_params"].iter().any(|x| x == &s) -} +fn render_attribute(attr: &ast::MetaItem) -> Option { + let name = attr.name(); -fn attribute_with_value(s: &str) -> bool { - ["export_name", "lang", "link_section", "must_use"].iter().any(|x| x == &s) -} + if attr.is_word() { + Some(format!("{}", name)) + } else if let Some(v) = attr.value_str() { + Some(format!("{} = {:?}", name, &v.as_str()[..])) + } else if let Some(values) = attr.meta_item_list() { + let display: Vec<_> = values.iter().filter_map(|attr| { + attr.meta_item().and_then(|mi| render_attribute(mi)) + }).collect(); -fn attribute_with_values(s: &str) -> bool { - ["repr"].iter().any(|x| x == &s) -} - -fn render_attribute(attr: &clean::Attribute, recurse: bool) -> Option { - match *attr { - clean::Word(ref s) if attribute_without_value(&*s) || recurse => { - Some(format!("{}", s)) - } - clean::NameValue(ref k, ref v) if attribute_with_value(&*k) => { - Some(format!("{} = \"{}\"", k, v)) - } - clean::List(ref k, ref values) if attribute_with_values(&*k) => { - let display: Vec<_> = values.iter() - .filter_map(|value| render_attribute(value, true)) - .map(|entry| format!("{}", entry)) - .collect(); - - if display.len() > 0 { - Some(format!("{}({})", k, display.join(", "))) - } else { - None - } - } - _ => { + if display.len() > 0 { + Some(format!("{}({})", name, display.join(", "))) + } else { None } + } else { + None } } +const ATTRIBUTE_WHITELIST: &'static [&'static str] = &[ + "export_name", + "lang", + "link_section", + "must_use", + "no_mangle", + "repr", + "unsafe_destructor_blind_to_params" +]; + fn render_attributes(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result { let mut attrs = String::new(); - for attr in &it.attrs { - if let Some(s) = render_attribute(attr, false) { + for attr in &it.attrs.other_attrs { + let name = attr.name(); + if !ATTRIBUTE_WHITELIST.contains(&&name.as_str()[..]) { + continue; + } + if let Some(s) = render_attribute(attr.meta()) { attrs.push_str(&format!("#[{}]\n", s)); } } @@ -2810,7 +2807,7 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi } write!(w, "")?; write!(w, "\n")?; - if let Some(ref dox) = i.impl_item.attrs.value("doc") { + if let Some(ref dox) = i.impl_item.doc_value() { write!(w, "
{}
", Markdown(dox))?; } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 3af7c20c133..60ce7ea5395 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -89,7 +89,7 @@ pub mod visit_ast; pub mod visit_lib; pub mod test; -use clean::Attributes; +use clean::AttributesExt; struct Output { krate: clean::Crate, @@ -280,43 +280,45 @@ pub fn main_args(args: &[String]) -> isize { !matches.opt_present("markdown-no-toc")), (false, false) => {} } - let out = match acquire_input(input, externs, &matches) { - Ok(out) => out, - Err(s) => { - println!("input error: {}", s); - return 1; + + let output_format = matches.opt_str("w"); + let res = acquire_input(input, externs, &matches, move |out| { + let Output { krate, passes, renderinfo } = out; + info!("going to format"); + match output_format.as_ref().map(|s| &**s) { + Some("html") | None => { + html::render::run(krate, &external_html, + output.unwrap_or(PathBuf::from("doc")), + passes.into_iter().collect(), + css_file_extension, + renderinfo) + .expect("failed to generate documentation"); + 0 + } + Some(s) => { + println!("unknown output format: {}", s); + 1 + } } - }; - let Output { krate, passes, renderinfo } = out; - info!("going to format"); - match matches.opt_str("w").as_ref().map(|s| &**s) { - Some("html") | None => { - html::render::run(krate, &external_html, - output.unwrap_or(PathBuf::from("doc")), - passes.into_iter().collect(), - css_file_extension, - renderinfo) - .expect("failed to generate documentation"); - 0 - } - Some(s) => { - println!("unknown output format: {}", s); - 1 - } - } + }); + res.unwrap_or_else(|s| { + println!("input error: {}", s); + 1 + }) } /// Looks inside the command line arguments to extract the relevant input format /// and files and then generates the necessary rustdoc output for formatting. -fn acquire_input(input: &str, - externs: Externs, - matches: &getopts::Matches) -> Result { +fn acquire_input(input: &str, + externs: Externs, + matches: &getopts::Matches, + f: F) + -> Result +where R: 'static + Send, F: 'static + Send + FnOnce(Output) -> R { match matches.opt_str("r").as_ref().map(|s| &**s) { - Some("rust") => Ok(rust_input(input, externs, matches)), + Some("rust") => Ok(rust_input(input, externs, matches, f)), Some(s) => Err(format!("unknown input format: {}", s)), - None => { - Ok(rust_input(input, externs, matches)) - } + None => Ok(rust_input(input, externs, matches, f)) } } @@ -342,7 +344,8 @@ fn parse_externs(matches: &getopts::Matches) -> Result { /// generated from the cleaned AST of the crate. /// /// This form of input will run all of the plug/cleaning passes -fn rust_input(cratefile: &str, externs: Externs, matches: &getopts::Matches) -> Output { +fn rust_input(cratefile: &str, externs: Externs, matches: &getopts::Matches, f: F) -> R +where R: 'static + Send, F: 'static + Send + FnOnce(Output) -> R { let mut default_passes = !matches.opt_present("no-defaults"); let mut passes = matches.opt_strs("passes"); let mut plugins = matches.opt_strs("plugins"); @@ -355,6 +358,8 @@ fn rust_input(cratefile: &str, externs: Externs, matches: &getopts::Matches) -> let cfgs = matches.opt_strs("cfg"); let triple = matches.opt_str("target"); let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from); + let crate_name = matches.opt_str("crate-name"); + let plugin_path = matches.opt_str("plugin-path"); let cr = PathBuf::from(cratefile); info!("starting to run rustc"); @@ -363,67 +368,68 @@ fn rust_input(cratefile: &str, externs: Externs, matches: &getopts::Matches) -> rustc_driver::monitor(move || { use rustc::session::config::Input; - tx.send(core::run_core(paths, cfgs, externs, Input::File(cr), - triple, maybe_sysroot)).unwrap(); - }); - let (mut krate, renderinfo) = rx.recv().unwrap(); - info!("finished with rustc"); + let (mut krate, renderinfo) = + core::run_core(paths, cfgs, externs, Input::File(cr), triple, maybe_sysroot); - if let Some(name) = matches.opt_str("crate-name") { - krate.name = name - } + info!("finished with rustc"); - // Process all of the crate attributes, extracting plugin metadata along - // with the passes which we are supposed to run. - for attr in krate.module.as_ref().unwrap().attrs.list("doc") { - match *attr { - clean::Word(ref w) if "no_default_passes" == *w => { - default_passes = false; - }, - clean::NameValue(ref name, ref value) => { - let sink = match &name[..] { - "passes" => &mut passes, - "plugins" => &mut plugins, + if let Some(name) = crate_name { + krate.name = name + } + + // Process all of the crate attributes, extracting plugin metadata along + // with the passes which we are supposed to run. + for attr in krate.module.as_ref().unwrap().attrs.lists("doc") { + let name = attr.name().map(|s| s.as_str()); + let name = name.as_ref().map(|s| &s[..]); + if attr.is_word() { + if name == Some("no_default_passes") { + default_passes = false; + } + } else if let Some(value) = attr.value_str() { + let sink = match name { + Some("passes") => &mut passes, + Some("plugins") => &mut plugins, _ => continue, }; - for p in value.split_whitespace() { + for p in value.as_str().split_whitespace() { sink.push(p.to_string()); } } - _ => (), } - } - if default_passes { - for name in passes::DEFAULT_PASSES.iter().rev() { - passes.insert(0, name.to_string()); + if default_passes { + for name in passes::DEFAULT_PASSES.iter().rev() { + passes.insert(0, name.to_string()); + } } - } - // Load all plugins/passes into a PluginManager - let path = matches.opt_str("plugin-path") - .unwrap_or("/tmp/rustdoc/plugins".to_string()); - let mut pm = plugins::PluginManager::new(PathBuf::from(path)); - for pass in &passes { - let plugin = match passes::PASSES.iter() - .position(|&(p, ..)| { - p == *pass - }) { - Some(i) => passes::PASSES[i].1, - None => { - error!("unknown pass {}, skipping", *pass); - continue - }, - }; - pm.add_plugin(plugin); - } - info!("loading plugins..."); - for pname in plugins { - pm.load_plugin(pname); - } + // Load all plugins/passes into a PluginManager + let path = plugin_path.unwrap_or("/tmp/rustdoc/plugins".to_string()); + let mut pm = plugins::PluginManager::new(PathBuf::from(path)); + for pass in &passes { + let plugin = match passes::PASSES.iter() + .position(|&(p, ..)| { + p == *pass + }) { + Some(i) => passes::PASSES[i].1, + None => { + error!("unknown pass {}, skipping", *pass); + continue + }, + }; + pm.add_plugin(plugin); + } + info!("loading plugins..."); + for pname in plugins { + pm.load_plugin(pname); + } - // Run everything! - info!("Executing passes/plugins"); - let krate = pm.run_plugins(krate); - Output { krate: krate, renderinfo: renderinfo, passes: passes } + // Run everything! + info!("Executing passes/plugins"); + let krate = pm.run_plugins(krate); + + tx.send(f(Output { krate: krate, renderinfo: renderinfo, passes: passes })).unwrap(); + }); + rx.recv().unwrap() } diff --git a/src/librustdoc/passes/collapse_docs.rs b/src/librustdoc/passes/collapse_docs.rs index c034ef93268..3c63302127c 100644 --- a/src/librustdoc/passes/collapse_docs.rs +++ b/src/librustdoc/passes/collapse_docs.rs @@ -8,40 +8,33 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::string::String; - use clean::{self, Item}; use plugins; use fold; use fold::DocFolder; pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult { - let mut collapser = Collapser; - let krate = collapser.fold_crate(krate); - krate + Collapser.fold_crate(krate) } struct Collapser; impl fold::DocFolder for Collapser { fn fold_item(&mut self, mut i: Item) -> Option { - let mut docstr = String::new(); - for attr in &i.attrs { - if let clean::NameValue(ref x, ref s) = *attr { - if "doc" == *x { - docstr.push_str(s); - docstr.push('\n'); - } - } - } - let mut a: Vec = i.attrs.iter().filter(|&a| match a { - &clean::NameValue(ref x, _) if "doc" == *x => false, - _ => true - }).cloned().collect(); - if !docstr.is_empty() { - a.push(clean::NameValue("doc".to_string(), docstr)); - } - i.attrs = a; + i.attrs.collapse_doc_comments(); self.fold_item_recur(i) } } + +impl clean::Attributes { + pub fn collapse_doc_comments(&mut self) { + let mut doc_string = self.doc_strings.join("\n"); + if doc_string.is_empty() { + self.doc_strings = vec![]; + } else { + // FIXME(eddyb) Is this still needed? + doc_string.push('\n'); + self.doc_strings = vec![doc_string]; + } + } +} diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs index 927ccf91719..68c1231fc6f 100644 --- a/src/librustdoc/passes/strip_hidden.rs +++ b/src/librustdoc/passes/strip_hidden.rs @@ -11,7 +11,7 @@ use rustc::util::nodemap::DefIdSet; use std::mem; -use clean::{self, Attributes}; +use clean::{self, AttributesExt, NestedAttributesExt}; use clean::Item; use plugins; use fold; @@ -41,7 +41,7 @@ struct Stripper<'a> { impl<'a> fold::DocFolder for Stripper<'a> { fn fold_item(&mut self, i: Item) -> Option { - if i.attrs.list("doc").has_word("hidden") { + if i.attrs.lists("doc").has_word("hidden") { debug!("found one in strip_hidden; removing"); // use a dedicated hidden item for given item type if any match i.inner { diff --git a/src/librustdoc/passes/unindent_comments.rs b/src/librustdoc/passes/unindent_comments.rs index 20640f3f885..4d94c308478 100644 --- a/src/librustdoc/passes/unindent_comments.rs +++ b/src/librustdoc/passes/unindent_comments.rs @@ -17,31 +17,26 @@ use plugins; use fold::{self, DocFolder}; pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult { - let mut cleaner = CommentCleaner; - let krate = cleaner.fold_crate(krate); - krate + CommentCleaner.fold_crate(krate) } struct CommentCleaner; impl fold::DocFolder for CommentCleaner { fn fold_item(&mut self, mut i: Item) -> Option { - let mut avec: Vec = Vec::new(); - for attr in &i.attrs { - match attr { - &clean::NameValue(ref x, ref s) - if "doc" == *x => { - avec.push(clean::NameValue("doc".to_string(), - unindent(s))) - } - x => avec.push(x.clone()) - } - } - i.attrs = avec; + i.attrs.unindent_doc_comments(); self.fold_item_recur(i) } } +impl clean::Attributes { + pub fn unindent_doc_comments(&mut self) { + for doc_string in &mut self.doc_strings { + *doc_string = unindent(doc_string); + } + } +} + fn unindent(s: &str) -> String { let lines = s.lines().collect:: >(); let mut saw_first_line = false; diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 939fd6ccfc8..f1eb65ee16e 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -28,7 +28,7 @@ use rustc::util::nodemap::FxHashSet; use rustc::hir; use core; -use clean::{self, Clean, Attributes}; +use clean::{self, AttributesExt, NestedAttributesExt}; use doctree::*; // looks to me like the first two of these are actually @@ -281,8 +281,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { fn inherits_doc_hidden(cx: &core::DocContext, mut node: ast::NodeId) -> bool { while let Some(id) = cx.map.get_enclosing_scope(node) { node = id; - let attrs = cx.map.attrs(node).clean(cx); - if attrs.list("doc").has_word("hidden") { + if cx.map.attrs(node).lists("doc").has_word("hidden") { return true; } if node == ast::CRATE_NODE_ID { @@ -299,10 +298,10 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let def = tcx.expect_def(id); let def_did = def.def_id(); - let use_attrs = tcx.map.attrs(id).clean(self.cx); + let use_attrs = tcx.map.attrs(id); // Don't inline doc(hidden) imports so they can be stripped at a later stage. - let is_no_inline = use_attrs.list("doc").has_word("no_inline") || - use_attrs.list("doc").has_word("hidden"); + let is_no_inline = use_attrs.lists("doc").has_word("no_inline") || + use_attrs.lists("doc").has_word("hidden"); // For cross-crate impl inlining we need to know whether items are // reachable in documentation - a previously nonreachable item can be @@ -310,7 +309,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // (this is done here because we need to know this upfront) if !def_did.is_local() && !is_no_inline { let attrs = clean::inline::load_attrs(self.cx, tcx, def_did); - let self_is_hidden = attrs.list("doc").has_word("hidden"); + let self_is_hidden = attrs.lists("doc").has_word("hidden"); match def { Def::Trait(did) | Def::Struct(did) | diff --git a/src/librustdoc/visit_lib.rs b/src/librustdoc/visit_lib.rs index 6d2830c5619..172d070c55d 100644 --- a/src/librustdoc/visit_lib.rs +++ b/src/librustdoc/visit_lib.rs @@ -16,7 +16,7 @@ use rustc::ty::Visibility; use std::cell::RefMut; -use clean::{Attributes, Clean}; +use clean::{AttributesExt, NestedAttributesExt}; // FIXME: this may not be exhaustive, but is sufficient for rustdocs current uses @@ -49,10 +49,7 @@ impl<'a, 'b, 'tcx> LibEmbargoVisitor<'a, 'b, 'tcx> { // Updates node level and returns the updated level fn update(&mut self, did: DefId, level: Option) -> Option { - let attrs: Vec<_> = self.cx.tcx().get_attrs(did).iter() - .map(|a| a.clean(self.cx)) - .collect(); - let is_hidden = attrs.list("doc").has_word("hidden"); + let is_hidden = self.cx.tcx().get_attrs(did).lists("doc").has_word("hidden"); let old_level = self.access_levels.map.get(&did).cloned(); // Accessibility levels can only grow