about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in5
-rw-r--r--mk/clean.mk4
-rw-r--r--mk/install.mk2
-rw-r--r--mk/tools.mk13
-rw-r--r--src/rustdoc_ng/.gitignore2
-rw-r--r--src/rustdoc_ng/clean.rs1081
-rw-r--r--src/rustdoc_ng/core.rs68
-rw-r--r--src/rustdoc_ng/doctree.rs158
-rw-r--r--src/rustdoc_ng/fold.rs101
-rw-r--r--src/rustdoc_ng/lib.rs27
-rw-r--r--src/rustdoc_ng/main.rs94
-rw-r--r--src/rustdoc_ng/passes.rs193
-rw-r--r--src/rustdoc_ng/plugins.rs80
-rw-r--r--src/rustdoc_ng/visit_ast.rs173
14 files changed, 2001 insertions, 0 deletions
diff --git a/Makefile.in b/Makefile.in
index ecccddb0cfa..171ce8f1925 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -214,6 +214,7 @@ CFG_LIBRUSTC_$(1) :=$(call CFG_LIB_NAME_$(1),rustc)
 CFG_LIBSYNTAX_$(1) :=$(call CFG_LIB_NAME_$(1),syntax)
 CFG_LIBRUSTPKG_$(1) :=$(call CFG_LIB_NAME_$(1),rustpkg)
 CFG_LIBRUSTDOC_$(1) :=$(call CFG_LIB_NAME_$(1),rustdoc)
+CFG_LIBRUSTDOCNG_$(1) :=$(call CFG_LIB_NAME_$(1),rustdoc_ng)
 CFG_LIBRUSTI_$(1) :=$(call CFG_LIB_NAME_$(1),rusti)
 CFG_LIBRUST_$(1) :=$(call CFG_LIB_NAME_$(1),rust)
 
@@ -223,6 +224,7 @@ LIBRUSTC_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustc)
 LIBSYNTAX_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),syntax)
 LIBRUSTPKG_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustpkg)
 LIBRUSTDOC_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustdoc)
+LIBRUSTDOCNG_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustdoc_ng)
 LIBRUSTI_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rusti)
 LIBRUST_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rust)
 EXTRALIB_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),extra)
@@ -231,6 +233,7 @@ LIBRUSTC_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustc)
 LIBSYNTAX_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),syntax)
 LIBRUSTPKG_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustpkg)
 LIBRUSTDOC_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustdoc)
+LIBRUSTDOCNG_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustdoc_ng)
 LIBRUSTI_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rusti)
 LIBRUST_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rust)
 
@@ -443,6 +446,7 @@ CSREQ$(1)_T_$(2)_H_$(3) = \
 	$$(HBIN$(1)_H_$(3))/rust$$(X_$(3)) \
 	$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTPKG_$(3)) \
 	$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTDOC_$(3)) \
+	$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(3)) \
 	$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTI_$(3)) \
 	$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUST_$(3)) \
 	$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2)) \
@@ -451,6 +455,7 @@ CSREQ$(1)_T_$(2)_H_$(3) = \
 	$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTC_$(2)) \
 	$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTPKG_$(2)) \
 	$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOC_$(2)) \
+	$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(2)) \
 	$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTI_$(2)) \
 	$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUST_$(2))
 
diff --git a/mk/clean.mk b/mk/clean.mk
index cb8861f6597..5e8d9835db2 100644
--- a/mk/clean.mk
+++ b/mk/clean.mk
@@ -72,6 +72,7 @@ clean$(1)_H_$(2):
 	$(Q)rm -f $$(HBIN$(1)_H_$(2))/rust$(X_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTPKG_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTDOC_$(2))
+	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTDOCNG_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_RUNTIME_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_STDLIB_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_EXTRALIB_$(2))
@@ -85,6 +86,7 @@ clean$(1)_H_$(2):
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBSYNTAX_GLOB_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTPKG_GLOB_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTDOC_GLOB_$(2))
+	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTDOCNG_GLOB_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTI_GLOB_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUST_GLOB_$(2))
 	$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_RUSTLLVM_$(2))
@@ -107,6 +109,7 @@ clean$(1)_T_$(2)_H_$(3):
 	$(Q)rm -f $$(TBIN$(1)_T_$(2)_H_$(3))/rust$(X_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTPKG_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOC_$(2))
+	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_RUNTIME_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_EXTRALIB_$(2))
@@ -120,6 +123,7 @@ clean$(1)_T_$(2)_H_$(3):
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBSYNTAX_GLOB_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTPKG_GLOB_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTDOC_GLOB_$(2))
+	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTDOCNG_GLOB_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTI_GLOB_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUST_GLOB_$(2))
 	$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_RUSTLLVM_$(2))
diff --git a/mk/install.mk b/mk/install.mk
index 4b50c5aa796..bc4633b8225 100644
--- a/mk/install.mk
+++ b/mk/install.mk
@@ -104,6 +104,7 @@ install-target-$(1)-host-$(2): $$(CSREQ$$(ISTAGE)_T_$(1)_H_$(2))
 	$$(Q)$$(call INSTALL_LIB,$$(LIBSYNTAX_GLOB_$(1)))
 	$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTPKG_GLOB_$(1)))
 	$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTDOC_GLOB_$(1)))
+	$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTDOCNG_GLOB_$(1)))
 	$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTI_GLOB_$(1)))
 	$$(Q)$$(call INSTALL_LIB,$$(LIBRUST_GLOB_$(1)))
 	$$(Q)$$(call INSTALL_LIB,libmorestack.a)
@@ -149,6 +150,7 @@ install-host: $(CSREQ$(ISTAGE)_T_$(CFG_BUILD_TRIPLE)_H_$(CFG_BUILD_TRIPLE))
 	$(Q)$(call INSTALL_LIB,$(LIBRUST_GLOB_$(CFG_BUILD_TRIPLE)))
 	$(Q)$(call INSTALL_LIB,$(LIBRUSTPKG_GLOB_$(CFG_BUILD_TRIPLE)))
 	$(Q)$(call INSTALL_LIB,$(LIBRUSTDOC_GLOB_$(CFG_BUILD_TRIPLE)))
+	$(Q)$(call INSTALL_LIB,$(LIBRUSTDOCNG_GLOB_$(CFG_BUILD_TRIPLE)))
 	$(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_RUNTIME_$(CFG_BUILD_TRIPLE)))
 	$(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_RUSTLLVM_$(CFG_BUILD_TRIPLE)))
 	$(Q)$(call INSTALL,$(S)/man, $(PREFIX_ROOT)/share/man/man1,rust.1)
