Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #3 and #4: Add field documentation and annotation #6

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 76 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,18 @@ enum Fields {

/// Defines a struct field.
#[derive(Debug, Clone)]
struct Field {
pub struct Field {
/// Field name
name: String,

/// Field type
ty: Type,

/// Field documentation
documentation: Vec<String>,

/// Field annotation
annotation: Vec<String>,
}

/// Defines an associated type.
Expand Down Expand Up @@ -582,6 +588,16 @@ impl Struct {
self
}

/// Push a named field to the struct.
///
/// A struct can either set named fields with this function or tuple fields
/// with `push_tuple_field`, but not both.
pub fn push_field(&mut self, field: Field) -> &mut Self
{
self.fields.push_named(field);
self
}

/// Add a named field to the struct.
///
/// A struct can either set named fields with this function or tuple fields
Expand Down Expand Up @@ -1073,31 +1089,63 @@ impl AssociatedType {
}
}

// ===== impl Field =====

impl Field {
/// Return a field definition with the provided name and type
pub fn new<T>(name: &str, ty: T) -> Self
where T: Into<Type>,
{
Field {
name: name.into(),
ty: ty.into(),
documentation: Vec::new(),
annotation: Vec::new(),
}
}

/// Set field's documentation.
pub fn doc(&mut self, documentation: Vec<&str>) -> &mut Self {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe doc(&mut self, documentation: &str) like import(&mut self, path: &str, ty: &str)?

self.documentation = documentation.iter().map(|doc| doc.to_string()).collect();
self
}

/// Set field's annotation.
pub fn annotation(&mut self, annotation: Vec<&str>) -> &mut Self {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe annotation(&mut self, annotation: &str) like import(&mut self, path: &str, ty: &str)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow. Do you mean using a &str instead of a Vec<&str> for the annotation() method?

It was changed to use a Vec in 6e17dc2 to support multiple lines.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant a method to add one annotation line per call, like you do for import(...) - each call results in a new import - instead of passing the whole Vec with all lines :)

self.annotation = annotation.iter().map(|ann| ann.to_string()).collect();
self
}
}

// ===== impl Fields =====

impl Fields {
fn named<T>(&mut self, name: &str, ty: T) -> &mut Self
where T: Into<Type>,
fn push_named(&mut self, field: Field) -> &mut Self
{
match *self {
Fields::Empty => {
*self = Fields::Named(vec![Field {
name: name.to_string(),
ty: ty.into(),
}]);
*self = Fields::Named(vec![field]);
}
Fields::Named(ref mut fields) => {
fields.push(Field {
name: name.to_string(),
ty: ty.into(),
});
fields.push(field);
}
_ => panic!("field list is named"),
}

self
}

fn named<T>(&mut self, name: &str, ty: T) -> &mut Self
where T: Into<Type>,
{
self.push_named(Field {
name: name.to_string(),
ty: ty.into(),
documentation: Vec::new(),
annotation: Vec::new(),
})
}

fn tuple<T>(&mut self, ty: T) -> &mut Self
where T: Into<Type>,
{
Expand All @@ -1121,6 +1169,16 @@ impl Fields {

fmt.block(|fmt| {
for f in fields {
if !f.documentation.is_empty() {
for doc in &f.documentation {
write!(fmt, "/// {}\n", doc)?;
}
}
if !f.annotation.is_empty() {
for ann in &f.annotation {
write!(fmt, "{}\n", ann)?;
}
}
write!(fmt, "{}: ", f.name)?;
f.ty.fmt(fmt)?;
write!(fmt, ",\n")?;
Expand Down Expand Up @@ -1196,6 +1254,8 @@ impl Impl {
self.assoc_tys.push(Field {
name: name.to_string(),
ty: ty.into(),
documentation: Vec::new(),
annotation: Vec::new(),
});

self
Expand Down Expand Up @@ -1340,6 +1400,11 @@ impl Function {
self.args.push(Field {
name: name.to_string(),
ty: ty.into(),
// While a `Field` is used here, both `documentation`
// and `annotation` does not make sense for function arguments.
// Simply use empty strings.
documentation: Vec::new(),
annotation: Vec::new(),
});

self
Expand Down
60 changes: 59 additions & 1 deletion tests/codegen.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extern crate codegen;

use codegen::Scope;
use codegen::{Field, Scope, Struct};

#[test]
fn empty_scope() {
Expand All @@ -26,6 +26,64 @@ struct Foo {
assert_eq!(scope.to_string(), &expect[1..]);
}

#[test]
fn struct_with_pushed_field() {
let mut scope = Scope::new();
let mut struct_ = Struct::new("Foo");
let mut field = Field::new("one", "usize");
struct_.push_field(field);
scope.push_struct(struct_);

let expect = r#"
struct Foo {
one: usize,
}"#;

assert_eq!(scope.to_string(), &expect[1..]);
}

#[test]
fn single_struct_documented_field() {
let mut scope = Scope::new();

let doc = vec!["Field's documentation", "Second line"];

let mut struct_ = Struct::new("Foo");

let mut field1 = Field::new("one", "usize");
field1.doc(doc.clone());
struct_.push_field(field1);

let mut field2 = Field::new("two", "usize");
field2.annotation(vec![r#"#[serde(rename = "bar")]"#]);
struct_.push_field(field2);

let mut field3 = Field::new("three", "usize");
field3.doc(doc).annotation(vec![
r#"#[serde(skip_serializing)]"#,
r#"#[serde(skip_deserializing)]"#,
]);
struct_.push_field(field3);

scope.push_struct(struct_);

let expect = r#"
struct Foo {
/// Field's documentation
/// Second line
one: usize,
#[serde(rename = "bar")]
two: usize,
/// Field's documentation
/// Second line
#[serde(skip_serializing)]
#[serde(skip_deserializing)]
three: usize,
}"#;

assert_eq!(scope.to_string(), &expect[1..]);
}

#[test]
fn empty_struct() {
let mut scope = Scope::new();
Expand Down