Auto merge of #36132 - nrc:save-std, r=@eddyb

Add --Zsave-analysis-api

This is a save-analysis variation which can be used with libraries distributed without their source (e.g., libstd). It will allow IDEs and other tools to get info about types and create URLs to docs and source, without the unnecessary clutter of internal-only save-analysis info. I'm sure we'll iterate somewhat on the design, but this is a first draft.
This commit is contained in:
bors 2016-09-04 02:40:31 -07:00 committed by GitHub
commit e77d86c142
9 changed files with 608 additions and 83 deletions

View File

@ -849,9 +849,13 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
ls: bool = (false, parse_bool, [UNTRACKED], ls: bool = (false, parse_bool, [UNTRACKED],
"list the symbols defined by a library crate"), "list the symbols defined by a library crate"),
save_analysis: bool = (false, parse_bool, [UNTRACKED], save_analysis: bool = (false, parse_bool, [UNTRACKED],
"write syntax and type analysis (in JSON format) information in addition to normal output"), "write syntax and type analysis (in JSON format) information, in \
addition to normal output"),
save_analysis_csv: bool = (false, parse_bool, [UNTRACKED], save_analysis_csv: bool = (false, parse_bool, [UNTRACKED],
"write syntax and type analysis (in CSV format) information in addition to normal output"), "write syntax and type analysis (in CSV format) information, in addition to normal output"),
save_analysis_api: bool = (false, parse_bool, [UNTRACKED],
"write syntax and type analysis information for opaque libraries (in JSON format), \
in addition to normal output"),
print_move_fragments: bool = (false, parse_bool, [UNTRACKED], print_move_fragments: bool = (false, parse_bool, [UNTRACKED],
"print out move-fragment data for every fn"), "print out move-fragment data for every fn"),
flowgraph_print_loans: bool = (false, parse_bool, [UNTRACKED], flowgraph_print_loans: bool = (false, parse_bool, [UNTRACKED],
@ -2365,6 +2369,8 @@ mod tests {
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
opts.debugging_opts.save_analysis_csv = true; opts.debugging_opts.save_analysis_csv = true;
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
opts.debugging_opts.save_analysis_api = true;
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
opts.debugging_opts.print_move_fragments = true; opts.debugging_opts.print_move_fragments = true;
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
opts.debugging_opts.flowgraph_print_loans = true; opts.debugging_opts.flowgraph_print_loans = true;

View File

@ -250,7 +250,8 @@ fn keep_hygiene_data(sess: &Session) -> bool {
fn keep_ast(sess: &Session) -> bool { fn keep_ast(sess: &Session) -> bool {
sess.opts.debugging_opts.keep_ast || sess.opts.debugging_opts.keep_ast ||
sess.opts.debugging_opts.save_analysis || sess.opts.debugging_opts.save_analysis ||
sess.opts.debugging_opts.save_analysis_csv sess.opts.debugging_opts.save_analysis_csv ||
sess.opts.debugging_opts.save_analysis_api
} }
/// The name used for source code that doesn't originate in a file /// The name used for source code that doesn't originate in a file

View File

@ -555,7 +555,8 @@ impl<'a> CompilerCalls<'a> for RustcDefaultCalls {
fn save_analysis(sess: &Session) -> bool { fn save_analysis(sess: &Session) -> bool {
sess.opts.debugging_opts.save_analysis || sess.opts.debugging_opts.save_analysis ||
sess.opts.debugging_opts.save_analysis_csv sess.opts.debugging_opts.save_analysis_csv ||
sess.opts.debugging_opts.save_analysis_api
} }
fn save_analysis_format(sess: &Session) -> save::Format { fn save_analysis_format(sess: &Session) -> save::Format {
@ -563,6 +564,8 @@ fn save_analysis_format(sess: &Session) -> save::Format {
save::Format::Json save::Format::Json
} else if sess.opts.debugging_opts.save_analysis_csv { } else if sess.opts.debugging_opts.save_analysis_csv {
save::Format::Csv save::Format::Csv
} else if sess.opts.debugging_opts.save_analysis_api {
save::Format::JsonApi
} else { } else {
unreachable!(); unreachable!();
} }

View File

@ -13,8 +13,9 @@
//! The `Dump` trait can be used together with `DumpVisitor` in order to //! The `Dump` trait can be used together with `DumpVisitor` in order to
//! retrieve the data from a crate. //! retrieve the data from a crate.
use rustc::hir;
use rustc::hir::def_id::DefId; use rustc::hir::def_id::DefId;
use syntax::ast::{CrateNum, NodeId}; use syntax::ast::{self, CrateNum, NodeId};
use syntax_pos::Span; use syntax_pos::Span;
pub struct CrateData { pub struct CrateData {
@ -76,6 +77,35 @@ pub enum Data {
VariableRefData(VariableRefData), VariableRefData(VariableRefData),
} }
#[derive(Eq, PartialEq, Clone, Copy, Debug, RustcEncodable)]
pub enum Visibility {
Public,
Restricted,
Inherited,
}
impl<'a> From<&'a ast::Visibility> for Visibility {
fn from(v: &'a ast::Visibility) -> Visibility {
match *v {
ast::Visibility::Public => Visibility::Public,
ast::Visibility::Crate(_) => Visibility::Restricted,
ast::Visibility::Restricted { .. } => Visibility::Restricted,
ast::Visibility::Inherited => Visibility::Inherited,
}
}
}
impl<'a> From<&'a hir::Visibility> for Visibility {
fn from(v: &'a hir::Visibility) -> Visibility {
match *v {
hir::Visibility::Public => Visibility::Public,
hir::Visibility::Crate => Visibility::Restricted,
hir::Visibility::Restricted { .. } => Visibility::Restricted,
hir::Visibility::Inherited => Visibility::Inherited,
}
}
}
/// Data for the prelude of a crate. /// Data for the prelude of a crate.
#[derive(Debug, RustcEncodable)] #[derive(Debug, RustcEncodable)]
pub struct CratePreludeData { pub struct CratePreludeData {
@ -103,7 +133,7 @@ pub struct EnumData {
pub span: Span, pub span: Span,
pub scope: NodeId, pub scope: NodeId,
pub variants: Vec<NodeId>, pub variants: Vec<NodeId>,
pub visibility: Visibility,
} }
/// Data for extern crates. /// Data for extern crates.
@ -135,6 +165,8 @@ pub struct FunctionData {
pub span: Span, pub span: Span,
pub scope: NodeId, pub scope: NodeId,
pub value: String, pub value: String,
pub visibility: Visibility,
pub parent: Option<NodeId>,
} }
/// Data about a function call. /// Data about a function call.
@ -215,6 +247,7 @@ pub struct MethodData {
pub scope: NodeId, pub scope: NodeId,
pub value: String, pub value: String,
pub decl_id: Option<DefId>, pub decl_id: Option<DefId>,
pub visibility: Visibility,
} }
/// Data for modules. /// Data for modules.
@ -227,6 +260,7 @@ pub struct ModData {
pub scope: NodeId, pub scope: NodeId,
pub filename: String, pub filename: String,
pub items: Vec<NodeId>, pub items: Vec<NodeId>,
pub visibility: Visibility,
} }
/// Data for a reference to a module. /// Data for a reference to a module.
@ -248,6 +282,7 @@ pub struct StructData {
pub scope: NodeId, pub scope: NodeId,
pub value: String, pub value: String,
pub fields: Vec<NodeId>, pub fields: Vec<NodeId>,
pub visibility: Visibility,
} }
#[derive(Debug, RustcEncodable)] #[derive(Debug, RustcEncodable)]
@ -258,7 +293,8 @@ pub struct StructVariantData {
pub qualname: String, pub qualname: String,
pub type_value: String, pub type_value: String,
pub value: String, pub value: String,
pub scope: NodeId pub scope: NodeId,
pub parent: Option<NodeId>,
} }
#[derive(Debug, RustcEncodable)] #[derive(Debug, RustcEncodable)]
@ -270,6 +306,7 @@ pub struct TraitData {
pub scope: NodeId, pub scope: NodeId,
pub value: String, pub value: String,
pub items: Vec<NodeId>, pub items: Vec<NodeId>,
pub visibility: Visibility,
} }
#[derive(Debug, RustcEncodable)] #[derive(Debug, RustcEncodable)]
@ -280,7 +317,8 @@ pub struct TupleVariantData {
pub qualname: String, pub qualname: String,
pub type_value: String, pub type_value: String,
pub value: String, pub value: String,
pub scope: NodeId pub scope: NodeId,
pub parent: Option<NodeId>,
} }
/// Data for a typedef. /// Data for a typedef.
@ -291,6 +329,8 @@ pub struct TypeDefData {
pub span: Span, pub span: Span,
pub qualname: String, pub qualname: String,
pub value: String, pub value: String,
pub visibility: Visibility,
pub parent: Option<NodeId>,
} }
/// Data for a reference to a type or trait. /// Data for a reference to a type or trait.
@ -308,7 +348,8 @@ pub struct UseData {
pub span: Span, pub span: Span,
pub name: String, pub name: String,
pub mod_id: Option<DefId>, pub mod_id: Option<DefId>,
pub scope: NodeId pub scope: NodeId,
pub visibility: Visibility,
} }
#[derive(Debug, RustcEncodable)] #[derive(Debug, RustcEncodable)]
@ -316,7 +357,8 @@ pub struct UseGlobData {
pub id: NodeId, pub id: NodeId,
pub span: Span, pub span: Span,
pub names: Vec<String>, pub names: Vec<String>,
pub scope: NodeId pub scope: NodeId,
pub visibility: Visibility,
} }
/// Data for local and global variables (consts and statics). /// Data for local and global variables (consts and statics).
@ -328,8 +370,10 @@ pub struct VariableData {
pub qualname: String, pub qualname: String,
pub span: Span, pub span: Span,
pub scope: NodeId, pub scope: NodeId,
pub parent: Option<NodeId>,
pub value: String, pub value: String,
pub type_value: String, pub type_value: String,
pub visibility: Visibility,
} }
#[derive(Debug, RustcEncodable)] #[derive(Debug, RustcEncodable)]