diff --git a/mk/tools.mk b/mk/tools.mk
index 09c3de01478..b5e413804ec 100644
--- a/mk/tools.mk
+++ b/mk/tools.mk
@@ -23,6 +23,11 @@ RUSTPKG_INPUTS := $(wildcard $(S)src/librustpkg/*.rs)
 RUSTDOC_LIB := $(S)src/librustdoc/rustdoc.rs
 RUSTDOC_INPUTS := $(wildcard $(S)src/librustdoc/*.rs)
 
+# rustdoc_ng, the next generation documentation tool
+
+RUSTDOCNG_LIB := $(S)src/rustdoc/lib.rs
+RUSTDOCNG_INPUTS := $(wildcard $(S)src/rustdoc/*.rs)
+
 # Rusti, the JIT REPL
 RUSTI_LIB := $(S)src/librusti/rusti.rs
 RUSTI_INPUTS := $(wildcard $(S)src/librusti/*.rs)
@@ -78,6 +83,14 @@ $$(TBIN$(1)_T_$(4)_H_$(3))/rustdoc$$(X_$(4)):			\
 	@$$(call E, compile_and_link: $$@)
 	$$(STAGE$(1)_T_$(4)_H_$(3)) --cfg rustdoc -o $$@ $$<
 
+$$(TBIN$(1)_T_$(4)_H_$(3))/rustdoc_ng$$(X_$(4)):			\
+		$$(DRIVER_CRATE) 							\
+		$$(TSREQ$(1)_T_$(4)_H_$(3))						\
+		$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(4))			\
+		| $$(TBIN$(1)_T_$(4)_H_$(3))/
+	@$$(call E, compile_and_link: $$@)
+	$$(STAGE$(1)_T_$(4)_H_$(3)) --cfg rustdoc_ng -o $$@ $$<
+
 $$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTI_$(4)):		\
 		$$(RUSTI_LIB) $$(RUSTI_INPUTS)			\
 		$$(SREQ$(1)_T_$(4)_H_$(3))			\
diff --git a/src/rustdoc_ng/.gitignore b/src/rustdoc_ng/.gitignore
new file mode 100644
index 00000000000..62f94a1a44e
--- /dev/null
+++ b/src/rustdoc_ng/.gitignore
@@ -0,0 +1,2 @@
+*.swp
+main
diff --git a/src/rustdoc_ng/clean.rs b/src/rustdoc_ng/clean.rs
new file mode 100644
index 00000000000..ed18fc04fad
--- /dev/null
+++ b/src/rustdoc_ng/clean.rs
@@ -0,0 +1,1081 @@
+//! This module contains the "cleaned" pieces of the AST, and the functions
+//! that clean them.
+
+use its = syntax::parse::token::ident_to_str;
+
+use rustc::metadata::{csearch,decoder,cstore};
+use syntax;
+use syntax::ast;
+
+use std;
+use doctree;
+use visit_ast;
+use std::local_data;
+
+pub trait Clean<T> {
+    fn clean(&self) -> T;
+}
+
+impl<T: Clean<U>, U> Clean<~[U]> for ~[T] {
+    fn clean(&self) -> ~[U] {
+        self.iter().map(|x| x.clean()).collect()
+    }
+}
+impl<T: Clean<U>, U> Clean<U> for @T {
+    fn clean(&self) -> U {
+        (**self).clean()
+    }
+}
+
+impl<T: Clean<U>, U> Clean<Option<U>> for Option<T> {
+    fn clean(&self) -> Option<U> {
+        match self {
+            &None => None,
+            &Some(ref v) => Some(v.clean())
+        }
+    }
+}
+
+impl<T: Clean<U>, U> Clean<~[U]> for syntax::opt_vec::OptVec<T> {
+    fn clean(&self) -> ~[U] {
+        match self {
+            &syntax::opt_vec::Empty => ~[],
+            &syntax::opt_vec::Vec(ref v) => v.clean()
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Crate {
+    name: ~str,
+    module: Option<Item>,
+}
+
+impl Clean<Crate> for visit_ast::RustdocVisitor {
+    fn clean(&self) -> Crate {
+        use syntax::attr::{find_linkage_metas, last_meta_item_value_str_by_name};
+        let maybe_meta = last_meta_item_value_str_by_name(find_linkage_metas(self.attrs), "name");
+
+        Crate {
+            name: match maybe_meta {
+                Some(x) => x.to_owned(),
+                None => fail!("rustdoc_ng requires a #[link(name=\"foo\")] crate attribute"),
+            },
+            module: Some(self.module.clean()),
+        }
+    }
+}
+
+/// Anything with a source location and set of attributes and, optionally, a
+/// name. That is, anything that can be documented. This doesn't correspond
+/// directly to the AST's concept of an item; it's a strict superset.
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Item {
+    /// Stringified span
+    source: ~str,
+    /// Not everything has a name. E.g., impls
+    name: Option<~str>,
+    attrs: ~[Attribute],
+    inner: ItemEnum,
+    visibility: Option<Visibility>,
+    id: ast::NodeId,
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub enum ItemEnum {
+    StructItem(Struct),
+    EnumItem(Enum),
+    FunctionItem(Function),
+    ModuleItem(Module),
+    TypedefItem(Typedef),
+    StaticItem(Static),
+    TraitItem(Trait),
+    ImplItem(Impl),
+    ViewItemItem(ViewItem),
+    TyMethodItem(TyMethod),
+    MethodItem(Method),
+    StructFieldItem(StructField),
+    VariantItem(Variant),
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Module {
+    items: ~[Item],
+}
+
+impl Clean<Item> for doctree::Module {
+    fn clean(&self) -> Item {
+        let name = if self.name.is_some() {
+            self.name.unwrap().clean()
+        } else {
+            ~""
+        };
+        Item {
+            name: Some(name),
+            attrs: self.attrs.clean(),
+            source: self.where.clean(),
+            visibility: self.vis.clean(),
+            id: self.id,
+            inner: ModuleItem(Module {
+               items: std::vec::concat(&[self.structs.clean(),
+                              self.enums.clean(), self.fns.clean(),
+                              self.mods.clean(), self.typedefs.clean(),
+                              self.statics.clean(), self.traits.clean(),
+                              self.impls.clean(), self.view_items.clean()])
+            })
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub enum Attribute {
+    Word(~str),
+    List(~str, ~[Attribute]),
+    NameValue(~str, ~str)
+}
+
+impl Clean<Attribute> for ast::MetaItem {
+    fn clean(&self) -> Attribute {
+        match self.node {
+            ast::MetaWord(s) => Word(s.to_owned()),
+            ast::MetaList(ref s, ref l) => List(s.to_owned(), l.clean()),
+            ast::MetaNameValue(s, ref v) => NameValue(s.to_owned(), lit_to_str(v))
+        }
+    }
+}
+
+impl Clean<Attribute> for ast::Attribute {
+    fn clean(&self) -> Attribute {
+        self.node.value.clean()
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct TyParam {
+    name: ~str,
+    id: ast::NodeId,
+    bounds: ~[TyParamBound]
+}
+
+impl Clean<TyParam> for ast::TyParam {
+    fn clean(&self) -> TyParam {
+        TyParam {
+            name: self.ident.clean(),
+            id: self.id,
+            bounds: self.bounds.clean(),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub enum TyParamBound {
+    RegionBound,
+    TraitBound(Type)
+}
+
+impl Clean<TyParamBound> for ast::TyParamBound {
+    fn clean(&self) -> TyParamBound {
+        match *self {
+            ast::RegionTyParamBound => RegionBound,
+            ast::TraitTyParamBound(ref t) => TraitBound(t.clean()),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Lifetime(~str);
+
+impl Clean<Lifetime> for ast::Lifetime {
+    fn clean(&self) -> Lifetime {
+        Lifetime(self.ident.clean())
+    }
+}
+
+// maybe use a Generic enum and use ~[Generic]?
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Generics {
+    lifetimes: ~[Lifetime],
+    type_params: ~[TyParam]
+}
+
+impl Generics {
+    fn new() -> Generics {
+        Generics {
+            lifetimes: ~[],
+            type_params: ~[]
+        }
+    }
+}
+
+impl Clean<Generics> for ast::Generics {
+    fn clean(&self) -> Generics {
+        Generics {
+            lifetimes: self.lifetimes.clean(),
+            type_params: self.ty_params.clean(),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Method {
+    generics: Generics,
+    self_: SelfTy,
+    purity: ast::purity,
+    decl: FnDecl,
+}
+
+impl Clean<Item> for ast::method {
+    fn clean(&self) -> Item {
+        Item {
+            name: Some(self.ident.clean()),
+            attrs: self.attrs.clean(),
+            source: self.span.clean(),
+            id: self.self_id.clone(),
+            visibility: None,
+            inner: MethodItem(Method {
+                generics: self.generics.clean(),
+                self_: self.explicit_self.clean(),
+                purity: self.purity.clone(),
+                decl: self.decl.clean(),
+            }),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct TyMethod {
+    purity: ast::purity,
+    decl: FnDecl,
+    generics: Generics,
+    self_: SelfTy,
+}
+
+impl Clean<Item> for ast::TypeMethod {
+    fn clean(&self) -> Item {
+        Item {
+            name: Some(self.ident.clean()),
+            attrs: self.attrs.clean(),
+            source: self.span.clean(),
+            id: self.id,
+            visibility: None,
+            inner: TyMethodItem(TyMethod {
+                purity: self.purity.clone(),
+                decl: self.decl.clean(),
+                self_: self.explicit_self.clean(),
+                generics: self.generics.clean(),
+            }),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub enum SelfTy {
+    SelfStatic,
+    SelfValue,
+    SelfBorrowed(Option<Lifetime>, Mutability),
+    SelfManaged(Mutability),
+    SelfOwned,
+}
+
+impl Clean<SelfTy> for ast::explicit_self {
+    fn clean(&self) -> SelfTy {
+        match self.node {
+            ast::sty_static => SelfStatic,
+            ast::sty_value => SelfValue,
+            ast::sty_uniq => SelfOwned,
+            ast::sty_region(lt, mt) => SelfBorrowed(lt.clean(), mt.clean()),
+            ast::sty_box(mt) => SelfManaged(mt.clean()),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Function {
+    decl: FnDecl,
+    generics: Generics,
+}
+
+impl Clean<Item> for doctree::Function {
+    fn clean(&self) -> Item {
+        Item {
+            name: Some(self.name.clean()),
+            attrs: self.attrs.clean(),
+            source: self.where.clean(),
+            visibility: self.vis.clean(),
+            id: self.id,
+            inner: FunctionItem(Function {
+                decl: self.decl.clean(),
+                generics: self.generics.clean(),
+            }),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct ClosureDecl {
+    sigil: ast::Sigil,
+    region: Option<Lifetime>,
+    lifetimes: ~[Lifetime],
+    decl: FnDecl,
+    onceness: ast::Onceness,
+    purity: ast::purity,
+    bounds: ~[TyParamBound]
+}
+
+impl Clean<ClosureDecl> for ast::TyClosure {
+    fn clean(&self) -> ClosureDecl {
+        ClosureDecl {
+            sigil: self.sigil,
+            region: self.region.clean(),
+            lifetimes: self.lifetimes.clean(),
+            decl: self.decl.clean(),
+            onceness: self.onceness,
+            purity: self.purity,
+            bounds: match self.bounds {
+                Some(ref x) => x.clean(),
+                None        => ~[]
+            },
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct FnDecl {
+    inputs: ~[Argument],
+    output: Type,
+    cf: RetStyle,
+    attrs: ~[Attribute]
+}
+
+impl Clean<FnDecl> for ast::fn_decl {
+    fn clean(&self) -> FnDecl {
+        FnDecl {
+            inputs: self.inputs.iter().map(|x| x.clean()).collect(),
+            output: (self.output.clean()),
+            cf: self.cf.clean(),
+            attrs: ~[]
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Argument {
+    type_: Type,
+    name: ~str,
+    id: ast::NodeId
+}
+
+impl Clean<Argument> for ast::arg {
+    fn clean(&self) -> Argument {
+        Argument {
+            name: name_from_pat(self.pat),
+            type_: (self.ty.clean()),
+            id: self.id
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub enum RetStyle {
+    NoReturn,
+    Return
+}
+
+impl Clean<RetStyle> for ast::ret_style {
+    fn clean(&self) -> RetStyle {
+        match *self {
+            ast::return_val => Return,
+            ast::noreturn => NoReturn
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Trait {
+    methods: ~[TraitMethod],
+    generics: Generics,
+    parents: ~[Type],
+}
+
+impl Clean<Item> for doctree::Trait {
+    fn clean(&self) -> Item {
+        Item {
+            name: Some(self.name.clean()),
+            attrs: self.attrs.clean(),
+            source: self.where.clean(),
+            id: self.id,
+            visibility: self.vis.clean(),
+            inner: TraitItem(Trait {
+                methods: self.methods.clean(),
+                generics: self.generics.clean(),
+                parents: self.parents.clean(),
+            }),
+        }
+    }
+}
+
+impl Clean<Type> for ast::trait_ref {
+    fn clean(&self) -> Type {
+        let t = Unresolved(self.path.clean(), None, self.ref_id);
+        resolve_type(&t)
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub enum TraitMethod {
+    Required(Item),
+    Provided(Item),
+}
+
+impl TraitMethod {
+    fn is_req(&self) -> bool {
+        match self {
+            &Required(*) => true,
+            _ => false,
+        }
+    }
+    fn is_def(&self) -> bool {
+        match self {
+            &Provided(*) => true,
+            _ => false,
+        }
+    }
+}
+
+impl Clean<TraitMethod> for ast::trait_method {
+    fn clean(&self) -> TraitMethod {
+        match self {
+            &ast::required(ref t) => Required(t.clean()),
+            &ast::provided(ref t) => Provided(t.clean()),
+        }
+    }
+}
+
+/// A representation of a Type suitable for hyperlinking purposes. Ideally one can get the original
+/// type out of the AST/ty::ctxt given one of these, if more information is needed. Most importantly
+/// it does not preserve mutability or boxes.
+#[deriving(Clone, Encodable, Decodable)]
+pub enum Type {
+    /// Most types start out as "Unresolved". It serves as an intermediate stage between cleaning
+    /// and type resolution.
+    Unresolved(Path, Option<~[TyParamBound]>, ast::NodeId),
+    /// structs/enums/traits (anything that'd be an ast::ty_path)
+    ResolvedPath { path: Path, typarams: Option<~[TyParamBound]>, id: ast::NodeId },
+    /// Reference to an item in an external crate (fully qualified path)
+    External(~str, ~str),
+    // I have no idea how to usefully use this.
+    TyParamBinder(ast::NodeId),
+    /// For parameterized types, so the consumer of the JSON don't go looking
+    /// for types which don't exist anywhere.
+    Generic(ast::NodeId),
+    /// For references to self
+    Self(ast::NodeId),
+    /// Primitives are just the fixed-size numeric types (plus int/uint/float), and char.
+    Primitive(ast::prim_ty),
+    Closure(~ClosureDecl),
+    /// extern "ABI" fn
+    BareFunction(~BareFunctionDecl),
+    Tuple(~[Type]),
+    Vector(~Type),
+    FixedVector(~Type, ~str),
+    String,
+    Bool,
+    /// aka ty_nil
+    Unit,
+    /// aka ty_bot
+    Bottom,
+    Unique(~Type),
+    Managed(Mutability, ~Type),
+    RawPointer(Mutability, ~Type),
+    BorrowedRef { lifetime: Option<Lifetime>, mutability: Mutability, type_: ~Type},
+    // region, raw, other boxes, mutable
+}
+
+impl Clean<Type> for ast::Ty {
+    fn clean(&self) -> Type {
+        use syntax::ast::*;
+        debug!("cleaning type `%?`", self);
+        let codemap = local_data::get(super::ctxtkey, |x| *x.unwrap()).sess.codemap;
+        debug!("span corresponds to `%s`", codemap.span_to_str(self.span));
+        let t = match self.node {
+            ty_nil => Unit,
+            ty_ptr(ref m) =>  RawPointer(m.mutbl.clean(), ~resolve_type(&m.ty.clean())),
+            ty_rptr(ref l, ref m) => 
+                BorrowedRef {lifetime: l.clean(), mutability: m.mutbl.clean(),
+                             type_: ~resolve_type(&m.ty.clean())},
+            ty_box(ref m) => Managed(m.mutbl.clean(), ~resolve_type(&m.ty.clean())),
+            ty_uniq(ref m) => Unique(~resolve_type(&m.ty.clean())),
+            ty_vec(ref m) => Vector(~resolve_type(&m.ty.clean())),
+            ty_fixed_length_vec(ref m, ref e) => FixedVector(~resolve_type(&m.ty.clean()),
+                                                             e.span.to_src()),
+            ty_tup(ref tys) => Tuple(tys.iter().map(|x| resolve_type(&x.clean())).collect()),
+            ty_path(ref p, ref tpbs, id) => Unresolved(p.clean(), tpbs.clean(), id),
+            ty_closure(ref c) => Closure(~c.clean()),
+            ty_bare_fn(ref barefn) => BareFunction(~barefn.clean()),
+            ty_bot => Bottom,
+            ref x => fail!("Unimplemented type %?", x),
+        };
+        resolve_type(&t)
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct StructField {
+    type_: Type,
+}
+
+impl Clean<Item> for ast::struct_field {
+    fn clean(&self) -> Item {
+        let (name, vis) = match self.node.kind {
+            ast::named_field(id, vis) => (Some(id), Some(vis)),
+            _ => (None, None)
+        };
+        Item {
+            name: name.clean(),
+            attrs: self.node.attrs.clean(),
+            source: self.span.clean(),
+            visibility: vis,
+            id: self.node.id,
+            inner: StructFieldItem(StructField {
+                type_: self.node.ty.clean(),
+            }),
+        }
+    }
+}
+
+pub type Visibility = ast::visibility;
+
+impl Clean<Option<Visibility>> for ast::visibility {
+    fn clean(&self) -> Option<Visibility> {
+        Some(*self)
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Struct {
+    struct_type: doctree::StructType,
+    generics: Generics,
+    fields: ~[Item],
+}
+
+impl Clean<Item> for doctree::Struct {
+    fn clean(&self) -> Item {
+        Item {
+            name: Some(self.name.clean()),
+            attrs: self.attrs.clean(),
+            source: self.where.clean(),
+            id: self.id,
+            visibility: self.vis.clean(),
+            inner: StructItem(Struct {
+                struct_type: self.struct_type,
+                generics: self.generics.clean(),
+                fields: self.fields.clean(),
+            }),
+        }
+    }
+}
+
+/// This is a more limited form of the standard Struct, different in that it
+/// it lacks the things most items have (name, id, parameterization). Found
+/// only as a variant in an enum.
+#[deriving(Clone, Encodable, Decodable)]
+pub struct VariantStruct {
+    struct_type: doctree::StructType,
+    fields: ~[Item],
+}
+
+impl Clean<VariantStruct> for syntax::ast::struct_def {
+    fn clean(&self) -> VariantStruct {
+        VariantStruct {
+            struct_type: doctree::struct_type_from_def(self),
+            fields: self.fields.clean(),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Enum {
+    variants: ~[Item],
+    generics: Generics,
+}
+
+impl Clean<Item> for doctree::Enum {
+    fn clean(&self) -> Item {
+        Item {
+            name: Some(self.name.clean()),
+            attrs: self.attrs.clean(),
+            source: self.where.clean(),
+            id: self.id,
+            visibility: self.vis.clean(),
+            inner: EnumItem(Enum {
+                variants: self.variants.clean(),
+                generics: self.generics.clean(),
+            }),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Variant {
+    kind: VariantKind,
+}
+
+impl Clean<Item> for doctree::Variant {
+    fn clean(&self) -> Item {
+        Item {
+            name: Some(self.name.clean()),
+            attrs: self.attrs.clean(),
+            source: self.where.clean(),
+            visibility: self.vis.clean(),
+            id: self.id,
+            inner: VariantItem(Variant {
+                kind: self.kind.clean(),
+            }),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub enum VariantKind {
+    CLikeVariant,
+    TupleVariant(~[Type]),
+    StructVariant(VariantStruct),
+}
+
+impl Clean<VariantKind> for ast::variant_kind {
+    fn clean(&self) -> VariantKind {
+        match self {
+            &ast::tuple_variant_kind(ref args) => {
+                if args.len() == 0 {
+                    CLikeVariant
+                } else {
+                    TupleVariant(args.iter().map(|x| x.ty.clean()).collect())
+                }
+            },
+            &ast::struct_variant_kind(ref sd) => StructVariant(sd.clean()),
+        }
+    }
+}
+
+impl Clean<~str> for syntax::codemap::span {
+    fn clean(&self) -> ~str {
+        let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap;
+        cm.span_to_str(*self)
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Path {
+    name: ~str,
+    lifetime: Option<Lifetime>,
+    typarams: ~[Type]
+}
+
+impl Clean<Path> for ast::Path {
+    fn clean(&self) -> Path {
+        Path {
+            name: path_to_str(self),
+            lifetime: self.rp.clean(),
+            typarams: self.types.clean(),
+        }
+    }
+}
+
+fn path_to_str(p: &ast::Path) -> ~str {
+    use syntax::parse::token::interner_get;
+
+    let mut s = ~"";
+    let mut first = true;
+    for i in p.idents.iter().map(|x| interner_get(x.name)) {
+        if !first || p.global {
+            s.push_str("::");
+        } else {
+            first = false;
+        }
+        s.push_str(i);
+    }
+    s
+}
+
+impl Clean<~str> for ast::ident {
+    fn clean(&self) -> ~str {
+        its(self).to_owned()
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Typedef {
+    type_: Type,
+    generics: Generics,
+}
+
+impl Clean<Item> for doctree::Typedef {
+    fn clean(&self) -> Item {
+        Item {
+            name: Some(self.name.clean()),
+            attrs: self.attrs.clean(),
+            source: self.where.clean(),
+            id: self.id.clone(),
+            visibility: self.vis.clean(),
+            inner: TypedefItem(Typedef {
+                type_: self.ty.clean(),
+                generics: self.gen.clean(),
+            }),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct BareFunctionDecl {
+    purity: ast::purity,
+    generics: Generics,
+    decl: FnDecl,
+    abi: ~str
+}
+
+impl Clean<BareFunctionDecl> for ast::TyBareFn {
+    fn clean(&self) -> BareFunctionDecl {
+        BareFunctionDecl {
+            purity: self.purity,
+            generics: Generics {
+                lifetimes: self.lifetimes.clean(),
+                type_params: ~[],
+            },
+            decl: self.decl.clean(),
+            abi: self.abis.to_str(),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Static {
+    type_: Type,
+    mutability: Mutability,
+    /// It's useful to have the value of a static documented, but I have no
+    /// desire to represent expressions (that'd basically be all of the AST,
+    /// which is huge!). So, have a string.
+    expr: ~str,
+}
+
+impl Clean<Item> for doctree::Static {
+    fn clean(&self) -> Item {
+        debug!("claning static %s: %?", self.name.clean(), self);
+        Item {
+            name: Some(self.name.clean()),
+            attrs: self.attrs.clean(),
+            source: self.where.clean(),
+            id: self.id,
+            visibility: self.vis.clean(),
+            inner: StaticItem(Static {
+                type_: self.type_.clean(),
+                mutability: self.mutability.clean(),
+                expr: self.expr.span.to_src(),
+            }),
+        }
+    }
+}
+
+#[deriving(ToStr, Clone, Encodable, Decodable)]
+pub enum Mutability {
+    Mutable,
+    Immutable,
+    Const,
+}
+
+impl Clean<Mutability> for ast::mutability {
+    fn clean(&self) -> Mutability {
+        match self {
+            &ast::m_mutbl => Mutable,
+            &ast::m_imm => Immutable,
+            &ast::m_const => Const
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct Impl {
+    generics: Generics,
+    trait_: Option<Type>,
+    for_: Type,
+    methods: ~[Item],
+}
+
+impl Clean<Item> for doctree::Impl {
+    fn clean(&self) -> Item {
+        Item {
+            name: None,
+            attrs: self.attrs.clean(),
+            source: self.where.clean(),
+            id: self.id,
+            visibility: self.vis.clean(),
+            inner: ImplItem(Impl {
+                generics: self.generics.clean(),
+                trait_: self.trait_.clean(),
+                for_: self.for_.clean(),
+                methods: self.methods.clean(),
+            }),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub struct ViewItem {
+    inner: ViewItemInner
+}
+
+impl Clean<Item> for ast::view_item {
+    fn clean(&self) -> Item {
+        Item {
+            name: None,
+            attrs: self.attrs.clean(),
+            source: self.span.clean(),
+            id: 0,
+            visibility: self.vis.clean(),
+            inner: ViewItemItem(ViewItem {
+                inner: self.node.clean()
+            }),
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub enum ViewItemInner {
+    ExternMod(~str, Option<~str>, ~[Attribute], ast::NodeId),
+    Import(~[ViewPath])
+}
+
+impl Clean<ViewItemInner> for ast::view_item_ {
+    fn clean(&self) -> ViewItemInner {
+        match self {
+            &ast::view_item_extern_mod(ref i, ref p, ref mi, ref id) =>
+                ExternMod(i.clean(), p.map(|x| x.to_owned()),  mi.clean(), *id),
+            &ast::view_item_use(ref vp) => Import(vp.clean())
+        }
+    }
+}
+
+#[deriving(Clone, Encodable, Decodable)]
+pub enum ViewPath {
+    SimpleImport(~str, Path, ast::NodeId),
+    GlobImport(Path, ast::NodeId),
+    ImportList(Path, ~[ViewListIdent], ast::NodeId)
+}
+
+impl Clean<ViewPath> for ast::view_path {
+    fn clean(&self) -> ViewPath {
+        match self.node {
+            ast::view_path_simple(ref i, ref p, ref id) => SimpleImport(i.clean(), p.clean(), *id),
+            ast::view_path_glob(ref p, ref id) => GlobImport(p.clean(), *id),
+            ast::view_path_list(ref p, ref pl, ref id) => ImportList(p.clean(), pl.clean(), *id),
+        }
+    }
+}
+
+pub type ViewListIdent = ~str;
+
+impl Clean<ViewListIdent> for ast::path_list_ident {
+    fn clean(&self) -> ViewListIdent {
+        self.node.name.clean()
+    }
+}
+
+// Utilities
+
+trait ToSource {
+    fn to_src(&self) -> ~str;
+}
+
+impl ToSource for syntax::codemap::span {
+    fn to_src(&self) -> ~str {
+        debug!("converting span %s to snippet", self.clean());
+        let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap.clone();
+        let sn = match cm.span_to_snippet(*self) {
+            Some(x) => x,
+            None    => ~""
+        };
+        debug!("got snippet %s", sn);
+        sn
+    }
+}
+
+fn lit_to_str(lit: &ast::lit) -> ~str {
+    match lit.node {
+        ast::lit_str(st) => st.to_owned(),
+        ast::lit_int(ch, ast::ty_char) => ~"'" + ch.to_str() + "'",
+        ast::lit_int(i, _t) => i.to_str(),
+        ast::lit_uint(u, _t) => u.to_str(),
+        ast::lit_int_unsuffixed(i) => i.to_str(),
+        ast::lit_float(f, _t) => f.to_str(),
+        ast::lit_float_unsuffixed(f) => f.to_str(),
+        ast::lit_bool(b) => b.to_str(),
+        ast::lit_nil => ~"",
+    }
+}
+
+fn name_from_pat(p: &ast::pat) -> ~str {
+    use syntax::ast::*;
+    match p.node {
+        pat_wild => ~"_",
+        pat_ident(_, ref p, _) => path_to_str(p),
+        pat_enum(ref p, _) => path_to_str(p),
+        pat_struct(*) => fail!("tried to get argument name from pat_struct, \
+                                 which is not allowed in function arguments"),
+        pat_tup(*) => ~"(tuple arg NYI)",
+        pat_box(p) => name_from_pat(p),
+        pat_uniq(p) => name_from_pat(p),
+        pat_region(p) => name_from_pat(p),
+        pat_lit(*) => fail!("tried to get argument name from pat_lit, \
+                             which is not allowed in function arguments"),
+        pat_range(*) => fail!("tried to get argument name from pat_range, \
+                               which is not allowed in function arguments"),
+        pat_vec(*) => fail!("tried to get argument name from pat_vec, \
+                             which is not allowed in function arguments")
+    }
+}
+
+fn remove_comment_tags(s: &str) -> ~str {
+    if s.starts_with("/") {
+        match s.slice(0,3) {
+            &"///" => return s.slice(3, s.len()).trim().to_owned(),
+            &"/**" | &"/*!" => return s.slice(3, s.len() - 2).trim().to_owned(),
+            _ => return s.trim().to_owned()
+        }
+    } else {
+        return s.to_owned();
+    }
+}
+
+/*fn collapse_docs(attrs: ~[Attribute]) -> ~[Attribute] {
+    let mut docstr = ~"";
+    for at in attrs.iter() {
+        match *at {
+            //XXX how should these be separated?
+            NameValue(~"doc", ref s) => docstr.push_str(fmt!("%s ", clean_comment_body(s.clone()))),
+            _ => ()
+        }
+    }
+    let mut a = attrs.iter().filter(|&a| match a {
+        &NameValue(~"doc", _) => false,
+        _ => true
+    }).map(|x| x.clone()).collect::<~[Attribute]>();
+    a.push(NameValue(~"doc", docstr.trim().to_owned()));
+    a
+}*/
+
+/// Given a Type, resolve it using the def_map
+fn resolve_type(t: &Type) -> Type {
+    use syntax::ast::*;
+
+    let (path, tpbs, id) = match t {
+        &Unresolved(ref path, ref tbps, id) => (path, tbps, id),
+        _ => return (*t).clone(),
+    };
+
+    let dm = local_data::get(super::ctxtkey, |x| *x.unwrap()).tycx.def_map;
+    debug!("searching for %? in defmap", id);
+    let d = match dm.find(&id) {
+        Some(k) => k,
+        None => {
+            let ctxt = local_data::get(super::ctxtkey, |x| *x.unwrap());
+            debug!("could not find %? in defmap (`%s`)", id,
+                   syntax::ast_map::node_id_to_str(ctxt.tycx.items, id, ctxt.sess.intr()));
+            fail!("Unexpected failure: unresolved id not in defmap (this is a bug!)")
+        }
+    };
+
+    let def_id = match *d {
+        def_fn(i, _) => i,
+        def_self(i, _) | def_self_ty(i) => return Self(i),
+        def_ty(i) => i,
+        def_trait(i) => {
+            debug!("saw def_trait in def_to_id");
+            i
+        },
+        def_prim_ty(p) => match p {
+            ty_str => return String,
+            ty_bool => return Bool,
+            _ => return Primitive(p)
+        },
+        def_ty_param(i, _) => return Generic(i.node),
+        def_struct(i) => i,
+        def_typaram_binder(i) => { 
+            debug!("found a typaram_binder, what is it? %d", i);
+            return TyParamBinder(i);
+        },
+        x => fail!("resolved type maps to a weird def %?", x),
+    };
+
+    if def_id.crate != ast::CRATE_NODE_ID {
+        let sess = local_data::get(super::ctxtkey, |x| *x.unwrap()).sess;
+        let mut path = ~"";
+        let mut ty = ~"";
+        do csearch::each_path(sess.cstore, def_id.crate) |pathstr, deflike, _vis| {
+            match deflike {
+                decoder::dl_def(di) => {
+                    let d2 = match di {
+                        def_fn(i, _) | def_ty(i) | def_trait(i) |
+                            def_struct(i) | def_mod(i) => Some(i),
+                        _ => None,
+                    };
+                    if d2.is_some() {
+                        let d2 = d2.unwrap();
+                        if def_id.node == d2.node {
+                            debug!("found external def: %?", di);
+                            path = pathstr.to_owned();
+                            ty = match di {
+                                def_fn(*) => ~"fn",
+                                def_ty(*) => ~"enum",
+                                def_trait(*) => ~"trait",
+                                def_prim_ty(p) => match p {
+                                    ty_str => ~"str",
+                                    ty_bool => ~"bool",
+                                    ty_int(t) => match t.to_str() {
+                                        ~"" => ~"i",
+                                        s => s
+                                    },
+                                    ty_uint(t) => t.to_str(),
+                                    ty_float(t) => t.to_str()
+                                },
+                                def_ty_param(*) => ~"generic",
+                                def_struct(*) => ~"struct",
+                                def_typaram_binder(*) => ~"typaram_binder",
+                                x => fail!("resolved external maps to a weird def %?", x),
+                            };
+
+                        }
+                    }
+                },
+                _ => (),
+            };
+            true
+        };
+        let cname = cstore::get_crate_data(sess.cstore, def_id.crate).name.to_owned();
+        External(cname + "::" + path, ty)
+    } else {
+        ResolvedPath {path: path.clone(), typarams: tpbs.clone(), id: def_id.node}
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::NameValue;
+
+    #[test]
+    fn test_doc_collapsing() {
+        assert_eq!(collapse_docs(~"// Foo\n//Bar\n // Baz\n"), ~"Foo\nBar\nBaz");
+        assert_eq!(collapse_docs(~"* Foo\n *  Bar\n *Baz\n"), ~"Foo\n Bar\nBaz");
+        assert_eq!(collapse_docs(~"* Short desc\n *\n * Bar\n *Baz\n"), ~"Short desc\n\nBar\nBaz");
+        assert_eq!(collapse_docs(~" * Foo"), ~"Foo");
+        assert_eq!(collapse_docs(~"\n *\n *\n * Foo"), ~"Foo");
+    }
+
+    fn collapse_docs(input: ~str) -> ~str {
+        let attrs = ~[NameValue(~"doc", input)];
+        let attrs_clean = super::collapse_docs(attrs);
+
+        match attrs_clean[0] {
+            NameValue(~"doc", s) => s,
+            _ => (fail!("dude where's my doc?"))
+        }
+    }
+}
diff --git a/src/rustdoc_ng/core.rs b/src/rustdoc_ng/core.rs
new file mode 100644
index 00000000000..8e12dbdce4d
--- /dev/null
+++ b/src/rustdoc_ng/core.rs
@@ -0,0 +1,68 @@
+use rustc;
+use rustc::{driver, middle};
+
+use syntax;
+use syntax::parse;
+use syntax::ast;
+
+use std::os;
+use std::local_data;
+
+use visit_ast::RustdocVisitor;
+use clean;
+use clean::Clean;
+
+pub struct DocContext {
+    crate: @ast::Crate,
+    tycx: middle::ty::ctxt,
+    sess: driver::session::Session
+}
+
+/// Parses, resolves, and typechecks the given crate
+fn get_ast_and_resolve(cpath: &Path, libs: ~[Path]) -> DocContext {
+    use syntax::codemap::dummy_spanned;
+    use rustc::driver::driver::*;
+
+    let parsesess = parse::new_parse_sess(None);
+    let input = file_input(cpath.clone());
+
+    let sessopts = @driver::session::options {
+        binary: @"rustdoc",
+        maybe_sysroot: Some(@os::self_exe_path().unwrap().pop()),
+        addl_lib_search_paths: @mut libs,
+        .. (*rustc::driver::session::basic_options()).clone()
+    };
+
+
+    let diagnostic_handler = syntax::diagnostic::mk_handler(None);
+    let span_diagnostic_handler =
+        syntax::diagnostic::mk_span_handler(diagnostic_handler, parsesess.cm);
+
+    let sess = driver::driver::build_session_(sessopts, parsesess.cm,
+                                                  syntax::diagnostic::emit,
+                                                  span_diagnostic_handler);
+
+    let mut cfg = build_configuration(sess, @"rustdoc_ng", &input);
+    cfg.push(@dummy_spanned(ast::MetaWord(@"stage2")));
+
+    let mut crate = phase_1_parse_input(sess, cfg.clone(), &input);
+    crate = phase_2_configure_and_expand(sess, cfg, crate);
+    let analysis = phase_3_run_analysis_passes(sess, crate);
+
+    debug!("crate: %?", crate);
+    DocContext { crate: crate, tycx: analysis.ty_cx, sess: sess }
+}
+
+pub fn run_core (libs: ~[Path], path: &Path) -> clean::Crate {
+    let ctxt = @get_ast_and_resolve(path, libs);
+    debug!("defmap:");
+    for (k, v) in ctxt.tycx.def_map.iter() {
+        debug!("%?: %?", k, v);
+    }
+    local_data::set(super::ctxtkey, ctxt);
+
+    let v = @mut RustdocVisitor::new();
+    v.visit(ctxt.crate);
+
+    v.clean()
+}
diff --git a/src/rustdoc_ng/doctree.rs b/src/rustdoc_ng/doctree.rs
new file mode 100644
index 00000000000..d5ad0fca30f
--- /dev/null
+++ b/src/rustdoc_ng/doctree.rs
@@ -0,0 +1,158 @@
+//! This module is used to store stuff from Rust's AST in a more convenient
+//! manner (and with prettier names) before cleaning.
+
+use syntax;
+use syntax::codemap::span;
+use syntax::ast;
+use syntax::ast::{ident, NodeId};
+
+pub struct Module {
+    name: Option<ident>,
+    attrs: ~[ast::Attribute],
+    where: span,
+    structs: ~[Struct],
+    enums: ~[Enum],
+    fns: ~[Function],
+    mods: ~[Module],
+    id: NodeId,
+    typedefs: ~[Typedef],
+    statics: ~[Static],
+    traits: ~[Trait],
+    vis: ast::visibility,
+    impls: ~[Impl],
+    view_items: ~[ast::view_item],
+}
+
+impl Module {
+    pub fn new(name: Option<ident>) -> Module {
+        Module {
+            name       : name,
+            id: 0,
+            vis: ast::private,
+            where: syntax::codemap::dummy_sp(),
+            attrs      : ~[],
+            structs    : ~[],
+            enums      : ~[],
+            fns        : ~[],
+            mods       : ~[],
+            typedefs   : ~[],
+            statics    : ~[],
+            traits     : ~[],
+            impls      : ~[],
+            view_items : ~[],
+        }
+    }
+}
+
+#[deriving(ToStr, Clone, Encodable, Decodable)]
+pub enum StructType {
+    /// A normal struct
+    Plain,
+    /// A tuple struct
+    Tuple,
+    /// A newtype struct (tuple struct with one element)
+    Newtype,
+    /// A unit struct
+    Unit
+}
+
+pub enum TypeBound {
+    RegionBound,
+    TraitBound(ast::trait_ref)
+}
+
+pub struct Struct {
+    vis: ast::visibility,
+    id: NodeId,
+    struct_type: StructType,
+    name: ident,
+    generics: ast::Generics,
+    attrs: ~[ast::Attribute],
+    fields: ~[@ast::struct_field],
+    where: span,
+}
+
+pub struct Enum {
+    vis: ast::visibility,
+    variants: ~[Variant],
+    generics: ast::Generics,
+    attrs: ~[ast::Attribute],
+    id: NodeId,
+    where: span,
+    name: ident,
+}
+
+pub struct Variant {
+    name: ident,
+    attrs: ~[ast::Attribute],
+    kind: ast::variant_kind,
+    id: ast::NodeId,
+    vis: ast::visibility,
+    where: span,
+}
+
+pub struct Function {
+    decl: ast::fn_decl,
+    attrs: ~[ast::Attribute],
+    id: NodeId,
+    name: ident,
+    vis: ast::visibility,
+    where: span,
+    generics: ast::Generics,
+}
+
+pub struct Typedef {
+    ty: ast::Ty,
+    gen: ast::Generics,
+    name: ast::ident,
+    id: ast::NodeId,
+    attrs: ~[ast::Attribute],
+    where: span,
+    vis: ast::visibility,
+}
+
+pub struct Static {
+    type_: ast::Ty,
+    mutability: ast::mutability,
+    expr: @ast::expr,
+    name: ast::ident,
+    attrs: ~[ast::Attribute],
+    vis: ast::visibility,
+    id: ast::NodeId,
+    where: span,
+}
+
+pub struct Trait {
+    name: ast::ident,
+    methods: ~[ast::trait_method], //should be TraitMethod
+    generics: ast::Generics,
+    parents: ~[ast::trait_ref],
+    attrs: ~[ast::Attribute],
+    id: ast::NodeId,
+    where: span,
+    vis: ast::visibility,
+}
+
+pub struct Impl {
+    generics: ast::Generics,
+    trait_: Option<ast::trait_ref>,
+    for_: ast::Ty,
+    methods: ~[@ast::method],
+    attrs: ~[ast::Attribute],
+    where: span,
+    vis: ast::visibility,
+    id: ast::NodeId,
+}
+
+pub fn struct_type_from_def(sd: &ast::struct_def) -> StructType {
+    if sd.ctor_id.is_some() {
+        // We are in a tuple-struct
+        match sd.fields.len() {
+            0 => Unit,
+            1 => Newtype,
+            _ => Tuple
+        }
+    } else {
+        Plain
+    }
+}
diff --git a/src/rustdoc_ng/fold.rs b/src/rustdoc_ng/fold.rs
new file mode 100644
index 00000000000..740e434f465
--- /dev/null
+++ b/src/rustdoc_ng/fold.rs
@@ -0,0 +1,101 @@
+use std;
+use clean::*;
+use std::iterator::Extendable;
+
+pub trait DocFolder {
+    fn fold_item(&mut self, item: Item) -> Option<Item> {
+        self.fold_item_recur(item)
+    }
+
+    /// don't override!
+    fn fold_item_recur(&mut self, item: Item) -> Option<Item> {
+        use std::util::swap;
+        let Item { attrs, name, source, visibility, id, inner } = item;
+        let inner = inner;
+        let c = |x| self.fold_item(x);
+        let inner = match inner {
+            StructItem(i) => {
+                let mut i = i;
+                let mut foo = ~[]; swap(&mut foo, &mut i.fields);
+                i.fields.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x)));
+                StructItem(i)
+            },
+            ModuleItem(i) => {
+                ModuleItem(self.fold_mod(i))
+            },
+            EnumItem(i) => {
+                let mut i = i;
+                let mut foo = ~[]; swap(&mut foo, &mut i.variants);
+                i.variants.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x)));
+                EnumItem(i)
+            },
+            TraitItem(i) => {
+                fn vtrm<T: DocFolder>(this: &mut T, trm: TraitMethod) -> Option<TraitMethod> {
+                    match trm {
+                        Required(it) => {
+                            match this.fold_item(it) {
+                                Some(x) => return Some(Required(x)),
+                                None => return None,
+                            }
+                        },
+                        Provided(it) => {
+                            match this.fold_item(it) {
+                                Some(x) => return Some(Provided(x)),
+                                None => return None,
+                            }
+                        },
+                    }
+                }
+                let mut i = i;
+                let mut foo = ~[]; swap(&mut foo, &mut i.methods);
+                i.methods.extend(&mut foo.move_iter().filter_map(|x| vtrm(self, x)));
+                TraitItem(i)
+            },
+            ImplItem(i) => {
+                let mut i = i;
+                let mut foo = ~[]; swap(&mut foo, &mut i.methods);
+                i.methods.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x)));
+                ImplItem(i)
+            },
+            VariantItem(i) => {
+                let i2 = i.clone(); // this clone is small
+                match i.kind {
+                    StructVariant(j) => {
+                        let mut j = j;
+                        let mut foo = ~[]; swap(&mut foo, &mut j.fields);
+                        j.fields.extend(&mut foo.move_iter().filter_map(c));
+                        VariantItem(Variant {kind: StructVariant(j), ..i2})
+                    },
+                    _ => VariantItem(i2)
+                }
+            },
+            x => x
+        };
+
+        Some(Item { attrs: attrs, name: name, source: source, inner: inner,
+                    visibility: visibility, id: id })
+    }
+
+    fn fold_mod(&mut self, m: Module) -> Module {
+        Module { items: m.items.move_iter().filter_map(|i| self.fold_item(i)).collect() }
+    }
+
+    fn fold_crate(&mut self, mut c: Crate) -> Crate {
+        let mut mod_ = None;
+        std::util::swap(&mut mod_, &mut c.module);
+        let mod_ = mod_.unwrap();
+        c.module = self.fold_item(mod_);
+        let Crate { name, module } = c;
+        match module {
+            Some(Item { inner: ModuleItem(m), name: name_, attrs: attrs_,
+            source, visibility: vis, id }) => {
+                return Crate { module: Some(Item { inner:
+                                            ModuleItem(self.fold_mod(m)),
+                                            name: name_, attrs: attrs_,
+                                            source: source, id: id, visibility: vis }), name: name};
+            },
+            Some(_) => fail!("non-module item set as module of crate"),
+            None => return Crate { module: None, name: name},
+        }
+    }
+}
diff --git a/src/rustdoc_ng/lib.rs b/src/rustdoc_ng/lib.rs
new file mode 100644
index 00000000000..6d34fae6090
--- /dev/null
+++ b/src/rustdoc_ng/lib.rs
@@ -0,0 +1,27 @@
+#[link(name = "rustdoc_ng",
+       vers = "0.1.0",
+       uuid = "8c6e4598-1596-4aa5-a24c-b811914bbbc6")];
+#[desc = "rustdoc, the Rust documentation extractor"];
+#[license = "MIT/ASL2"];
+#[crate_type = "lib"];
+
+#[deny(warnings)];
+
+extern mod syntax;
+extern mod rustc;
+
+extern mod extra;
+
+use extra::serialize::Encodable;
+
+pub mod core;
+pub mod doctree;
+pub mod clean;
+pub mod visit_ast;
+pub mod fold;
+pub mod plugins;
+pub mod passes;
+
+pub static SCHEMA_VERSION: &'static str = "0.8.0";
+
+pub static ctxtkey: std::local_data::Key<@core::DocContext> = &std::local_data::Key;
diff --git a/src/rustdoc_ng/main.rs b/src/rustdoc_ng/main.rs
new file mode 100644
index 00000000000..47b37052917
--- /dev/null
+++ b/src/rustdoc_ng/main.rs
@@ -0,0 +1,94 @@
+#[link(name = "rustdoc_ng",
+       vers = "0.1.0",
+       uuid = "8c6e4598-1596-4aa5-a24c-b811914bbbc6")];
+#[desc = "rustdoc, the Rust documentation extractor"];
+#[license = "MIT/ASL2"];
+#[crate_type = "bin"];
+
+extern mod extra;
+extern mod rustdoc_ng;
+
+use rustdoc_ng::*;
+use std::cell::Cell;
+
+use extra::serialize::Encodable;
+
+fn main() {
+    use extra::getopts::*;
+    use extra::getopts::groups::*;
+
+    let args = std::os::args();
+    let opts = ~[
+        optmulti("L", "library-path", "directory to add to crate search path", "DIR"),
+        optmulti("p", "plugin", "plugin to load and run", "NAME"),
+        optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
+        // auxillary pass (defaults to hidden_strip
+        optmulti("a", "pass", "auxillary pass to run", "NAME"),
+        optflag("n", "no-defult-passes", "do not run the default passes"),
+        optflag("h", "help", "show this help message"),
+    ];
+
+    let matches = getopts(args.tail(), opts).unwrap();
+
+    if opt_present(&matches, "h") || opt_present(&matches, "help") {
+        println(usage(args[0], opts));
+        return;
+    }
+
+    let libs = Cell::new(opt_strs(&matches, "L").map(|s| Path(*s)));
+
+    let mut passes = if opt_present(&matches, "n") {
+        ~[]
+    } else {
+        ~[~"collapse-docs", ~"clean-comments", ~"collapse-privacy" ]
+    };
+
+    opt_strs(&matches, "a").map(|x| passes.push(x.clone()));
+
+    if matches.free.len() != 1 {
+        println(usage(args[0], opts));
+        return;
+    }
+
+    let cr = Cell::new(Path(matches.free[0]));
+
+    let crate = std::task::try(|| {let cr = cr.take(); core::run_core(libs.take(), &cr)}).unwrap();
+
+    // { "schema": version, "crate": { parsed crate ... }, "plugins": { output of plugins ... }}
+    let mut json = ~extra::treemap::TreeMap::new();
+    json.insert(~"schema", extra::json::String(SCHEMA_VERSION.to_owned()));
+
+    let mut pm = plugins::PluginManager::new(Path("/tmp/rustdoc_ng/plugins"));
+
+    for pass in passes.iter() {
+        pm.add_plugin(match pass.as_slice() {
+            "strip-hidden" => passes::strip_hidden,
+            "clean-comments" => passes::clean_comments,
+            "collapse-docs" => passes::collapse_docs,
+            "collapse-privacy" => passes::collapse_privacy,
+            s => { error!("unknown pass %s, skipping", s); passes::noop },
+        })
+    }
+
+    for pname in opt_strs(&matches, "p").move_iter() {
+        pm.load_plugin(pname);
+    }
+
+    let (crate, res) = pm.run_plugins(crate);
+    let plugins_json = ~res.move_iter().filter_map(|opt| opt).collect();
+
+    // FIXME: yuck, Rust -> str -> JSON round trip! No way to .encode
+    // straight to the Rust JSON representation.
+    let crate_json_str = do std::io::with_str_writer |w| {
+        crate.encode(&mut extra::json::Encoder(w));
+    };
+    let crate_json = match extra::json::from_str(crate_json_str) {
+        Ok(j) => j,
+        Err(_) => fail!("Rust generated JSON is invalid??")
+    };
+
+    json.insert(~"crate", crate_json);
+    json.insert(~"plugins", extra::json::Object(plugins_json));
+
+    println(extra::json::Object(json).to_str());
+}
diff --git a/src/rustdoc_ng/passes.rs b/src/rustdoc_ng/passes.rs
new file mode 100644
index 00000000000..73f94ef0f27
--- /dev/null
+++ b/src/rustdoc_ng/passes.rs
@@ -0,0 +1,193 @@
+use std;
+use clean;
+use syntax::ast;
+use clean::Item;
+use plugins;
+use fold;
+use fold::DocFolder;
+
+/// A sample pass showing the minimum required work for a plugin.
+pub fn noop(crate: clean::Crate) -> plugins::PluginResult {
+    (crate, None)
+}
+
+/// Strip items marked `#[doc(hidden)]`
+pub fn strip_hidden(crate: clean::Crate) -> plugins::PluginResult {
+    struct Stripper;
+    impl fold::DocFolder for Stripper {
+        fn fold_item(&mut self, i: Item) -> Option<Item> {
+            for attr in i.attrs.iter() {
+                match attr {
+                    &clean::List(~"doc", ref l) => {
+                        for innerattr in l.iter() {
+                            match innerattr {
+                                &clean::Word(ref s) if "hidden" == *s => {
+                                    info!("found one in strip_hidden; removing");
+                                    return None;
+                                },
+                                _ => (),
+                            }
+                        }
+                    },
+                    _ => ()
+                }
+            }
+            self.fold_item_recur(i)
+        }
+    }
+    let mut stripper = Stripper;
+    let crate = stripper.fold_crate(crate);
+    (crate, None)
+}
+
+pub fn clean_comments(crate: clean::Crate) -> plugins::PluginResult {
+    struct CommentCleaner;
+    impl fold::DocFolder for CommentCleaner {
+        fn fold_item(&mut self, i: Item) -> Option<Item> {
+            let mut i = i;
+            let mut avec: ~[clean::Attribute] = ~[];
+            for attr in i.attrs.iter() {
+                match attr {
+                    &clean::NameValue(~"doc", ref s) => avec.push(
+                        clean::NameValue(~"doc", clean_comment_body(s.clone()))),
+                    x => avec.push(x.clone())
+                }
+            }
+            i.attrs = avec;
+            self.fold_item_recur(i)
+        }
+    }
+    let mut cleaner = CommentCleaner;
+    let crate = cleaner.fold_crate(crate);
+    (crate, None)
+}
+
+pub fn collapse_privacy(crate: clean::Crate) -> plugins::PluginResult {
+    struct PrivacyCollapser {
+        stack: ~[clean::Visibility]
+    }
+    impl fold::DocFolder for PrivacyCollapser {
+        fn fold_item(&mut self, mut i: Item) -> Option<Item> {
+            if i.visibility.is_some() {
+                if i.visibility == Some(ast::inherited) {
+                    i.visibility = Some(self.stack.last().clone());
+                } else {
+                    self.stack.push(i.visibility.clone().unwrap());
+                }
+            }
+            self.fold_item_recur(i)
+        }
+    }
+    let mut privacy = PrivacyCollapser { stack: ~[] };
+    let crate = privacy.fold_crate(crate);
+    (crate, None)
+}
+
+pub fn collapse_docs(crate: clean::Crate) -> plugins::PluginResult {
+    struct Collapser;
+    impl fold::DocFolder for Collapser {
+        fn fold_item(&mut self, i: Item) -> Option<Item> {
+            let mut docstr = ~"";
+            let mut i = i;
+            for attr in i.attrs.iter() {
+                match *attr {
+                    clean::NameValue(~"doc", ref s) => {
+                        docstr.push_str(s.clone());
+                        docstr.push_char('\n');
+                    },
+                    _ => ()
+                }
+            }
+            let mut a: ~[clean::Attribute] = i.attrs.iter().filter(|&a| match a {
+                &clean::NameValue(~"doc", _) => false,
+                _ => true
+            }).map(|x| x.clone()).collect();
+            if "" != docstr {
+                a.push(clean::NameValue(~"doc", docstr.trim().to_owned()));
+            }
+            i.attrs = a;
+            self.fold_item_recur(i)
+        }
+    }
+    let mut collapser = Collapser;
+    let crate = collapser.fold_crate(crate);
+    (crate, None)
+}
+
+//Utility
+enum CleanCommentStates {
+    Collect,
+    Strip,
+    Stripped,
+}
+
+/// Returns the index of the last character all strings have common in their
+/// prefix.
+fn longest_common_prefix(s: ~[~str]) -> uint {
+    // find the longest common prefix
+
+    debug!("lcp: looking into %?", s);
+    // index of the last character all the strings share
+    let mut index = 0u;
+
+    if s.len() <= 1 {
+        return 0;
+    }
+
+    // whether one of the strings has been exhausted of characters yet
+    let mut exhausted = false;
+
+    // character iterators for all the lines
+    let mut lines = s.iter().filter(|x| x.len() != 0).map(|x| x.iter()).to_owned_vec();
+
+    'outer: loop {
+        // because you can't label a while loop
+        if exhausted == true {
+            break;
+        }
+        debug!("lcp: index %u", index);
+        let mut lines = lines.mut_iter();
+        let ch = match lines.next().unwrap().next() {
+            Some(c) => c,
+            None => { exhausted = true; loop },
+        };
+        debug!("looking for char %c", ch);
+        for line in lines {
+            match line.next() {
+                Some(c) => if c == ch { loop } else { exhausted = true; loop 'outer },
+                None => { exhausted = true; loop 'outer }
+            }
+        }
+        index += 1;
+    }
+
+    debug!("lcp: last index %u", index);
+    index
+}
+
+fn clean_comment_body(s: ~str) -> ~str {
+    // FIXME #31: lots of copies in here.
+    let lines = s.line_iter().to_owned_vec();
+    match lines.len() {
+        0 => return ~"",
+        1 => return lines[0].slice_from(2).trim().to_owned(),
+        _ => (),
+    }
+            
+    let mut ol = std::vec::with_capacity(lines.len());
+    for line in lines.clone().move_iter() {
+        // replace meaningless things with a single newline
+        match line {
+            x if ["/**", "/*!", "///", "//!", "*/"].contains(&x.trim()) => ol.push(~""),
+            x if x.trim() == "" => ol.push(~""),
+            x => ol.push(x.to_owned())
+        }
+    }
+    let li = longest_common_prefix(ol.clone());
+    
+    let x = ol.iter()
+         .filter(|x| { debug!("cleaning line: %s", **x); true })
+         .map(|x| if x.len() == 0 { ~"" } else { x.slice_chars(li, x.char_len()).to_owned() })
+         .to_owned_vec().connect("\n");
+    x.trim().to_owned()
+}
diff --git a/src/rustdoc_ng/plugins.rs b/src/rustdoc_ng/plugins.rs
new file mode 100644
index 00000000000..db243c9e21c
--- /dev/null
+++ b/src/rustdoc_ng/plugins.rs
@@ -0,0 +1,80 @@
+use clean;
+
+use extra;
+use dl = std::unstable::dynamic_lib;
+
+pub type PluginJson = Option<(~str, extra::json::Json)>;
+pub type PluginResult = (clean::Crate, PluginJson);
+pub type plugin_callback = extern fn (clean::Crate) -> PluginResult;
+
+/// Manages loading and running of plugins
+pub struct PluginManager {
+    priv dylibs: ~[dl::DynamicLibrary],
+    priv callbacks: ~[plugin_callback],
+    /// The directory plugins will be loaded from
+    prefix: Path,
+}
+
+impl PluginManager {
+    /// Create a new plugin manager
+    pub fn new(prefix: Path) -> PluginManager {
+        PluginManager {
+            dylibs: ~[],
+            callbacks: ~[],
+            prefix: prefix,
+        }
+    }
+
+    /// Load a plugin with the given name.
+    ///
+    /// Turns `name` into the proper dynamic library filename for the given
+    /// platform. On windows, it turns into name.dll, on OS X, name.dylib, and
+    /// elsewhere, libname.so.
+    pub fn load_plugin(&mut self, name: ~str) {
+        let x = self.prefix.push(libname(name));
+        let lib_result = dl::DynamicLibrary::open(Some(&x));
+        let lib = lib_result.unwrap();
+        let plugin = unsafe { lib.symbol("rustdoc_plugin_entrypoint") }.unwrap();
+        self.dylibs.push(lib);
+        self.callbacks.push(plugin);
+    }
+
+    /// Load a normal Rust function as a plugin.
+    ///
+    /// This is to run passes over the cleaned crate. Plugins run this way
+    /// correspond to the A-aux tag on Github.
+    pub fn add_plugin(&mut self, plugin: plugin_callback) {
+        self.callbacks.push(plugin);
+    }
+    /// Run all the loaded plugins over the crate, returning their results
+    pub fn run_plugins(&self, crate: clean::Crate) -> (clean::Crate, ~[PluginJson]) {
+        let mut out_json = ~[];
+        let mut crate = crate;
+        for &callback in self.callbacks.iter() {
+            let (c, res) = callback(crate);
+            crate = c;
+            out_json.push(res);
+        }
+        (crate, out_json)
+    }
+}
+
+#[cfg(target_os="win32")]
+fn libname(mut n: ~str) -> ~str {
+    n.push_str(".dll");
+    n
+}
+
+#[cfg(target_os="macos")]
+fn libname(mut n: ~str) -> ~str {
+    n.push_str(".dylib");
+    n
+}
+
+#[cfg(not(target_os="win32"), not(target_os="macos"))]
+fn libname(n: ~str) -> ~str {
+    let mut i = ~"lib";
+    i.push_str(n);
+    i.push_str(".so");
+    i
+}
diff --git a/src/rustdoc_ng/visit_ast.rs b/src/rustdoc_ng/visit_ast.rs
new file mode 100644
index 00000000000..2e437a028ec
--- /dev/null
+++ b/src/rustdoc_ng/visit_ast.rs
@@ -0,0 +1,173 @@
+//! Rust AST Visitor. Extracts useful information and massages it into a form
+//! usable for clean
+
+use syntax::abi::AbiSet;
+use syntax::{ast, ast_map};
+use syntax::codemap::span;
+
+use doctree::*;
+use std::local_data;
+
+pub struct RustdocVisitor {
+    module: Module,
+    attrs: ~[ast::Attribute],
+}
+
+impl RustdocVisitor {
+    pub fn new() -> RustdocVisitor {
+        RustdocVisitor {
+            module: Module::new(None),
+            attrs: ~[],
+        }
+    }
+}
+
+impl RustdocVisitor {
+    pub fn visit(@mut self, crate: &ast::Crate) {
+        self.attrs = crate.attrs.clone();
+        fn visit_struct_def(item: &ast::item, sd: @ast::struct_def, generics:
+                            &ast::Generics) -> Struct {
+            debug!("Visiting struct");
+            let struct_type = struct_type_from_def(sd);
+            Struct {
+                id: item.id,
+                struct_type: struct_type,
+                name: item.ident,
+                vis: item.vis,
+                attrs: item.attrs.clone(),
+                generics: generics.clone(),
+                fields: sd.fields.iter().map(|x| (*x).clone()).to_owned_vec(),
+                where: item.span
+            }
+        }
+
+        fn visit_enum_def(it: &ast::item, def: &ast::enum_def, params: &ast::Generics) -> Enum {
+            debug!("Visiting enum");
+            let mut vars: ~[Variant] = ~[];
+            for x in def.variants.iter() {
+                vars.push(Variant {
+                    name: x.node.name,
+                    attrs: x.node.attrs.clone(),
+                    vis: x.node.vis,
+                    id: x.node.id,
+                    kind: x.node.kind.clone(),
+                    where: x.span,
+                });
+            }
+            Enum {
+                name: it.ident,
+                variants: vars,
+                vis: it.vis,
+                generics: params.clone(),
+                attrs: it.attrs.clone(),
+                id: it.id,
+                where: it.span,
+            }
+        }
+
+        fn visit_fn(item: &ast::item, fd: &ast::fn_decl, _purity: &ast::purity,
+                     _abi: &AbiSet, gen: &ast::Generics) -> Function {
+            debug!("Visiting fn");
+            Function {
+                id: item.id,
+                vis: item.vis,
+                attrs: item.attrs.clone(),
+                decl: fd.clone(),
+                name: item.ident,
+                where: item.span,
+                generics: gen.clone(),
+            }
+        }
+
+        fn visit_mod_contents(span: span, attrs: ~[ast::Attribute], vis:
+                              ast::visibility, id: ast::NodeId, m: &ast::_mod) -> Module {
+            let am = local_data::get(super::ctxtkey, |x| *x.unwrap()).tycx.items;
+            let name = match am.find(&id) {
+                Some(m) => match m {
+                    &ast_map::node_item(ref it, _) => Some(it.ident),
+                    _ => fail!("mod id mapped to non-item in the ast map")
+                },
+                None => None
+            };
+            let mut om = Module::new(name);
+            om.view_items = m.view_items.clone();
+            om.where = span;
+            om.attrs = attrs;
+            om.vis = vis;
+            om.id = id;
+            for i in m.items.iter() {
+                visit_item(*i, &mut om);
+            }
+            om
+        }
+
+        fn visit_item(item: &ast::item, om: &mut Module) {
+            debug!("Visiting item %?", item);
+            match item.node {
+                ast::item_mod(ref m) => {
+                    om.mods.push(visit_mod_contents(item.span, item.attrs.clone(),
+                                                    item.vis, item.id, m));
+                },
+                ast::item_enum(ref ed, ref gen) => om.enums.push(visit_enum_def(item, ed, gen)),
+                ast::item_struct(sd, ref gen) => om.structs.push(visit_struct_def(item, sd, gen)),
+                ast::item_fn(ref fd, ref pur, ref abi, ref gen, _) =>
+                    om.fns.push(visit_fn(item, fd, pur, abi, gen)),
+                ast::item_ty(ref ty, ref gen) => {
+                    let t = Typedef {
+                        ty: ty.clone(),
+                        gen: gen.clone(),
+                        name: item.ident,
+                        id: item.id,
+                        attrs: item.attrs.clone(),
+                        where: item.span,
+                        vis: item.vis,
+                    };
+                    om.typedefs.push(t);
+                },
+                ast::item_static(ref ty, ref mut_, ref exp) => {
+                    let s = Static {
+                        type_: ty.clone(),
+                        mutability: mut_.clone(),
+                        expr: exp.clone(),
+                        id: item.id,
+                        name: item.ident,
+                        attrs: item.attrs.clone(),
+                        where: item.span,
+                        vis: item.vis,
+                    };
+                    om.statics.push(s);
+                },
+                ast::item_trait(ref gen, ref tr, ref met) => {
+                    let t = Trait {
+                        name: item.ident,
+                        methods: met.clone(),
+                        generics: gen.clone(),
+                        parents: tr.clone(),
+                        id: item.id,
+                        attrs: item.attrs.clone(),
+                        where: item.span,
+                        vis: item.vis,
+                    };
+                    om.traits.push(t);
+                },
+                ast::item_impl(ref gen, ref tr, ref ty, ref meths) => {
+                    let i = Impl {
+                        generics: gen.clone(),
+                        trait_: tr.clone(),
+                        for_: ty.clone(),
+                        methods: meths.clone(),
+                        attrs: item.attrs.clone(),
+                        id: item.id,
+                        where: item.span,
+                        vis: item.vis,
+                    };
+                    om.impls.push(i);
+                },
+                _ => (),
+            }
+        }
+
+        self.module = visit_mod_contents(crate.span, crate.attrs.clone(),
+                                         ast::public, ast::CRATE_NODE_ID, &crate.module);
+    }
+}