1
Fork 0

auto merge of #15586 : aturon/rust/stability-dashboard, r=alexcrichton

This PR adds a crate-level dashboard summarizing the stability levels of all items for all submodules of the crate.

The information is also written as a json file, intended for consumption by pages like http://huonw.github.io/isrustfastyet/

Along the way, fixes a few bugs in stability tracking and places where rustdoc was not pulling the existing stability data.

Closes #13541
This commit is contained in:
bors 2014-07-11 22:06:43 +00:00
commit 1e401159c1
8 changed files with 327 additions and 14 deletions

View file

@ -693,6 +693,10 @@ fn encode_info_for_struct(ecx: &EncodeContext,
encode_name(ebml_w, nm);
encode_type(ecx, ebml_w, node_id_to_type(tcx, id));
encode_def_id(ebml_w, local_def(id));
let stab = stability::lookup(ecx.tcx, field.id);
encode_stability(ebml_w, stab);
ebml_w.end_tag();
}
index

View file

@ -14,9 +14,10 @@
use util::nodemap::{NodeMap, DefIdMap};
use syntax::codemap::Span;
use syntax::{attr, visit};
use syntax::ast;
use syntax::ast::{Attribute, Block, Crate, DefId, FnDecl, NodeId, Variant};
use syntax::ast::{Item, Required, Provided, TraitMethod, TypeMethod, Method};
use syntax::ast::{Generics, StructDef, Ident};
use syntax::ast::{Generics, StructDef, StructField, Ident};
use syntax::ast_util::is_local;
use syntax::attr::Stability;
use syntax::visit::{FnKind, FkMethod, Visitor};
@ -91,6 +92,11 @@ impl Visitor<Option<Stability>> for Annotator {
s.ctor_id.map(|id| self.annotate(id, &[], parent.clone()));
visit::walk_struct_def(self, s, parent)
}
fn visit_struct_field(&mut self, s: &StructField, parent: Option<Stability>) {
let stab = self.annotate(s.node.id, s.node.attrs.as_slice(), parent);
visit::walk_struct_field(self, s, stab)
}
}
impl Index {
@ -102,8 +108,8 @@ impl Index {
extern_cache: DefIdMap::new()
}
};
visit::walk_crate(&mut annotator, krate,
attr::find_stability(krate.attrs.as_slice()));
let stab = annotator.annotate(ast::CRATE_NODE_ID, krate.attrs.as_slice(), None);
visit::walk_crate(&mut annotator, krate, stab);
annotator.index
}
}

View file

@ -1461,12 +1461,15 @@ impl Clean<Item> for ty::VariantInfo {
name: Some(name.clean()),
attrs: Vec::new(),
visibility: Some(ast::Public),
stability: get_stability(self.id),
// FIXME: this is not accurate, we need an id for
// the specific field but we're using the id
// for the whole variant. Nothing currently
// uses this so we should be good for now.
// for the whole variant. Thus we read the
// stability from the whole variant as well.
// Struct variants are experimental and need
// more infrastructure work before we can get
// at the needed information here.
def_id: self.id,
stability: get_stability(self.id),
inner: StructFieldItem(
TypedStructField(ty.clean())
)
@ -1482,7 +1485,7 @@ impl Clean<Item> for ty::VariantInfo {
visibility: Some(ast::Public),
def_id: self.id,
inner: VariantItem(Variant { kind: kind }),
stability: None,
stability: get_stability(self.id),
}
}
}
@ -1890,7 +1893,7 @@ impl Clean<Item> for ast::ForeignItem {
source: self.span.clean(),
def_id: ast_util::local_def(self.id),
visibility: self.vis.clean(),
stability: None,
stability: get_stability(ast_util::local_def(self.id)),
inner: inner,
}
}

View file