View File

@ -365,7 +365,9 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
qualname: format!("{}::{}", qualname, path_to_string(p)), qualname: format!("{}::{}", qualname, path_to_string(p)),
type_value: typ, type_value: typ,
value: String::new(), value: String::new(),
scope: 0 scope: 0,
parent: None,
visibility: Visibility::Inherited,
}.lower(self.tcx)); }.lower(self.tcx));
} }
} }
@ -377,6 +379,7 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
body: Option<&ast::Block>, body: Option<&ast::Block>,
id: ast::NodeId, id: ast::NodeId,
name: ast::Name, name: ast::Name,
vis: Visibility,
span: Span) { span: Span) {
debug!("process_method: {}:{}", id, name); debug!("process_method: {}:{}", id, name);
@ -417,6 +420,7 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
qualname: method_data.qualname.clone(), qualname: method_data.qualname.clone(),
value: sig_str, value: sig_str,
decl_id: decl_id, decl_id: decl_id,
visibility: vis,
}.lower(self.tcx)); }.lower(self.tcx));
} }
@ -484,7 +488,9 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
name: name, name: name,
id: param.id, id: param.id,
qualname: qualname, qualname: qualname,
value: String::new() value: String::new(),
visibility: Visibility::Inherited,
parent: None,
}.lower(self.tcx)); }.lower(self.tcx));
} }
} }
@ -528,12 +534,14 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
self.visit_expr(expr); self.visit_expr(expr);
} }
fn process_const(&mut self, fn process_assoc_const(&mut self,
id: ast::NodeId, id: ast::NodeId,
name: ast::Name, name: ast::Name,
span: Span, span: Span,
typ: &ast::Ty, typ: &ast::Ty,
expr: &ast::Expr) { expr: &ast::Expr,
parent_id: NodeId,
vis: Visibility) {
let qualname = format!("::{}", self.tcx.node_path_str(id)); let qualname = format!("::{}", self.tcx.node_path_str(id));
let sub_span = self.span.sub_span_after_keyword(span, keywords::Const); let sub_span = self.span.sub_span_after_keyword(span, keywords::Const);
@ -547,7 +555,9 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
qualname: qualname, qualname: qualname,
value: self.span.snippet(expr.span), value: self.span.snippet(expr.span),
type_value: ty_to_string(&typ), type_value: ty_to_string(&typ),
scope: self.cur_scope scope: self.cur_scope,
parent: Some(parent_id),
visibility: vis,
}.lower(self.tcx)); }.lower(self.tcx));
} }
@ -589,6 +599,7 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
scope: self.cur_scope, scope: self.cur_scope,
value: val, value: val,
fields: fields, fields: fields,
visibility: From::from(&item.vis),
}.lower(self.tcx)); }.lower(self.tcx));
} }
@ -640,7 +651,8 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
qualname: qualname, qualname: qualname,
type_value: enum_data.qualname.clone(), type_value: enum_data.qualname.clone(),
value: val, value: val,
scope: enum_data.scope scope: enum_data.scope,
parent: Some(item.id),
}.lower(self.tcx)); }.lower(self.tcx));
} }
} }
@ -663,7 +675,8 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
qualname: qualname, qualname: qualname,
type_value: enum_data.qualname.clone(), type_value: enum_data.qualname.clone(),
value: val, value: val,
scope: enum_data.scope scope: enum_data.scope,
parent: Some(item.id),
}.lower(self.tcx)); }.lower(self.tcx));
} }
} }
@ -716,7 +729,7 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
} }
self.process_generic_params(type_parameters, item.span, "", item.id); self.process_generic_params(type_parameters, item.span, "", item.id);
for impl_item in impl_items { for impl_item in impl_items {
self.visit_impl_item(impl_item); self.process_impl_item(impl_item, item.id);
} }
} }
@ -745,6 +758,7 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
scope: self.cur_scope, scope: self.cur_scope,
value: val, value: val,
items: methods.iter().map(|i| i.id).collect(), items: methods.iter().map(|i| i.id).collect(),
visibility: From::from(&item.vis),
}.lower(self.tcx)); }.lower(self.tcx));
} }
@ -785,7 +799,7 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
// walk generics and methods // walk generics and methods
self.process_generic_params(generics, item.span, &qualname, item.id); self.process_generic_params(generics, item.span, &qualname, item.id);
for method in methods { for method in methods {
self.visit_trait_item(method) self.process_trait_item(method, item.id)
} }
} }
@ -990,7 +1004,9 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
qualname: format!("{}${}", path_to_string(p), id), qualname: format!("{}${}", path_to_string(p), id),
value: value, value: value,
type_value: typ, type_value: typ,
scope: 0 scope: 0,
parent: None,
visibility: Visibility::Inherited,
}.lower(self.tcx)); }.lower(self.tcx));
} }
} }
@ -1038,6 +1054,57 @@ impl<'l, 'tcx: 'l, 'll, D: Dump + 'll> DumpVisitor<'l, 'tcx, 'll, D> {
} }
} }
} }
fn process_trait_item(&mut self, trait_item: &ast::TraitItem, trait_id: NodeId) {
self.process_macro_use(trait_item.span, trait_item.id);
match trait_item.node {
ast::TraitItemKind::Const(ref ty, Some(ref expr)) => {
self.process_assoc_const(trait_item.id,
trait_item.ident.name,
trait_item.span,
&ty,
&expr,
trait_id,
Visibility::Public);
}
ast::TraitItemKind::Method(ref sig, ref body) => {
self.process_method(sig,
body.as_ref().map(|x| &**x),
trait_item.id,
trait_item.ident.name,
Visibility::Public,
trait_item.span);
}
ast::TraitItemKind::Const(_, None) |
ast::TraitItemKind::Type(..) |
ast::TraitItemKind::Macro(_) => {}
}
}
fn process_impl_item(&mut self, impl_item: &ast::ImplItem, impl_id: NodeId) {
self.process_macro_use(impl_item.span, impl_item.id);
match impl_item.node {
ast::ImplItemKind::Const(ref ty, ref expr) => {
self.process_assoc_const(impl_item.id,
impl_item.ident.name,
impl_item.span,
&ty,
&expr,
impl_id,
From::from(&impl_item.vis));
}
ast::ImplItemKind::Method(ref sig, ref body) => {
self.process_method(sig,
Some(body),
impl_item.id,
impl_item.ident.name,
From::from(&impl_item.vis),
impl_item.span);
}
ast::ImplItemKind::Type(_) |
ast::ImplItemKind::Macro(_) => {}
}
}
} }
impl<'l, 'tcx: 'l, 'll, D: Dump +'ll> Visitor for DumpVisitor<'l, 'tcx, 'll, D> { impl<'l, 'tcx: 'l, 'll, D: Dump +'ll> Visitor for DumpVisitor<'l, 'tcx, 'll, D> {
@ -1073,7 +1140,8 @@ impl<'l, 'tcx: 'l, 'll, D: Dump +'ll> Visitor for DumpVisitor<'l, 'tcx, 'll, D>
id: item.id, id: item.id,
mod_id: mod_id, mod_id: mod_id,
name: ident.to_string(), name: ident.to_string(),
scope: self.cur_scope scope: self.cur_scope,
visibility: From::from(&item.vis),
}.lower(self.tcx)); }.lower(self.tcx));
} }
self.write_sub_paths_truncated(path, true); self.write_sub_paths_truncated(path, true);
@ -1096,7 +1164,8 @@ impl<'l, 'tcx: 'l, 'll, D: Dump +'ll> Visitor for DumpVisitor<'l, 'tcx, 'll, D>
span: sub_span.expect("No span found for use glob"), span: sub_span.expect("No span found for use glob"),
id: item.id, id: item.id,
names: names, names: names,
scope: self.cur_scope scope: self.cur_scope,
visibility: From::from(&item.vis),
}.lower(self.tcx)); }.lower(self.tcx));
} }
self.write_sub_paths(path, true); self.write_sub_paths(path, true);
@ -1168,7 +1237,9 @@ impl<'l, 'tcx: 'l, 'll, D: Dump +'ll> Visitor for DumpVisitor<'l, 'tcx, 'll, D>
name: item.ident.to_string(), name: item.ident.to_string(),
id: item.id, id: item.id,
qualname: qualname.clone(), qualname: qualname.clone(),
value: value value: value,
visibility: From::from(&item.vis),
parent: None,
}.lower(self.tcx)); }.lower(self.tcx));
} }
@ -1193,51 +1264,6 @@ impl<'l, 'tcx: 'l, 'll, D: Dump +'ll> Visitor for DumpVisitor<'l, 'tcx, 'll, D>
} }
} }
fn visit_trait_item(&mut self, trait_item: &ast::TraitItem) {
self.process_macro_use(trait_item.span, trait_item.id);
match trait_item.node {
ast::TraitItemKind::Const(ref ty, Some(ref expr)) => {
self.process_const(trait_item.id,
trait_item.ident.name,
trait_item.span,
&ty,
&expr);
}
ast::TraitItemKind::Method(ref sig, ref body) => {
self.process_method(sig,
body.as_ref().map(|x| &**x),
trait_item.id,
trait_item.ident.name,
trait_item.span);
}
ast::TraitItemKind::Const(_, None) |
ast::TraitItemKind::Type(..) |
ast::TraitItemKind::Macro(_) => {}
}
}
fn visit_impl_item(&mut self, impl_item: &ast::ImplItem) {
self.process_macro_use(impl_item.span, impl_item.id);
match impl_item.node {
ast::ImplItemKind::Const(ref ty, ref expr) => {
self.process_const(impl_item.id,
impl_item.ident.name,
impl_item.span,
&ty,
&expr);
}
ast::ImplItemKind::Method(ref sig, ref body) => {
self.process_method(sig,
Some(body),
impl_item.id,
impl_item.ident.name,
impl_item.span);
}
ast::ImplItemKind::Type(_) |
ast::ImplItemKind::Macro(_) => {}
}
}
fn visit_ty(&mut self, t: &ast::Ty) { fn visit_ty(&mut self, t: &ast::Ty) {
self.process_macro_use(t.span, t.id); self.process_macro_use(t.span, t.id);
match t.node { match t.node {
@ -1400,7 +1426,9 @@ impl<'l, 'tcx: 'l, 'll, D: Dump +'ll> Visitor for DumpVisitor<'l, 'tcx, 'll, D>
qualname: format!("{}${}", path_to_string(p), id), qualname: format!("{}${}", path_to_string(p), id),
value: value, value: value,
type_value: String::new(), type_value: String::new(),
scope: 0 scope: 0,
parent: None,
visibility: Visibility::Inherited,
}.lower(self.tcx)); }.lower(self.tcx));
} }
} }

