diff --git a/.vscode/settings.json b/.vscode/settings.json index 9083f916a7f39..b35f976fbde47 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,9 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "prettier.enable": true, + "rust-analyzer.rustfmt.extraArgs": [ + "+nightly" + ], // Enable these to get Relay GraphQL editor support working on test files. diff --git a/compiler/Cargo.lock b/compiler/Cargo.lock index 40707328fbc50..edf32f54aa68b 100644 --- a/compiler/Cargo.lock +++ b/compiler/Cargo.lock @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.10" +version = "3.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69c5a7f9997be616e47f0577ee38c91decb33392c5be4866494f34cdf329a9aa" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" dependencies = [ "atty", "bitflags", @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.7" +version = "3.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -402,6 +402,27 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "errors" version = "0.0.0" @@ -490,9 +511,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.13" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -505,9 +526,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.13" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -515,15 +536,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.13" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.13" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -532,17 +553,16 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.13" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-macro" -version = "0.3.13" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -550,21 +570,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.13" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.13" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.13" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures 0.1.31", "futures-channel", @@ -576,8 +596,6 @@ dependencies = [ "memchr", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] @@ -692,6 +710,7 @@ dependencies = [ "graphql-syntax", "intern", "relay-test-schema", + "relay-transforms", "schema", ] @@ -726,9 +745,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", "serde", @@ -847,9 +866,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", "hashbrown", @@ -895,6 +914,12 @@ dependencies = [ "serde", ] +[[package]] +name = "io-lifetimes" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" + [[package]] name = "iovec" version = "0.1.4" @@ -952,9 +977,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.112" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "linux-raw-sys" +version = "0.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" [[package]] name = "lock_api" @@ -1314,18 +1345,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - [[package]] name = "proc-macro2" version = "1.0.39" @@ -1488,7 +1507,7 @@ dependencies = [ "extract-graphql", "fixture-tests", "fnv", - "futures 0.3.13", + "futures 0.3.24", "glob", "graphql-cli", "graphql-ir", @@ -1689,6 +1708,20 @@ dependencies = [ "schema", ] +[[package]] +name = "rustix" +version = "0.35.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.5" @@ -2042,19 +2075,19 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.1.17" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +checksum = "8440c860cf79def6164e4a0a983bcc2305d82419177a0e0c71930d049e3ac5a1" dependencies = [ - "libc", - "winapi", + "rustix", + "windows-sys", ] [[package]] name = "textwrap" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" dependencies = [ "terminal_size", "unicode-width", @@ -2321,7 +2354,7 @@ checksum = "839fea2d85719bb69089290d7970bba2131f544448db8f990ea75813c30775ca" dependencies = [ "anyhow", "bytes 1.0.1", - "futures 0.3.13", + "futures 0.3.24", "maplit", "serde", "serde_bser", @@ -2362,6 +2395,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "zstd" version = "0.11.1+zstd.1.5.2" diff --git a/compiler/crates/common/src/feature_flags.rs b/compiler/crates/common/src/feature_flags.rs index 78bb5882b8a1f..7349cf11f15ee 100644 --- a/compiler/crates/common/src/feature_flags.rs +++ b/compiler/crates/common/src/feature_flags.rs @@ -58,6 +58,10 @@ pub struct FeatureFlags { /// Enable support for the experimental `@alias` directive on fragment spreads. #[serde(default)] pub enable_fragment_aliases: FeatureFlag, + + /// Print queries in compact form + #[serde(default)] + pub compact_query_text: FeatureFlag, } #[derive(Debug, Deserialize, Clone, Serialize)] diff --git a/compiler/crates/graphql-ir/src/node_identifier.rs b/compiler/crates/graphql-ir/src/node_identifier.rs index 964273266262c..7277d01f3129d 100644 --- a/compiler/crates/graphql-ir/src/node_identifier.rs +++ b/compiler/crates/graphql-ir/src/node_identifier.rs @@ -13,8 +13,11 @@ use std::sync::Arc; use common::DirectiveName; use common::WithLocation; +use graphql_syntax::OperationKind; use intern::string_key::StringKey; +use schema::FieldID; use schema::SDLSchema; +use schema::Type; use crate::*; @@ -277,7 +280,7 @@ impl LocationAgnosticPartialEq for WithLocation } } -impl LocationAgnosticPartialEq for Option<&T> { +impl LocationAgnosticPartialEq for Option { fn location_agnostic_eq(&self, other: &Self) -> bool { match (self, other) { (Some(l), Some(r)) => l.location_agnostic_eq::(r), @@ -304,6 +307,9 @@ pub trait DirectlyComparableIR {} impl DirectlyComparableIR for Value {} impl DirectlyComparableIR for ConstantArgument {} impl DirectlyComparableIR for ConstantValue {} +impl DirectlyComparableIR for Selection {} +impl DirectlyComparableIR for ExecutableDefinition {} +impl DirectlyComparableIR for VariableDefinition {} impl LocationAgnosticPartialEq for Vec { fn location_agnostic_eq(&self, other: &Self) -> bool { @@ -415,6 +421,16 @@ impl LocationAgnosticPartialEq for Argument { } } +impl LocationAgnosticPartialEq for Option<&Argument> { + fn location_agnostic_eq(&self, other: &Self) -> bool { + match (self, other) { + (Some(l), Some(r)) => l.location_agnostic_eq::(r), + (None, None) => true, + _ => false, + } + } +} + impl LocationAgnosticHash for Value { fn location_agnostic_hash(&self, state: &mut H) { match self { @@ -534,3 +550,157 @@ impl LocationAgnosticPartialEq for ConditionValue { } } } + +impl LocationAgnosticPartialEq for ExecutableDefinition { + fn location_agnostic_eq(&self, other: &Self) -> bool { + match (self, other) { + (ExecutableDefinition::Operation(left), ExecutableDefinition::Operation(right)) => { + left.location_agnostic_eq::(right) + } + (ExecutableDefinition::Fragment(left), ExecutableDefinition::Fragment(right)) => { + left.location_agnostic_eq::(right) + } + _ => false, + } + } +} + +impl LocationAgnosticPartialEq for VariableDefinition { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.name.location_agnostic_eq::(&other.name) + && self.type_ == other.type_ + && self + .default_value + .location_agnostic_eq::(&other.default_value) + && self.directives.location_agnostic_eq::(&other.directives) + } +} + +impl LocationAgnosticPartialEq for OperationDefinition { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.kind.location_agnostic_eq::(&other.kind) + && self.name.location_agnostic_eq::(&other.name) + && self.type_.location_agnostic_eq::(&other.type_) + && self.kind.location_agnostic_eq::(&other.kind) + && self + .variable_definitions + .location_agnostic_eq::(&other.variable_definitions) + && self.directives.location_agnostic_eq::(&other.directives) + && self.selections.location_agnostic_eq::(&other.selections) + } +} + +impl LocationAgnosticPartialEq for OperationKind { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self == other + } +} +impl LocationAgnosticPartialEq for Type { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self == other + } +} + +impl LocationAgnosticPartialEq for Selection { + fn location_agnostic_eq(&self, other: &Self) -> bool { + match (self, other) { + (Selection::FragmentSpread(l), Selection::FragmentSpread(r)) => { + l.location_agnostic_eq::(r) + } + (Selection::InlineFragment(l), Selection::InlineFragment(r)) => { + l.location_agnostic_eq::(r) + } + (Selection::LinkedField(l), Selection::LinkedField(r)) => { + l.location_agnostic_eq::(r) + } + (Selection::ScalarField(l), Selection::ScalarField(r)) => { + l.location_agnostic_eq::(r) + } + (Selection::Condition(l), Selection::Condition(r)) => l.location_agnostic_eq::(r), + _ => false, + } + } +} + +impl LocationAgnosticPartialEq for FragmentSpread { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.fragment.location_agnostic_eq::(&other.fragment) + && self.arguments.location_agnostic_eq::(&other.arguments) + && self.directives.location_agnostic_eq::(&other.directives) + } +} + +impl LocationAgnosticPartialEq for InlineFragment { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.type_condition + .location_agnostic_eq::(&other.type_condition) + && self.directives.location_agnostic_eq::(&other.directives) + && self.selections.location_agnostic_eq::(&other.selections) + } +} + +impl LocationAgnosticPartialEq for LinkedField { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.alias.location_agnostic_eq::(&other.alias) + && self.definition.location_agnostic_eq::(&other.definition) + && self.arguments.location_agnostic_eq::(&other.arguments) + && self.directives.location_agnostic_eq::(&other.directives) + && self.selections.location_agnostic_eq::(&other.selections) + } +} + +impl LocationAgnosticPartialEq for ScalarField { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.alias.location_agnostic_eq::(&other.alias) + && self.definition.location_agnostic_eq::(&other.definition) + && self.arguments.location_agnostic_eq::(&other.arguments) + && self.directives.location_agnostic_eq::(&other.directives) + } +} + +impl LocationAgnosticPartialEq for Condition { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.selections.location_agnostic_eq::(&other.selections) + && self.value.location_agnostic_eq::(&other.value) + && self.passing_value == other.passing_value + } +} + +impl LocationAgnosticPartialEq for FragmentDefinition { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.name.location_agnostic_eq::(&other.name) + && self + .variable_definitions + .location_agnostic_eq::(&other.variable_definitions) + && self + .used_global_variables + .location_agnostic_eq::(&other.used_global_variables) + && self + .type_condition + .location_agnostic_eq::(&other.type_condition) + && self.directives.location_agnostic_eq::(&other.directives) + && self.selections.location_agnostic_eq::(&other.selections) + } +} + +impl LocationAgnosticPartialEq for FragmentDefinitionName { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} +impl LocationAgnosticPartialEq for OperationDefinitionName { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} +impl LocationAgnosticPartialEq for VariableName { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl LocationAgnosticPartialEq for FieldID { + fn location_agnostic_eq(&self, other: &Self) -> bool { + self == other + } +} diff --git a/compiler/crates/graphql-text-printer/Cargo.toml b/compiler/crates/graphql-text-printer/Cargo.toml index cfd28330dbbc3..a5c14c57abaa5 100644 --- a/compiler/crates/graphql-text-printer/Cargo.toml +++ b/compiler/crates/graphql-text-printer/Cargo.toml @@ -25,3 +25,4 @@ schema = { path = "../schema" } [dev-dependencies] fixture-tests = { path = "../fixture-tests" } relay-test-schema = { path = "../relay-test-schema" } +relay-transforms = { path = "../relay-transforms" } diff --git a/compiler/crates/graphql-text-printer/src/print_full_operation.rs b/compiler/crates/graphql-text-printer/src/print_full_operation.rs index 8145b411768b9..393f3589f5127 100644 --- a/compiler/crates/graphql-text-printer/src/print_full_operation.rs +++ b/compiler/crates/graphql-text-printer/src/print_full_operation.rs @@ -18,9 +18,14 @@ use graphql_ir::Visitor; use crate::print_fragment; use crate::print_operation; +use crate::PrinterOptions; -pub fn print_full_operation(program: &Program, operation: &OperationDefinition) -> String { - let mut printer = OperationPrinter::new(program); +pub fn print_full_operation( + program: &Program, + operation: &OperationDefinition, + options: PrinterOptions, +) -> String { + let mut printer = OperationPrinter::new(program, options); printer.print(operation) } @@ -28,28 +33,36 @@ pub struct OperationPrinter<'s> { fragment_result: FnvHashMap, reachable_fragments: FnvHashMap>, program: &'s Program, + options: PrinterOptions, } impl<'s> OperationPrinter<'s> { - pub fn new(program: &'s Program) -> Self { + pub fn new(program: &'s Program, options: PrinterOptions) -> Self { Self { fragment_result: Default::default(), reachable_fragments: Default::default(), program, + options, } } pub fn print(&mut self, operation: &OperationDefinition) -> String { - let mut result = print_operation(&self.program.schema, operation, Default::default()); + let mut result = print_operation(&self.program.schema, operation, self.options); self.visit_operation(operation); let mut fragments: Vec<(FragmentDefinitionName, Arc)> = self.reachable_fragments.drain().collect(); fragments.sort_unstable_by_key(|(name, _)| *name); for (_, fragment) in fragments { - result.push_str("\n\n"); + if self.options.compact { + result.push_str(" "); + } else { + result.push_str("\n\n"); + } result.push_str(self.print_fragment(&fragment)); } - result.push('\n'); + if !self.options.compact { + result.push('\n'); + } result } @@ -57,7 +70,7 @@ impl<'s> OperationPrinter<'s> { let schema = &self.program.schema; self.fragment_result .entry(fragment.name.item) - .or_insert_with(|| print_fragment(schema, fragment, Default::default())) + .or_insert_with(|| print_fragment(schema, fragment, self.options)) } } diff --git a/compiler/crates/graphql-text-printer/src/print_to_text.rs b/compiler/crates/graphql-text-printer/src/print_to_text.rs index 4c9ef9d0b4537..3f2fa592aeefc 100644 --- a/compiler/crates/graphql-text-printer/src/print_to_text.rs +++ b/compiler/crates/graphql-text-printer/src/print_to_text.rs @@ -15,6 +15,7 @@ use common::WithLocation; use graphql_ir::Argument; use graphql_ir::Condition; use graphql_ir::ConditionValue; +use graphql_ir::ConstantArgument; use graphql_ir::ConstantValue; use graphql_ir::Directive; use graphql_ir::ExecutableDefinition; @@ -32,6 +33,8 @@ use intern::string_key::Intern; use intern::string_key::StringKey; use schema::SDLSchema; use schema::Schema; +use schema::Type; +use schema::TypeReference; pub fn print_ir(schema: &SDLSchema, definitions: &[ExecutableDefinition]) -> Vec { definitions @@ -220,17 +223,19 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { OperationKind::Mutation => "mutation", OperationKind::Subscription => "subscription", }; - let operation_name = operation.name.item.0; - write!(self.writer, "{} {}", operation_kind, operation_name)?; + write!(self.writer, "{}", operation_kind)?; + self.space(false)?; + write!(self.writer, "{}", operation.name.item.0)?; self.print_variable_definitions(&operation.variable_definitions)?; self.print_directives(&operation.directives, None, None)?; self.print_selections(&operation.selections) } fn print_fragment(mut self, fragment: &FragmentDefinition) -> FmtResult { - let fragment_name = fragment.name.item; - let type_condition_name = self.schema.get_type_name(fragment.type_condition); - write!(self.writer, "fragment {}", fragment_name)?; + self.text("fragment")?; + self.space(false)?; + write!(self.writer, "{}", fragment.name.item)?; + if fragment .directives .named(DirectiveName("argumentDefinitions".intern())) @@ -238,8 +243,9 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { { self.print_variable_definitions(&fragment.variable_definitions)?; } - write!(self.writer, " on {}", type_condition_name)?; + self.space(false)?; + self.print_type_condition(fragment.type_condition)?; self.print_directives( &fragment.directives, None, @@ -251,20 +257,21 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { fn print_selections(&mut self, selections: &[Selection]) -> FmtResult { let len = selections.len(); if len > 0 { - write!(self.writer, " {{")?; + self.space(true)?; + self.text("{")?; self.indentation += 1; - self.next_line()?; + self.next_line(true)?; for (i, selection) in selections.iter().enumerate() { self.print_selection(selection, None)?; if i != len - 1 { - self.next_line()?; + self.item_separator()?; } } self.indentation -= 1; - self.next_line()?; - write!(self.writer, "}}")?; + self.next_line(true)?; + self.text("}")?; } else { panic!( "Cannot print empty selections. Please, check transforms that may produce invalid selections." @@ -279,23 +286,12 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { conditions: Option>, ) -> FmtResult { match selection { - Selection::ScalarField(field) => { - self.print_scalar_field(field, conditions)?; - } - Selection::LinkedField(field) => { - self.print_linked_field(field, conditions)?; - } - Selection::FragmentSpread(field) => { - self.print_fragment_spread(field, conditions)?; - } - Selection::InlineFragment(field) => { - self.print_inline_fragment(field, conditions)?; - } - Selection::Condition(field) => { - self.print_condition(field)?; - } + Selection::ScalarField(field) => self.print_scalar_field(field, conditions), + Selection::LinkedField(field) => self.print_linked_field(field, conditions), + Selection::FragmentSpread(field) => self.print_fragment_spread(field, conditions), + Selection::InlineFragment(field) => self.print_inline_fragment(field, conditions), + Selection::Condition(field) => self.print_condition(field), } - Ok(()) } fn print_scalar_field( @@ -331,7 +327,8 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { write!(self.writer, "...{}", fragment_name)?; self.print_directives(&field.directives, conditions, None)?; if !field.arguments.is_empty() { - write!(self.writer, " @arguments")?; + self.space(true)?; + self.text("@arguments")?; self.print_arguments(&field.arguments) } else { Ok(()) @@ -343,13 +340,10 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { field: &InlineFragment, conditions: Option>, ) -> FmtResult { - write!(self.writer, "...")?; + self.text("...")?; if let Some(type_condition) = field.type_condition { - write!( - self.writer, - " on {}", - self.schema.get_type_name(type_condition).lookup(), - )?; + self.space(true)?; + self.print_type_condition(type_condition)?; }; self.print_directives(&field.directives, conditions, None)?; self.print_selections(&field.selections) @@ -369,7 +363,7 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { if is_first_selection { is_first_selection = false; } else { - self.next_line()?; + self.next_line(false)?; } self.print_selection(selection, Some(accum_conditions.iter().rev().collect()))?; maybe_current_condition = None; @@ -399,16 +393,17 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { } fn print_directive(&mut self, directive: &Directive) -> FmtResult { - write!(self.writer, " @{}", directive.name.item.0)?; + self.space(true)?; + write!(self.writer, "@{}", directive.name.item.0)?; self.print_arguments(&directive.arguments)?; if self.options.debug_directive_data { if let Some(data) = &directive.data { for debug_line in format!("{:#?}", data).lines() { - self.next_line()?; + self.next_line(false)?; write!(self.writer, "# {}", debug_line)?; } - self.next_line()?; + self.next_line(false)?; } } @@ -417,23 +412,23 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { fn print_condition_directives(&mut self, conditions: Vec<&Condition>) -> FmtResult { for condition in conditions { + self.space(true)?; write!( self.writer, - " @{}", + "@{}", if condition.passing_value { "include" } else { "skip" } )?; + self.text("(if")?; + self.colon_separator()?; match &condition.value { - ConditionValue::Constant(value) => { - write!(self.writer, "(if: {})", value)?; - } - ConditionValue::Variable(variable) => { - write!(self.writer, "(if: ${})", variable.name.item)?; - } - } + ConditionValue::Constant(value) => write!(self.writer, "{}", value), + ConditionValue::Variable(variable) => self.print_variable(&variable.name.item.0), + }?; + self.text(")")?; } Ok(()) } @@ -443,25 +438,19 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { variable_definitions: &[VariableDefinition], ) -> FmtResult { if !variable_definitions.is_empty() { - write!(self.writer, "(")?; + self.text("(")?; self.indentation += 1; - for var_def in variable_definitions.iter() { - self.next_line()?; - let type_name = self.schema.get_type_string(&var_def.type_); - write!(self.writer, "${}: {}", var_def.name.item, type_name)?; - - match &var_def.default_value { - None => {} - Some(default_value) => { - write!(self.writer, " = ")?; - self.print_constant_value(&default_value.item)?; - } + for (i, var_def) in variable_definitions.iter().enumerate() { + if i == 0 { + self.next_line(true)?; + } else { + self.item_separator()?; } - - self.print_directives(&var_def.directives, None, None)?; + self.print_variable_definition(var_def)?; } self.indentation -= 1; - write!(self.writer, "\n)")?; + self.next_line(true)?; + self.text(")")?; } Ok(()) } @@ -471,25 +460,33 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { argument_definitions: &[VariableDefinition], ) -> FmtResult { if !argument_definitions.is_empty() { - write!(self.writer, " @argumentDefinitions(")?; + self.space(true)?; + self.text("@argumentDefinitions(")?; self.indentation += 1; - for arg_def in argument_definitions.iter() { - self.next_line()?; - let type_name = self.schema.get_type_string(&arg_def.type_); - write!( - self.writer, - "{}: {{type: \"{}\"", - arg_def.name.item, type_name - )?; - + for (i, arg_def) in argument_definitions.iter().enumerate() { + if i == 0 { + self.next_line(true)?; + } else { + self.item_separator()?; + } + write!(self.writer, "{}", arg_def.name.item)?; + self.colon_separator()?; + self.text("{type")?; + self.colon_separator()?; + self.text("\"")?; + self.print_type(&arg_def.type_)?; + self.text("\"")?; if let Some(default_value) = &arg_def.default_value { - write!(self.writer, ", defaultValue: ")?; + self.comma_separator()?; + self.text("defaultValue")?; + self.colon_separator()?; self.print_constant_value(&default_value.item)?; } - write!(self.writer, "}}")?; + self.text("}")?; } self.indentation -= 1; - write!(self.writer, "\n)")?; + self.next_line(true)?; + self.text(")")?; } Ok(()) } @@ -498,7 +495,6 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { if arguments.is_empty() { Ok(()) } else { - write!(self.writer, "(")?; let sorted_arguments = if self.options.sort_keys { let mut sorted_arguments = arguments.to_vec(); sorted_arguments.sort_by_key(|arg| arg.name()); @@ -509,31 +505,27 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { let maybe_sorted_arguments = sorted_arguments .as_ref() .map_or(arguments, |v| v.as_slice()); - for (i, argument) in maybe_sorted_arguments.iter().enumerate() { - write!(self.writer, "{}:", argument.name.item)?; - if !self.options.compact { - write!(self.writer, " ")?; - } - self.print_value(&argument.value.item)?; + self.text("(")?; + for (i, argument) in maybe_sorted_arguments.iter().enumerate() { + self.print_argument(argument)?; if i != arguments.len() - 1 { - write!(self.writer, ",")?; - if !self.options.compact { - write!(self.writer, " ")?; - } + self.comma_separator()?; } } - write!(self.writer, ")")?; - Ok(()) + self.text(")") } } fn print_value(&mut self, val: &Value) -> FmtResult { match val { Value::Constant(constant_val) => self.print_constant_value(constant_val), - Value::Variable(variable_val) => write!(self.writer, "${}", variable_val.name.item), + Value::Variable(variable_val) => self.print_variable(&variable_val.name.item.0), Value::Object(object) => { - write!(self.writer, "{{")?; + if self.options.json_format { + write!(self.writer, "\"")?; + } + self.text("{")?; let mut first = true; let sorted_object = if self.options.sort_keys { let mut maybe_sorted_object = object.clone(); @@ -552,22 +544,14 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { if first { first = false; } else { - write!(self.writer, ",")?; - if !self.options.compact { - write!(self.writer, " ")?; - } + self.comma_separator()?; } - if self.options.json_format { - write!(self.writer, "\"{}\":", arg.name.item)?; - } else { - write!(self.writer, "{}:", arg.name.item)?; - } - if !self.options.compact { - write!(self.writer, " ")?; - } - self.print_value(&arg.value.item)?; + self.print_argument(arg)?; + } + self.text("}")?; + if self.options.json_format { + write!(self.writer, "\"")?; } - write!(self.writer, "}}")?; Ok(()) } Value::List(list) => { @@ -580,15 +564,11 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { if first { first = false; } else { - write!(self.writer, ",")?; - if !self.options.compact { - write!(self.writer, " ")?; - } + self.comma_separator()?; } self.print_value(value)?; } - write!(self.writer, "]")?; - Ok(()) + write!(self.writer, "]") } } } @@ -608,7 +588,7 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { } } ConstantValue::Object(object) => { - write!(self.writer, "{{")?; + self.text("{")?; let mut first = true; let sorted_object = if self.options.sort_keys { let mut maybe_sorted_object = object.clone(); @@ -624,23 +604,11 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { if first { first = false; } else { - write!(self.writer, ",")?; - if !self.options.compact { - write!(self.writer, " ")?; - } - } - if self.options.json_format { - write!(self.writer, "\"{}\":", arg.name.item)?; - } else { - write!(self.writer, "{}:", arg.name.item)?; - } - if !self.options.compact { - write!(self.writer, " ")?; + self.comma_separator()?; } - self.print_constant_value(&arg.value.item)?; + self.print_object_field(arg)?; } - write!(self.writer, "}}")?; - Ok(()) + self.text("}") } ConstantValue::List(list) => { write!(self.writer, "[")?; @@ -649,15 +617,11 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { if first { first = false; } else { - write!(self.writer, ",")?; - if !self.options.compact { - write!(self.writer, " ")?; - } + self.comma_separator()?; } self.print_constant_value(value)?; } - write!(self.writer, "]")?; - Ok(()) + write!(self.writer, "]") } } } @@ -669,17 +633,99 @@ impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> { ) -> FmtResult { if let Some(alias) = alias { if alias.item != name { - write!(self.writer, "{}: ", alias.item)?; + write!(self.writer, "{}", alias.item)?; + self.colon_separator()?; } } write!(self.writer, "{}", name) } - fn next_line(&mut self) -> FmtResult { + fn print_argument(&mut self, argument: &Argument) -> FmtResult { + write!(self.writer, "{}", argument.name.item)?; + self.colon_separator()?; + self.print_value(&argument.value.item) + } + + fn print_type_condition(&mut self, type_condition: Type) -> FmtResult { + self.text("on")?; + self.space(false)?; + write!(self.writer, "{}", self.schema.get_type_name(type_condition)) + } + + fn print_variable_definition(&mut self, var_def: &VariableDefinition) -> FmtResult { + self.print_variable(&var_def.name.item.0)?; + self.colon_separator()?; + self.print_type(&var_def.type_)?; + + if let Some(default_value) = &var_def.default_value { + self.print_default_value(&default_value.item)?; + } + + self.print_directives(&var_def.directives, None, None) + } + + fn print_object_field(&mut self, arg: &ConstantArgument) -> FmtResult { + if self.options.json_format { + write!(self.writer, "\"{}\"", arg.name.item)?; + } else { + write!(self.writer, "{}", arg.name.item)?; + } + self.colon_separator()?; + self.print_constant_value(&arg.value.item) + } + + fn print_variable(&mut self, variable: &StringKey) -> FmtResult { + write!(self.writer, "${}", variable) + } + + fn print_default_value(&mut self, value: &ConstantValue) -> FmtResult { + self.space(true)?; + self.text("=")?; + self.space(true)?; + self.print_constant_value(&value) + } + + fn print_type(&mut self, type_: &TypeReference) -> FmtResult { + write!(self.writer, "{}", self.schema.get_type_string(&type_)) + } + + fn next_line(&mut self, can_collapse: bool) -> FmtResult { + if self.options.compact && can_collapse { + return Ok(()); + } writeln!(self.writer)?; for _ in 0..self.indentation { write!(self.writer, " ")?; } Ok(()) } + + fn text(&mut self, text: &str) -> FmtResult { + write!(self.writer, "{}", text) + } + + fn space(&mut self, can_collapse: bool) -> FmtResult { + if self.options.compact && can_collapse { + return Ok(()); + } + write!(self.writer, " ") + } + + fn comma_separator(&mut self) -> FmtResult { + self.text(",")?; + self.space(true) + } + + fn colon_separator(&mut self) -> FmtResult { + self.text(":")?; + self.space(true) + } + + fn item_separator(&mut self) -> FmtResult { + if self.options.compact { + write!(self.writer, ",") + } else { + self.next_line(false) + } + } } diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_directives.expected b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_directives.expected new file mode 100644 index 0000000000000..c7160cda7d060 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_directives.expected @@ -0,0 +1,21 @@ +==================================== INPUT ==================================== +query MyQuery($id: ID, $cond: Boolean!) { + my_alias: node(id: $id) { + id + ... on User @include(if: $cond) { + name + } + ...UserFragment @include(if: $cond) + } +} + +fragment UserFragment on User { + id + name @include(if: $cond) + otherName: name @customDirective(level: 3) @skip(if: $cond) + address @skip(if: true) { + city + } +} +==================================== OUTPUT =================================== +query MyQuery($id:ID,$cond:Boolean!){my_alias:node(id:$id){id,...on User@include(if:$cond){name},...UserFragment@include(if:$cond)}} fragment UserFragment on User{id,name@include(if:$cond),otherName:name@skip(if:$cond)@customDirective(level:3),address@skip(if:true){city}} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_directives.graphql b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_directives.graphql new file mode 100644 index 0000000000000..0cc0451e7b34f --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_directives.graphql @@ -0,0 +1,18 @@ +query MyQuery($id: ID, $cond: Boolean!) { + my_alias: node(id: $id) { + id + ... on User @include(if: $cond) { + name + } + ...UserFragment @include(if: $cond) + } +} + +fragment UserFragment on User { + id + name @include(if: $cond) + otherName: name @customDirective(level: 3) @skip(if: $cond) + address @skip(if: true) { + city + } +} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_query.expected b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_query.expected new file mode 100644 index 0000000000000..38254ef3e34a1 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_query.expected @@ -0,0 +1,14 @@ +==================================== INPUT ==================================== +query MyQuery($id: ID) { + my_alias: node(id: $id) { + id + ... on User { + name + likers(first: 5) { + count + } + } + } +} +==================================== OUTPUT =================================== +query MyQuery($id:ID){my_alias:node(id:$id){id,...on User{name,likers(first:5){count}}}} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_query.graphql b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_query.graphql new file mode 100644 index 0000000000000..12f6b9f3b6d70 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_query.graphql @@ -0,0 +1,11 @@ +query MyQuery($id: ID) { + my_alias: node(id: $id) { + id + ... on User { + name + likers(first: 5) { + count + } + } + } +} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_var_defs.expected b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_var_defs.expected new file mode 100644 index 0000000000000..7b9c7d1286d6d --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_var_defs.expected @@ -0,0 +1,17 @@ +==================================== INPUT ==================================== +query MyQuery($id: ID, $count: Int! = 5, $envs: [Environment!]! = [WEB]) { + my_alias: node(id: $id) { + id + ... on User { + name + likers(first: $count) { + count + } + checkins(environments: $envs) { + query + } + } + } +} +==================================== OUTPUT =================================== +query MyQuery($id:ID,$count:Int!=5,$envs:[Environment!]!=[WEB]){my_alias:node(id:$id){id,...on User{name,likers(first:$count){count},checkins(environments:$envs){query}}}} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_var_defs.graphql b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_var_defs.graphql new file mode 100644 index 0000000000000..0e9dfbba909d1 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/basic_var_defs.graphql @@ -0,0 +1,14 @@ +query MyQuery($id: ID, $count: Int! = 5, $envs: [Environment!]! = [WEB]) { + my_alias: node(id: $id) { + id + ... on User { + name + likers(first: $count) { + count + } + checkins(environments: $envs) { + query + } + } + } +} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/compact_test.expected b/compiler/crates/graphql-text-printer/tests/compact/fixtures/compact_test.expected new file mode 100644 index 0000000000000..c00518bb17fe9 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/compact_test.expected @@ -0,0 +1,17 @@ +==================================== INPUT ==================================== +query Test { + #comments go away + aliasOfFoo : username(name : "val1" ) @customDirective(level: 2) { # and this comment as well + emailAddresses + firstName(if: false, unless: false) + } + ...FX +} + +fragment FX on Query { + aliased : username(name : "argVal") { + thin: emailAddresses + } +} +==================================== OUTPUT =================================== +query Test{aliasOfFoo:username(name:"val1")@customDirective(level:2){emailAddresses,firstName(if:false,unless:false)},...FX} fragment FX on Query{aliased:username(name:"argVal"){thin:emailAddresses}} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/compact_test.graphql b/compiler/crates/graphql-text-printer/tests/compact/fixtures/compact_test.graphql new file mode 100644 index 0000000000000..92e893b7905ae --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/compact_test.graphql @@ -0,0 +1,14 @@ +query Test { + #comments go away + aliasOfFoo : username(name : "val1" ) @customDirective(level: 2) { # and this comment as well + emailAddresses + firstName(if: false, unless: false) + } + ...FX +} + +fragment FX on Query { + aliased : username(name : "argVal") { + thin: emailAddresses + } +} \ No newline at end of file diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/empty_args.expected b/compiler/crates/graphql-text-printer/tests/compact/fixtures/empty_args.expected new file mode 100644 index 0000000000000..4fd4224e3eaa8 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/empty_args.expected @@ -0,0 +1,11 @@ +==================================== INPUT ==================================== +query EmptyArgs { + route(waypoints: []) { + __typename + } + items(filter: {}) { + __typename + } +} +==================================== OUTPUT =================================== +query EmptyArgs{route(waypoints:[]){__typename},items(filter:{}){__typename}} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/empty_args.graphql b/compiler/crates/graphql-text-printer/tests/compact/fixtures/empty_args.graphql new file mode 100644 index 0000000000000..9c92aec663e24 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/empty_args.graphql @@ -0,0 +1,8 @@ +query EmptyArgs { + route(waypoints: []) { + __typename + } + items(filter: {}) { + __typename + } +} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/kitchen-sink.expected b/compiler/crates/graphql-text-printer/tests/compact/fixtures/kitchen-sink.expected new file mode 100644 index 0000000000000..59433b573b213 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/kitchen-sink.expected @@ -0,0 +1,79 @@ +==================================== INPUT ==================================== +query NodeQuery( + $cond: Boolean! = false + $id: ID! + $size: [Int] = [32] + $query: CheckinSearchInput! +) { + node(id: $id) { + id + ... on User @include(if: $cond) { + name + } + ...UserFragment @include(if: $cond) @arguments(size: $size) + } + checkinSearchQuery(query: $query) { + query + } +} + +fragment UserFragment on User + @argumentDefinitions( + after: {type: "ID"} + first: {type: "Int", defaultValue: 5} + size: {type: "[Int]"} + storyType: {type: "StoryType", defaultValue: DIRECTED} + # storyType_invalid_string: {type: "StoryType", defaultValue: "DIRECTED"} + # storyType_invalid_lowercase: {type: "StoryType", defaultValue: directed} + ) { + id + __typename + checkins(environments: [WEB]) { + __typename + } + nakedEnum: checkins(environments: WEB) { + __typename + } + friends(after: $after, first: $first, traits: [HELPFUL]) { + count + } + secondFriends: friends(first: 10) { + count + } + name @include(if: $cond) + otherName: name @customDirective(level: 3) + thumbnail: profilePicture2( + size: 32 + cropPosition: CENTER + fileExtension: PNG + # TODO add support for custom scalars + # additionalParameters: {filter: "Boston"} + options: {newName: null} + ) { + height + width + src: uri + } + profilePicture(size: $size) @include(if: $cond) @skip(if: $foo) { + height + width + src: uri + } + storySearch(query: {type: DIRECTED}) { + id + } + ... @include(if: $cond) @skip(if: $foo) { + address { + city + } + alternate_name + } + + ... @include(if: $cond) { + address { + city + } + } +} +==================================== OUTPUT =================================== +query NodeQuery($cond:Boolean!=false,$id:ID!,$size:[Int]=[32],$query:CheckinSearchInput!){node(id:$id){id,...on User@include(if:$cond){name},...UserFragment@include(if:$cond)@arguments(size:$size)},checkinSearchQuery(query:$query){query}} fragment UserFragment on User@argumentDefinitions(after:{type:"ID"},first:{type:"Int",defaultValue:5},size:{type:"[Int]"},storyType:{type:"StoryType",defaultValue:DIRECTED}){id,__typename,checkins(environments:[WEB]){__typename},nakedEnum:checkins(environments:WEB){__typename},friends(after:$after,first:$first,traits:[HELPFUL]){count},secondFriends:friends(first:10){count},name@include(if:$cond),otherName:name@customDirective(level:3),thumbnail:profilePicture2(size:32,cropPosition:CENTER,fileExtension:PNG,options:{newName:null}){height,width,src:uri},profilePicture(size:$size)@include(if:$cond)@skip(if:$foo){height,width,src:uri},storySearch(query:{type:DIRECTED}){id},...@include(if:$cond)@skip(if:$foo){address{city},alternate_name},...@include(if:$cond){address{city}}} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/kitchen-sink.graphql b/compiler/crates/graphql-text-printer/tests/compact/fixtures/kitchen-sink.graphql new file mode 100644 index 0000000000000..3adfe1c5eabdb --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/kitchen-sink.graphql @@ -0,0 +1,76 @@ +query NodeQuery( + $cond: Boolean! = false + $id: ID! + $size: [Int] = [32] + $query: CheckinSearchInput! +) { + node(id: $id) { + id + ... on User @include(if: $cond) { + name + } + ...UserFragment @include(if: $cond) @arguments(size: $size) + } + checkinSearchQuery(query: $query) { + query + } +} + +fragment UserFragment on User + @argumentDefinitions( + after: {type: "ID"} + first: {type: "Int", defaultValue: 5} + size: {type: "[Int]"} + storyType: {type: "StoryType", defaultValue: DIRECTED} + # storyType_invalid_string: {type: "StoryType", defaultValue: "DIRECTED"} + # storyType_invalid_lowercase: {type: "StoryType", defaultValue: directed} + ) { + id + __typename + checkins(environments: [WEB]) { + __typename + } + nakedEnum: checkins(environments: WEB) { + __typename + } + friends(after: $after, first: $first, traits: [HELPFUL]) { + count + } + secondFriends: friends(first: 10) { + count + } + name @include(if: $cond) + otherName: name @customDirective(level: 3) + thumbnail: profilePicture2( + size: 32 + cropPosition: CENTER + fileExtension: PNG + # TODO add support for custom scalars + # additionalParameters: {filter: "Boston"} + options: {newName: null} + ) { + height + width + src: uri + } + profilePicture(size: $size) @include(if: $cond) @skip(if: $foo) { + height + width + src: uri + } + storySearch(query: {type: DIRECTED}) { + id + } + ... @include(if: $cond) @skip(if: $foo) { + address { + city + } + alternate_name + } + + ... @include(if: $cond) { + address { + city + } + } +} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/single-value-array-of-objects.expected b/compiler/crates/graphql-text-printer/tests/compact/fixtures/single-value-array-of-objects.expected new file mode 100644 index 0000000000000..08905768f58c8 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/single-value-array-of-objects.expected @@ -0,0 +1,11 @@ +==================================== INPUT ==================================== +query SingleValueArrayQuery { + route(waypoints: {lat: "123", lon: "456"}) { + steps { + lat + lon + } + } +} +==================================== OUTPUT =================================== +query SingleValueArrayQuery{route(waypoints:{lat:"123",lon:"456"}){steps{lat,lon}}} diff --git a/compiler/crates/graphql-text-printer/tests/compact/fixtures/single-value-array-of-objects.graphql b/compiler/crates/graphql-text-printer/tests/compact/fixtures/single-value-array-of-objects.graphql new file mode 100644 index 0000000000000..2ef9bcc746dfd --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/fixtures/single-value-array-of-objects.graphql @@ -0,0 +1,8 @@ +query SingleValueArrayQuery { + route(waypoints: {lat: "123", lon: "456"}) { + steps { + lat + lon + } + } +} diff --git a/compiler/crates/graphql-text-printer/tests/compact/mod.rs b/compiler/crates/graphql-text-printer/tests/compact/mod.rs new file mode 100644 index 0000000000000..68869747f158c --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact/mod.rs @@ -0,0 +1,52 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +use std::sync::Arc; + +use common::SourceLocationKey; +use fixture_tests::Fixture; +use graphql_ir::build; +use graphql_ir::node_identifier::LocationAgnosticPartialEq; +use graphql_ir::ExecutableDefinition; +use graphql_ir::Program; +use graphql_syntax::parse_executable; +use graphql_text_printer::print_full_operation; +use graphql_text_printer::PrinterOptions; +use relay_test_schema::TEST_SCHEMA; +use relay_transforms::RelayLocationAgnosticBehavior; + +pub fn transform_fixture(fixture: &Fixture<'_>) -> Result { + let source_location = SourceLocationKey::standalone(fixture.file_name); + let initial_ast = parse_executable(fixture.content, source_location).unwrap(); + let initial_ir = build(&TEST_SCHEMA, &initial_ast.definitions).unwrap(); + let initial_ir_copy = initial_ir.clone(); + let program = Program::from_definitions(Arc::clone(&TEST_SCHEMA), initial_ir.clone()); + let options = PrinterOptions { + compact: true, + ..Default::default() + }; + + // Print the IR into a GraphQL string for the fixture + let output = initial_ir + .into_iter() + .filter_map(|definition| match definition { + ExecutableDefinition::Operation(operation) => Some(operation), + _ => None, + }) + .map(|operation| print_full_operation(&program, &operation, options)) + .collect::>() + .join("\n\n"); + + // Roundtrip the output back into an IR + let roundtrip_ast = parse_executable(output.as_str(), SourceLocationKey::Generated).unwrap(); + let roundtrip_ir = build(&TEST_SCHEMA, &roundtrip_ast.definitions).unwrap(); + + // Check the roundtripped IR matches the initial IR to ensure we printed a valid schema + assert!(roundtrip_ir.location_agnostic_eq::(&initial_ir_copy)); + + Ok(output) +} diff --git a/compiler/crates/graphql-text-printer/tests/compact_test.rs b/compiler/crates/graphql-text-printer/tests/compact_test.rs new file mode 100644 index 0000000000000..ec1936868af15 --- /dev/null +++ b/compiler/crates/graphql-text-printer/tests/compact_test.rs @@ -0,0 +1,104 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated SignedSource<> + */ + +mod compact; + +use compact::transform_fixture; +use fixture_tests::test_fixture; + +#[test] +fn compact_test() { + let input = include_str!("compact/fixtures/compact_test.graphql"); + let expected = include_str!("compact/fixtures/compact_test.expected"); + test_fixture( + transform_fixture, + "compact_test.graphql", + "compact/fixtures/compact_test.expected", + input, + expected, + ); +} + +#[test] +fn compact_basic_directives() { + let input = include_str!("compact/fixtures/basic_directives.graphql"); + let expected = include_str!("compact/fixtures/basic_directives.expected"); + test_fixture( + transform_fixture, + "basic_directives.graphql", + "compact/fixtures/basic_directives.expected", + input, + expected, + ); +} + +#[test] +fn compact_basic_query() { + let input = include_str!("compact/fixtures/basic_query.graphql"); + let expected = include_str!("compact/fixtures/basic_query.expected"); + test_fixture( + transform_fixture, + "basic_query.graphql", + "compact/fixtures/basic_query.expected", + input, + expected, + ); +} + +#[test] +fn compact_basic_var_defs() { + let input = include_str!("compact/fixtures/basic_var_defs.graphql"); + let expected = include_str!("compact/fixtures/basic_var_defs.expected"); + test_fixture( + transform_fixture, + "basic_var_defs.graphql", + "compact/fixtures/basic_var_defs.expected", + input, + expected, + ); +} + +#[test] +fn compact_empty_args() { + let input = include_str!("compact/fixtures/empty_args.graphql"); + let expected = include_str!("compact/fixtures/empty_args.expected"); + test_fixture( + transform_fixture, + "empty_args.graphql", + "compact/fixtures/empty_args.expected", + input, + expected, + ); +} + +#[test] +fn compact_kitchen_sink() { + let input = include_str!("compact/fixtures/kitchen-sink.graphql"); + let expected = include_str!("compact/fixtures/kitchen-sink.expected"); + test_fixture( + transform_fixture, + "kitchen-sink.graphql", + "compact/fixtures/kitchen-sink.expected", + input, + expected, + ); +} + +#[test] +fn compact_single_value_array_of_objects() { + let input = include_str!("compact/fixtures/single-value-array-of-objects.graphql"); + let expected = include_str!("compact/fixtures/single-value-array-of-objects.expected"); + test_fixture( + transform_fixture, + "single-value-array-of-objects.graphql", + "compact/fixtures/single-value-array-of-objects.expected", + input, + expected, + ); +} diff --git a/compiler/crates/graphql-text-printer/tests/operation_printer/mod.rs b/compiler/crates/graphql-text-printer/tests/operation_printer/mod.rs index 9eccd43ec9172..88ae4bfbc91b6 100644 --- a/compiler/crates/graphql-text-printer/tests/operation_printer/mod.rs +++ b/compiler/crates/graphql-text-printer/tests/operation_printer/mod.rs @@ -28,7 +28,11 @@ pub fn transform_fixture(fixture: &Fixture<'_>) -> Result { .into_iter() .filter_map(|definition| { if let ExecutableDefinition::Operation(operation) = definition { - Some(print_full_operation(&program, &operation)) + Some(print_full_operation( + &program, + &operation, + Default::default(), + )) } else { None } diff --git a/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs b/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs index c6add4e3518c9..987b73d2d1be6 100644 --- a/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs +++ b/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs @@ -14,6 +14,7 @@ use fnv::FnvHashMap; use graphql_ir::FragmentDefinition; use graphql_ir::OperationDefinition; use graphql_text_printer::OperationPrinter; +use graphql_text_printer::PrinterOptions; use intern::string_key::StringKey; use relay_transforms::ClientEdgeGeneratedQueryMetadataDirective; use relay_transforms::Programs; @@ -45,7 +46,14 @@ pub fn generate_artifacts( programs: &Programs, source_hashes: Arc, ) -> Vec { - let mut operation_printer = OperationPrinter::new(&programs.operation_text); + let printer_options = PrinterOptions { + compact: project_config + .feature_flags + .compact_query_text + .is_fully_enabled(), + ..Default::default() + }; + let mut operation_printer = OperationPrinter::new(&programs.operation_text, printer_options); return group_operations(programs) .into_iter() .map(|(_, operations)| -> Artifact { diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/mod.rs b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/mod.rs index b554a0d19a896..4e322bb3c9e32 100644 --- a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/mod.rs +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/mod.rs @@ -116,6 +116,7 @@ pub fn transform_fixture(fixture: &Fixture<'_>) -> Result { enable_client_edges: FeatureFlag::Enabled, skip_printing_nulls: FeatureFlag::Disabled, enable_fragment_aliases: FeatureFlag::Enabled, + compact_query_text: FeatureFlag::Disabled, }; let default_project_config = ProjectConfig { @@ -230,7 +231,11 @@ pub fn transform_fixture(fixture: &Fixture<'_>) -> Result { let text = print_operation_node.map_or_else( || "Query Text is Empty.".to_string(), |print_operation_node| { - print_full_operation(&programs.operation_text, print_operation_node) + print_full_operation( + &programs.operation_text, + print_operation_node, + Default::default(), + ) }, ); diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts_with_custom_id/mod.rs b/compiler/crates/relay-compiler/tests/compile_relay_artifacts_with_custom_id/mod.rs index dc4489a60bb7e..a6d76518a8dbb 100644 --- a/compiler/crates/relay-compiler/tests/compile_relay_artifacts_with_custom_id/mod.rs +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts_with_custom_id/mod.rs @@ -86,6 +86,7 @@ pub fn transform_fixture(fixture: &Fixture<'_>) -> Result { enable_client_edges: FeatureFlag::Enabled, skip_printing_nulls: FeatureFlag::Disabled, enable_fragment_aliases: FeatureFlag::Enabled, + compact_query_text: FeatureFlag::Disabled, }; let project_config = ProjectConfig { @@ -136,7 +137,11 @@ pub fn transform_fixture(fixture: &Fixture<'_>) -> Result { let text = print_operation_node.map_or_else( || "Query Text is Empty.".to_string(), |print_operation_node| { - print_full_operation(&programs.operation_text, print_operation_node) + print_full_operation( + &programs.operation_text, + print_operation_node, + Default::default(), + ) }, ); diff --git a/compiler/crates/relay-lsp/src/graphql_tools/mod.rs b/compiler/crates/relay-lsp/src/graphql_tools/mod.rs index 2fa9828c9ff8c..978c5517754c9 100644 --- a/compiler/crates/relay-lsp/src/graphql_tools/mod.rs +++ b/compiler/crates/relay-lsp/src/graphql_tools/mod.rs @@ -159,6 +159,7 @@ fn print_full_operation_text(programs: Programs, operation_name: StringKey) -> O Some(print_full_operation( &programs.operation_text, print_operation_node, + Default::default(), )) }