diff --git a/src/format.rs b/src/format.rs index 62f2f9d..0983391 100644 --- a/src/format.rs +++ b/src/format.rs @@ -18,12 +18,14 @@ pub(crate) struct Formatter<'a> { #[derive(Debug, PartialEq, Clone)] pub struct Style { indent: u32, + multiline_arguments: bool, } impl Default for Style { fn default() -> Style { Style { indent: 2, + multiline_arguments: false, } } } @@ -34,6 +36,12 @@ impl Style { self.indent = indent; self } + + /// Set whether to add new lines between arguments + pub fn multiline_arguments(&mut self, multiline_arguments: bool) -> &mut Self { + self.multiline_arguments = multiline_arguments; + self + } } pub(crate) trait Displayable { @@ -59,15 +67,44 @@ impl<'a> Formatter<'a> { self.buf.push('\n'); } + pub fn start_argument_block(&mut self, open_char: char) { + self.buf.push(open_char); + if self.style.multiline_arguments { + self.inc_indent(); + } + } + + pub fn end_argument_block(&mut self, close_char: char) { + if self.style.multiline_arguments { + self.endline(); + self.dec_indent(); + self.indent(); + } + self.buf.push(close_char); + } + + pub fn start_argument(&mut self) { + if self.style.multiline_arguments { + self.endline(); + self.indent(); + } + } + + pub fn deliniate_argument(&mut self) { + self.buf.push(','); + if !self.style.multiline_arguments { + self.buf.push(' '); + } + } + pub fn start_block(&mut self) { self.buf.push('{'); self.endline(); - self.indent += self.style.indent; + self.inc_indent(); } pub fn end_block(&mut self) { - self.indent = self.indent.checked_sub(self.style.indent) - .expect("negative indent"); + self.dec_indent(); self.indent(); self.buf.push('}'); self.endline(); @@ -128,6 +165,15 @@ impl<'a> Formatter<'a> { self.buf.push_str(r#"""""#); } } + + fn inc_indent(&mut self) { + self.indent += self.style.indent; + } + + fn dec_indent(&mut self) { + self.indent = self.indent.checked_sub(self.style.indent) + .expect("negative indent"); + } } pub(crate) fn format_directives<'a, T>(dirs: &[Directive<'a, T>], f: &mut Formatter) diff --git a/src/query/format.rs b/src/query/format.rs index be78e7d..0a8ba4b 100644 --- a/src/query/format.rs +++ b/src/query/format.rs @@ -1,11 +1,11 @@ use std::fmt; -use crate::format::{Displayable, Formatter, Style, format_directives}; +use crate::format::{format_directives, Displayable, Formatter, Style}; use crate::query::ast::*; -impl<'a, T: Text<'a>> Document<'a, T> +impl<'a, T: Text<'a>> Document<'a, T> where T: Text<'a>, { /// Format a document according to style @@ -23,7 +23,7 @@ fn to_string(v: &T) -> String { formatter.into_string() } -impl<'a, T: Text<'a>> Displayable for Document<'a, T> +impl<'a, T: Text<'a>> Displayable for Document<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -33,7 +33,7 @@ impl<'a, T: Text<'a>> Displayable for Document<'a, T> } } -impl<'a, T: Text<'a>> Displayable for Definition<'a, T> +impl<'a, T: Text<'a>> Displayable for Definition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -44,7 +44,7 @@ impl<'a, T: Text<'a>> Displayable for Definition<'a, T> } } -impl<'a, T: Text<'a>> Displayable for OperationDefinition<'a, T> +impl<'a, T: Text<'a>> Displayable for OperationDefinition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -57,7 +57,7 @@ impl<'a, T: Text<'a>> Displayable for OperationDefinition<'a, T> } } -impl<'a, T: Text<'a>> Displayable for FragmentDefinition<'a, T> +impl<'a, T: Text<'a>> Displayable for FragmentDefinition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -77,7 +77,7 @@ impl<'a, T: Text<'a>> Displayable for FragmentDefinition<'a, T> } } -impl<'a, T: Text<'a>> Displayable for SelectionSet<'a, T> +impl<'a, T: Text<'a>> Displayable for SelectionSet<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -91,7 +91,7 @@ impl<'a, T: Text<'a>> Displayable for SelectionSet<'a, T> } } -impl<'a, T: Text<'a>> Displayable for Selection<'a, T> +impl<'a, T: Text<'a>> Displayable for Selection<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -103,25 +103,27 @@ impl<'a, T: Text<'a>> Displayable for Selection<'a, T> } } -fn format_arguments<'a, T: Text<'a>>(arguments: &[(T::Value, Value<'a, T>)], f: &mut Formatter) +fn format_arguments<'a, T: Text<'a>>(arguments: &[(T::Value, Value<'a, T>)], f: &mut Formatter) where T: Text<'a>, { if !arguments.is_empty() { - f.write("("); - f.write(arguments[0].0.as_ref()); + f.start_argument_block('('); + f.start_argument(); + f.write(&arguments[0].0.as_ref()); f.write(": "); arguments[0].1.display(f); for arg in &arguments[1..] { - f.write(", "); - f.write(arg.0.as_ref()); + f.deliniate_argument(); + f.start_argument(); + f.write(&arg.0.as_ref()); f.write(": "); arg.1.display(f); } - f.write(")"); + f.end_argument_block(')'); } } -impl<'a, T: Text<'a>> Displayable for Field<'a, T> +impl<'a, T: Text<'a>> Displayable for Field<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -146,7 +148,7 @@ impl<'a, T: Text<'a>> Displayable for Field<'a, T> } } -impl<'a, T: Text<'a>> Displayable for Query<'a, T> +impl<'a, T: Text<'a>> Displayable for Query<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -176,7 +178,7 @@ impl<'a, T: Text<'a>> Displayable for Query<'a, T> } } -impl<'a, T: Text<'a>> Displayable for Mutation<'a, T> +impl<'a, T: Text<'a>> Displayable for Mutation<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -206,7 +208,7 @@ impl<'a, T: Text<'a>> Displayable for Mutation<'a, T> } } -impl<'a, T: Text<'a>> Displayable for Subscription<'a, T> +impl<'a, T: Text<'a>> Displayable for Subscription<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -234,7 +236,7 @@ impl<'a, T: Text<'a>> Displayable for Subscription<'a, T> } } -impl<'a, T: Text<'a>> Displayable for VariableDefinition<'a, T> +impl<'a, T: Text<'a>> Displayable for VariableDefinition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -249,7 +251,7 @@ impl<'a, T: Text<'a>> Displayable for VariableDefinition<'a, T> } } -impl<'a, T: Text<'a>> Displayable for Type<'a, T> +impl<'a, T: Text<'a>> Displayable for Type<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -268,12 +270,15 @@ impl<'a, T: Text<'a>> Displayable for Type<'a, T> } } -impl<'a, T: Text<'a>> Displayable for Value<'a, T> +impl<'a, T: Text<'a>> Displayable for Value<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { match *self { - Value::Variable(ref name) => { f.write("$"); f.write(name.as_ref()); }, + Value::Variable(ref name) => { + f.write("$"); + f.write(name.as_ref()); + } Value::Int(ref num) => f.write(&format!("{}", num.0)), Value::Float(val) => f.write(&format!("{}", val)), Value::String(ref val) => f.write_quoted(val), @@ -282,36 +287,39 @@ impl<'a, T: Text<'a>> Displayable for Value<'a, T> Value::Null => f.write("null"), Value::Enum(ref name) => f.write(name.as_ref()), Value::List(ref items) => { - f.write("["); + f.start_argument_block('['); if !items.is_empty() { + f.start_argument(); items[0].display(f); for item in &items[1..] { - f.write(", "); + f.deliniate_argument(); + f.start_argument(); item.display(f); } } - f.write("]"); + f.end_argument_block(']'); } Value::Object(ref items) => { - f.write("{"); + f.start_argument_block('{'); let mut first = true; for (name, value) in items.iter() { if first { first = false; } else { - f.write(", "); + f.deliniate_argument(); } + f.start_argument(); f.write(name.as_ref()); f.write(": "); value.display(f); } - f.write("}"); + f.end_argument_block('}'); } } } } -impl<'a, T: Text<'a>> Displayable for InlineFragment<'a, T> +impl<'a, T: Text<'a>> Displayable for InlineFragment<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -331,7 +339,7 @@ impl<'a, T: Text<'a>> Displayable for InlineFragment<'a, T> } } -impl<'a, T: Text<'a>> Displayable for TypeCondition<'a, T> +impl<'a, T: Text<'a>> Displayable for TypeCondition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -344,7 +352,7 @@ impl<'a, T: Text<'a>> Displayable for TypeCondition<'a, T> } } -impl<'a, T: Text<'a>> Displayable for FragmentSpread<'a, T> +impl<'a, T: Text<'a>> Displayable for FragmentSpread<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -356,7 +364,7 @@ impl<'a, T: Text<'a>> Displayable for FragmentSpread<'a, T> } } -impl<'a, T: Text<'a>> Displayable for Directive<'a, T> +impl<'a, T: Text<'a>> Displayable for Directive<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { @@ -366,9 +374,8 @@ impl<'a, T: Text<'a>> Displayable for Directive<'a, T> } } - impl_display!( - 'a + 'a Document, Definition, OperationDefinition, diff --git a/tests/queries/directive_args_multiline.graphql b/tests/queries/directive_args_multiline.graphql new file mode 100644 index 0000000..664028a --- /dev/null +++ b/tests/queries/directive_args_multiline.graphql @@ -0,0 +1,9 @@ +query { + node @dir( + a: 1, + b: "2", + c: true, + d: false, + e: null + ) +} diff --git a/tests/queries/query_arguments_multiline.graphql b/tests/queries/query_arguments_multiline.graphql new file mode 100644 index 0000000..1447e76 --- /dev/null +++ b/tests/queries/query_arguments_multiline.graphql @@ -0,0 +1,9 @@ +query { + node( + id: 1 + ) + node( + id: 1, + one: 3 + ) +} diff --git a/tests/queries/query_array_argument_multiline.graphql b/tests/queries/query_array_argument_multiline.graphql new file mode 100644 index 0000000..caa6144 --- /dev/null +++ b/tests/queries/query_array_argument_multiline.graphql @@ -0,0 +1,9 @@ +query { + node( + id: [ + 5, + 6, + 7 + ] + ) +} diff --git a/tests/queries/query_object_argument_multiline.graphql b/tests/queries/query_object_argument_multiline.graphql new file mode 100644 index 0000000..b6a55c8 --- /dev/null +++ b/tests/queries/query_object_argument_multiline.graphql @@ -0,0 +1,9 @@ +query { + node( + id: 1, + obj: { + key1: 123, + key2: 456 + } + ) +} diff --git a/tests/query_roundtrips.rs b/tests/query_roundtrips.rs index 2eaeb1c..5c0725a 100644 --- a/tests/query_roundtrips.rs +++ b/tests/query_roundtrips.rs @@ -1,18 +1,28 @@ extern crate graphql_parser; -#[cfg(test)] #[macro_use] extern crate pretty_assertions; +#[cfg(test)] +#[macro_use] +extern crate pretty_assertions; -use std::io::Read; use std::fs::File; +use std::io::Read; + +use graphql_parser::{parse_query, Style}; + +fn roundtrip_multiline_args(filename: &str) { + roundtrip(filename, Style::default().multiline_arguments(true)) +} -use graphql_parser::parse_query; +fn roundtrip_default(filename: &str) { + roundtrip(filename, &Style::default()) +} -fn roundtrip(filename: &str) { +fn roundtrip(filename: &str, style: &Style) { let mut buf = String::with_capacity(1024); let path = format!("tests/queries/{}.graphql", filename); let mut f = File::open(&path).unwrap(); f.read_to_string(&mut buf).unwrap(); let ast = parse_query::(&buf).unwrap().to_owned(); - assert_eq!(ast.to_string(), buf); + assert_eq!(ast.format(style), buf); } fn roundtrip2(filename: &str) { @@ -29,32 +39,135 @@ fn roundtrip2(filename: &str) { assert_eq!(ast.to_string(), buf); } -#[test] fn minimal() { roundtrip("minimal"); } -#[test] fn minimal_query() { roundtrip("minimal_query"); } -#[test] fn named_query() { roundtrip("named_query"); } -#[test] fn query_vars() { roundtrip("query_vars"); } -#[test] fn query_nameless_vars() { roundtrip("query_nameless_vars"); } -#[test] fn query_nameless_vars_multiple_fields() { roundtrip2("query_nameless_vars_multiple_fields"); } -#[test] fn query_var_defaults() { roundtrip("query_var_defaults"); } -#[test] fn query_var_defaults1() { roundtrip("query_var_default_string"); } -#[test] fn query_var_defaults2() { roundtrip("query_var_default_float"); } -#[test] fn query_var_defaults3() { roundtrip("query_var_default_list"); } -#[test] fn query_var_defaults4() { roundtrip("query_var_default_object"); } -#[test] fn query_aliases() { roundtrip("query_aliases"); } -#[test] fn query_arguments() { roundtrip("query_arguments"); } -#[test] fn query_directive() { roundtrip("query_directive"); } -#[test] fn mutation_directive() { roundtrip("mutation_directive"); } -#[test] fn mutation_nameless_vars() { roundtrip("mutation_nameless_vars"); } -#[test] fn subscription_directive() { roundtrip("subscription_directive"); } -#[test] fn string_literal() { roundtrip("string_literal"); } -#[test] fn triple_quoted_literal() { roundtrip("triple_quoted_literal"); } -#[test] fn query_list_arg() { roundtrip("query_list_argument"); } -#[test] fn query_object_arg() { roundtrip("query_object_argument"); } -#[test] fn nested_selection() { roundtrip("nested_selection"); } -#[test] fn inline_fragment() { roundtrip("inline_fragment"); } -#[test] fn inline_fragment_dir() { roundtrip("inline_fragment_dir"); } -#[test] fn fragment_spread() { roundtrip("fragment_spread"); } -#[test] fn minimal_mutation() { roundtrip("minimal_mutation"); } -#[test] fn fragment() { roundtrip("fragment"); } -#[test] fn directive_args() { roundtrip("directive_args"); } -#[test] fn kitchen_sink() { roundtrip2("kitchen-sink"); } +#[test] +fn minimal() { + roundtrip_default("minimal"); +} +#[test] +fn minimal_query() { + roundtrip_default("minimal_query"); +} +#[test] +fn named_query() { + roundtrip_default("named_query"); +} +#[test] +fn query_vars() { + roundtrip_default("query_vars"); +} +#[test] +fn query_nameless_vars() { + roundtrip_default("query_nameless_vars"); +} +#[test] +fn query_nameless_vars_multiple_fields() { + roundtrip2("query_nameless_vars_multiple_fields"); +} +#[test] +fn query_var_defaults() { + roundtrip_default("query_var_defaults"); +} +#[test] +fn query_var_defaults1() { + roundtrip_default("query_var_default_string"); +} +#[test] +fn query_var_defaults2() { + roundtrip_default("query_var_default_float"); +} +#[test] +fn query_var_defaults3() { + roundtrip_default("query_var_default_list"); +} +#[test] +fn query_var_defaults4() { + roundtrip_default("query_var_default_object"); +} +#[test] +fn query_aliases() { + roundtrip_default("query_aliases"); +} +#[test] +fn query_arguments() { + roundtrip_default("query_arguments"); +} +#[test] +fn query_arguments_multiline() { + roundtrip_multiline_args("query_arguments_multiline"); +} +#[test] +fn query_directive() { + roundtrip_default("query_directive"); +} +#[test] +fn mutation_directive() { + roundtrip_default("mutation_directive"); +} +#[test] +fn mutation_nameless_vars() { + roundtrip("mutation_nameless_vars"); +} +#[test] +fn subscription_directive() { + roundtrip_default("subscription_directive"); +} +#[test] +fn string_literal() { + roundtrip_default("string_literal"); +} +#[test] +fn triple_quoted_literal() { + roundtrip_default("triple_quoted_literal"); +} +#[test] +fn query_list_arg() { + roundtrip_default("query_list_argument"); +} +#[test] +fn query_object_arg() { + roundtrip_default("query_object_argument"); +} +#[test] +fn query_object_arg_multiline() { + roundtrip_multiline_args("query_object_argument_multiline"); +} +#[test] +fn query_array_arg_multiline() { + roundtrip_multiline_args("query_array_argument_multiline"); +} +#[test] +fn nested_selection() { + roundtrip_default("nested_selection"); +} +#[test] +fn inline_fragment() { + roundtrip_default("inline_fragment"); +} +#[test] +fn inline_fragment_dir() { + roundtrip_default("inline_fragment_dir"); +} +#[test] +fn fragment_spread() { + roundtrip_default("fragment_spread"); +} +#[test] +fn minimal_mutation() { + roundtrip_default("minimal_mutation"); +} +#[test] +fn fragment() { + roundtrip_default("fragment"); +} +#[test] +fn directive_args() { + roundtrip_default("directive_args"); +} +#[test] +fn directive_args_multiline() { + roundtrip_multiline_args("directive_args_multiline"); +} +#[test] +fn kitchen_sink() { + roundtrip2("kitchen-sink"); +} \ No newline at end of file