View File

@ -15,7 +15,7 @@ use syntax::ast::{CrateNum, NodeId};
use syntax::codemap::CodeMap; use syntax::codemap::CodeMap;
use syntax_pos::Span; use syntax_pos::Span;
use data; use data::{self, Visibility};
// FIXME: this should be pub(crate), but the current snapshot doesn't allow it yet // FIXME: this should be pub(crate), but the current snapshot doesn't allow it yet
pub trait Lower { pub trait Lower {
@ -91,7 +91,8 @@ pub struct EnumData {
pub qualname: String, pub qualname: String,
pub span: SpanData, pub span: SpanData,
pub scope: DefId, pub scope: DefId,
pub variants: Vec<DefId> pub variants: Vec<DefId>,
pub visibility: Visibility,
} }
impl Lower for data::EnumData { impl Lower for data::EnumData {
@ -106,6 +107,7 @@ impl Lower for data::EnumData {
span: SpanData::from_span(self.span, tcx.sess.codemap()), span: SpanData::from_span(self.span, tcx.sess.codemap()),
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
variants: self.variants.into_iter().map(|id| make_def_id(id, &tcx.map)).collect(), variants: self.variants.into_iter().map(|id| make_def_id(id, &tcx.map)).collect(),
visibility: self.visibility,
} }
} }
} }
@ -166,6 +168,8 @@ pub struct FunctionData {
pub span: SpanData, pub span: SpanData,
pub scope: DefId, pub scope: DefId,
pub value: String, pub value: String,
pub visibility: Visibility,
pub parent: Option<DefId>,
} }
impl Lower for data::FunctionData { impl Lower for data::FunctionData {
@ -180,6 +184,8 @@ impl Lower for data::FunctionData {
span: SpanData::from_span(self.span, tcx.sess.codemap()), span: SpanData::from_span(self.span, tcx.sess.codemap()),
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
value: self.value, value: self.value,
visibility: self.visibility,
parent: self.parent.map(|id| make_def_id(id, &tcx.map)),
} }
} }
} }
@ -323,6 +329,8 @@ pub struct MethodData {
pub scope: DefId, pub scope: DefId,
pub value: String, pub value: String,
pub decl_id: Option<DefId>, pub decl_id: Option<DefId>,
pub visibility: Visibility,
pub parent: Option<DefId>
} }
impl Lower for data::MethodData { impl Lower for data::MethodData {
@ -337,6 +345,8 @@ impl Lower for data::MethodData {
qualname: self.qualname, qualname: self.qualname,
value: self.value, value: self.value,
decl_id: self.decl_id, decl_id: self.decl_id,
visibility: self.visibility,
parent: Some(make_def_id(self.scope, &tcx.map)),
} }
} }
} }
@ -351,6 +361,7 @@ pub struct ModData {
pub scope: DefId, pub scope: DefId,
pub filename: String, pub filename: String,
pub items: Vec<DefId>, pub items: Vec<DefId>,
pub visibility: Visibility,
} }
impl Lower for data::ModData { impl Lower for data::ModData {
@ -365,6 +376,7 @@ impl Lower for data::ModData {
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
filename: self.filename, filename: self.filename,
items: self.items.into_iter().map(|id| make_def_id(id, &tcx.map)).collect(), items: self.items.into_iter().map(|id| make_def_id(id, &tcx.map)).collect(),
visibility: self.visibility,
} }
} }
} }
@ -401,6 +413,7 @@ pub struct StructData {
pub scope: DefId, pub scope: DefId,
pub value: String, pub value: String,
pub fields: Vec<DefId>, pub fields: Vec<DefId>,
pub visibility: Visibility,
} }
impl Lower for data::StructData { impl Lower for data::StructData {
@ -416,6 +429,7 @@ impl Lower for data::StructData {
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
value: self.value, value: self.value,
fields: self.fields.into_iter().map(|id| make_def_id(id, &tcx.map)).collect(), fields: self.fields.into_iter().map(|id| make_def_id(id, &tcx.map)).collect(),
visibility: self.visibility,
} }
} }
} }
@ -428,7 +442,8 @@ pub struct StructVariantData {
pub qualname: String, pub qualname: String,
pub type_value: String, pub type_value: String,
pub value: String, pub value: String,
pub scope: DefId pub scope: DefId,
pub parent: Option<DefId>,
} }
impl Lower for data::StructVariantData { impl Lower for data::StructVariantData {
@ -443,6 +458,7 @@ impl Lower for data::StructVariantData {
type_value: self.type_value, type_value: self.type_value,
value: self.value, value: self.value,
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
parent: self.parent.map(|id| make_def_id(id, &tcx.map)),
} }
} }
} }
@ -456,6 +472,7 @@ pub struct TraitData {
pub scope: DefId, pub scope: DefId,
pub value: String, pub value: String,
pub items: Vec<DefId>, pub items: Vec<DefId>,
pub visibility: Visibility,
} }
impl Lower for data::TraitData { impl Lower for data::TraitData {
@ -470,6 +487,7 @@ impl Lower for data::TraitData {
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
value: self.value, value: self.value,
items: self.items.into_iter().map(|id| make_def_id(id, &tcx.map)).collect(), items: self.items.into_iter().map(|id| make_def_id(id, &tcx.map)).collect(),
visibility: self.visibility,
} }
} }
} }
@ -483,6 +501,7 @@ pub struct TupleVariantData {
pub type_value: String, pub type_value: String,
pub value: String, pub value: String,
pub scope: DefId, pub scope: DefId,
pub parent: Option<DefId>,
} }
impl Lower for data::TupleVariantData { impl Lower for data::TupleVariantData {
@ -497,6 +516,7 @@ impl Lower for data::TupleVariantData {
type_value: self.type_value, type_value: self.type_value,
value: self.value, value: self.value,
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
parent: self.parent.map(|id| make_def_id(id, &tcx.map)),
} }
} }
} }
@ -509,6 +529,8 @@ pub struct TypeDefData {
pub span: SpanData, pub span: SpanData,
pub qualname: String, pub qualname: String,
pub value: String, pub value: String,
pub visibility: Visibility,
pub parent: Option<DefId>,
} }
impl Lower for data::TypeDefData { impl Lower for data::TypeDefData {
@ -521,6 +543,8 @@ impl Lower for data::TypeDefData {
span: SpanData::from_span(self.span, tcx.sess.codemap()), span: SpanData::from_span(self.span, tcx.sess.codemap()),
qualname: self.qualname, qualname: self.qualname,
value: self.value, value: self.value,
visibility: self.visibility,
parent: self.parent.map(|id| make_def_id(id, &tcx.map)),
} }
} }
} }
@ -553,7 +577,8 @@ pub struct UseData {
pub span: SpanData, pub span: SpanData,
pub name: String, pub name: String,
pub mod_id: Option<DefId>, pub mod_id: Option<DefId>,
pub scope: DefId pub scope: DefId,
pub visibility: Visibility,
} }
impl Lower for data::UseData { impl Lower for data::UseData {
@ -566,6 +591,7 @@ impl Lower for data::UseData {
name: self.name, name: self.name,
mod_id: self.mod_id, mod_id: self.mod_id,
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
visibility: self.visibility,
} }
} }
} }
@ -575,7 +601,8 @@ pub struct UseGlobData {
pub id: DefId, pub id: DefId,
pub span: SpanData, pub span: SpanData,
pub names: Vec<String>, pub names: Vec<String>,
pub scope: DefId pub scope: DefId,
pub visibility: Visibility,
} }
impl Lower for data::UseGlobData { impl Lower for data::UseGlobData {
@ -587,6 +614,7 @@ impl Lower for data::UseGlobData {
span: SpanData::from_span(self.span, tcx.sess.codemap()), span: SpanData::from_span(self.span, tcx.sess.codemap()),
names: self.names, names: self.names,
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
visibility: self.visibility,
} }
} }
} }
@ -602,6 +630,8 @@ pub struct VariableData {
pub scope: DefId, pub scope: DefId,
pub value: String, pub value: String,
pub type_value: String, pub type_value: String,
pub parent: Option<DefId>,
pub visibility: Visibility,
} }
impl Lower for data::VariableData { impl Lower for data::VariableData {
@ -617,6 +647,8 @@ impl Lower for data::VariableData {
scope: make_def_id(self.scope, &tcx.map), scope: make_def_id(self.scope, &tcx.map),
value: self.value, value: self.value,
type_value: self.type_value, type_value: self.type_value,
parent: self.parent.map(|id| make_def_id(id, &tcx.map)),
visibility: self.visibility,
} }
} }
} }

