// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Converts the Rust AST to the rustdoc document model use astsrv; use doc::ItemUtils; use doc; use syntax::ast; use syntax::parse::token::{ident_interner, ident_to_str}; use syntax::parse::token; // Hack; rather than thread an interner through everywhere, rely on // thread-local data // Hack-Becomes-Feature: using thread-local-state everywhere... pub fn to_str(id: ast::ident) -> ~str { /* bad */ ident_to_str(&id).to_owned() } // get rid of this pointless function: pub fn interner() -> @ident_interner { return token::get_ident_interner(); } pub fn from_srv( srv: astsrv::Srv, default_name: ~str ) -> doc::Doc { //! Use the AST service to create a document tree do astsrv::exec(srv) |ctxt| { extract(ctxt.ast, default_name.clone()) } } pub fn extract( crate: @ast::Crate, default_name: ~str ) -> doc::Doc { doc::Doc { pages: ~[ doc::CratePage(doc::CrateDoc { topmod: top_moddoc_from_crate(crate, default_name), }) ] } } fn top_moddoc_from_crate( crate: @ast::Crate, default_name: ~str ) -> doc::ModDoc { moddoc_from_mod(mk_itemdoc(ast::CRATE_NODE_ID, default_name), crate.module.clone()) } fn mk_itemdoc(id: ast::NodeId, name: ~str) -> doc::ItemDoc { doc::ItemDoc { id: id, name: name, path: ~[], brief: None, desc: None, sections: ~[], reexport: false } } fn moddoc_from_mod( itemdoc: doc::ItemDoc, module_: ast::_mod ) -> doc::ModDoc { doc::ModDoc { item: itemdoc, items: do module_.items.iter().filter_map |item| { let ItemDoc = mk_itemdoc(item.id, to_str(item.ident)); match item.node.clone() { ast::item_mod(m) => { Some(doc::ModTag( moddoc_from_mod(ItemDoc, m) )) } ast::item_foreign_mod(nm) => { Some(doc::NmodTag( nmoddoc_from_mod(ItemDoc, nm) )) } ast::item_fn(*) => { Some(doc::FnTag( fndoc_from_fn(ItemDoc) )) } ast::item_static(*) => { Some(doc::StaticTag( staticdoc_from_static(ItemDoc) )) } ast::item_enum(enum_definition, _) => { Some(doc::EnumTag( enumdoc_from_enum(ItemDoc, enum_definition.variants.clone()) )) } ast::item_trait(_, _, methods) => { Some(doc::TraitTag( traitdoc_from_trait(ItemDoc, methods) )) } ast::item_impl(_, _, _, methods) => { Some(doc::ImplTag( impldoc_from_impl(ItemDoc, methods) )) } ast::item_ty(_, _) => { Some(doc::TyTag( tydoc_from_ty(ItemDoc) )) } ast::item_struct(def, _) => { Some(doc::StructTag( structdoc_from_struct(ItemDoc, def) )) } _ => None } }.collect(), index: None } } fn nmoddoc_from_mod( itemdoc: doc::ItemDoc, module_: ast::foreign_mod ) -> doc::NmodDoc { let mut fns = ~[]; for item in module_.items.iter() { let ItemDoc = mk_itemdoc(item.id, to_str(item.ident)); match item.node { ast::foreign_item_fn(*) => { fns.push(fndoc_from_fn(ItemDoc)); } ast::foreign_item_static(*) => {} // XXX: Not implemented. } } doc::NmodDoc { item: itemdoc, fns: fns, index: None } } fn fndoc_from_fn(itemdoc: doc::ItemDoc) -> doc::FnDoc { doc::SimpleItemDoc { item: itemdoc, sig: None } } fn staticdoc_from_static(itemdoc: doc::ItemDoc) -> doc::StaticDoc { doc::SimpleItemDoc { item: itemdoc, sig: None } } fn enumdoc_from_enum( itemdoc: doc::ItemDoc, variants: ~[ast::variant] ) -> doc::EnumDoc { doc::EnumDoc { item: itemdoc, variants: variantdocs_from_variants(variants) } } fn variantdocs_from_variants( variants: ~[ast::variant] ) -> ~[doc::VariantDoc] { variants.iter().map(variantdoc_from_variant).collect() } fn variantdoc_from_variant(variant: &ast::variant) -> doc::VariantDoc { doc::VariantDoc { name: to_str(variant.node.name), desc: None, sig: None } } fn traitdoc_from_trait( itemdoc: doc::ItemDoc, methods: ~[ast::trait_method] ) -> doc::TraitDoc { doc::TraitDoc { item: itemdoc, methods: do methods.iter().map |method| { match (*method).clone() { ast::required(ty_m) => { doc::MethodDoc { name: to_str(ty_m.ident), brief: None, desc: None, sections: ~[], sig: None, implementation: doc::Required, } } ast::provided(m) => { doc::MethodDoc { name: to_str(m.ident), brief: None, desc: None, sections: ~[], sig: None, implementation: doc::Provided, } } } }.collect() } } fn impldoc_from_impl( itemdoc: doc::ItemDoc, methods: ~[@ast::method] ) -> doc::ImplDoc { doc::ImplDoc { item: itemdoc, bounds_str: None, trait_types: ~[], self_ty: None, methods: do methods.iter().map |method| { doc::MethodDoc { name: to_str(method.ident), brief: None, desc: None, sections: ~[], sig: None, implementation: doc::Provided, } }.collect() } } fn tydoc_from_ty( itemdoc: doc::ItemDoc ) -> doc::TyDoc { doc::SimpleItemDoc { item: itemdoc, sig: None } } fn structdoc_from_struct( itemdoc: doc::ItemDoc, struct_def: @ast::struct_def ) -> doc::StructDoc { doc::StructDoc { item: itemdoc, fields: do struct_def.fields.map |field| { match field.node.kind { ast::named_field(ident, _) => to_str(ident), ast::unnamed_field => ~"(unnamed)", } }, sig: None } } #[cfg(test)] mod test { use astsrv; use doc; use extract::{extract, from_srv}; use parse; fn mk_doc(source: @str) -> doc::Doc { let ast = parse::from_str(source); extract(ast, ~"") } #[test] fn extract_empty_crate() { let doc = mk_doc(@""); assert!(doc.cratemod().mods().is_empty()); assert!(doc.cratemod().fns().is_empty()); } #[test] fn extract_mods() { let doc = mk_doc(@"mod a { mod b { } mod c { } }"); assert!(doc.cratemod().mods()[0].name_() == ~"a"); assert!(doc.cratemod().mods()[0].mods()[0].name_() == ~"b"); assert!(doc.cratemod().mods()[0].mods()[1].name_() == ~"c"); } #[test] fn extract_fns_from_foreign_mods() { let doc = mk_doc(@"extern { fn a(); }"); assert!(doc.cratemod().nmods()[0].fns[0].name_() == ~"a"); } #[test] fn extract_mods_deep() { let doc = mk_doc(@"mod a { mod b { mod c { } } }"); assert!(doc.cratemod().mods()[0].mods()[0].mods()[0].name_() == ~"c"); } #[test] fn extract_should_set_mod_ast_id() { let doc = mk_doc(@"mod a { }"); assert!(doc.cratemod().mods()[0].id() != 0); } #[test] fn extract_fns() { let doc = mk_doc( @"fn a() { } \ mod b { fn c() { } }"); assert!(doc.cratemod().fns()[0].name_() == ~"a"); assert!(doc.cratemod().mods()[0].fns()[0].name_() == ~"c"); } #[test] fn extract_should_set_fn_ast_id() { let doc = mk_doc(@"fn a() { }"); assert!(doc.cratemod().fns()[0].id() != 0); } #[test] fn extract_should_use_default_crate_name() { let source = @""; let ast = parse::from_str(source); let doc = extract(ast, ~"burp"); assert!(doc.cratemod().name_() == ~"burp"); } #[test] fn extract_from_seq_srv() { let source = ~""; do astsrv::from_str(source) |srv| { let doc = from_srv(srv, ~"name"); assert!(doc.cratemod().name_() == ~"name"); } } #[test] fn should_extract_static_name_and_id() { let doc = mk_doc(@"static a: int = 0;"); assert!(doc.cratemod().statics()[0].id() != 0); assert!(doc.cratemod().statics()[0].name_() == ~"a"); } #[test] fn should_extract_enums() { let doc = mk_doc(@"enum e { v }"); assert!(doc.cratemod().enums()[0].id() != 0); assert!(doc.cratemod().enums()[0].name_() == ~"e"); } #[test] fn should_extract_enum_variants() { let doc = mk_doc(@"enum e { v }"); assert!(doc.cratemod().enums()[0].variants[0].name == ~"v"); } #[test] fn should_extract_traits() { let doc = mk_doc(@"trait i { fn f(); }"); assert!(doc.cratemod().traits()[0].name_() == ~"i"); } #[test] fn should_extract_trait_methods() { let doc = mk_doc(@"trait i { fn f(); }"); assert!(doc.cratemod().traits()[0].methods[0].name == ~"f"); } #[test] fn should_extract_impl_methods() { let doc = mk_doc(@"impl int { fn f() { } }"); assert!(doc.cratemod().impls()[0].methods[0].name == ~"f"); } #[test] fn should_extract_tys() { let doc = mk_doc(@"type a = int;"); assert!(doc.cratemod().types()[0].name_() == ~"a"); } #[test] fn should_extract_structs() { let doc = mk_doc(@"struct Foo { field: () }"); assert!(doc.cratemod().structs()[0].name_() == ~"Foo"); } #[test] fn should_extract_struct_fields() { let doc = mk_doc(@"struct Foo { field: () }"); assert!(doc.cratemod().structs()[0].fields[0] == ~"field"); } }