Skip to content

Commit

Permalink
Implemented a simple JSON output format (without spans).
Browse files Browse the repository at this point in the history
See rust-lang#108 for details.
  • Loading branch information
ibabushkin committed Jul 22, 2019
1 parent 19d78da commit 056366b
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 19 deletions.
8 changes: 8 additions & 0 deletions src/bin/cargo_semver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ fn run(config: &cargo::Config, matches: &getopts::Matches) -> Result<()> {

let explain = matches.opt_present("e");
let compact = matches.opt_present("compact");
let json = matches.opt_present("json");

// Obtain WorkInfo for the "current"
let current = if let Some(name_and_version) = matches.opt_str("C") {
Expand All @@ -122,6 +123,7 @@ fn run(config: &cargo::Config, matches: &getopts::Matches) -> Result<()> {
};
let name = current.package.name().to_owned();

// TODO: JSON output here
if matches.opt_present("show-public") {
let (current_rlib, current_deps_output) =
current.rlib_and_dep_output(config, &name, true, matches)?;
Expand Down Expand Up @@ -224,6 +226,7 @@ fn run(config: &cargo::Config, matches: &getopts::Matches) -> Result<()> {
.env("RUST_SEMVER_CRATE_VERSION", stable_version)
.env("RUST_SEMVER_VERBOSE", format!("{}", explain))
.env("RUST_SEMVER_COMPACT", format!("{}", compact))
.env("RUST_SEMVER_JSON", format!("{}", json))
.env(
"RUST_SEMVER_API_GUIDELINES",
if matches.opt_present("a") {
Expand Down Expand Up @@ -308,6 +311,11 @@ mod cli {
"compact",
"Only output the suggested version on stdout for further processing",
);
opts.optflag(
"j",
"json",
"Output a JSON-formatted description of all collected data on stdout.",
);
opts.optopt(
"s",
"stable-path",
Expand Down
8 changes: 7 additions & 1 deletion src/bin/rust_semverver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ fn main() {
env::var("RUST_SEMVER_VERBOSE") == Ok("true".to_string());
let compact =
env::var("RUST_SEMVER_COMPACT") == Ok("true".to_string());
let json =
env::var("RUST_SEMVER_JSON") == Ok("true".to_string());
let api_guidelines =
env::var("RUST_SEMVER_API_GUIDELINES") == Ok("true".to_string());
let version = if let Ok(ver) = env::var("RUST_SEMVER_CRATE_VERSION") {
Expand Down Expand Up @@ -81,7 +83,11 @@ fn main() {
if let [(_, old_def_id), (_, new_def_id)] = *crates.as_slice() {
debug!("running semver analysis");
let changes = run_analysis(tcx, old_def_id, new_def_id);
changes.output(tcx.sess, &version, verbose, compact, api_guidelines);
if json {
changes.output_json(&version);
} else {
changes.output(tcx.sess, &version, verbose, compact, api_guidelines);
}
} else {
tcx.sess.err("could not find `old` and `new` crates");
}
Expand Down
145 changes: 127 additions & 18 deletions src/changes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ use std::{
use syntax::symbol::Symbol;
use syntax_pos::Span;

use serde::Serialize;
use serde::ser::{Serializer, SerializeStruct};

/// The categories we use when analyzing changes between crate versions.
///
/// These directly correspond to the semantic versioning spec, with the exception that some
Expand All @@ -31,7 +34,7 @@ use syntax_pos::Span;
/// exotic and/or unlikely scenarios, while we have a separate category for them.
///
/// [1]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub enum ChangeCategory {
/// A patch-level change - no change to the public API of a crate.
Patch,
Expand Down Expand Up @@ -64,33 +67,60 @@ impl<'a> fmt::Display for ChangeCategory {
}
}

pub struct RSymbol(pub Symbol);

impl Serialize for RSymbol {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,
{
serializer.serialize_str(&format!("{}", self.0))
}
}

/// Different ways to refer to a changed item.
///
/// Used in the header of a change description to identify an item that was subject to change.
pub enum Name {
/// The changed item's name.
Symbol(Symbol),
Symbol(RSymbol),
/// A textutal description of the item, used for trait impls.
ImplDesc(String),
}

impl<'a> fmt::Display for Name {
impl Name {
pub fn symbol(symbol: Symbol) -> Self {
Name::Symbol(RSymbol(symbol))
}
}

impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Name::Symbol(name) => write!(f, "`{}`", name),
Name::Symbol(ref name) => write!(f, "`{}`", name.0),
Name::ImplDesc(ref desc) => write!(f, "`{}`", desc),
}
}
}

impl Serialize for Name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,
{
match *self {
Name::Symbol(ref name) => serializer.serialize_str(&format!("{}", name.0)),
Name::ImplDesc(ref desc) => serializer.serialize_str(&format!("{}", desc)),
}
}
}

/// A change record of newly introduced or removed paths to an item.
///
/// NB: `Eq` and `Ord` instances are constructed to only regard the span of the associated item
/// definition. All other spans are only present for later display of the change record.
pub struct PathChange {
/// The name of the item - this doesn't use `Name` because this change structure only gets
/// generated for removals and additions of named items, not impls.
name: Symbol,
name: RSymbol,
/// The definition span of the item.
def_span: Span,
/// The set of spans of added exports of the item.
Expand All @@ -103,7 +133,7 @@ impl PathChange {
/// Construct a new empty path change record for an item.
fn new(name: Symbol, def_span: Span) -> Self {
Self {
name,
name: RSymbol(name),
def_span,
additions: BTreeSet::new(),
removals: BTreeSet::new(),
Expand Down Expand Up @@ -142,7 +172,7 @@ impl PathChange {
return;
}

let msg = format!("path changes to `{}`", self.name);
let msg = format!("path changes to `{}`", self.name.0);
let mut builder = if cat == Breaking {
session.struct_span_err(self.def_span, &msg)
} else {
Expand Down Expand Up @@ -189,6 +219,18 @@ impl Ord for PathChange {
}
}

impl Serialize for PathChange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,
{
let mut state = serializer.serialize_struct("PathChange", 3)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("additions", &!self.additions.is_empty())?;
state.serialize_field("removals", &!self.removals.is_empty())?;
state.end()
}
}

/// The types of changes we identify between items present in both crate versions.
#[derive(Clone, Debug)]
pub enum ChangeType<'tcx> {
Expand Down Expand Up @@ -605,6 +647,14 @@ impl<'a> fmt::Display for ChangeType<'a> {
}
}

impl<'tcx> Serialize for ChangeType<'tcx> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,
{
serializer.serialize_str(&format!("{}", self))
}
}

/// A change record of an item present in both crate versions.
///
/// NB: `Eq` and `Ord` instances are constucted to only regard the *new* span of the associated
Expand Down Expand Up @@ -758,6 +808,21 @@ impl<'tcx> Ord for Change<'tcx> {
}
}

impl<'tcx> Serialize for Change<'tcx> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,
{
let mut state = serializer.serialize_struct("Change", 3)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("max_category", &self.max)?;

let changes: Vec<_> = self.changes.iter().map(|(t, _)| t.clone()).collect();

state.serialize_field("changes", &changes)?;
state.end()
}
}

/// The total set of changes recorded for two crate versions.
#[derive(Default)]
pub struct ChangeSet<'tcx> {
Expand Down Expand Up @@ -811,7 +876,7 @@ impl<'tcx> ChangeSet<'tcx> {
new_span: Span,
output: bool,
) {
let change = Change::new(Name::Symbol(name), new_span, output);
let change = Change::new(Name::symbol(name), new_span, output);

self.spans.insert(old_span, old_def_id);
self.spans.insert(new_span, new_def_id);
Expand Down Expand Up @@ -874,15 +939,7 @@ impl<'tcx> ChangeSet<'tcx> {
.map_or(false, Change::trait_item_breaking)
}

/// Format the contents of a change set for user output.
pub fn output(
&self,
session: &Session,
version: &str,
verbose: bool,
compact: bool,
api_guidelines: bool,
) {
fn get_new_version(&self, version: &str) -> Option<String> {
if let Ok(mut new_version) = Version::parse(version) {
if new_version.major == 0 {
new_version.increment_patch();
Expand All @@ -894,6 +951,41 @@ impl<'tcx> ChangeSet<'tcx> {
}
}

Some(format!("{}", new_version))
} else {
None
}
}

pub fn output_json(&self, version: &str) {
#[derive(Serialize)]
struct Output<'a, 'tcx> {
old_version: String,
new_version: String,
changes: &'a ChangeSet<'tcx>,
}

let new_version = self.get_new_version(version).unwrap_or_else(|| "parse error".to_owned());

let output = Output {
old_version: version.to_owned(),
new_version,
changes: self,
};

println!("{}", serde_json::to_string(&output).unwrap());
}

/// Format the contents of a change set for user output.
pub fn output(
&self,
session: &Session,
version: &str,
verbose: bool,
compact: bool,
api_guidelines: bool,
) {
if let Some(new_version) = self.get_new_version(version) {
if compact {
println!("{}", new_version);
} else {
Expand Down Expand Up @@ -932,6 +1024,23 @@ impl<'tcx> ChangeSet<'tcx> {
}
}

impl<'tcx> Serialize for ChangeSet<'tcx> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,
{
let mut state = serializer.serialize_struct("ChangeSet", 3)?;

let path_changes: Vec<_> = self.path_changes.values().collect();
state.serialize_field("path_changes", &path_changes)?;

let changes: Vec<_> = self.changes.values().filter(|c| c.output && !c.changes.is_empty()).collect();
state.serialize_field("changes", &changes)?;

state.serialize_field("max_category", &self.max)?;
state.end()
}
}

#[cfg(test)]
pub mod tests {
pub use super::*;
Expand Down Expand Up @@ -1150,7 +1259,7 @@ pub mod tests {
changes: Vec<(ChangeType_, Option<Span_>)>,
) -> Change<'a> {
let mut interner = Interner::default();
let mut change = Change::new(Name::Symbol(interner.intern("test")), s1, output);
let mut change = Change::new(Name::Symbol(RSymbol(interner.intern("test"))), s1, output);

for (type_, span) in changes {
change.insert(type_.inner(), span.map(|s| s.inner()));
Expand Down

0 comments on commit 056366b

Please sign in to comment.