View File

@ -0,0 +1,393 @@
// Copyright 2016 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.
use std::io::Write;
use rustc::hir::def_id::DefId;
use rustc_serialize::json::as_json;
use external_data::*;
use data::{VariableKind, Visibility};
use dump::Dump;
// A dumper to dump a restricted set of JSON information, designed for use with
// libraries distributed without their source. Clients are likely to use type
// information here, and (for example) generate Rustdoc URLs, but don't need
// information for navigating the source of the crate.
// Relative to the regular JSON save-analysis info, this form is filtered to
// remove non-visible items, but includes some extra info for items (e.g., the
// parent field for finding the struct to which a field belongs).
pub struct JsonApiDumper<'b, W: Write + 'b> {
output: &'b mut W,
result: Analysis,
}
impl<'b, W: Write> JsonApiDumper<'b, W> {
pub fn new(writer: &'b mut W) -> JsonApiDumper<'b, W> {
JsonApiDumper { output: writer, result: Analysis::new() }
}
}
impl<'b, W: Write> Drop for JsonApiDumper<'b, W> {
fn drop(&mut self) {
if let Err(_) = write!(self.output, "{}", as_json(&self.result)) {
error!("Error writing output");
}
}
}
macro_rules! impl_fn {
($fn_name: ident, $data_type: ident, $bucket: ident) => {
fn $fn_name(&mut self, data: $data_type) {
if let Some(datum) = From::from(data) {
self.result.$bucket.push(datum);
}
}
}
}
impl<'b, W: Write + 'b> Dump for JsonApiDumper<'b, W> {
fn crate_prelude(&mut self, data: CratePreludeData) {
self.result.prelude = Some(data)
}
impl_fn!(use_data, UseData, imports);
impl_fn!(use_glob, UseGlobData, imports);
impl_fn!(enum_data, EnumData, defs);
impl_fn!(tuple_variant, TupleVariantData, defs);
impl_fn!(struct_variant, StructVariantData, defs);
impl_fn!(struct_data, StructData, defs);
impl_fn!(trait_data, TraitData, defs);
impl_fn!(function, FunctionData, defs);
impl_fn!(method, MethodData, defs);
impl_fn!(macro_data, MacroData, defs);
impl_fn!(mod_data, ModData, defs);
impl_fn!(typedef, TypeDefData, defs);
impl_fn!(variable, VariableData, defs);
}
// FIXME methods. The defs have information about possible overriding and the
// refs have decl information (e.g., a trait method where we know the required
// method, but not the supplied method). In both cases, we are currently
// ignoring it.
#[derive(Debug, RustcEncodable)]
struct Analysis {
prelude: Option<CratePreludeData>,
imports: Vec<Import>,
defs: Vec<Def>,
}
impl Analysis {
fn new() -> Analysis {
Analysis {
prelude: None,
imports: vec![],
defs: vec![],
}
}
}
// DefId::index is a newtype and so the JSON serialisation is ugly. Therefore
// we use our own Id which is the same, but without the newtype.
#[derive(Debug, RustcEncodable)]
struct Id {
krate: u32,
index: u32,
}
impl From<DefId> for Id {
fn from(id: DefId) -> Id {
Id {
krate: id.krate,
index: id.index.as_u32(),
}
}
}
#[derive(Debug, RustcEncodable)]
struct Import {
kind: ImportKind,
id: Id,
span: SpanData,
name: String,
value: String,
}
#[derive(Debug, RustcEncodable)]
enum ImportKind {
Use,
GlobUse,
}
impl From<UseData> for Option<Import> {
fn from(data: UseData) -> Option<Import> {
match data.visibility {
Visibility::Public => Some(Import {
kind: ImportKind::Use,
id: From::from(data.id),
span: data.span,
name: data.name,
value: String::new(),
}),
_ => None,
}
}
}
impl From<UseGlobData> for Option<Import> {
fn from(data: UseGlobData) -> Option<Import> {
match data.visibility {
Visibility::Public => Some(Import {
kind: ImportKind::GlobUse,
id: From::from(data.id),
span: data.span,
name: "*".to_owned(),
value: data.names.join(", "),
}),
_ => None,
}
}
}
#[derive(Debug, RustcEncodable)]
struct Def {
kind: DefKind,
id: Id,
span: SpanData,
name: String,
qualname: String,
value: String,
parent: Option<Id>,
children: Vec<Id>,
decl_id: Option<Id>,
}
#[derive(Debug, RustcEncodable)]
enum DefKind {
// value = variant names
Enum,
// value = enum name + variant name + types
Tuple,
// value = [enum name +] name + fields
Struct,
// value = signature
Trait,
// value = type + generics
Function,
// value = type + generics
Method,
// No id, no value.
Macro,
// value = file_name
Mod,
// value = aliased type
Type,
// value = type and init expression (for all variable kinds).
Static,
Const,
Field,
}
impl From<EnumData> for Option<Def> {
fn from(data: EnumData) -> Option<Def> {
match data.visibility {
Visibility::Public => Some(Def {
kind: DefKind::Enum,
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.value,
parent: None,
children: data.variants.into_iter().map(|id| From::from(id)).collect(),
decl_id: None,
}),
_ => None,
}
}
}
impl From<TupleVariantData> for Option<Def> {
fn from(data: TupleVariantData) -> Option<Def> {
Some(Def {
kind: DefKind::Tuple,
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.value,
parent: data.parent.map(|id| From::from(id)),
children: vec![],
decl_id: None,
})
}
}
impl From<StructVariantData> for Option<Def> {
fn from(data: StructVariantData) -> Option<Def> {
Some(Def {
kind: DefKind::Struct,
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.value,
parent: data.parent.map(|id| From::from(id)),
children: vec![],
decl_id: None,
})
}
}
impl From<StructData> for Option<Def> {
fn from(data: StructData) -> Option<Def> {
match data.visibility {
Visibility::Public => Some(Def {
kind: DefKind::Struct,
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.value,
parent: None,
children: data.fields.into_iter().map(|id| From::from(id)).collect(),
decl_id: None,
}),
_ => None,
}
}
}
impl From<TraitData> for Option<Def> {
fn from(data: TraitData) -> Option<Def> {
match data.visibility {
Visibility::Public => Some(Def {
kind: DefKind::Trait,
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.value,
children: data.items.into_iter().map(|id| From::from(id)).collect(),
parent: None,
decl_id: None,
}),
_ => None,
}
}
}
impl From<FunctionData> for Option<Def> {
fn from(data: FunctionData) -> Option<Def> {
match data.visibility {
Visibility::Public => Some(Def {
kind: DefKind::Function,
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.value,
children: vec![],
parent: data.parent.map(|id| From::from(id)),
decl_id: None,
}),
_ => None,
}
}
}
impl From<MethodData> for Option<Def> {
fn from(data: MethodData) -> Option<Def> {
match data.visibility {
Visibility::Public => Some(Def {
kind: DefKind::Method,
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.value,
children: vec![],
parent: data.parent.map(|id| From::from(id)),
decl_id: data.decl_id.map(|id| From::from(id)),
}),
_ => None,
}
}
}
impl From<MacroData> for Option<Def> {
fn from(data: MacroData) -> Option<Def> {
Some(Def {
kind: DefKind::Macro,
id: From::from(null_def_id()),
span: data.span,
name: data.name,
qualname: data.qualname,
value: String::new(),
children: vec![],
parent: None,
decl_id: None,
})
}
}
impl From<ModData> for Option<Def> {
fn from(data:ModData) -> Option<Def> {
match data.visibility {
Visibility::Public => Some(Def {
kind: DefKind::Mod,
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.filename,
children: data.items.into_iter().map(|id| From::from(id)).collect(),
parent: None,
decl_id: None,
}),
_ => None,
}
}
}
impl From<TypeDefData> for Option<Def> {
fn from(data: TypeDefData) -> Option<Def> {
match data.visibility {
Visibility::Public => Some(Def {
kind: DefKind::Type,
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.value,
children: vec![],
parent: data.parent.map(|id| From::from(id)),
decl_id: None,
}),
_ => None,
}
}
}
impl From<VariableData> for Option<Def> {
fn from(data: VariableData) -> Option<Def> {
match data.visibility {
Visibility::Public => Some(Def {
kind: match data.kind {
VariableKind::Static => DefKind::Static,
VariableKind::Const => DefKind::Const,
VariableKind::Local => { return None }
VariableKind::Field => DefKind::Field,
},
id: From::from(data.id),
span: data.span,
name: data.name,
qualname: data.qualname,
value: data.value,
children: vec![],
parent: data.parent.map(|id| From::from(id)),
decl_id: None,
}),
_ => None,
}
}
}