@ -22,6 +22,7 @@ use syntax::ast;
use syntax::ast_util;
use clean;
use stability_summary::ModuleSummary;
use html::item_type;
use html::item_type::ItemType;
use html::render;
@ -631,3 +632,72 @@ impl<'a> fmt::Show for ConciseStability<'a> {
}
}
}
impl fmt::Show for ModuleSummary {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt_inner<'a>(f: &mut fmt::Formatter,
context: &mut Vec<&'a str>,
m: &'a ModuleSummary)
-> fmt::Result {
let cnt = m.counts;
let tot = cnt.total();
if tot == 0 { return Ok(()) }
context.push(m.name.as_slice());
let path = context.connect("::");
// the total width of each row's stability summary, in pixels
let width = 500;
try!(write!(f, "<tr>"));
try!(write!(f, "<td class='summary'>\
<a class='summary' href='{}'>{}</a></td>",
Vec::from_slice(context.slice_from(1))
.append_one("index.html").connect("/"),
path));
try!(write!(f, "<td>"));
try!(write!(f, "<span class='summary Stable' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.stable)/tot));
try!(write!(f, "<span class='summary Unstable' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.unstable)/tot));
try!(write!(f, "<span class='summary Experimental' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.experimental)/tot));
try!(write!(f, "<span class='summary Deprecated' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.deprecated)/tot));
try!(write!(f, "<span class='summary Unmarked' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.unmarked)/tot));
try!(write!(f, "</td></tr>"));
for submodule in m.submodules.iter() {
try!(fmt_inner(f, context, submodule));
}
context.pop();
Ok(())
}
let mut context = Vec::new();
try!(write!(f,
r"<h1 class='fqn'>Stability dashboard: crate <a class='mod' href='index.html'>{}</a></h1>
This dashboard summarizes the stability levels for all of the public modules of
the crate, according to the total number of items at each level in the module and its children:
<blockquote>
<a class='stability Stable'></a> stable,<br/>
<a class='stability Unstable'></a> unstable,<br/>
<a class='stability Experimental'></a> experimental,<br/>
<a class='stability Deprecated'></a> deprecated,<br/>
<a class='stability Unmarked'></a> unmarked
</blockquote>
The counts do not include methods or trait
implementations that are visible only through a re-exported type.",
self.name));
try!(write!(f, "<table>"))
try!(fmt_inner(f, &mut context, self));
write!(f, "</table>")
}
}

View file

@ -43,6 +43,8 @@ use std::sync::Arc;
use externalfiles::ExternalHtml;
use serialize::json;
use serialize::Encodable;
use serialize::json::ToJson;
use syntax::ast;
use syntax::ast_util;
@ -59,6 +61,7 @@ use html::item_type;
use html::layout;
use html::markdown::Markdown;
use html::markdown;
use stability_summary;
/// Major driving force in all rustdoc rendering. This contains information
/// about where in the tree-like hierarchy rendering is occurring and controls
@ -249,6 +252,11 @@ pub fn run(mut krate: clean::Crate, external_html: &ExternalHtml, dst: Path) ->
try!(mkdir(&cx.dst));
// Crawl the crate, building a summary of the stability levels. NOTE: this
// summary *must* be computed with the original `krate`; the folding below
// removes the impls from their modules.
let summary = stability_summary::build(&krate);
// Crawl the crate attributes looking for attributes which control how we're
// going to emit HTML
match krate.module.as_ref().map(|m| m.doc_list().unwrap_or(&[])) {
@ -361,7 +369,7 @@ pub fn run(mut krate: clean::Crate, external_html: &ExternalHtml, dst: Path) ->
let krate = try!(render_sources(&mut cx, krate));
// And finally render the whole crate's documentation
cx.krate(krate)
cx.krate(krate, summary)
}
fn build_index(krate: &clean::Crate, cache: &mut Cache) -> io::IoResult<String> {
@ -1045,13 +1053,34 @@ impl Context {
///
/// This currently isn't parallelized, but it'd be pretty easy to add
/// parallelization to this function.
fn krate(self, mut krate: clean::Crate) -> io::IoResult<()> {
fn krate(mut self, mut krate: clean::Crate,
stability: stability_summary::ModuleSummary) -> io::IoResult<()> {
let mut item = match krate.module.take() {
Some(i) => i,
None => return Ok(())
};
item.name = Some(krate.name);
// render stability dashboard
try!(self.recurse(stability.name.clone(), |this| {
let json_dst = &this.dst.join("stability.json");
let mut json_out = BufferedWriter::new(try!(File::create(json_dst)));
try!(stability.encode(&mut json::Encoder::new(&mut json_out)));
let title = stability.name.clone().append(" - Stability dashboard");
let page = layout::Page {
ty: "mod",
root_path: this.root_path.as_slice(),
title: title.as_slice(),
};
let html_dst = &this.dst.join("stability.html");
let mut html_out = BufferedWriter::new(try!(File::create(html_dst)));
layout::render(&mut html_out, &this.layout, &page,
&Sidebar{ cx: this, item: &item },
&stability)
}));
// render the crate documentation
let mut work = vec!((self, item));
loop {
match work.pop() {
@ -1061,6 +1090,7 @@ impl Context {
None => break,
}
}
Ok(())
}
@ -1233,6 +1263,8 @@ impl<'a> Item<'a> {
}
}
impl<'a> fmt::Show for Item<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
// Write the breadcrumb trail header for the top
@ -1269,6 +1301,17 @@ impl<'a> fmt::Show for Item<'a> {
// Write stability level
try!(write!(fmt, "{}", Stability(&self.item.stability)));
// Links to out-of-band information, i.e. src and stability dashboard
try!(write!(fmt, "<span class='out-of-band'>"));
// Write stability dashboard link
match self.item.inner {
clean::ModuleItem(ref m) if m.is_crate => {
try!(write!(fmt, "<a href='stability.html'>[stability dashboard]</a> "));
}
_ => {}
};
// Write `src` tag
//
// When this item is part of a `pub use` in a downstream crate, the
@ -1278,14 +1321,15 @@ impl<'a> fmt::Show for Item<'a> {
if self.cx.include_sources && !is_primitive {
match self.href() {
Some(l) => {
try!(write!(fmt,
"<a class='source' id='src-{}' \
href='{}'>[src]</a>",
try!(write!(fmt, "<a id='src-{}' href='{}'>[src]</a>",
self.item.def_id.node, l));
}
None => {}
}
}
try!(write!(fmt, "</span>"));
try!(write!(fmt, "</h1>\n"));
match self.item.inner {
@ -1355,6 +1399,7 @@ fn document(w: &mut fmt::Formatter, item: &clean::Item) -> fmt::Result {
fn item_module(w: &mut fmt::Formatter, cx: &Context,
item: &clean::Item, items: &[clean::Item]) -> fmt::Result {
try!(document(w, item));
let mut indices = range(0, items.len()).filter(|i| {
!ignore_private_item(&items[*i])
}).collect::<Vec<uint>>();
@ -1514,6 +1559,7 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
}
}
}
write!(w, "</table>")
}

View file

@ -238,7 +238,7 @@ nav.sub {
.docblock h2 { font-size: 1.15em; }
.docblock h3, .docblock h4, .docblock h5 { font-size: 1em; }
.content .source {
.content .out-of-band {
float: right;
font-size: 23px;
}
@ -409,6 +409,15 @@ h1 .stability {
.stability.Locked { border-color: #0084B6; color: #00668c; }
.stability.Unmarked { border-color: #FFFFFF; }
.summary {
padding-right: 0px;
}
.summary.Deprecated { background-color: #A071A8; }
.summary.Experimental { background-color: #D46D6A; }
.summary.Unstable { background-color: #D4B16A; }
.summary.Stable { background-color: #54A759; }
.summary.Unmarked { background-color: #FFFFFF; }
:target { background: #FDFFD3; }
/* Code highlighting */

View file

@ -56,6 +56,7 @@ pub mod html {
pub mod markdown;
pub mod passes;
pub mod plugins;
pub mod stability_summary;
pub mod visit_ast;
pub mod test;
mod flock;

View file

@ -0,0 +1,174 @@
// Copyright 2014 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! This module crawls a `clean::Crate` and produces a summarization of the
//! stability levels within the crate. The summary contains the module
//! hierarchy, with item counts for every stability level per module. A parent
//! module's count includes its childrens's.
use std::ops::Add;
use std::num::Zero;
use std::iter::AdditiveIterator;
use syntax::attr::{Deprecated, Experimental, Unstable, Stable, Frozen, Locked};
use syntax::ast::Public;
use clean::{Crate, Item, ModuleItem, Module, StructItem, Struct, EnumItem, Enum};
use clean::{ImplItem, Impl, TraitItem, Trait, TraitMethod, Provided, Required};
use clean::{ViewItemItem, PrimitiveItem};
#[deriving(Zero, Encodable, Decodable, PartialEq, Eq)]
/// The counts for each stability level.
pub struct Counts {
pub deprecated: uint,
pub experimental: uint,
pub unstable: uint,
pub stable: uint,
pub frozen: uint,
pub locked: uint,
/// No stability level, inherited or otherwise.
pub unmarked: uint,
}
impl Add<Counts, Counts> for Counts {
fn add(&self, other: &Counts) -> Counts {
Counts {
deprecated: self.deprecated + other.deprecated,
experimental: self.experimental + other.experimental,
unstable: self.unstable + other.unstable,
stable: self.stable + other.stable,
frozen: self.frozen + other.frozen,
locked: self.locked + other.locked,
unmarked: self.unmarked + other.unmarked,
}
}
}
impl Counts {
pub fn total(&self) -> uint {
self.deprecated + self.experimental + self.unstable + self.stable +
self.frozen + self.locked + self.unmarked
}
}
#[deriving(Encodable, Decodable, PartialEq, Eq)]
/// A summarized module, which includes total counts and summarized chilcren
/// modules.
pub struct ModuleSummary {
pub name: String,
pub counts: Counts,
pub submodules: Vec<ModuleSummary>,
}
impl PartialOrd for ModuleSummary {
fn partial_cmp(&self, other: &ModuleSummary) -> Option<Ordering> {
self.name.partial_cmp(&other.name)
}
}
impl Ord for ModuleSummary {
fn cmp(&self, other: &ModuleSummary) -> Ordering {
self.name.cmp(&other.name)
}
}
// is the item considered publically visible?
fn visible(item: &Item) -> bool {
match item.inner {
ImplItem(_) => true,
_ => item.visibility == Some(Public)
}
}
// Produce the summary for an arbitrary item. If the item is a module, include a
// module summary. The counts for items with nested items (e.g. modules, traits,
// impls) include all children counts.
fn summarize_item(item: &Item) -> (Counts, Option<ModuleSummary>) {
// count this item
let item_counts = match item.stability {
None => Counts { unmarked: 1, .. Zero::zero() },
Some(ref stab) => match stab.level {
Deprecated => Counts { deprecated: 1, .. Zero::zero() },
Experimental => Counts { experimental: 1, .. Zero::zero() },
Unstable => Counts { unstable: 1, .. Zero::zero() },
Stable => Counts { stable: 1, .. Zero::zero() },
Frozen => Counts { frozen: 1, .. Zero::zero() },
Locked => Counts { locked: 1, .. Zero::zero() },
}
};
// Count this item's children, if any. Note that a trait impl is
// considered to have no children.
match item.inner {
// Require explicit `pub` to be visible
StructItem(Struct { fields: ref subitems, .. }) |
ImplItem(Impl { methods: ref subitems, trait_: None, .. }) => {
let subcounts = subitems.iter().filter(|i| visible(*i))
.map(summarize_item)
.map(|s| s.val0())
.sum();
(item_counts + subcounts, None)
}
// `pub` automatically
EnumItem(Enum { variants: ref subitems, .. }) => {
let subcounts = subitems.iter().map(summarize_item)
.map(|s| s.val0())
.sum();
(item_counts + subcounts, None)
}
TraitItem(Trait { methods: ref methods, .. }) => {
fn extract_item<'a>(meth: &'a TraitMethod) -> &'a Item {
match *meth {
Provided(ref item) | Required(ref item) => item
}
}
let subcounts = methods.iter().map(extract_item)
.map(summarize_item)
.map(|s| s.val0())
.sum();
(item_counts + subcounts, None)
}
ModuleItem(Module { items: ref items, .. }) => {
let mut counts = item_counts;
let mut submodules = Vec::new();
for (subcounts, submodule) in items.iter().filter(|i| visible(*i))
.map(summarize_item) {
counts = counts + subcounts;
submodule.map(|m| submodules.push(m));
}
submodules.sort();
(counts, Some(ModuleSummary {
name: item.name.as_ref().map_or("".to_string(), |n| n.clone()),
counts: counts,
submodules: submodules,
}))
}
// no stability information for the following items:
ViewItemItem(_) | PrimitiveItem(_) => (Zero::zero(), None),
_ => (item_counts, None)
}
}
/// Summarizes the stability levels in a crate.
pub fn build(krate: &Crate) -> ModuleSummary {
match krate.module {
None => ModuleSummary {
name: krate.name.clone(),
counts: Zero::zero(),
submodules: Vec::new(),
},
Some(ref item) => ModuleSummary {
name: krate.name.clone(), .. summarize_item(item).val1().unwrap()
}
}
}