From 6139c4c189200b030d69016fcf93a192f370b6ff Mon Sep 17 00:00:00 2001 From: Dylan Owen Date: Sat, 6 Jul 2019 16:20:54 -0700 Subject: [PATCH 1/3] Added the ability to add newlines when formatting arguments --- src/format.rs | 52 +++++++++++++- src/query/format.rs | 15 +++-- .../queries/directive_args_multiline.graphql | 9 +++ .../queries/query_arguments_multiline.graphql | 9 +++ .../query_object_argument_multiline.graphql | 9 +++ tests/query_roundtrips.rs | 67 +++++++++++-------- 6 files changed, 124 insertions(+), 37 deletions(-) create mode 100644 tests/queries/directive_args_multiline.graphql create mode 100644 tests/queries/query_arguments_multiline.graphql create mode 100644 tests/queries/query_object_argument_multiline.graphql diff --git a/src/format.rs b/src/format.rs index 6935d93..3f1639e 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(dirs: &[Directive], f: &mut Formatter) { diff --git a/src/query/format.rs b/src/query/format.rs index 0292c0e..4063f0e 100644 --- a/src/query/format.rs +++ b/src/query/format.rs @@ -91,17 +91,19 @@ impl Displayable for Selection { fn format_arguments(arguments: &[(String, Value)], f: &mut Formatter) { if !arguments.is_empty() { - f.write("("); + f.start_argument_block('('); + f.start_argument(); f.write(&arguments[0].0); f.write(": "); arguments[0].1.display(f); for arg in &arguments[1..] { - f.write(", "); + f.deliniate_argument(); + f.start_argument(); f.write(&arg.0); f.write(": "); arg.1.display(f); } - f.write(")"); + f.end_argument_block(')'); } } @@ -261,19 +263,20 @@ impl Displayable for Value { f.write("]"); } 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); f.write(": "); value.display(f); } - f.write("}"); + f.end_argument_block('}'); } } } 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_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 962a9f4..8c47337 100644 --- a/tests/query_roundtrips.rs +++ b/tests/query_roundtrips.rs @@ -4,15 +4,23 @@ extern crate graphql_parser; use std::io::Read; use std::fs::File; -use graphql_parser::parse_query; +use graphql_parser::{parse_query, Style}; -fn roundtrip(filename: &str) { +fn roundtrip_multiline_args(filename: &str) { + roundtrip(filename, Style::default().multiline_arguments(true)) +} + +fn roundtrip_default(filename: &str) { + roundtrip(filename, &Style::default()) +} + +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(); - assert_eq!(ast.to_string(), buf); + assert_eq!(ast.format(style), buf); } fn roundtrip2(filename: &str) { @@ -29,29 +37,32 @@ 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_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 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 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_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 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 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"); } From 04f99c8b931b04bcfe177eb1ae7f9776fcc0b177 Mon Sep 17 00:00:00 2001 From: Dylan Owen Date: Sat, 6 Jul 2019 16:41:00 -0700 Subject: [PATCH 2/3] added support for multiline array arguments --- src/query/format.rs | 8 +++++--- tests/queries/query_array_argument_multiline.graphql | 9 +++++++++ tests/query_roundtrips.rs | 2 ++ 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 tests/queries/query_array_argument_multiline.graphql diff --git a/src/query/format.rs b/src/query/format.rs index 4063f0e..e86c431 100644 --- a/src/query/format.rs +++ b/src/query/format.rs @@ -252,15 +252,17 @@ impl Displayable for Value { Value::Null => f.write("null"), Value::Enum(ref name) => f.write(name), 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.start_argument_block('{'); 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/query_roundtrips.rs b/tests/query_roundtrips.rs index 8c47337..a765c9b 100644 --- a/tests/query_roundtrips.rs +++ b/tests/query_roundtrips.rs @@ -20,6 +20,7 @@ fn roundtrip(filename: &str, style: &Style) { let mut f = File::open(&path).unwrap(); f.read_to_string(&mut buf).unwrap(); let ast = parse_query(&buf).unwrap(); + println!("{}", ast.format(style)); assert_eq!(ast.format(style), buf); } @@ -57,6 +58,7 @@ fn roundtrip2(filename: &str) { #[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"); } From ad877f2abf044f3ca74b54af8124af755588891b Mon Sep 17 00:00:00 2001 From: "dylan.owen" Date: Mon, 20 Sep 2021 11:16:51 -0700 Subject: [PATCH 3/3] uptake new changes --- src/query/format.rs | 54 ++++++++++++++++++++------------------- src/query/grammar.rs | 9 +------ tests/query_roundtrips.rs | 3 +-- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/query/format.rs b/src/query/format.rs index 9c45066..050cee5 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,19 +103,19 @@ 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.start_argument_block('('); f.start_argument(); - f.write(&arguments[0].0); + f.write(&arguments[0].0.as_ref()); f.write(": "); arguments[0].1.display(f); for arg in &arguments[1..] { f.deliniate_argument(); f.start_argument(); - f.write(&arg.0); + f.write(&arg.0.as_ref()); f.write(": "); arg.1.display(f); } @@ -123,7 +123,7 @@ fn format_arguments<'a, T: Text<'a>>(arguments: &[(T::Value, Value<'a, T>)], f: } } -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) { @@ -148,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) { @@ -178,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 +206,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 +234,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 +249,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 +268,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), @@ -304,7 +307,7 @@ impl<'a, T: Text<'a>> Displayable for Value<'a, T> f.deliniate_argument(); } f.start_argument(); - f.write(name); + f.write(name.as_ref()); f.write(": "); value.display(f); } @@ -314,7 +317,7 @@ impl<'a, T: Text<'a>> Displayable for Value<'a, T> } } -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) { @@ -334,7 +337,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) { @@ -347,7 +350,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) { @@ -359,7 +362,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) { @@ -369,9 +372,8 @@ impl<'a, T: Text<'a>> Displayable for Directive<'a, T> } } - impl_display!( - 'a + 'a Document, Definition, OperationDefinition, diff --git a/src/query/grammar.rs b/src/query/grammar.rs index 494039b..ecdbd10 100644 --- a/src/query/grammar.rs +++ b/src/query/grammar.rs @@ -193,7 +193,7 @@ pub fn definition<'a, S>(input: &mut TokenStream<'a>) } /// Parses a piece of query language and returns an AST -pub fn parse_query<'a, S>(s: &'a str) -> Result, ParseError> +pub fn parse_query<'a, S>(s: &'a str) -> Result, ParseError> where S: Text<'a>, { let mut tokens = TokenStream::new(s); @@ -329,11 +329,4 @@ mod test { let err = format!("{}", err); assert_eq!(err, "query parse error: Parse error at 1:1\nUnexpected `where[Name]`\nExpected `{`, `query`, `mutation`, `subscription` or `fragment`\n"); } - - fn recursion_too_deep() { - let query = format!("{}(b: {}{}){}", "{ a".repeat(30), "[".repeat(25), "]".repeat(25), "}".repeat(30)); - let result = parse_query::<&str>(&query); - let err = format!("{}", result.unwrap_err()); - assert_eq!(&err, "query parse error: Parse error at 1:114\nExpected `]`\nRecursion limit exceeded\n") - } } diff --git a/tests/query_roundtrips.rs b/tests/query_roundtrips.rs index 97043fc..ecbb8c1 100644 --- a/tests/query_roundtrips.rs +++ b/tests/query_roundtrips.rs @@ -21,8 +21,7 @@ fn roundtrip(filename: &str, style: &Style) { 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(); - println!("{}", ast.format(style)); + let ast = parse_query::(&buf).unwrap().to_owned(); assert_eq!(ast.format(style), buf); }