View File

@ -30,6 +30,7 @@ extern crate serialize as rustc_serialize;
extern crate syntax_pos; extern crate syntax_pos;
mod csv_dumper; mod csv_dumper;
mod json_api_dumper;
mod json_dumper; mod json_dumper;
mod data; mod data;
mod dump; mod dump;
@ -57,6 +58,7 @@ use syntax::codemap::MacroAttribute;
use syntax_pos::*; use syntax_pos::*;
pub use self::csv_dumper::CsvDumper; pub use self::csv_dumper::CsvDumper;
pub use self::json_api_dumper::JsonApiDumper;
pub use self::json_dumper::JsonDumper; pub use self::json_dumper::JsonDumper;
pub use self::data::*; pub use self::data::*;
pub use self::dump::Dump; pub use self::dump::Dump;
@ -138,6 +140,8 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
span: sub_span.unwrap(), span: sub_span.unwrap(),
scope: self.enclosing_scope(item.id), scope: self.enclosing_scope(item.id),
value: make_signature(decl, generics), value: make_signature(decl, generics),
visibility: From::from(&item.vis),
parent: None,
})) }))
} }
ast::ItemKind::Static(ref typ, mt, ref expr) => { ast::ItemKind::Static(ref typ, mt, ref expr) => {
@ -160,8 +164,10 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
qualname: qualname, qualname: qualname,
span: sub_span.unwrap(), span: sub_span.unwrap(),
scope: self.enclosing_scope(item.id), scope: self.enclosing_scope(item.id),
parent: None,
value: value, value: value,
type_value: ty_to_string(&typ), type_value: ty_to_string(&typ),
visibility: From::from(&item.vis),
})) }))
} }
ast::ItemKind::Const(ref typ, ref expr) => { ast::ItemKind::Const(ref typ, ref expr) => {
@ -175,8 +181,10 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
qualname: qualname, qualname: qualname,
span: sub_span.unwrap(), span: sub_span.unwrap(),
scope: self.enclosing_scope(item.id), scope: self.enclosing_scope(item.id),
parent: None,
value: self.span_utils.snippet(expr.span), value: self.span_utils.snippet(expr.span),
type_value: ty_to_string(&typ), type_value: ty_to_string(&typ),
visibility: From::from(&item.vis),
})) }))
} }
ast::ItemKind::Mod(ref m) => { ast::ItemKind::Mod(ref m) => {
@ -195,6 +203,7 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
scope: self.enclosing_scope(item.id), scope: self.enclosing_scope(item.id),
filename: filename, filename: filename,
items: m.items.iter().map(|i| i.id).collect(), items: m.items.iter().map(|i| i.id).collect(),
visibility: From::from(&item.vis),
})) }))
} }
ast::ItemKind::Enum(ref def, _) => { ast::ItemKind::Enum(ref def, _) => {
@ -215,6 +224,7 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
qualname: qualname, qualname: qualname,
scope: self.enclosing_scope(item.id), scope: self.enclosing_scope(item.id),
variants: def.variants.iter().map(|v| v.node.data.id()).collect(), variants: def.variants.iter().map(|v| v.node.data.id()).collect(),
visibility: From::from(&item.vis),
})) }))
} }
ast::ItemKind::Impl(_, _, _, ref trait_ref, ref typ, _) => { ast::ItemKind::Impl(_, _, _, ref trait_ref, ref typ, _) => {
@ -277,8 +287,10 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
qualname: qualname, qualname: qualname,
span: sub_span.unwrap(), span: sub_span.unwrap(),
scope: scope, scope: scope,
parent: Some(scope),
value: "".to_owned(), value: "".to_owned(),
type_value: typ, type_value: typ,
visibility: From::from(&field.vis),
}) })
} else { } else {
None None
@ -291,7 +303,7 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
name: ast::Name, span: Span) -> Option<FunctionData> { name: ast::Name, span: Span) -> Option<FunctionData> {
// The qualname for a method is the trait name or name of the struct in an impl in // The qualname for a method is the trait name or name of the struct in an impl in
// which the method is declared in, followed by the method's name. // which the method is declared in, followed by the method's name.
let qualname = match self.tcx.impl_of_method(self.tcx.map.local_def_id(id)) { let (qualname, vis) = match self.tcx.impl_of_method(self.tcx.map.local_def_id(id)) {
Some(impl_id) => match self.tcx.map.get_if_local(impl_id) { Some(impl_id) => match self.tcx.map.get_if_local(impl_id) {
Some(NodeItem(item)) => { Some(NodeItem(item)) => {
match item.node { match item.node {
@ -304,7 +316,7 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
result.push_str(&self.tcx.item_path_str(def_id)); result.push_str(&self.tcx.item_path_str(def_id));
} }
result.push_str(">"); result.push_str(">");
result (result, From::from(&item.vis))
} }
_ => { _ => {
span_bug!(span, span_bug!(span,
@ -325,8 +337,8 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
None => match self.tcx.trait_of_item(self.tcx.map.local_def_id(id)) { None => match self.tcx.trait_of_item(self.tcx.map.local_def_id(id)) {
Some(def_id) => { Some(def_id) => {
match self.tcx.map.get_if_local(def_id) { match self.tcx.map.get_if_local(def_id) {
Some(NodeItem(_)) => { Some(NodeItem(item)) => {
format!("::{}", self.tcx.item_path_str(def_id)) (format!("::{}", self.tcx.item_path_str(def_id)), From::from(&item.vis))
} }
r => { r => {
span_bug!(span, span_bug!(span,
@ -358,6 +370,7 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
let sub_span = self.span_utils.sub_span_after_keyword(span, keywords::Fn); let sub_span = self.span_utils.sub_span_after_keyword(span, keywords::Fn);
filter!(self.span_utils, sub_span, span, None); filter!(self.span_utils, sub_span, span, None);
let parent_scope = self.enclosing_scope(id);
Some(FunctionData { Some(FunctionData {
id: id, id: id,
name: name.to_string(), name: name.to_string(),
@ -367,6 +380,8 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
scope: self.enclosing_scope(id), scope: self.enclosing_scope(id),
// FIXME you get better data here by using the visitor. // FIXME you get better data here by using the visitor.
value: String::new(), value: String::new(),
visibility: vis,
parent: Some(parent_scope),
}) })
} }
@ -728,13 +743,14 @@ impl Visitor for PathCollector {
pub enum Format { pub enum Format {
Csv, Csv,
Json, Json,
JsonApi,
} }
impl Format { impl Format {
fn extension(&self) -> &'static str { fn extension(&self) -> &'static str {
match *self { match *self {
Format::Csv => ".csv", Format::Csv => ".csv",
Format::Json => ".json", Format::Json | Format::JsonApi => ".json",
} }
} }
} }
@ -804,6 +820,7 @@ pub fn process_crate<'l, 'tcx>(tcx: TyCtxt<'l, 'tcx, 'tcx>,
match format { match format {
Format::Csv => dump!(CsvDumper::new(output)), Format::Csv => dump!(CsvDumper::new(output)),
Format::Json => dump!(JsonDumper::new(output)), Format::Json => dump!(JsonDumper::new(output)),
Format::JsonApi => dump!(JsonApiDumper::new(output)),
} }
} }

View File

@ -5,3 +5,4 @@ krate2: krate2.rs
code: foo.rs krate2 code: foo.rs krate2
$(RUSTC) foo.rs -Zsave-analysis-csv $(RUSTC) foo.rs -Zsave-analysis-csv
$(RUSTC) foo.rs -Zsave-analysis $(RUSTC) foo.rs -Zsave-analysis
$(RUSTC) foo.rs -Zsave-analysis-api