From ab5c96b51751185be0650aa96fd8d8caed9654cf Mon Sep 17 00:00:00 2001 From: Maarten Staa Date: Thu, 17 Sep 2020 16:34:24 -0700 Subject: [PATCH] Add support for Typescript in Rust Relay compiler (#3182) Summary: See https://github.com/facebook/relay/issues/3181 and https://github.com/facebook/relay/issues/3180. This PR adds a language option to the typegen_config and refactors the relay-typegen crate to allow completely different output based on the configured language, currently typescript and flow (which is the default when no language is configured, or an invalid one is set). I also made some changes so the watchman query looks for .ts files when using typescript, and the output files also use .ts as their extension. This probably needs a bit more work before it's completely ready. Off the top of my head: - [ ] In `relay-compiler/build_project/artifact_content.rs` some output is generated such as `flow` and a commented out `import type` statement? Those should probably be left out for Typescript. - [x] Currently the language used for typegen is Typescript if the language in the config file is "typescript", and Flow otherwise. We should probably display an error if an invalid language is set, e.g. "typesrcipt". - [ ] I was not able to actually test the compiler due to an error returned by Watchman: `failed to parse query: must use ["suffix", "suffixstring"]`, and I don't have a clue why. This error was already returned before I made the changes to look for .ts files when using Typescript. - [ ] I did not at all look into `relay-lsp` and whether any changes are needed there for Typescript. Looking forward to your feedback! cc kassens josephsavona maraisr Pull Request resolved: https://github.com/facebook/relay/pull/3182 Reviewed By: tyao1 Differential Revision: D23670620 Pulled By: kassens fbshipit-source-id: 0e3e9e6860c6701d934b406c895dc04bfb5b6322 --- .../extract_module_name.rs | 8 +- .../src/build_project/generate_artifacts.rs | 14 +- .../src/watchman/query_builder.rs | 45 +- compiler/crates/relay-typegen/src/config.rs | 17 + compiler/crates/relay-typegen/src/flow.rs | 270 +++++---- compiler/crates/relay-typegen/src/lib.rs | 128 +++-- .../crates/relay-typegen/src/typescript.rs | 513 ++++++++++++++++++ compiler/crates/relay-typegen/src/writer.rs | 51 ++ .../fixtures/conditional.expected | 36 ++ .../fixtures/conditional.graphql | 9 + .../fixtures/fragment-spread.expected | 184 +++++++ .../fixtures/fragment-spread.graphql | 48 ++ .../fixtures/inline-fragment.expected | 157 ++++++ .../fixtures/inline-fragment.graphql | 64 +++ .../fixtures/linked-field.expected | 70 +++ .../fixtures/linked-field.graphql | 27 + .../fixtures/match-field-in-query.expected | 73 +++ .../fixtures/match-field-in-query.graphql | 23 + .../fixtures/match-field.expected | 75 +++ .../fixtures/match-field.graphql | 22 + .../mutaion-with-client-extension.expected | 52 ++ .../mutaion-with-client-extension.graphql | 19 + ...with-response-on-inline-fragments.expected | 108 ++++ ...-with-response-on-inline-fragments.graphql | 27 + .../mutation-input-has-array.expected | 36 ++ .../fixtures/mutation-input-has-array.graphql | 7 + .../mutation-with-enums-on-fragment.expected | 105 ++++ .../mutation-with-enums-on-fragment.graphql | 27 + .../mutation-with-nested-fragments.expected | 121 +++++ .../mutation-with-nested-fragments.graphql | 31 ++ .../fixtures/mutation.expected | 48 ++ .../fixtures/mutation.graphql | 15 + .../fixtures/plural-fragment.expected | 17 + .../fixtures/plural-fragment.graphql | 3 + .../fixtures/query-with-handles.expected | 70 +++ .../fixtures/query-with-handles.graphql | 17 + .../fixtures/query-with-match-fields.expected | 137 +++++ .../fixtures/query-with-match-fields.graphql | 28 + .../fixtures/query-with-module-field.expected | 86 +++ .../fixtures/query-with-module-field.graphql | 17 + .../query-with-multiple-match-fields.expected | 215 ++++++++ .../query-with-multiple-match-fields.graphql | 48 ++ ...-with-raw-response-on-conditional.expected | 66 +++ ...y-with-raw-response-on-conditional.graphql | 16 + ...w-response-on-literal-conditional.expected | 67 +++ ...aw-response-on-literal-conditional.graphql | 22 + .../query-with-stream-connection.expected | 67 +++ .../query-with-stream-connection.graphql | 17 + .../fixtures/query-with-stream.expected | 61 +++ .../fixtures/query-with-stream.graphql | 18 + .../fixtures/recursive-fragments.expected | 21 + .../fixtures/recursive-fragments.graphql | 6 + .../fixtures/refetchable-fragment.expected | 40 ++ .../fixtures/refetchable-fragment.graphql | 7 + .../fixtures/refetchable.expected | 38 ++ .../fixtures/refetchable.graphql | 7 + .../fixtures/relay-client-id-field.expected | 58 ++ .../fixtures/relay-client-id-field.graphql | 27 + ...ough-inline-fragments-to-fragment.expected | 28 + ...rough-inline-fragments-to-fragment.graphql | 8 + .../required-bubbles-to-fragment.expected | 19 + .../required-bubbles-to-fragment.graphql | 4 + ...d-bubbles-to-item-in-plural-field.expected | 25 + ...ed-bubbles-to-item-in-plural-field.graphql | 7 + .../required-bubbles-to-query.expected | 19 + .../required-bubbles-to-query.graphql | 6 + ...d-bubbles-up-to-mutation-response.expected | 34 ++ ...ed-bubbles-up-to-mutation-response.graphql | 7 + ...solates-concrete-inline-fragments.expected | 70 +++ ...isolates-concrete-inline-fragments.graphql | 28 + .../required-raw-response-type.expected | 26 + .../required-raw-response-type.graphql | 6 + ...-throw-doesnt-bubbles-to-fragment.expected | 19 + ...d-throw-doesnt-bubbles-to-fragment.graphql | 4 + ...red-throw-doesnt-bubbles-to-query.expected | 19 + ...ired-throw-doesnt-bubbles-to-query.graphql | 6 + .../fixtures/required-throws-nested.expected | 19 + .../fixtures/required-throws-nested.graphql | 6 + .../fixtures/required.expected | 19 + .../fixtures/required.graphql | 6 + .../fixtures/roots.expected | 97 ++++ .../fixtures/roots.graphql | 25 + .../fixtures/scalar-field.expected | 38 ++ .../fixtures/scalar-field.graphql | 13 + .../fixtures/simple.expected | 27 + .../fixtures/simple.graphql | 8 + ...me-inside-with-overlapping-fields.expected | 45 ++ ...ame-inside-with-overlapping-fields.graphql | 16 + .../fixtures/typename-on-union.expected | 242 +++++++++ .../fixtures/typename-on-union.graphql | 80 +++ .../unmasked-fragment-spreads.expected | 87 +++ .../unmasked-fragment-spreads.graphql | 26 + .../tests/generate_typescript/mod.rs | 82 +++ .../tests/generate_typescript_test.rs | 300 ++++++++++ 94 files changed, 4903 insertions(+), 174 deletions(-) create mode 100644 compiler/crates/relay-typegen/src/typescript.rs create mode 100644 compiler/crates/relay-typegen/src/writer.rs create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/conditional.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/conditional.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/fragment-spread.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/fragment-spread.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/inline-fragment.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/inline-fragment.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-field.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-field.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field-in-query.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field-in-query.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-client-extension.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-client-extension.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-input-has-array.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-input-has-array.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-enums-on-fragment.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-enums-on-fragment.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-nested-fragments.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-nested-fragments.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/plural-fragment.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/plural-fragment.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-handles.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-handles.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-match-fields.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-match-fields.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-module-field.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-module-field.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-multiple-match-fields.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-multiple-match-fields.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-conditional.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-conditional.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream-connection.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream-connection.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/recursive-fragments.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/recursive-fragments.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable-fragment.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable-fragment.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/relay-client-id-field.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/relay-client-id-field.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-fragment.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-fragment.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-query.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-query.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-up-to-mutation-response.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-up-to-mutation-response.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-isolates-concrete-inline-fragments.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-isolates-concrete-inline-fragments.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-raw-response-type.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-raw-response-type.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throws-nested.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throws-nested.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/roots.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/roots.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-field.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-field.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/simple.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/simple.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-inside-with-overlapping-fields.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-inside-with-overlapping-fields.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-on-union.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-on-union.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/unmasked-fragment-spreads.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/unmasked-fragment-spreads.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/mod.rs create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript_test.rs diff --git a/compiler/crates/graphql-transforms/src/validations/validate_module_names/extract_module_name.rs b/compiler/crates/graphql-transforms/src/validations/validate_module_names/extract_module_name.rs index 67b794070fec2..ae1b01227a378 100644 --- a/compiler/crates/graphql-transforms/src/validations/validate_module_names/extract_module_name.rs +++ b/compiler/crates/graphql-transforms/src/validations/validate_module_names/extract_module_name.rs @@ -121,12 +121,12 @@ mod tests { Some("SliderIos".to_string()) ); assert_eq!( - extract_module_name("/path/Typescript.ts"), - Some("Typescript".to_string()) + extract_module_name("/path/TypeScript.ts"), + Some("TypeScript".to_string()) ); assert_eq!( - extract_module_name("/path/Typescript.tsx"), - Some("Typescript".to_string()) + extract_module_name("/path/TypeScript.tsx"), + Some("TypeScript".to_string()) ); assert_eq!( extract_module_name("/path/button/index.js"), 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 75cad0b68ab83..aede5b2ef6445 100644 --- a/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs +++ b/compiler/crates/relay-compiler/src/build_project/generate_artifacts.rs @@ -18,6 +18,7 @@ use graphql_text_printer::{ }; use graphql_transforms::{RefetchableDerivedFromMetadata, SplitOperationMetaData, MATCH_CONSTANTS}; use interner::StringKey; +use relay_typegen::TypegenLanguage; use std::path::PathBuf; use std::sync::Arc; @@ -60,7 +61,7 @@ pub fn generate_artifacts( artifacts.push(Artifact { source_definition_names: metadata.parent_sources.into_iter().collect(), - path: path_for_js_artifact( + path: path_for_artifact( project_config, source_file, normalization_operation.name.item, @@ -175,7 +176,7 @@ fn generate_normalization_artifact<'a>( .expect("a type fragment should be generated for this operation"); Ok(Artifact { source_definition_names: vec![source_definition_name], - path: path_for_js_artifact(project_config, source_file, name), + path: path_for_artifact(project_config, source_file, name), content: ArtifactContent::Operation { normalization_operation: Arc::clone(normalization_operation), reader_operation: Arc::clone(reader_operation), @@ -201,7 +202,7 @@ fn generate_reader_artifact( .expect("a type fragment should be generated for this fragment"); Artifact { source_definition_names: vec![name], - path: path_for_js_artifact( + path: path_for_artifact( project_config, reader_fragment.name.location.source_location(), name, @@ -254,7 +255,7 @@ pub fn create_path_for_artifact( } } -fn path_for_js_artifact( +fn path_for_artifact( project_config: &ProjectConfig, source_file: SourceLocationKey, definition_name: StringKey, @@ -262,7 +263,10 @@ fn path_for_js_artifact( create_path_for_artifact( project_config, source_file, - format!("{}.graphql.js", definition_name), + match &project_config.typegen_config.language { + TypegenLanguage::Flow => format!("{}.graphql.js", definition_name), + TypegenLanguage::TypeScript => format!("{}.graphql.ts", definition_name), + }, false, ) } diff --git a/compiler/crates/relay-compiler/src/watchman/query_builder.rs b/compiler/crates/relay-compiler/src/watchman/query_builder.rs index de14c13247841..2a5e67d7621c8 100644 --- a/compiler/crates/relay-compiler/src/watchman/query_builder.rs +++ b/compiler/crates/relay-compiler/src/watchman/query_builder.rs @@ -5,22 +5,45 @@ * LICENSE file in the root directory of this source tree. */ +use crate::compiler_state::SourceSet; use crate::config::{Config, SchemaLocation}; +use relay_typegen::TypegenLanguage; use std::path::PathBuf; use watchman_client::prelude::*; pub fn get_watchman_expr(config: &Config) -> Expr { - let mut sources_conditions = vec![ - // ending in *.js - Expr::Suffix(vec!["js".into()]), - // in one of the source roots - expr_any( - get_source_roots(&config) - .into_iter() - .map(|path| Expr::DirName(DirNameTerm { path, depth: None })) - .collect(), - ), - ]; + let mut sources_conditions = vec![expr_any( + config + .sources + .iter() + .flat_map(|(path, name)| match name { + SourceSet::SourceSetName(name) => { + std::iter::once((path, config.projects.get(&name))).collect::>() + } + SourceSet::SourceSetNames(names) => names + .iter() + .map(|name| (path, config.projects.get(name))) + .collect::>(), + }) + .filter_map(|(path, project)| match project { + Some(p) if p.enabled => Some(Expr::All(vec![ + // Ending in *.js(x) or *.ts(x) depending on the project language. + Expr::Suffix(match &p.typegen_config.language { + TypegenLanguage::Flow => vec![PathBuf::from("js"), PathBuf::from("jsx")], + TypegenLanguage::TypeScript => { + vec![PathBuf::from("ts"), PathBuf::from("tsx")] + } + }), + // In the related source root. + Expr::DirName(DirNameTerm { + path: path.clone(), + depth: None, + }), + ])), + _ => None, + }) + .collect(), + )]; // not excluded by any glob if !config.excludes.is_empty() { sources_conditions.push(Expr::Not(Box::new(expr_any( diff --git a/compiler/crates/relay-typegen/src/config.rs b/compiler/crates/relay-typegen/src/config.rs index 1e85bfc6375c4..5bfcb5687702b 100644 --- a/compiler/crates/relay-typegen/src/config.rs +++ b/compiler/crates/relay-typegen/src/config.rs @@ -9,9 +9,26 @@ use fnv::FnvHashMap; use interner::StringKey; use serde::Deserialize; +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "lowercase")] +pub enum TypegenLanguage { + Flow, + TypeScript, +} + +impl Default for TypegenLanguage { + fn default() -> Self { + Self::Flow + } +} + #[derive(Debug, Deserialize, Default)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct TypegenConfig { + /// The desired output language, "flow" or "typescript". + #[serde(default)] + pub language: TypegenLanguage, + /// # For Flow type generation /// When set, enum values are imported from a module with this suffix. /// For example, an enum Foo and this property set to ".test" would be diff --git a/compiler/crates/relay-typegen/src/flow.rs b/compiler/crates/relay-typegen/src/flow.rs index 3a8585201fb88..7b044364f892e 100644 --- a/compiler/crates/relay-typegen/src/flow.rs +++ b/compiler/crates/relay-typegen/src/flow.rs @@ -5,224 +5,254 @@ * LICENSE file in the root directory of this source tree. */ -use interner::{Intern, StringKey}; -use lazy_static::lazy_static; +use crate::writer::{Prop, Writer, AST, SPREAD_KEY}; +use interner::StringKey; use std::fmt::{Result, Write}; -#[derive(Debug, Clone)] -pub enum AST { - Union(Vec), - Intersection(Vec), - ReadOnlyArray(Box), - Nullable(Box), - Identifier(StringKey), - /// Printed as is, should be valid Flow code. - RawType(StringKey), - String, - StringLiteral(StringKey), - /// Prints as `"%other" with a comment explaining open enums. - OtherEnumValue, - Local3DPayload(StringKey, Box), - ExactObject(Vec), - InexactObject(Vec), - Number, - Boolean, - Any, +pub struct FlowPrinter { + indentation: u32, } -lazy_static! { - /// Special key for `Prop` that turns into an object spread: ...value - pub static ref SPREAD_KEY: StringKey = "\0SPREAD".intern(); -} +impl Writer for FlowPrinter { + fn write_ast(&mut self, ast: &AST) -> String { + let mut writer = String::new(); + self.write(&mut writer, ast) + .expect("Expected Ok result from writing Flow code"); -#[derive(Debug, Clone)] -pub struct Prop { - pub key: StringKey, - pub value: AST, - pub read_only: bool, - pub optional: bool, + writer + } } -pub fn print_type(ast: &AST) -> String { - let mut printer = Printer { - writer: String::new(), - indentation: 0, - }; - printer.write(ast).unwrap(); - printer.writer -} +impl FlowPrinter { + pub fn new() -> Self { + Self { indentation: 0 } + } -struct Printer { - writer: W, - indentation: u32, -} -impl Printer { - fn write(&mut self, ast: &AST) -> Result { + fn write(&mut self, writer: &mut dyn Write, ast: &AST) -> Result { match ast { - AST::Any => write!(self.writer, "any")?, - AST::String => write!(self.writer, "string")?, - AST::StringLiteral(literal) => self.write_string_literal(*literal)?, - AST::OtherEnumValue => self.write_other_string()?, - AST::Number => write!(self.writer, "number")?, - AST::Boolean => write!(self.writer, "boolean")?, - AST::Identifier(identifier) => write!(self.writer, "{}", identifier)?, - AST::RawType(raw) => write!(self.writer, "{}", raw)?, - AST::Union(members) => self.write_union(members)?, - AST::Intersection(members) => self.write_intersection(members)?, - AST::ReadOnlyArray(of_type) => self.write_read_only_array(of_type)?, - AST::Nullable(of_type) => self.write_nullable(of_type)?, - AST::ExactObject(props) => self.write_object(props, true)?, - AST::InexactObject(props) => self.write_object(props, false)?, + AST::Any => write!(writer, "any")?, + AST::String => write!(writer, "string")?, + AST::StringLiteral(literal) => self.write_string_literal(writer, *literal)?, + AST::OtherEnumValue => self.write_other_string(writer)?, + AST::Number => write!(writer, "number")?, + AST::Boolean => write!(writer, "boolean")?, + AST::Identifier(identifier) => write!(writer, "{}", identifier)?, + AST::RawType(raw) => write!(writer, "{}", raw)?, + AST::Union(members) => self.write_union(writer, members)?, + AST::Intersection(members) => self.write_intersection(writer, members)?, + AST::ReadOnlyArray(of_type) => self.write_read_only_array(writer, of_type)?, + AST::Nullable(of_type) => self.write_nullable(writer, of_type)?, + AST::ExactObject(props) => self.write_object(writer, props, true)?, + AST::InexactObject(props) => self.write_object(writer, props, false)?, AST::Local3DPayload(document_name, selections) => { - self.write_local_3d_payload(*document_name, selections)? + self.write_local_3d_payload(writer, *document_name, selections)? + } + AST::ImportType(types, from) => self.write_import_type(writer, types, from)?, + AST::DeclareExportOpaqueType(alias, value) => { + self.write_declare_export_opaque_type(writer, alias, value)? + } + AST::ExportTypeEquals(name, value) => { + self.write_export_type_equals(writer, name, value)? } + AST::ExportList(names) => self.write_export_list(writer, names)?, } + Ok(()) } - fn write_indentation(&mut self) -> Result { + fn write_indentation(&mut self, writer: &mut dyn Write) -> Result { for _ in 0..self.indentation { - write!(self.writer, " ")?; + write!(writer, " ")?; } Ok(()) } - fn write_string_literal(&mut self, literal: StringKey) -> Result { - write!(self.writer, "\"{}\"", literal) + fn write_string_literal(&mut self, writer: &mut dyn Write, literal: StringKey) -> Result { + write!(writer, "\"{}\"", literal) } - fn write_other_string(&mut self) -> Result { - write!(self.writer, r#""%other""#) + fn write_other_string(&mut self, writer: &mut dyn Write) -> Result { + write!(writer, r#""%other""#) } - fn write_union(&mut self, members: &[AST]) -> Result { + fn write_union(&mut self, writer: &mut dyn Write, members: &[AST]) -> Result { let mut first = true; for member in members { if first { first = false; } else { - write!(self.writer, " | ")?; + write!(writer, " | ")?; } - self.write(member)?; + self.write(writer, member)?; } Ok(()) } - fn write_intersection(&mut self, members: &[AST]) -> Result { + fn write_intersection(&mut self, writer: &mut dyn Write, members: &[AST]) -> Result { let mut first = true; for member in members { if first { first = false; } else { - write!(self.writer, " & ")?; + write!(writer, " & ")?; } - self.write(member)?; + self.write(writer, member)?; } Ok(()) } - fn write_read_only_array(&mut self, of_type: &AST) -> Result { - write!(self.writer, "$ReadOnlyArray<")?; - self.write(of_type)?; - write!(self.writer, ">") + fn write_read_only_array(&mut self, writer: &mut dyn Write, of_type: &AST) -> Result { + write!(writer, "$ReadOnlyArray<")?; + self.write(writer, of_type)?; + write!(writer, ">") } - fn write_nullable(&mut self, of_type: &AST) -> Result { - write!(self.writer, "?")?; + fn write_nullable(&mut self, writer: &mut dyn Write, of_type: &AST) -> Result { + write!(writer, "?")?; match of_type { AST::Union(members) if members.len() > 1 => { - write!(self.writer, "(")?; - self.write(of_type)?; - write!(self.writer, ")")?; + write!(writer, "(")?; + self.write(writer, of_type)?; + write!(writer, ")")?; } _ => { - self.write(of_type)?; + self.write(writer, of_type)?; } } Ok(()) } - fn write_object(&mut self, props: &[Prop], exact: bool) -> Result { + fn write_object(&mut self, writer: &mut dyn Write, props: &[Prop], exact: bool) -> Result { if props.is_empty() && exact { - write!(self.writer, "{{||}}")?; + write!(writer, "{{||}}")?; return Ok(()); } // Replication of babel printer oddity: objects only containing a spread // are missing a newline. if props.len() == 1 && props[0].key == *SPREAD_KEY { - write!(self.writer, "{{| ...")?; - self.write(&props[0].value)?; - writeln!(self.writer)?; - self.write_indentation()?; - write!(self.writer, "|}}")?; + write!(writer, "{{| ...")?; + self.write(writer, &props[0].value)?; + writeln!(writer)?; + self.write_indentation(writer)?; + write!(writer, "|}}")?; return Ok(()); } if exact { - writeln!(self.writer, "{{|")?; + writeln!(writer, "{{|")?; } else { - writeln!(self.writer, "{{")?; + writeln!(writer, "{{")?; } self.indentation += 1; let mut first = true; for prop in props { - self.write_indentation()?; + self.write_indentation(writer)?; if prop.key == *SPREAD_KEY { - write!(self.writer, "...")?; - self.write(&prop.value)?; - writeln!(self.writer, ",")?; + write!(writer, "...")?; + self.write(writer, &prop.value)?; + writeln!(writer, ",")?; continue; } if let AST::OtherEnumValue = prop.value { + writeln!(writer, "// This will never be '%other', but we need some")?; + self.write_indentation(writer)?; writeln!( - self.writer, - "// This will never be '%other', but we need some" - )?; - self.write_indentation()?; - writeln!( - self.writer, + writer, "// value in case none of the concrete values match." )?; - self.write_indentation()?; + self.write_indentation(writer)?; } if prop.read_only { - write!(self.writer, "+")?; + write!(writer, "+")?; } - write!(self.writer, "{}", prop.key)?; + write!(writer, "{}", prop.key)?; if prop.optional { - write!(self.writer, "?")?; + write!(writer, "?")?; } - write!(self.writer, ": ")?; - self.write(&prop.value)?; + write!(writer, ": ")?; + self.write(writer, &prop.value)?; if first && props.len() == 1 && exact { - writeln!(self.writer)?; + writeln!(writer)?; } else { - writeln!(self.writer, ",")?; + writeln!(writer, ",")?; } first = false; } if !exact { - self.write_indentation()?; - writeln!(self.writer, "...")?; + self.write_indentation(writer)?; + writeln!(writer, "...")?; } self.indentation -= 1; - self.write_indentation()?; + self.write_indentation(writer)?; if exact { - write!(self.writer, "|}}")?; + write!(writer, "|}}")?; } else { - write!(self.writer, "}}")?; + write!(writer, "}}")?; } Ok(()) } - fn write_local_3d_payload(&mut self, document_name: StringKey, selections: &AST) -> Result { - write!(self.writer, "Local3DPayload<\"{}\", ", document_name)?; - self.write(selections)?; - write!(self.writer, ">")?; + fn write_local_3d_payload( + &mut self, + writer: &mut dyn Write, + document_name: StringKey, + selections: &AST, + ) -> Result { + write!(writer, "Local3DPayload<\"{}\", ", document_name)?; + self.write(writer, selections)?; + write!(writer, ">")?; Ok(()) } + + fn write_import_type( + &mut self, + writer: &mut dyn Write, + types: &Vec, + from: &StringKey, + ) -> Result { + write!( + writer, + "import type {{ {} }} from \"{}\";", + types + .iter() + .map(|t| format!("{}", t)) + .collect::>() + .join(", "), + from + ) + } + + fn write_declare_export_opaque_type( + &mut self, + writer: &mut dyn Write, + alias: &StringKey, + value: &StringKey, + ) -> Result { + write!(writer, "declare export opaque type {}: {};", alias, value) + } + + fn write_export_type_equals( + &mut self, + writer: &mut dyn Write, + name: &StringKey, + value: &AST, + ) -> Result { + write!(writer, "export type {} = {};", name, self.write_ast(value)) + } + + fn write_export_list(&mut self, writer: &mut dyn Write, names: &Vec) -> Result { + write!( + writer, + "export type {{ {} }};", + names + .iter() + .map(|t| format!("{}", t)) + .collect::>() + .join(", "), + ) + } } #[cfg(test)] @@ -230,6 +260,10 @@ mod tests { use super::*; use interner::Intern; + fn print_type(ast: &AST) -> String { + FlowPrinter::new().write_ast(ast) + } + #[test] fn scalar_types() { assert_eq!(print_type(&AST::Any), "any".to_string()); diff --git a/compiler/crates/relay-typegen/src/lib.rs b/compiler/crates/relay-typegen/src/lib.rs index 4cb2e9866e4cf..688be6c31d0c3 100644 --- a/compiler/crates/relay-typegen/src/lib.rs +++ b/compiler/crates/relay-typegen/src/lib.rs @@ -11,10 +11,14 @@ mod config; mod flow; +mod typescript; +mod writer; +use crate::flow::FlowPrinter; +use crate::typescript::TypeScriptPrinter; +use crate::writer::Writer; use common::NamedItem; -pub use config::TypegenConfig; -use flow::{print_type, Prop, AST, SPREAD_KEY}; +pub use config::{TypegenConfig, TypegenLanguage}; use fnv::FnvHashSet; use graphql_ir::{ Condition, Directive, FragmentDefinition, FragmentSpread, InlineFragment, LinkedField, @@ -30,6 +34,7 @@ use lazy_static::lazy_static; use schema::{EnumID, ScalarID, Schema, Type, TypeReference}; use std::fmt::{Result, Write}; use std::hash::Hash; +use writer::{Prop, AST, SPREAD_KEY}; lazy_static! { static ref RAW_RESPONSE_TYPE_DIRECTIVE_NAME: StringKey = "raw_response_type".intern(); @@ -95,6 +100,7 @@ struct TypeGenerator<'schema, 'config> { typegen_config: &'config TypegenConfig, runtime_imports: RuntimeImports, match_fields: IndexMap, + writer: Box, } impl<'schema, 'config> TypeGenerator<'schema, 'config> { fn new(schema: &'schema Schema, typegen_config: &'config TypegenConfig) -> Self { @@ -108,6 +114,14 @@ impl<'schema, 'config> TypeGenerator<'schema, 'config> { typegen_config, match_fields: Default::default(), runtime_imports: RuntimeImports::default(), + writer: Self::create_writer(typegen_config), + } + } + + fn create_writer(typegen_config: &TypegenConfig) -> Box { + match &typegen_config.language { + TypegenLanguage::Flow => Box::new(FlowPrinter::new()), + TypegenLanguage::TypeScript => Box::new(TypeScriptPrinter::new()), } } @@ -149,15 +163,19 @@ impl<'schema, 'config> TypeGenerator<'schema, 'config> { self.write_input_object_types()?; writeln!( self.result, - "export type {} = {};", - input_variables_identifier, - print_type(&input_variables_type) + "{}", + self.writer.write_ast(&AST::ExportTypeEquals( + input_variables_identifier, + Box::from(input_variables_type) + )) )?; writeln!( self.result, - "export type {} = {};", - response_identifier, - print_type(&response_type) + "{}", + self.writer.write_ast(&AST::ExportTypeEquals( + response_identifier, + Box::from(response_type) + )), )?; let mut operation_types = vec![ @@ -177,15 +195,22 @@ impl<'schema, 'config> TypeGenerator<'schema, 'config> { if let Some(raw_response_type) = raw_response_type { for (key, ast) in self.match_fields.iter() { - writeln!(self.result, "export type {} = {};", key, print_type(ast))?; + writeln!( + self.result, + "{}", + self.writer + .write_ast(&AST::ExportTypeEquals(*key, Box::from(ast.clone()))) + )?; } let raw_response_identifier = format!("{}RawResponse", typegen_operation.name.item).intern(); writeln!( self.result, - "export type {} = {};", - raw_response_identifier, - print_type(&raw_response_type) + "{}", + self.writer.write_ast(&AST::ExportTypeEquals( + raw_response_identifier, + Box::from(raw_response_type) + )) )?; operation_types.push(Prop { key: *KEY_RAW_RESPONSE, @@ -197,9 +222,11 @@ impl<'schema, 'config> TypeGenerator<'schema, 'config> { writeln!( self.result, - "export type {} = {};", - typegen_operation.name.item, - print_type(&AST::ExactObject(operation_types)) + "{}", + self.writer.write_ast(&AST::ExportTypeEquals( + typegen_operation.name.item, + Box::from(AST::ExactObject(operation_types)) + )) )?; Ok(()) } @@ -291,42 +318,59 @@ impl<'schema, 'config> TypeGenerator<'schema, 'config> { if let Some(refetchable_metadata) = refetchable_metadata { writeln!( self.result, - r#"import type {{ {}, {} }} from "{}.graphql";"#, - old_fragment_type_name, new_fragment_type_name, refetchable_metadata.operation_name + "{}", + self.writer.write_ast(&AST::ImportType( + vec![old_fragment_type_name, new_fragment_type_name], + format!("{}.graphql", refetchable_metadata.operation_name).intern() + )) )?; writeln!( self.result, - r#"export type {{ {}, {} }};"#, - old_fragment_type_name, new_fragment_type_name + "{}", + self.writer.write_ast(&AST::ExportList(vec![ + old_fragment_type_name, + new_fragment_type_name + ])) )?; } else { writeln!( self.result, - "declare export opaque type {}: FragmentReference;", - old_fragment_type_name + "{}", + self.writer.write_ast(&AST::DeclareExportOpaqueType( + old_fragment_type_name, + "FragmentReference".intern() + )) )?; writeln!( self.result, - "declare export opaque type {}: {};", - new_fragment_type_name, old_fragment_type_name + "{}", + self.writer.write_ast(&AST::DeclareExportOpaqueType( + new_fragment_type_name, + old_fragment_type_name + )) )?; } writeln!( self.result, - "export type {} = {};", - node.name.item, - print_type(&type_) + "{}", + self.writer + .write_ast(&AST::ExportTypeEquals(node.name.item, Box::from(type_))) )?; writeln!( self.result, - "export type {} = {};", - data_type_name, data_type + "{}", + self.writer.write_ast(&AST::ExportTypeEquals( + data_type_name.intern(), + Box::from(AST::RawType(data_type.intern())) + )) )?; writeln!( self.result, - "export type {} = {};", - ref_type_name, - print_type(&ref_type) + "{}", + self.writer.write_ast(&AST::ExportTypeEquals( + ref_type_name.intern(), + Box::from(ref_type) + )) )?; Ok(()) @@ -976,21 +1020,33 @@ impl<'schema, 'config> TypeGenerator<'schema, 'config> { fragment_reference: true, } => writeln!( self.result, - r#"import type {{ FragmentReference, Local3DPayload }} from "relay-runtime";"# + "{}", + self.writer.write_ast(&AST::ImportType( + vec!["FragmentReference".intern(), "Local3DPayload".intern()], + "relay-runtime".intern() + )) ), RuntimeImports { local_3d_payload: true, fragment_reference: false, } => writeln!( self.result, - r#"import type {{ Local3DPayload }} from "relay-runtime";"# + "{}", + self.writer.write_ast(&AST::ImportType( + vec!["Local3DPayload".intern()], + "relay-runtime".intern() + )) ), RuntimeImports { local_3d_payload: false, fragment_reference: true, } => writeln!( self.result, - r#"import type {{ FragmentReference }} from "relay-runtime";"# + "{}", + self.writer.write_ast(&AST::ImportType( + vec!["FragmentReference".intern()], + "relay-runtime".intern() + )) ), RuntimeImports { local_3d_payload: false, @@ -1067,7 +1123,7 @@ impl<'schema, 'config> TypeGenerator<'schema, 'config> { self.result, "export type {} = {};", enum_type.name, - print_type(&AST::Union(members)) + self.writer.write_ast(&AST::Union(members)) )?; } } @@ -1096,7 +1152,7 @@ impl<'schema, 'config> TypeGenerator<'schema, 'config> { self.result, "export type {} = {};", type_identifier, - print_type(&input_object_type) + self.writer.write_ast(&input_object_type) )?; } GeneratedInputObject::Pending => panic!("expected a resolved type here"), diff --git a/compiler/crates/relay-typegen/src/typescript.rs b/compiler/crates/relay-typegen/src/typescript.rs new file mode 100644 index 0000000000000..f7f27596a25ff --- /dev/null +++ b/compiler/crates/relay-typegen/src/typescript.rs @@ -0,0 +1,513 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +use crate::writer::{Prop, Writer, AST, SPREAD_KEY}; +use interner::{Intern, StringKey}; +use std::fmt::{Result, Write}; + +pub struct TypeScriptPrinter { + indentation: u32, +} + +impl Writer for TypeScriptPrinter { + fn write_ast(&mut self, ast: &AST) -> String { + let mut writer = String::new(); + self.write(&mut writer, ast) + .expect("Expected Ok result from writing TypeScript code"); + + writer + } +} + +impl TypeScriptPrinter { + pub fn new() -> Self { + Self { indentation: 0 } + } + + fn write(&mut self, writer: &mut dyn Write, ast: &AST) -> Result { + match ast { + AST::Any => write!(writer, "any")?, + AST::String => write!(writer, "string")?, + AST::StringLiteral(literal) => self.write_string_literal(writer, *literal)?, + AST::OtherEnumValue => self.write_other_string(writer)?, + AST::Number => write!(writer, "number")?, + AST::Boolean => write!(writer, "boolean")?, + AST::Identifier(identifier) => write!(writer, "{}", identifier)?, + AST::RawType(raw) => write!(writer, "{}", raw)?, + AST::Union(members) => self.write_union(writer, members)?, + AST::Intersection(members) => self.write_intersection(writer, members)?, + AST::ReadOnlyArray(of_type) => self.write_read_only_array(writer, of_type)?, + AST::Nullable(of_type) => self.write_nullable(writer, of_type)?, + AST::ExactObject(props) => self.write_object(writer, props, true)?, + AST::InexactObject(props) => self.write_object(writer, props, false)?, + AST::Local3DPayload(document_name, selections) => { + self.write_local_3d_payload(writer, *document_name, selections)? + } + AST::ImportType(types, from) => self.write_import_type(writer, types, from)?, + AST::DeclareExportOpaqueType(alias, value) => { + self.write_declare_export_opaque_type(writer, alias, value)? + } + AST::ExportTypeEquals(name, value) => { + self.write_export_type_equals(writer, name, value)? + } + AST::ExportList(names) => self.write_export_list(writer, names)?, + } + + Ok(()) + } + + fn write_indentation(&mut self, writer: &mut dyn Write) -> Result { + for _ in 0..self.indentation { + write!(writer, " ")?; + } + Ok(()) + } + + fn write_string_literal(&mut self, writer: &mut dyn Write, literal: StringKey) -> Result { + write!(writer, "\"{}\"", literal) + } + + fn write_other_string(&mut self, writer: &mut dyn Write) -> Result { + write!(writer, r#""%other""#) + } + + fn write_and_wrap_union(&mut self, writer: &mut dyn Write, ast: &AST) -> Result { + match ast { + AST::Union(members) if members.len() > 1 => { + write!(writer, "(")?; + self.write_union(writer, members)?; + write!(writer, ")")?; + } + _ => { + self.write(writer, ast)?; + } + } + + Ok(()) + } + + fn write_union(&mut self, writer: &mut dyn Write, members: &[AST]) -> Result { + let mut first = true; + for member in members { + if first { + first = false; + } else { + write!(writer, " | ")?; + } + self.write(writer, member)?; + } + Ok(()) + } + + fn write_intersection(&mut self, writer: &mut dyn Write, members: &[AST]) -> Result { + let mut first = true; + for member in members { + if first { + first = false; + } else { + write!(writer, " & ")?; + } + + self.write_and_wrap_union(writer, member)?; + } + Ok(()) + } + + fn write_read_only_array(&mut self, writer: &mut dyn Write, of_type: &AST) -> Result { + write!(writer, "ReadonlyArray<")?; + self.write(writer, of_type)?; + write!(writer, ">") + } + + fn write_nullable(&mut self, writer: &mut dyn Write, of_type: &AST) -> Result { + let null_type = AST::RawType("null".intern()); + if let AST::Union(members) = of_type { + let mut new_members = Vec::with_capacity(members.len() + 1); + new_members.extend_from_slice(members); + new_members.push(null_type); + self.write_union(writer, &*new_members)?; + } else { + self.write_union(writer, &*vec![of_type.clone(), null_type])?; + } + Ok(()) + } + + fn write_object(&mut self, writer: &mut dyn Write, props: &[Prop], exact: bool) -> Result { + if props.is_empty() { + write!(writer, "{{}}")?; + return Ok(()); + } + + // Replication of babel printer oddity: objects only containing a spread + // are missing a newline. + if props.len() == 1 && props[0].key == *SPREAD_KEY { + write!(writer, "{{}}")?; + return Ok(()); + } + + writeln!(writer, "{{")?; + self.indentation += 1; + + let mut first = true; + for prop in props { + if prop.key == *SPREAD_KEY { + continue; + } + + self.write_indentation(writer)?; + if let AST::OtherEnumValue = prop.value { + writeln!(writer, "// This will never be '%other', but we need some")?; + self.write_indentation(writer)?; + writeln!( + writer, + "// value in case none of the concrete values match." + )?; + self.write_indentation(writer)?; + } + if prop.read_only { + write!(writer, "readonly ")?; + } + write!(writer, "{}", prop.key)?; + if match &prop.value { + AST::Nullable(_) => true, + _ => prop.optional, + } { + write!(writer, "?")?; + } + write!(writer, ": ")?; + self.write( + writer, + if let AST::Nullable(value) = &prop.value { + value + } else { + &prop.value + }, + )?; + if first && props.len() == 1 && exact { + writeln!(writer)?; + } else { + writeln!(writer, ",")?; + } + first = false; + } + self.indentation -= 1; + self.write_indentation(writer)?; + write!(writer, "}}")?; + Ok(()) + } + + fn write_local_3d_payload( + &mut self, + writer: &mut dyn Write, + document_name: StringKey, + selections: &AST, + ) -> Result { + write!(writer, "Local3DPayload<\"{}\", ", document_name)?; + self.write(writer, selections)?; + write!(writer, ">")?; + Ok(()) + } + + fn write_import_type( + &mut self, + writer: &mut dyn Write, + types: &Vec, + from: &StringKey, + ) -> Result { + write!( + writer, + "import {{ {} }} from \"{}\";", + types + .iter() + .map(|t| format!("{}", t)) + .collect::>() + .join(", "), + from + ) + } + + fn write_declare_export_opaque_type( + &mut self, + writer: &mut dyn Write, + alias: &StringKey, + value: &StringKey, + ) -> Result { + write!( + writer, + "export type {} = {} & {{ _: \"{}\" }};", + alias, value, alias + ) + } + + fn write_export_type_equals( + &mut self, + writer: &mut dyn Write, + name: &StringKey, + value: &AST, + ) -> Result { + write!(writer, "export type {} = {};", name, self.write_ast(value)) + } + + fn write_export_list(&mut self, writer: &mut dyn Write, names: &Vec) -> Result { + write!( + writer, + "export {{ {} }};", + names + .iter() + .map(|t| format!("{}", t)) + .collect::>() + .join(", "), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use interner::Intern; + + fn print_type(ast: &AST) -> String { + TypeScriptPrinter::new().write_ast(ast) + } + + #[test] + fn scalar_types() { + assert_eq!(print_type(&AST::Any), "any".to_string()); + assert_eq!(print_type(&AST::String), "string".to_string()); + assert_eq!(print_type(&AST::Number), "number".to_string()); + } + + #[test] + fn union_type() { + assert_eq!( + print_type(&AST::Union(vec![AST::String, AST::Number])), + "string | number".to_string() + ); + } + + #[test] + fn read_only_array_type() { + assert_eq!( + print_type(&AST::ReadOnlyArray(Box::new(AST::String))), + "ReadonlyArray".to_string() + ); + } + + #[test] + fn nullable_type() { + assert_eq!( + print_type(&AST::Nullable(Box::new(AST::String))), + "string | null".to_string() + ); + + assert_eq!( + print_type(&AST::Nullable(Box::new(AST::Union(vec![ + AST::String, + AST::Number, + ])))), + "string | number | null" + ) + } + + #[test] + fn intersections() { + assert_eq!( + print_type(&AST::Intersection(vec![ + AST::ExactObject(vec![Prop { + key: "first".intern(), + optional: false, + read_only: false, + value: AST::String + }]), + AST::ExactObject(vec![Prop { + key: "second".intern(), + optional: false, + read_only: false, + value: AST::Number + }]), + ])), + r"{ + first: string +} & { + second: number +}" + ); + + assert_eq!( + print_type(&AST::Intersection(vec![ + AST::Union(vec![ + AST::ExactObject(vec![Prop { + key: "first".intern(), + optional: false, + read_only: false, + value: AST::String + }]), + AST::ExactObject(vec![Prop { + key: "second".intern(), + optional: false, + read_only: false, + value: AST::Number + }]), + ]), + AST::ExactObject(vec![Prop { + key: "third".intern(), + optional: false, + read_only: false, + value: AST::Number + }]), + ],)), + r"({ + first: string +} | { + second: number +}) & { + third: number +}" + ); + } + + #[test] + fn exact_object() { + assert_eq!(print_type(&AST::ExactObject(Vec::new())), r"{}".to_string()); + + assert_eq!( + print_type(&AST::ExactObject(vec![Prop { + key: "single".intern(), + optional: false, + read_only: false, + value: AST::String, + },])), + r"{ + single: string +}" + .to_string() + ); + assert_eq!( + print_type(&AST::ExactObject(vec![ + Prop { + key: "foo".intern(), + optional: true, + read_only: false, + value: AST::String, + }, + Prop { + key: "bar".intern(), + optional: false, + read_only: true, + value: AST::Number, + }, + ])), + r"{ + foo?: string, + readonly bar: number, +}" + .to_string() + ); + } + + #[test] + fn nested_object() { + assert_eq!( + print_type(&AST::ExactObject(vec![ + Prop { + key: "foo".intern(), + optional: true, + read_only: false, + value: AST::ExactObject(vec![ + Prop { + key: "nested_foo".intern(), + optional: true, + read_only: false, + value: AST::String, + }, + Prop { + key: "nested_foo2".intern(), + optional: false, + read_only: true, + value: AST::Number, + }, + ]), + }, + Prop { + key: "bar".intern(), + optional: false, + read_only: true, + value: AST::Number, + }, + ])), + r"{ + foo?: { + nested_foo?: string, + readonly nested_foo2: number, + }, + readonly bar: number, +}" + .to_string() + ); + } + + #[test] + fn inexact_object() { + assert_eq!( + print_type(&AST::InexactObject(Vec::new())), + "{}".to_string() + ); + + assert_eq!( + print_type(&AST::InexactObject(vec![Prop { + key: "single".intern(), + optional: false, + read_only: false, + value: AST::String, + },])), + r"{ + single: string, +}" + .to_string() + ); + + assert_eq!( + print_type(&AST::InexactObject(vec![ + Prop { + key: "foo".intern(), + optional: false, + read_only: false, + value: AST::String, + }, + Prop { + key: "bar".intern(), + optional: true, + read_only: true, + value: AST::Number, + } + ])), + r"{ + foo: string, + readonly bar?: number, +}" + .to_string() + ); + } + + #[test] + fn other_comment() { + assert_eq!( + print_type(&AST::ExactObject(vec![Prop { + key: "with_comment".intern(), + optional: false, + read_only: false, + value: AST::OtherEnumValue, + },])), + r#"{ + // This will never be '%other', but we need some + // value in case none of the concrete values match. + with_comment: "%other" +}"# + .to_string() + ); + } +} diff --git a/compiler/crates/relay-typegen/src/writer.rs b/compiler/crates/relay-typegen/src/writer.rs new file mode 100644 index 0000000000000..70680cb395d2f --- /dev/null +++ b/compiler/crates/relay-typegen/src/writer.rs @@ -0,0 +1,51 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +use interner::{Intern, StringKey}; +use lazy_static::lazy_static; + +#[derive(Debug, Clone)] +pub enum AST { + Union(Vec), + Intersection(Vec), + ReadOnlyArray(Box), + Nullable(Box), + Identifier(StringKey), + /// Printed as is, should be valid Flow code. + RawType(StringKey), + String, + StringLiteral(StringKey), + /// Prints as `"%other" with a comment explaining open enums. + OtherEnumValue, + Local3DPayload(StringKey, Box), + ExactObject(Vec), + InexactObject(Vec), + Number, + Boolean, + Any, + ImportType(Vec, StringKey), + DeclareExportOpaqueType(StringKey, StringKey), + ExportList(Vec), + ExportTypeEquals(StringKey, Box), +} + +#[derive(Debug, Clone)] +pub struct Prop { + pub key: StringKey, + pub value: AST, + pub read_only: bool, + pub optional: bool, +} + +lazy_static! { + /// Special key for `Prop` that turns into an object spread: ...value + pub static ref SPREAD_KEY: StringKey = "\0SPREAD".intern(); +} + +pub trait Writer { + fn write_ast(&mut self, ast: &AST) -> String; +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/conditional.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/conditional.expected new file mode 100644 index 0000000000000..66781a28c5d22 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/conditional.expected @@ -0,0 +1,36 @@ +==================================== INPUT ==================================== +fragment ConditionField on Node { + id @include(if: $condition) +} + +fragment NestedCondition on Node { + ... @include(if: $condition) { + id + } +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type ConditionField$ref = FragmentReference & { _: "ConditionField$ref" }; +export type ConditionField$fragmentType = ConditionField$ref & { _: "ConditionField$fragmentType" }; +export type ConditionField = { + readonly id?: string, + readonly $refType: ConditionField$ref, +}; +export type ConditionField$data = ConditionField; +export type ConditionField$key = { + readonly $data?: ConditionField$data, + readonly $fragmentRefs: ConditionField$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type NestedCondition$ref = FragmentReference & { _: "NestedCondition$ref" }; +export type NestedCondition$fragmentType = NestedCondition$ref & { _: "NestedCondition$fragmentType" }; +export type NestedCondition = { + readonly id?: string, + readonly $refType: NestedCondition$ref, +}; +export type NestedCondition$data = NestedCondition; +export type NestedCondition$key = { + readonly $data?: NestedCondition$data, + readonly $fragmentRefs: NestedCondition$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/conditional.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/conditional.graphql new file mode 100644 index 0000000000000..bf1f28db68089 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/conditional.graphql @@ -0,0 +1,9 @@ +fragment ConditionField on Node { + id @include(if: $condition) +} + +fragment NestedCondition on Node { + ... @include(if: $condition) { + id + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/fragment-spread.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/fragment-spread.expected new file mode 100644 index 0000000000000..daae24c71795a --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/fragment-spread.expected @@ -0,0 +1,184 @@ +==================================== INPUT ==================================== +fragment FragmentSpread on Node { + id + ...OtherFragment + justFrag: profilePicture { + ...PictureFragment + } + fragAndField: profilePicture { + uri + ...PictureFragment + } + ... on User { + ...UserFrag1 + ...UserFrag2 + } +} + +fragment ConcreateTypes on Viewer { + actor { + __typename + ... on Page { + id + ...PageFragment + } + ... on User { + name + } + } +} + +fragment PictureFragment on Image { + __typename +} + +fragment OtherFragment on Node { + __typename +} + +fragment PageFragment on Page { + __typename +} + +fragment UserFrag1 on User { + __typename +} + +fragment UserFrag2 on User { + __typename +} +==================================== OUTPUT =================================== +import type { PageFragment$ref } from "PageFragment.graphql"; +import { FragmentReference } from "relay-runtime"; +export type ConcreateTypes$ref = FragmentReference & { _: "ConcreateTypes$ref" }; +export type ConcreateTypes$fragmentType = ConcreateTypes$ref & { _: "ConcreateTypes$fragmentType" }; +export type ConcreateTypes = { + readonly actor?: { + readonly __typename: "Page", + readonly id: string, + readonly $fragmentRefs: PageFragment$ref, + } | { + readonly __typename: "User", + readonly name?: string, + } | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other" + }, + readonly $refType: ConcreateTypes$ref, +}; +export type ConcreateTypes$data = ConcreateTypes; +export type ConcreateTypes$key = { + readonly $data?: ConcreateTypes$data, + readonly $fragmentRefs: ConcreateTypes$ref, +}; +------------------------------------------------------------------------------- +import type { OtherFragment$ref } from "OtherFragment.graphql"; +import type { PictureFragment$ref } from "PictureFragment.graphql"; +import type { UserFrag1$ref } from "UserFrag1.graphql"; +import type { UserFrag2$ref } from "UserFrag2.graphql"; +import { FragmentReference } from "relay-runtime"; +export type FragmentSpread$ref = FragmentReference & { _: "FragmentSpread$ref" }; +export type FragmentSpread$fragmentType = FragmentSpread$ref & { _: "FragmentSpread$fragmentType" }; +export type FragmentSpread = { + readonly id: string, + readonly justFrag?: { + readonly $fragmentRefs: PictureFragment$ref + }, + readonly fragAndField?: { + readonly uri?: string, + readonly $fragmentRefs: PictureFragment$ref, + }, + readonly $fragmentRefs: OtherFragment$ref & UserFrag1$ref & UserFrag2$ref, + readonly $refType: FragmentSpread$ref, +}; +export type FragmentSpread$data = FragmentSpread; +export type FragmentSpread$key = { + readonly $data?: FragmentSpread$data, + readonly $fragmentRefs: FragmentSpread$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type OtherFragment$ref = FragmentReference & { _: "OtherFragment$ref" }; +export type OtherFragment$fragmentType = OtherFragment$ref & { _: "OtherFragment$fragmentType" }; +export type OtherFragment = { + readonly __typename: string, + readonly $refType: OtherFragment$ref, +}; +export type OtherFragment$data = OtherFragment; +export type OtherFragment$key = { + readonly $data?: OtherFragment$data, + readonly $fragmentRefs: OtherFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type PageFragment$ref = FragmentReference & { _: "PageFragment$ref" }; +export type PageFragment$fragmentType = PageFragment$ref & { _: "PageFragment$fragmentType" }; +export type PageFragment = { + readonly __typename: "Page", + readonly $refType: PageFragment$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + readonly $refType: PageFragment$ref, +}; +export type PageFragment$data = PageFragment; +export type PageFragment$key = { + readonly $data?: PageFragment$data, + readonly $fragmentRefs: PageFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type PictureFragment$ref = FragmentReference & { _: "PictureFragment$ref" }; +export type PictureFragment$fragmentType = PictureFragment$ref & { _: "PictureFragment$fragmentType" }; +export type PictureFragment = { + readonly __typename: "Image", + readonly $refType: PictureFragment$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + readonly $refType: PictureFragment$ref, +}; +export type PictureFragment$data = PictureFragment; +export type PictureFragment$key = { + readonly $data?: PictureFragment$data, + readonly $fragmentRefs: PictureFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type UserFrag1$ref = FragmentReference & { _: "UserFrag1$ref" }; +export type UserFrag1$fragmentType = UserFrag1$ref & { _: "UserFrag1$fragmentType" }; +export type UserFrag1 = { + readonly __typename: "User", + readonly $refType: UserFrag1$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + readonly $refType: UserFrag1$ref, +}; +export type UserFrag1$data = UserFrag1; +export type UserFrag1$key = { + readonly $data?: UserFrag1$data, + readonly $fragmentRefs: UserFrag1$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type UserFrag2$ref = FragmentReference & { _: "UserFrag2$ref" }; +export type UserFrag2$fragmentType = UserFrag2$ref & { _: "UserFrag2$fragmentType" }; +export type UserFrag2 = { + readonly __typename: "User", + readonly $refType: UserFrag2$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + readonly $refType: UserFrag2$ref, +}; +export type UserFrag2$data = UserFrag2; +export type UserFrag2$key = { + readonly $data?: UserFrag2$data, + readonly $fragmentRefs: UserFrag2$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/fragment-spread.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/fragment-spread.graphql new file mode 100644 index 0000000000000..e48ea9cb89545 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/fragment-spread.graphql @@ -0,0 +1,48 @@ +fragment FragmentSpread on Node { + id + ...OtherFragment + justFrag: profilePicture { + ...PictureFragment + } + fragAndField: profilePicture { + uri + ...PictureFragment + } + ... on User { + ...UserFrag1 + ...UserFrag2 + } +} + +fragment ConcreateTypes on Viewer { + actor { + __typename + ... on Page { + id + ...PageFragment + } + ... on User { + name + } + } +} + +fragment PictureFragment on Image { + __typename +} + +fragment OtherFragment on Node { + __typename +} + +fragment PageFragment on Page { + __typename +} + +fragment UserFrag1 on User { + __typename +} + +fragment UserFrag2 on User { + __typename +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/inline-fragment.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/inline-fragment.expected new file mode 100644 index 0000000000000..12873541acd25 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/inline-fragment.expected @@ -0,0 +1,157 @@ +==================================== INPUT ==================================== +fragment InlineFragment on Node { + id + ... on Actor { + id + name + } + ... on User { + message { + text + } + } +} + +fragment InlineFragmentWithOverlappingFields on Actor { + ... on User { + hometown { + id + name + } + } + ... on Page { + name + hometown { + id + message { + text + } + } + } +} + +fragment InlineFragmentConditionalID on Node { + ... on Actor { + id # nullable since it's conditional + name + } +} + +fragment InlineFragmentKitchenSink on Story { + actor { + id + profilePicture { + uri + } + ... on User { + id + name + ...SomeFragment + profilePicture { + width + } + } + ... on Page { + profilePicture { + uri + height + } + } + } +} + +fragment SomeFragment on User { + __typename +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type InlineFragment$ref = FragmentReference & { _: "InlineFragment$ref" }; +export type InlineFragment$fragmentType = InlineFragment$ref & { _: "InlineFragment$fragmentType" }; +export type InlineFragment = { + readonly id: string, + readonly name?: string, + readonly message?: { + readonly text?: string + }, + readonly $refType: InlineFragment$ref, +}; +export type InlineFragment$data = InlineFragment; +export type InlineFragment$key = { + readonly $data?: InlineFragment$data, + readonly $fragmentRefs: InlineFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type InlineFragmentConditionalID$ref = FragmentReference & { _: "InlineFragmentConditionalID$ref" }; +export type InlineFragmentConditionalID$fragmentType = InlineFragmentConditionalID$ref & { _: "InlineFragmentConditionalID$fragmentType" }; +export type InlineFragmentConditionalID = { + readonly id?: string, + readonly name?: string, + readonly $refType: InlineFragmentConditionalID$ref, +}; +export type InlineFragmentConditionalID$data = InlineFragmentConditionalID; +export type InlineFragmentConditionalID$key = { + readonly $data?: InlineFragmentConditionalID$data, + readonly $fragmentRefs: InlineFragmentConditionalID$ref, +}; +------------------------------------------------------------------------------- +import type { SomeFragment$ref } from "SomeFragment.graphql"; +import { FragmentReference } from "relay-runtime"; +export type InlineFragmentKitchenSink$ref = FragmentReference & { _: "InlineFragmentKitchenSink$ref" }; +export type InlineFragmentKitchenSink$fragmentType = InlineFragmentKitchenSink$ref & { _: "InlineFragmentKitchenSink$fragmentType" }; +export type InlineFragmentKitchenSink = { + readonly actor?: { + readonly id: string, + readonly profilePicture?: { + readonly uri?: string, + readonly width?: number, + readonly height?: number, + }, + readonly name?: string, + readonly $fragmentRefs: SomeFragment$ref, + }, + readonly $refType: InlineFragmentKitchenSink$ref, +}; +export type InlineFragmentKitchenSink$data = InlineFragmentKitchenSink; +export type InlineFragmentKitchenSink$key = { + readonly $data?: InlineFragmentKitchenSink$data, + readonly $fragmentRefs: InlineFragmentKitchenSink$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type InlineFragmentWithOverlappingFields$ref = FragmentReference & { _: "InlineFragmentWithOverlappingFields$ref" }; +export type InlineFragmentWithOverlappingFields$fragmentType = InlineFragmentWithOverlappingFields$ref & { _: "InlineFragmentWithOverlappingFields$fragmentType" }; +export type InlineFragmentWithOverlappingFields = { + readonly hometown?: { + readonly id: string, + readonly name?: string, + readonly message?: { + readonly text?: string + }, + }, + readonly name?: string, + readonly $refType: InlineFragmentWithOverlappingFields$ref, +}; +export type InlineFragmentWithOverlappingFields$data = InlineFragmentWithOverlappingFields; +export type InlineFragmentWithOverlappingFields$key = { + readonly $data?: InlineFragmentWithOverlappingFields$data, + readonly $fragmentRefs: InlineFragmentWithOverlappingFields$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type SomeFragment$ref = FragmentReference & { _: "SomeFragment$ref" }; +export type SomeFragment$fragmentType = SomeFragment$ref & { _: "SomeFragment$fragmentType" }; +export type SomeFragment = { + readonly __typename: "User", + readonly $refType: SomeFragment$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + readonly $refType: SomeFragment$ref, +}; +export type SomeFragment$data = SomeFragment; +export type SomeFragment$key = { + readonly $data?: SomeFragment$data, + readonly $fragmentRefs: SomeFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/inline-fragment.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/inline-fragment.graphql new file mode 100644 index 0000000000000..66b33a1a41b02 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/inline-fragment.graphql @@ -0,0 +1,64 @@ +fragment InlineFragment on Node { + id + ... on Actor { + id + name + } + ... on User { + message { + text + } + } +} + +fragment InlineFragmentWithOverlappingFields on Actor { + ... on User { + hometown { + id + name + } + } + ... on Page { + name + hometown { + id + message { + text + } + } + } +} + +fragment InlineFragmentConditionalID on Node { + ... on Actor { + id # nullable since it's conditional + name + } +} + +fragment InlineFragmentKitchenSink on Story { + actor { + id + profilePicture { + uri + } + ... on User { + id + name + ...SomeFragment + profilePicture { + width + } + } + ... on Page { + profilePicture { + uri + height + } + } + } +} + +fragment SomeFragment on User { + __typename +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-field.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-field.expected new file mode 100644 index 0000000000000..ec60fce46e30a --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-field.expected @@ -0,0 +1,70 @@ +==================================== INPUT ==================================== +fragment LinkedField on User { + profilePicture { + uri + width + height + } + hometown { + # object + id + profilePicture { + uri + } + } + actor { + # interface + id + } +} + +query UnionTypeTest { + neverNode { + __typename + ... on FakeNode { + id + } + } +} +==================================== OUTPUT =================================== +export type UnionTypeTestVariables = {||}; +export type UnionTypeTestResponse = {| + +neverNode: ?({| + +__typename: "FakeNode", + +id: string, + |} | {| + // This will never be '%other', but we need some + // value in case none of the concrete values match. + +__typename: "%other" + |}) +|}; +export type UnionTypeTest = {| + variables: UnionTypeTestVariables, + response: UnionTypeTestResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type LinkedField$ref = FragmentReference & { _: "LinkedField$ref" }; +export type LinkedField$fragmentType = LinkedField$ref & { _: "LinkedField$fragmentType" }; +export type LinkedField = { + readonly profilePicture?: { + readonly uri?: string, + readonly width?: number, + readonly height?: number, + }, + readonly hometown?: { + readonly id: string, + readonly profilePicture?: { + readonly uri?: string + }, + }, + readonly actor?: { + readonly id: string + }, + readonly $refType: LinkedField$ref, +}; +export type LinkedField$data = LinkedField; +export type LinkedField$key = { + readonly $data?: LinkedField$data, + readonly $fragmentRefs: LinkedField$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-field.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-field.graphql new file mode 100644 index 0000000000000..50f1ed1e23ebb --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-field.graphql @@ -0,0 +1,27 @@ +fragment LinkedField on User { + profilePicture { + uri + width + height + } + hometown { + # object + id + profilePicture { + uri + } + } + actor { + # interface + id + } +} + +query UnionTypeTest { + neverNode { + __typename + ... on FakeNode { + id + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field-in-query.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field-in-query.expected new file mode 100644 index 0000000000000..0e4ce6d1ba6ea --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field-in-query.expected @@ -0,0 +1,73 @@ +==================================== INPUT ==================================== +query NameRendererQuery { + me { + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } + } +} + +fragment PlainUserNameRenderer_name on PlainUserNameRenderer { + plaintext + data { + text + } +} + +fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { + markdown + data { + markup + } +} +==================================== OUTPUT =================================== +import type { MarkdownUserNameRenderer_name$ref } from "MarkdownUserNameRenderer_name.graphql"; +import type { PlainUserNameRenderer_name$ref } from "PlainUserNameRenderer_name.graphql"; +export type NameRendererQueryVariables = {||}; +export type NameRendererQueryResponse = {| + +me: ?{| + +nameRenderer: ?{| + +__fragmentPropName?: ?string, + +__module_component?: ?string, + +$fragmentRefs: PlainUserNameRenderer_name$ref & MarkdownUserNameRenderer_name$ref, + |} + |} +|}; +export type NameRendererQuery = {| + variables: NameRendererQueryVariables, + response: NameRendererQueryResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type MarkdownUserNameRenderer_name$ref = FragmentReference & { _: "MarkdownUserNameRenderer_name$ref" }; +export type MarkdownUserNameRenderer_name$fragmentType = MarkdownUserNameRenderer_name$ref & { _: "MarkdownUserNameRenderer_name$fragmentType" }; +export type MarkdownUserNameRenderer_name = { + readonly markdown?: string, + readonly data?: { + readonly markup?: string + }, + readonly $refType: MarkdownUserNameRenderer_name$ref, +}; +export type MarkdownUserNameRenderer_name$data = MarkdownUserNameRenderer_name; +export type MarkdownUserNameRenderer_name$key = { + readonly $data?: MarkdownUserNameRenderer_name$data, + readonly $fragmentRefs: MarkdownUserNameRenderer_name$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type PlainUserNameRenderer_name$ref = FragmentReference & { _: "PlainUserNameRenderer_name$ref" }; +export type PlainUserNameRenderer_name$fragmentType = PlainUserNameRenderer_name$ref & { _: "PlainUserNameRenderer_name$fragmentType" }; +export type PlainUserNameRenderer_name = { + readonly plaintext?: string, + readonly data?: { + readonly text?: string + }, + readonly $refType: PlainUserNameRenderer_name$ref, +}; +export type PlainUserNameRenderer_name$data = PlainUserNameRenderer_name; +export type PlainUserNameRenderer_name$key = { + readonly $data?: PlainUserNameRenderer_name$data, + readonly $fragmentRefs: PlainUserNameRenderer_name$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field-in-query.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field-in-query.graphql new file mode 100644 index 0000000000000..31af215ae45d1 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field-in-query.graphql @@ -0,0 +1,23 @@ +query NameRendererQuery { + me { + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } + } +} + +fragment PlainUserNameRenderer_name on PlainUserNameRenderer { + plaintext + data { + text + } +} + +fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { + markdown + data { + markup + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field.expected new file mode 100644 index 0000000000000..1414a19275ac7 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field.expected @@ -0,0 +1,75 @@ +==================================== INPUT ==================================== +fragment NameRendererFragment on User { + id + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } +} + +fragment PlainUserNameRenderer_name on PlainUserNameRenderer { + plaintext + data { + text + } +} + +fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { + markdown + data { + markup + } +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type MarkdownUserNameRenderer_name$ref = FragmentReference & { _: "MarkdownUserNameRenderer_name$ref" }; +export type MarkdownUserNameRenderer_name$fragmentType = MarkdownUserNameRenderer_name$ref & { _: "MarkdownUserNameRenderer_name$fragmentType" }; +export type MarkdownUserNameRenderer_name = { + readonly markdown?: string, + readonly data?: { + readonly markup?: string + }, + readonly $refType: MarkdownUserNameRenderer_name$ref, +}; +export type MarkdownUserNameRenderer_name$data = MarkdownUserNameRenderer_name; +export type MarkdownUserNameRenderer_name$key = { + readonly $data?: MarkdownUserNameRenderer_name$data, + readonly $fragmentRefs: MarkdownUserNameRenderer_name$ref, +}; +------------------------------------------------------------------------------- +import type { MarkdownUserNameRenderer_name$ref } from "MarkdownUserNameRenderer_name.graphql"; +import type { PlainUserNameRenderer_name$ref } from "PlainUserNameRenderer_name.graphql"; +import { FragmentReference } from "relay-runtime"; +export type NameRendererFragment$ref = FragmentReference & { _: "NameRendererFragment$ref" }; +export type NameRendererFragment$fragmentType = NameRendererFragment$ref & { _: "NameRendererFragment$fragmentType" }; +export type NameRendererFragment = { + readonly id: string, + readonly nameRenderer?: { + readonly __fragmentPropName?: string, + readonly __module_component?: string, + readonly $fragmentRefs: PlainUserNameRenderer_name$ref & MarkdownUserNameRenderer_name$ref, + }, + readonly $refType: NameRendererFragment$ref, +}; +export type NameRendererFragment$data = NameRendererFragment; +export type NameRendererFragment$key = { + readonly $data?: NameRendererFragment$data, + readonly $fragmentRefs: NameRendererFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type PlainUserNameRenderer_name$ref = FragmentReference & { _: "PlainUserNameRenderer_name$ref" }; +export type PlainUserNameRenderer_name$fragmentType = PlainUserNameRenderer_name$ref & { _: "PlainUserNameRenderer_name$fragmentType" }; +export type PlainUserNameRenderer_name = { + readonly plaintext?: string, + readonly data?: { + readonly text?: string + }, + readonly $refType: PlainUserNameRenderer_name$ref, +}; +export type PlainUserNameRenderer_name$data = PlainUserNameRenderer_name; +export type PlainUserNameRenderer_name$key = { + readonly $data?: PlainUserNameRenderer_name$data, + readonly $fragmentRefs: PlainUserNameRenderer_name$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field.graphql new file mode 100644 index 0000000000000..40567f2343c58 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/match-field.graphql @@ -0,0 +1,22 @@ +fragment NameRendererFragment on User { + id + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } +} + +fragment PlainUserNameRenderer_name on PlainUserNameRenderer { + plaintext + data { + text + } +} + +fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { + markdown + data { + markup + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-client-extension.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-client-extension.expected new file mode 100644 index 0000000000000..19a6626bbbe1c --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-client-extension.expected @@ -0,0 +1,52 @@ +==================================== INPUT ==================================== +mutation Test($input: UpdateAllSeenStateInput) @raw_response_type { + viewerNotificationsUpdateAllSeenState(input: $input) { + stories { + foos { + bar + } + } + } +} + +#%extensions% + +extend type Story { + foos: [Foo] +} + +type Foo { + bar: String +} +==================================== OUTPUT =================================== +export type UpdateAllSeenStateInput = {| + clientMutationId?: ?string, + storyIds?: ?$ReadOnlyArray, +|}; +export type TestVariables = {| + input?: ?UpdateAllSeenStateInput +|}; +export type TestResponse = {| + +viewerNotificationsUpdateAllSeenState: ?{| + +stories: ?$ReadOnlyArray + |}> + |} +|}; +export type TestRawResponse = {| + +viewerNotificationsUpdateAllSeenState: ?{| + +stories: ?$ReadOnlyArray, + |}> + |} +|}; +export type Test = {| + variables: TestVariables, + response: TestResponse, + rawResponse: TestRawResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-client-extension.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-client-extension.graphql new file mode 100644 index 0000000000000..18e9d908f6fcb --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-client-extension.graphql @@ -0,0 +1,19 @@ +mutation Test($input: UpdateAllSeenStateInput) @raw_response_type { + viewerNotificationsUpdateAllSeenState(input: $input) { + stories { + foos { + bar + } + } + } +} + +#%extensions% + +extend type Story { + foos: [Foo] +} + +type Foo { + bar: String +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.expected new file mode 100644 index 0000000000000..7fbe23ed78b0b --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.expected @@ -0,0 +1,108 @@ +==================================== INPUT ==================================== +mutation TestMutation($input: CommentCreateInput!) @raw_response_type { + commentCreate(input: $input) { + viewer { + actor { + ...InlineFragmentWithOverlappingFields + } + } + } +} + +fragment InlineFragmentWithOverlappingFields on Actor { + ... on User { + hometown { + id + name + } + } + ... on Page { + name + hometown { + id + message { + text + } + } + } +} +==================================== OUTPUT =================================== +import type { InlineFragmentWithOverlappingFields$ref } from "InlineFragmentWithOverlappingFields.graphql"; +export type CommentCreateInput = {| + clientMutationId?: ?string, + feedbackId?: ?string, + feedback?: ?CommentfeedbackFeedback, +|}; +export type CommentfeedbackFeedback = {| + comment?: ?FeedbackcommentComment +|}; +export type FeedbackcommentComment = {| + feedback?: ?CommentfeedbackFeedback +|}; +export type TestMutationVariables = {| + input: CommentCreateInput +|}; +export type TestMutationResponse = {| + +commentCreate: ?{| + +viewer: ?{| + +actor: ?{| + +$fragmentRefs: InlineFragmentWithOverlappingFields$ref + |} + |} + |} +|}; +export type TestMutationRawResponse = {| + +commentCreate: ?{| + +viewer: ?{| + +actor: ?({| + +__typename: "User", + +__isActor: "User", + +id: string, + +hometown: ?{| + +id: string, + +name: ?string, + |}, + |} | {| + +__typename: "Page", + +__isActor: "Page", + +id: string, + +name: ?string, + +hometown: ?{| + +id: string, + +message: ?{| + +text: ?string + |}, + |}, + |} | {| + +__typename: string, + +__isActor: string, + +id: string, + |}) + |} + |} +|}; +export type TestMutation = {| + variables: TestMutationVariables, + response: TestMutationResponse, + rawResponse: TestMutationRawResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type InlineFragmentWithOverlappingFields$ref = FragmentReference & { _: "InlineFragmentWithOverlappingFields$ref" }; +export type InlineFragmentWithOverlappingFields$fragmentType = InlineFragmentWithOverlappingFields$ref & { _: "InlineFragmentWithOverlappingFields$fragmentType" }; +export type InlineFragmentWithOverlappingFields = { + readonly hometown?: { + readonly id: string, + readonly name?: string, + readonly message?: { + readonly text?: string + }, + }, + readonly name?: string, + readonly $refType: InlineFragmentWithOverlappingFields$ref, +}; +export type InlineFragmentWithOverlappingFields$data = InlineFragmentWithOverlappingFields; +export type InlineFragmentWithOverlappingFields$key = { + readonly $data?: InlineFragmentWithOverlappingFields$data, + readonly $fragmentRefs: InlineFragmentWithOverlappingFields$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.graphql new file mode 100644 index 0000000000000..031b989fdbc94 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.graphql @@ -0,0 +1,27 @@ +mutation TestMutation($input: CommentCreateInput!) @raw_response_type { + commentCreate(input: $input) { + viewer { + actor { + ...InlineFragmentWithOverlappingFields + } + } + } +} + +fragment InlineFragmentWithOverlappingFields on Actor { + ... on User { + hometown { + id + name + } + } + ... on Page { + name + hometown { + id + message { + text + } + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-input-has-array.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-input-has-array.expected new file mode 100644 index 0000000000000..6a3454c07951e --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-input-has-array.expected @@ -0,0 +1,36 @@ +==================================== INPUT ==================================== +mutation InputHasArray($input: UpdateAllSeenStateInput) @raw_response_type { + viewerNotificationsUpdateAllSeenState(input: $input) { + stories { + actorCount + } + } +} +==================================== OUTPUT =================================== +export type UpdateAllSeenStateInput = {| + clientMutationId?: ?string, + storyIds?: ?$ReadOnlyArray, +|}; +export type InputHasArrayVariables = {| + input?: ?UpdateAllSeenStateInput +|}; +export type InputHasArrayResponse = {| + +viewerNotificationsUpdateAllSeenState: ?{| + +stories: ?$ReadOnlyArray + |} +|}; +export type InputHasArrayRawResponse = {| + +viewerNotificationsUpdateAllSeenState: ?{| + +stories: ?$ReadOnlyArray + |} +|}; +export type InputHasArray = {| + variables: InputHasArrayVariables, + response: InputHasArrayResponse, + rawResponse: InputHasArrayRawResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-input-has-array.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-input-has-array.graphql new file mode 100644 index 0000000000000..1d0fc0aca2053 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-input-has-array.graphql @@ -0,0 +1,7 @@ +mutation InputHasArray($input: UpdateAllSeenStateInput) @raw_response_type { + viewerNotificationsUpdateAllSeenState(input: $input) { + stories { + actorCount + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-enums-on-fragment.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-enums-on-fragment.expected new file mode 100644 index 0000000000000..a48e8686fdddc --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-enums-on-fragment.expected @@ -0,0 +1,105 @@ +==================================== INPUT ==================================== +mutation CommentCreateMutation( + $input: CommentCreateInput! + $first: Int + $orderBy: [String!] +) @raw_response_type { + commentCreate(input: $input) { + comment { + friends(first: $first, orderby: $orderBy) { + edges { + node { + id + __typename + ...FriendFragment + } + } + } + } + } +} + +fragment FriendFragment on User { + name + lastName + profilePicture2 { + test_enums + } +} +==================================== OUTPUT =================================== +import type { FriendFragment$ref } from "FriendFragment.graphql"; +export type TestEnums = "mark" | "zuck" | "%future added value"; +export type CommentCreateInput = {| + clientMutationId?: ?string, + feedbackId?: ?string, + feedback?: ?CommentfeedbackFeedback, +|}; +export type CommentfeedbackFeedback = {| + comment?: ?FeedbackcommentComment +|}; +export type FeedbackcommentComment = {| + feedback?: ?CommentfeedbackFeedback +|}; +export type CommentCreateMutationVariables = {| + input: CommentCreateInput, + first?: ?number, + orderBy?: ?$ReadOnlyArray, +|}; +export type CommentCreateMutationResponse = {| + +commentCreate: ?{| + +comment: ?{| + +friends: ?{| + +edges: ?$ReadOnlyArray + |} + |} + |} +|}; +export type CommentCreateMutationRawResponse = {| + +commentCreate: ?{| + +comment: ?{| + +friends: ?{| + +edges: ?$ReadOnlyArray + |}, + +id: string, + |} + |} +|}; +export type CommentCreateMutation = {| + variables: CommentCreateMutationVariables, + response: CommentCreateMutationResponse, + rawResponse: CommentCreateMutationRawResponse, +|}; +------------------------------------------------------------------------------- +export type TestEnums = "mark" | "zuck" | "%future added value"; +import { FragmentReference } from "relay-runtime"; +export type FriendFragment$ref = FragmentReference & { _: "FriendFragment$ref" }; +export type FriendFragment$fragmentType = FriendFragment$ref & { _: "FriendFragment$fragmentType" }; +export type FriendFragment = { + readonly name?: string, + readonly lastName?: string, + readonly profilePicture2?: { + readonly test_enums?: TestEnums + }, + readonly $refType: FriendFragment$ref, +}; +export type FriendFragment$data = FriendFragment; +export type FriendFragment$key = { + readonly $data?: FriendFragment$data, + readonly $fragmentRefs: FriendFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-enums-on-fragment.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-enums-on-fragment.graphql new file mode 100644 index 0000000000000..c611ff829c485 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-enums-on-fragment.graphql @@ -0,0 +1,27 @@ +mutation CommentCreateMutation( + $input: CommentCreateInput! + $first: Int + $orderBy: [String!] +) @raw_response_type { + commentCreate(input: $input) { + comment { + friends(first: $first, orderby: $orderBy) { + edges { + node { + id + __typename + ...FriendFragment + } + } + } + } + } +} + +fragment FriendFragment on User { + name + lastName + profilePicture2 { + test_enums + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-nested-fragments.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-nested-fragments.expected new file mode 100644 index 0000000000000..c4d11d2e21284 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-nested-fragments.expected @@ -0,0 +1,121 @@ +==================================== INPUT ==================================== +mutation CommentCreateMutation( + $input: CommentCreateInput! + $first: Int + $orderBy: [String!] +) @raw_response_type { + commentCreate(input: $input) { + comment { + friends(first: $first, orderby: $orderBy) { + edges { + node { + lastName + ...FriendFragment + } + } + } + } + } +} + +fragment FriendFragment on User { + name + lastName + feedback { + ...FeedbackFragment + } +} + +fragment FeedbackFragment on Feedback { + id + name +} +==================================== OUTPUT =================================== +import type { FriendFragment$ref } from "FriendFragment.graphql"; +export type CommentCreateInput = {| + clientMutationId?: ?string, + feedbackId?: ?string, + feedback?: ?CommentfeedbackFeedback, +|}; +export type CommentfeedbackFeedback = {| + comment?: ?FeedbackcommentComment +|}; +export type FeedbackcommentComment = {| + feedback?: ?CommentfeedbackFeedback +|}; +export type CommentCreateMutationVariables = {| + input: CommentCreateInput, + first?: ?number, + orderBy?: ?$ReadOnlyArray, +|}; +export type CommentCreateMutationResponse = {| + +commentCreate: ?{| + +comment: ?{| + +friends: ?{| + +edges: ?$ReadOnlyArray + |} + |} + |} +|}; +export type CommentCreateMutationRawResponse = {| + +commentCreate: ?{| + +comment: ?{| + +friends: ?{| + +edges: ?$ReadOnlyArray + |}, + +id: string, + |} + |} +|}; +export type CommentCreateMutation = {| + variables: CommentCreateMutationVariables, + response: CommentCreateMutationResponse, + rawResponse: CommentCreateMutationRawResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type FeedbackFragment$ref = FragmentReference & { _: "FeedbackFragment$ref" }; +export type FeedbackFragment$fragmentType = FeedbackFragment$ref & { _: "FeedbackFragment$fragmentType" }; +export type FeedbackFragment = { + readonly id: string, + readonly name?: string, + readonly $refType: FeedbackFragment$ref, +}; +export type FeedbackFragment$data = FeedbackFragment; +export type FeedbackFragment$key = { + readonly $data?: FeedbackFragment$data, + readonly $fragmentRefs: FeedbackFragment$ref, +}; +------------------------------------------------------------------------------- +import type { FeedbackFragment$ref } from "FeedbackFragment.graphql"; +import { FragmentReference } from "relay-runtime"; +export type FriendFragment$ref = FragmentReference & { _: "FriendFragment$ref" }; +export type FriendFragment$fragmentType = FriendFragment$ref & { _: "FriendFragment$fragmentType" }; +export type FriendFragment = { + readonly name?: string, + readonly lastName?: string, + readonly feedback?: { + readonly $fragmentRefs: FeedbackFragment$ref + }, + readonly $refType: FriendFragment$ref, +}; +export type FriendFragment$data = FriendFragment; +export type FriendFragment$key = { + readonly $data?: FriendFragment$data, + readonly $fragmentRefs: FriendFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-nested-fragments.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-nested-fragments.graphql new file mode 100644 index 0000000000000..2c25b8bd9ff62 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation-with-nested-fragments.graphql @@ -0,0 +1,31 @@ +mutation CommentCreateMutation( + $input: CommentCreateInput! + $first: Int + $orderBy: [String!] +) @raw_response_type { + commentCreate(input: $input) { + comment { + friends(first: $first, orderby: $orderBy) { + edges { + node { + lastName + ...FriendFragment + } + } + } + } + } +} + +fragment FriendFragment on User { + name + lastName + feedback { + ...FeedbackFragment + } +} + +fragment FeedbackFragment on Feedback { + id + name +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation.expected new file mode 100644 index 0000000000000..73dfff5c2df67 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation.expected @@ -0,0 +1,48 @@ +==================================== INPUT ==================================== +mutation CommentCreateMutation( + $input: CommentCreateInput! + $first: Int + $orderBy: [String!] +) { + commentCreate(input: $input) { + comment { + id + name + friends(first: $first, orderby: $orderBy) { + count + } + } + } +} +==================================== OUTPUT =================================== +export type CommentCreateInput = {| + clientMutationId?: ?string, + feedbackId?: ?string, + feedback?: ?CommentfeedbackFeedback, +|}; +export type CommentfeedbackFeedback = {| + comment?: ?FeedbackcommentComment +|}; +export type FeedbackcommentComment = {| + feedback?: ?CommentfeedbackFeedback +|}; +export type CommentCreateMutationVariables = {| + input: CommentCreateInput, + first?: ?number, + orderBy?: ?$ReadOnlyArray, +|}; +export type CommentCreateMutationResponse = {| + +commentCreate: ?{| + +comment: ?{| + +id: string, + +name: ?string, + +friends: ?{| + +count: ?number + |}, + |} + |} +|}; +export type CommentCreateMutation = {| + variables: CommentCreateMutationVariables, + response: CommentCreateMutationResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation.graphql new file mode 100644 index 0000000000000..dc38129e976fe --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/mutation.graphql @@ -0,0 +1,15 @@ +mutation CommentCreateMutation( + $input: CommentCreateInput! + $first: Int + $orderBy: [String!] +) { + commentCreate(input: $input) { + comment { + id + name + friends(first: $first, orderby: $orderBy) { + count + } + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/plural-fragment.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/plural-fragment.expected new file mode 100644 index 0000000000000..257819cd17055 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/plural-fragment.expected @@ -0,0 +1,17 @@ +==================================== INPUT ==================================== +fragment PluralFragment on Node @relay(plural: true) { + id +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type PluralFragment$ref = FragmentReference & { _: "PluralFragment$ref" }; +export type PluralFragment$fragmentType = PluralFragment$ref & { _: "PluralFragment$fragmentType" }; +export type PluralFragment = ReadonlyArray<{ + readonly id: string, + readonly $refType: PluralFragment$ref, +}>; +export type PluralFragment$data = PluralFragment; +export type PluralFragment$key = ReadonlyArray<{ + readonly $data?: PluralFragment$data, + readonly $fragmentRefs: PluralFragment$ref, +}>; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/plural-fragment.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/plural-fragment.graphql new file mode 100644 index 0000000000000..6f2e96b0d8793 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/plural-fragment.graphql @@ -0,0 +1,3 @@ +fragment PluralFragment on Node @relay(plural: true) { + id +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-handles.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-handles.expected new file mode 100644 index 0000000000000..54fdc48eb8006 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-handles.expected @@ -0,0 +1,70 @@ +==================================== INPUT ==================================== +query LinkedHandleField($id: ID!) @raw_response_type { + node(id: $id) { + ... on User { + friends(first: 10) @__clientField(handle: "clientFriends") { + count + } + } + } +} + +query ScalarHandleField($id: ID!) @raw_response_type { + node(id: $id) { + ... on User { + name @__clientField(handle: "clientName") + } + } +} +==================================== OUTPUT =================================== +export type LinkedHandleFieldVariables = {| + id: string +|}; +export type LinkedHandleFieldResponse = {| + +node: ?{| + +friends?: ?{| + +count: ?number + |} + |} +|}; +export type LinkedHandleFieldRawResponse = {| + +node: ?({| + +__typename: "User", + +id: string, + +friends: ?{| + +count: ?number + |}, + |} | {| + +__typename: string, + +id: string, + |}) +|}; +export type LinkedHandleField = {| + variables: LinkedHandleFieldVariables, + response: LinkedHandleFieldResponse, + rawResponse: LinkedHandleFieldRawResponse, +|}; +------------------------------------------------------------------------------- +export type ScalarHandleFieldVariables = {| + id: string +|}; +export type ScalarHandleFieldResponse = {| + +node: ?{| + +name?: ?string + |} +|}; +export type ScalarHandleFieldRawResponse = {| + +node: ?({| + +__typename: "User", + +id: string, + +name: ?string, + |} | {| + +__typename: string, + +id: string, + |}) +|}; +export type ScalarHandleField = {| + variables: ScalarHandleFieldVariables, + response: ScalarHandleFieldResponse, + rawResponse: ScalarHandleFieldRawResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-handles.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-handles.graphql new file mode 100644 index 0000000000000..983ab805b7ca6 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-handles.graphql @@ -0,0 +1,17 @@ +query LinkedHandleField($id: ID!) @raw_response_type { + node(id: $id) { + ... on User { + friends(first: 10) @__clientField(handle: "clientFriends") { + count + } + } + } +} + +query ScalarHandleField($id: ID!) @raw_response_type { + node(id: $id) { + ... on User { + name @__clientField(handle: "clientName") + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-match-fields.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-match-fields.expected new file mode 100644 index 0000000000000..cedd2a7b5cda6 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-match-fields.expected @@ -0,0 +1,137 @@ +==================================== INPUT ==================================== +query Test @raw_response_type { + node(id: "1") { + ...NameRendererFragment + } +} + +fragment NameRendererFragment on User { + id + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } +} + +fragment PlainUserNameRenderer_name on PlainUserNameRenderer { + plaintext + data { + text + } +} + +fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { + markdown + data { + markup + } +} +==================================== OUTPUT =================================== +import type { Local3DPayload } from "relay-runtime"; +import type { NameRendererFragment$ref } from "NameRendererFragment.graphql"; +export type TestVariables = {||}; +export type TestResponse = {| + +node: ?{| + +$fragmentRefs: NameRendererFragment$ref + |} +|}; +export type PlainUserNameRenderer_name = {| + +plaintext: ?string, + +data: ?{| + +text: ?string, + +id: ?string, + |}, +|}; +export type MarkdownUserNameRenderer_name = {| + +markdown: ?string, + +data: ?{| + +markup: ?string, + +id: ?string, + |}, +|}; +export type TestRawResponse = {| + +node: ?({| + +__typename: "User", + +id: string, + +nameRenderer: ?({| + +__typename: "PlainUserNameRenderer", + +__module_operation_NameRendererFragment: ?any, + +__module_component_NameRendererFragment: ?any, + ...PlainUserNameRenderer_name, + |} | Local3DPayload<"NameRendererFragment", {| + +__typename: "PlainUserNameRenderer", + ...PlainUserNameRenderer_name, + |}> | {| + +__typename: "MarkdownUserNameRenderer", + +__module_operation_NameRendererFragment: ?any, + +__module_component_NameRendererFragment: ?any, + ...MarkdownUserNameRenderer_name, + |} | Local3DPayload<"NameRendererFragment", {| + +__typename: "MarkdownUserNameRenderer", + ...MarkdownUserNameRenderer_name, + |}> | {| + +__typename: string + |}), + |} | {| + +__typename: string, + +id: string, + |}) +|}; +export type Test = {| + variables: TestVariables, + response: TestResponse, + rawResponse: TestRawResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type MarkdownUserNameRenderer_name$ref = FragmentReference & { _: "MarkdownUserNameRenderer_name$ref" }; +export type MarkdownUserNameRenderer_name$fragmentType = MarkdownUserNameRenderer_name$ref & { _: "MarkdownUserNameRenderer_name$fragmentType" }; +export type MarkdownUserNameRenderer_name = { + readonly markdown?: string, + readonly data?: { + readonly markup?: string + }, + readonly $refType: MarkdownUserNameRenderer_name$ref, +}; +export type MarkdownUserNameRenderer_name$data = MarkdownUserNameRenderer_name; +export type MarkdownUserNameRenderer_name$key = { + readonly $data?: MarkdownUserNameRenderer_name$data, + readonly $fragmentRefs: MarkdownUserNameRenderer_name$ref, +}; +------------------------------------------------------------------------------- +import type { MarkdownUserNameRenderer_name$ref } from "MarkdownUserNameRenderer_name.graphql"; +import type { PlainUserNameRenderer_name$ref } from "PlainUserNameRenderer_name.graphql"; +import { FragmentReference } from "relay-runtime"; +export type NameRendererFragment$ref = FragmentReference & { _: "NameRendererFragment$ref" }; +export type NameRendererFragment$fragmentType = NameRendererFragment$ref & { _: "NameRendererFragment$fragmentType" }; +export type NameRendererFragment = { + readonly id: string, + readonly nameRenderer?: { + readonly __fragmentPropName?: string, + readonly __module_component?: string, + readonly $fragmentRefs: PlainUserNameRenderer_name$ref & MarkdownUserNameRenderer_name$ref, + }, + readonly $refType: NameRendererFragment$ref, +}; +export type NameRendererFragment$data = NameRendererFragment; +export type NameRendererFragment$key = { + readonly $data?: NameRendererFragment$data, + readonly $fragmentRefs: NameRendererFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type PlainUserNameRenderer_name$ref = FragmentReference & { _: "PlainUserNameRenderer_name$ref" }; +export type PlainUserNameRenderer_name$fragmentType = PlainUserNameRenderer_name$ref & { _: "PlainUserNameRenderer_name$fragmentType" }; +export type PlainUserNameRenderer_name = { + readonly plaintext?: string, + readonly data?: { + readonly text?: string + }, + readonly $refType: PlainUserNameRenderer_name$ref, +}; +export type PlainUserNameRenderer_name$data = PlainUserNameRenderer_name; +export type PlainUserNameRenderer_name$key = { + readonly $data?: PlainUserNameRenderer_name$data, + readonly $fragmentRefs: PlainUserNameRenderer_name$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-match-fields.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-match-fields.graphql new file mode 100644 index 0000000000000..f71fdc1b78d34 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-match-fields.graphql @@ -0,0 +1,28 @@ +query Test @raw_response_type { + node(id: "1") { + ...NameRendererFragment + } +} + +fragment NameRendererFragment on User { + id + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } +} + +fragment PlainUserNameRenderer_name on PlainUserNameRenderer { + plaintext + data { + text + } +} + +fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { + markdown + data { + markup + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-module-field.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-module-field.expected new file mode 100644 index 0000000000000..0b3b0847b30cd --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-module-field.expected @@ -0,0 +1,86 @@ +==================================== INPUT ==================================== +query Test @raw_response_type { + node(id: "1") { + ...Test_user + } +} + +fragment Test_user on User { + plainUserRenderer { + ...Test_userRenderer @module(name: "Renderer.react") + } +} + +fragment Test_userRenderer on PlainUserRenderer { + user { + username + } +} +==================================== OUTPUT =================================== +import type { Local3DPayload } from "relay-runtime"; +import type { Test_user$ref } from "Test_user.graphql"; +export type TestVariables = {||}; +export type TestResponse = {| + +node: ?{| + +$fragmentRefs: Test_user$ref + |} +|}; +export type Test_userRenderer = {| + +user: ?{| + +username: ?string, + +id: string, + |} +|}; +export type TestRawResponse = {| + +node: ?({| + +__typename: "User", + +id: string, + +plainUserRenderer: ?({| + +__module_operation_Test_user: ?any, + +__module_component_Test_user: ?any, + ...Test_userRenderer, + |} | Local3DPayload<"Test_user", {| ...Test_userRenderer + |}>), + |} | {| + +__typename: string, + +id: string, + |}) +|}; +export type Test = {| + variables: TestVariables, + response: TestResponse, + rawResponse: TestRawResponse, +|}; +------------------------------------------------------------------------------- +import type { Test_userRenderer$ref } from "Test_userRenderer.graphql"; +import { FragmentReference } from "relay-runtime"; +export type Test_user$ref = FragmentReference & { _: "Test_user$ref" }; +export type Test_user$fragmentType = Test_user$ref & { _: "Test_user$fragmentType" }; +export type Test_user = { + readonly plainUserRenderer?: { + readonly __fragmentPropName?: string, + readonly __module_component?: string, + readonly $fragmentRefs: Test_userRenderer$ref, + }, + readonly $refType: Test_user$ref, +}; +export type Test_user$data = Test_user; +export type Test_user$key = { + readonly $data?: Test_user$data, + readonly $fragmentRefs: Test_user$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type Test_userRenderer$ref = FragmentReference & { _: "Test_userRenderer$ref" }; +export type Test_userRenderer$fragmentType = Test_userRenderer$ref & { _: "Test_userRenderer$fragmentType" }; +export type Test_userRenderer = { + readonly user?: { + readonly username?: string + }, + readonly $refType: Test_userRenderer$ref, +}; +export type Test_userRenderer$data = Test_userRenderer; +export type Test_userRenderer$key = { + readonly $data?: Test_userRenderer$data, + readonly $fragmentRefs: Test_userRenderer$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-module-field.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-module-field.graphql new file mode 100644 index 0000000000000..0b0028134a733 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-module-field.graphql @@ -0,0 +1,17 @@ +query Test @raw_response_type { + node(id: "1") { + ...Test_user + } +} + +fragment Test_user on User { + plainUserRenderer { + ...Test_userRenderer @module(name: "Renderer.react") + } +} + +fragment Test_userRenderer on PlainUserRenderer { + user { + username + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-multiple-match-fields.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-multiple-match-fields.expected new file mode 100644 index 0000000000000..28ae226c177f7 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-multiple-match-fields.expected @@ -0,0 +1,215 @@ +==================================== INPUT ==================================== +query Test @raw_response_type { + node(id: "1") { + ... on User { + username + ...NameRendererFragment + } + } + viewer { + actor { + ... on User { + name + ...AnotherNameRendererFragment + } + } + } +} + +fragment NameRendererFragment on User { + id + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } +} + +fragment AnotherNameRendererFragment on User { + name + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } +} + +fragment PlainUserNameRenderer_name on PlainUserNameRenderer { + plaintext + data { + text + } +} + +fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { + markdown + data { + markup + } +} +==================================== OUTPUT =================================== +import type { Local3DPayload } from "relay-runtime"; +import type { AnotherNameRendererFragment$ref } from "AnotherNameRendererFragment.graphql"; +import type { NameRendererFragment$ref } from "NameRendererFragment.graphql"; +export type TestVariables = {||}; +export type TestResponse = {| + +node: ?{| + +username?: ?string, + +$fragmentRefs: NameRendererFragment$ref, + |}, + +viewer: ?{| + +actor: ?{| + +name?: ?string, + +$fragmentRefs: AnotherNameRendererFragment$ref, + |} + |}, +|}; +export type PlainUserNameRenderer_name = {| + +plaintext: ?string, + +data: ?{| + +text: ?string, + +id: ?string, + |}, +|}; +export type MarkdownUserNameRenderer_name = {| + +markdown: ?string, + +data: ?{| + +markup: ?string, + +id: ?string, + |}, +|}; +export type TestRawResponse = {| + +node: ?({| + +__typename: "User", + +id: string, + +username: ?string, + +nameRenderer: ?({| + +__typename: "PlainUserNameRenderer", + +__module_operation_NameRendererFragment: ?any, + +__module_component_NameRendererFragment: ?any, + ...PlainUserNameRenderer_name, + |} | Local3DPayload<"NameRendererFragment", {| + +__typename: "PlainUserNameRenderer", + ...PlainUserNameRenderer_name, + |}> | {| + +__typename: "MarkdownUserNameRenderer", + +__module_operation_NameRendererFragment: ?any, + +__module_component_NameRendererFragment: ?any, + ...MarkdownUserNameRenderer_name, + |} | Local3DPayload<"NameRendererFragment", {| + +__typename: "MarkdownUserNameRenderer", + ...MarkdownUserNameRenderer_name, + |}> | {| + +__typename: string + |}), + |} | {| + +__typename: string, + +id: string, + |}), + +viewer: ?{| + +actor: ?({| + +__typename: "User", + +id: string, + +name: ?string, + +nameRenderer: ?({| + +__typename: "PlainUserNameRenderer", + +__module_operation_AnotherNameRendererFragment: ?any, + +__module_component_AnotherNameRendererFragment: ?any, + ...PlainUserNameRenderer_name, + |} | Local3DPayload<"AnotherNameRendererFragment", {| + +__typename: "PlainUserNameRenderer", + ...PlainUserNameRenderer_name, + |}> | {| + +__typename: "MarkdownUserNameRenderer", + +__module_operation_AnotherNameRendererFragment: ?any, + +__module_component_AnotherNameRendererFragment: ?any, + ...MarkdownUserNameRenderer_name, + |} | Local3DPayload<"AnotherNameRendererFragment", {| + +__typename: "MarkdownUserNameRenderer", + ...MarkdownUserNameRenderer_name, + |}> | {| + +__typename: string + |}), + |} | {| + +__typename: string, + +id: string, + |}) + |}, +|}; +export type Test = {| + variables: TestVariables, + response: TestResponse, + rawResponse: TestRawResponse, +|}; +------------------------------------------------------------------------------- +import type { MarkdownUserNameRenderer_name$ref } from "MarkdownUserNameRenderer_name.graphql"; +import type { PlainUserNameRenderer_name$ref } from "PlainUserNameRenderer_name.graphql"; +import { FragmentReference } from "relay-runtime"; +export type AnotherNameRendererFragment$ref = FragmentReference & { _: "AnotherNameRendererFragment$ref" }; +export type AnotherNameRendererFragment$fragmentType = AnotherNameRendererFragment$ref & { _: "AnotherNameRendererFragment$fragmentType" }; +export type AnotherNameRendererFragment = { + readonly name?: string, + readonly nameRenderer?: { + readonly __fragmentPropName?: string, + readonly __module_component?: string, + readonly $fragmentRefs: PlainUserNameRenderer_name$ref & MarkdownUserNameRenderer_name$ref, + }, + readonly $refType: AnotherNameRendererFragment$ref, +}; +export type AnotherNameRendererFragment$data = AnotherNameRendererFragment; +export type AnotherNameRendererFragment$key = { + readonly $data?: AnotherNameRendererFragment$data, + readonly $fragmentRefs: AnotherNameRendererFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type MarkdownUserNameRenderer_name$ref = FragmentReference & { _: "MarkdownUserNameRenderer_name$ref" }; +export type MarkdownUserNameRenderer_name$fragmentType = MarkdownUserNameRenderer_name$ref & { _: "MarkdownUserNameRenderer_name$fragmentType" }; +export type MarkdownUserNameRenderer_name = { + readonly markdown?: string, + readonly data?: { + readonly markup?: string + }, + readonly $refType: MarkdownUserNameRenderer_name$ref, +}; +export type MarkdownUserNameRenderer_name$data = MarkdownUserNameRenderer_name; +export type MarkdownUserNameRenderer_name$key = { + readonly $data?: MarkdownUserNameRenderer_name$data, + readonly $fragmentRefs: MarkdownUserNameRenderer_name$ref, +}; +------------------------------------------------------------------------------- +import type { MarkdownUserNameRenderer_name$ref } from "MarkdownUserNameRenderer_name.graphql"; +import type { PlainUserNameRenderer_name$ref } from "PlainUserNameRenderer_name.graphql"; +import { FragmentReference } from "relay-runtime"; +export type NameRendererFragment$ref = FragmentReference & { _: "NameRendererFragment$ref" }; +export type NameRendererFragment$fragmentType = NameRendererFragment$ref & { _: "NameRendererFragment$fragmentType" }; +export type NameRendererFragment = { + readonly id: string, + readonly nameRenderer?: { + readonly __fragmentPropName?: string, + readonly __module_component?: string, + readonly $fragmentRefs: PlainUserNameRenderer_name$ref & MarkdownUserNameRenderer_name$ref, + }, + readonly $refType: NameRendererFragment$ref, +}; +export type NameRendererFragment$data = NameRendererFragment; +export type NameRendererFragment$key = { + readonly $data?: NameRendererFragment$data, + readonly $fragmentRefs: NameRendererFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type PlainUserNameRenderer_name$ref = FragmentReference & { _: "PlainUserNameRenderer_name$ref" }; +export type PlainUserNameRenderer_name$fragmentType = PlainUserNameRenderer_name$ref & { _: "PlainUserNameRenderer_name$fragmentType" }; +export type PlainUserNameRenderer_name = { + readonly plaintext?: string, + readonly data?: { + readonly text?: string + }, + readonly $refType: PlainUserNameRenderer_name$ref, +}; +export type PlainUserNameRenderer_name$data = PlainUserNameRenderer_name; +export type PlainUserNameRenderer_name$key = { + readonly $data?: PlainUserNameRenderer_name$data, + readonly $fragmentRefs: PlainUserNameRenderer_name$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-multiple-match-fields.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-multiple-match-fields.graphql new file mode 100644 index 0000000000000..e404097022924 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-multiple-match-fields.graphql @@ -0,0 +1,48 @@ +query Test @raw_response_type { + node(id: "1") { + ... on User { + username + ...NameRendererFragment + } + } + viewer { + actor { + ... on User { + name + ...AnotherNameRendererFragment + } + } + } +} + +fragment NameRendererFragment on User { + id + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } +} + +fragment AnotherNameRendererFragment on User { + name + nameRenderer @match { + ...PlainUserNameRenderer_name @module(name: "PlainUserNameRenderer.react") + ...MarkdownUserNameRenderer_name + @module(name: "MarkdownUserNameRenderer.react") + } +} + +fragment PlainUserNameRenderer_name on PlainUserNameRenderer { + plaintext + data { + text + } +} + +fragment MarkdownUserNameRenderer_name on MarkdownUserNameRenderer { + markdown + data { + markup + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-conditional.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-conditional.expected new file mode 100644 index 0000000000000..38c277115e923 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-conditional.expected @@ -0,0 +1,66 @@ +==================================== INPUT ==================================== +query ExampleQuery($id: ID!, $condition: Boolean!) @raw_response_type { + node(id: $id) { + ...FriendFragment + } +} + +fragment FriendFragment on User { + ... @include(if: $condition) { + name + lastName + feedback { + id + name + } + } +} +==================================== OUTPUT =================================== +import type { FriendFragment$ref } from "FriendFragment.graphql"; +export type ExampleQueryVariables = {| + id: string, + condition: boolean, +|}; +export type ExampleQueryResponse = {| + +node: ?{| + +$fragmentRefs: FriendFragment$ref + |} +|}; +export type ExampleQueryRawResponse = {| + +node: ?({| + +__typename: "User", + +id: string, + +name: ?string, + +lastName: ?string, + +feedback: ?{| + +id: string, + +name: ?string, + |}, + |} | {| + +__typename: string, + +id: string, + |}) +|}; +export type ExampleQuery = {| + variables: ExampleQueryVariables, + response: ExampleQueryResponse, + rawResponse: ExampleQueryRawResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type FriendFragment$ref = FragmentReference & { _: "FriendFragment$ref" }; +export type FriendFragment$fragmentType = FriendFragment$ref & { _: "FriendFragment$fragmentType" }; +export type FriendFragment = { + readonly name?: string, + readonly lastName?: string, + readonly feedback?: { + readonly id: string, + readonly name?: string, + }, + readonly $refType: FriendFragment$ref, +}; +export type FriendFragment$data = FriendFragment; +export type FriendFragment$key = { + readonly $data?: FriendFragment$data, + readonly $fragmentRefs: FriendFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-conditional.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-conditional.graphql new file mode 100644 index 0000000000000..4eddb76ea8fe3 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-conditional.graphql @@ -0,0 +1,16 @@ +query ExampleQuery($id: ID!, $condition: Boolean!) @raw_response_type { + node(id: $id) { + ...FriendFragment + } +} + +fragment FriendFragment on User { + ... @include(if: $condition) { + name + lastName + feedback { + id + name + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.expected new file mode 100644 index 0000000000000..291580c1e4d87 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.expected @@ -0,0 +1,67 @@ +==================================== INPUT ==================================== +query ExampleQuery($id: ID!) @raw_response_type { + node(id: $id) { + username + ...FriendFragment + ... @include(if: false) { + friends(first: 0) { + count + } + } + } +} + +fragment FriendFragment on User { + ... @include(if: false) { + name + lastName + feedback { + id + name + } + } +} +==================================== OUTPUT =================================== +import type { FriendFragment$ref } from "FriendFragment.graphql"; +export type ExampleQueryVariables = {| + id: string +|}; +export type ExampleQueryResponse = {| + +node: ?{| + +username: ?string, + +friends?: ?{| + +count: ?number + |}, + +$fragmentRefs: FriendFragment$ref, + |} +|}; +export type ExampleQueryRawResponse = {| + +node: ?{| + +__typename: string, + +username: ?string, + +id: string, + |} +|}; +export type ExampleQuery = {| + variables: ExampleQueryVariables, + response: ExampleQueryResponse, + rawResponse: ExampleQueryRawResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type FriendFragment$ref = FragmentReference & { _: "FriendFragment$ref" }; +export type FriendFragment$fragmentType = FriendFragment$ref & { _: "FriendFragment$fragmentType" }; +export type FriendFragment = { + readonly name?: string, + readonly lastName?: string, + readonly feedback?: { + readonly id: string, + readonly name?: string, + }, + readonly $refType: FriendFragment$ref, +}; +export type FriendFragment$data = FriendFragment; +export type FriendFragment$key = { + readonly $data?: FriendFragment$data, + readonly $fragmentRefs: FriendFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.graphql new file mode 100644 index 0000000000000..b9511176fc1c1 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.graphql @@ -0,0 +1,22 @@ +query ExampleQuery($id: ID!) @raw_response_type { + node(id: $id) { + username + ...FriendFragment + ... @include(if: false) { + friends(first: 0) { + count + } + } + } +} + +fragment FriendFragment on User { + ... @include(if: false) { + name + lastName + feedback { + id + name + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream-connection.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream-connection.expected new file mode 100644 index 0000000000000..db869695cc30f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream-connection.expected @@ -0,0 +1,67 @@ +==================================== INPUT ==================================== +query TestDefer @raw_response_type { + node(id: "1") { + ... on User { + name + friends(first: 10) + @stream_connection(key: "TestDefer_friends", initial_count: 0) { + edges { + node { + actor { + name + } + } + } + } + } + } +} +==================================== OUTPUT =================================== +export type TestDeferVariables = {||}; +export type TestDeferResponse = {| + +node: ?{| + +name?: ?string, + +friends?: ?{| + +edges: ?$ReadOnlyArray + |}, + |} +|}; +export type TestDeferRawResponse = {| + +node: ?({| + +__typename: "User", + +id: string, + +name: ?string, + +friends: ?{| + +edges: ?$ReadOnlyArray, + +pageInfo: ?{| + +endCursor: ?string, + +hasNextPage: ?boolean, + |}, + |}, + |} | {| + +__typename: string, + +id: string, + |}) +|}; +export type TestDefer = {| + variables: TestDeferVariables, + response: TestDeferResponse, + rawResponse: TestDeferRawResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream-connection.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream-connection.graphql new file mode 100644 index 0000000000000..d318b27e3e3f0 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream-connection.graphql @@ -0,0 +1,17 @@ +query TestDefer @raw_response_type { + node(id: "1") { + ... on User { + name + friends(first: 10) + @stream_connection(key: "TestDefer_friends", initial_count: 0) { + edges { + node { + actor { + name + } + } + } + } + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream.expected new file mode 100644 index 0000000000000..e79ebc313b654 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream.expected @@ -0,0 +1,61 @@ +==================================== INPUT ==================================== +query TestStream @raw_response_type { + node(id: "1") { + ... on User { + name + friends(first: 10) + @stream_connection( + key: "PaginationFragment_friends" + initial_count: 1 + ) { + edges { + node { + id + } + } + } + } + } +} +==================================== OUTPUT =================================== +export type TestStreamVariables = {||}; +export type TestStreamResponse = {| + +node: ?{| + +name?: ?string, + +friends?: ?{| + +edges: ?$ReadOnlyArray + |}, + |} +|}; +export type TestStreamRawResponse = {| + +node: ?({| + +__typename: "User", + +id: string, + +name: ?string, + +friends: ?{| + +edges: ?$ReadOnlyArray, + +pageInfo: ?{| + +endCursor: ?string, + +hasNextPage: ?boolean, + |}, + |}, + |} | {| + +__typename: string, + +id: string, + |}) +|}; +export type TestStream = {| + variables: TestStreamVariables, + response: TestStreamResponse, + rawResponse: TestStreamRawResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream.graphql new file mode 100644 index 0000000000000..4d2caacc587ba --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/query-with-stream.graphql @@ -0,0 +1,18 @@ +query TestStream @raw_response_type { + node(id: "1") { + ... on User { + name + friends(first: 10) + @stream_connection( + key: "PaginationFragment_friends" + initial_count: 1 + ) { + edges { + node { + id + } + } + } + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/recursive-fragments.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/recursive-fragments.expected new file mode 100644 index 0000000000000..adbc2f93ceb3c --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/recursive-fragments.expected @@ -0,0 +1,21 @@ +==================================== INPUT ==================================== +fragment FragmentSpread on Node { + id + ... @include(if: $condition) { + ...FragmentSpread + } +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type FragmentSpread$ref = FragmentReference & { _: "FragmentSpread$ref" }; +export type FragmentSpread$fragmentType = FragmentSpread$ref & { _: "FragmentSpread$fragmentType" }; +export type FragmentSpread = { + readonly id: string, + readonly $fragmentRefs: FragmentSpread$ref, + readonly $refType: FragmentSpread$ref, +}; +export type FragmentSpread$data = FragmentSpread; +export type FragmentSpread$key = { + readonly $data?: FragmentSpread$data, + readonly $fragmentRefs: FragmentSpread$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/recursive-fragments.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/recursive-fragments.graphql new file mode 100644 index 0000000000000..36cfdf2fb6110 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/recursive-fragments.graphql @@ -0,0 +1,6 @@ +fragment FragmentSpread on Node { + id + ... @include(if: $condition) { + ...FragmentSpread + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable-fragment.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable-fragment.expected new file mode 100644 index 0000000000000..37a6fee6a8d7c --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable-fragment.expected @@ -0,0 +1,40 @@ +==================================== INPUT ==================================== +fragment RefetchableFragment on Node + @refetchable(queryName: "RefetchableFragmentQuery") { + id + fragAndField: profilePicture { + uri + } +} +==================================== OUTPUT =================================== +import type { FragmentReference } from "relay-runtime"; +declare export opaque type RefetchableFragment$ref: FragmentReference; +declare export opaque type RefetchableFragment$fragmentType: RefetchableFragment$ref; +export type RefetchableFragmentQueryVariables = {| + id: string +|}; +export type RefetchableFragmentQueryResponse = {| + +node: ?{| + +$fragmentRefs: RefetchableFragment$ref + |} +|}; +export type RefetchableFragmentQuery = {| + variables: RefetchableFragmentQueryVariables, + response: RefetchableFragmentQueryResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +import { RefetchableFragment$ref, RefetchableFragment$fragmentType } from "RefetchableFragmentQuery.graphql"; +export { RefetchableFragment$ref, RefetchableFragment$fragmentType }; +export type RefetchableFragment = { + readonly id: string, + readonly fragAndField?: { + readonly uri?: string + }, + readonly $refType: RefetchableFragment$ref, +}; +export type RefetchableFragment$data = RefetchableFragment; +export type RefetchableFragment$key = { + readonly $data?: RefetchableFragment$data, + readonly $fragmentRefs: RefetchableFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable-fragment.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable-fragment.graphql new file mode 100644 index 0000000000000..9cbff7ae07cda --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable-fragment.graphql @@ -0,0 +1,7 @@ +fragment RefetchableFragment on Node + @refetchable(queryName: "RefetchableFragmentQuery") { + id + fragAndField: profilePicture { + uri + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable.expected new file mode 100644 index 0000000000000..8c720efa0754a --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable.expected @@ -0,0 +1,38 @@ +==================================== INPUT ==================================== +fragment FlowRefetchableFragment on Node + @refetchable(queryName: "FlowRefetchableFragmentQuery") { + id + ... on User { + name + } +} +==================================== OUTPUT =================================== +import type { FragmentReference } from "relay-runtime"; +declare export opaque type FlowRefetchableFragment$ref: FragmentReference; +declare export opaque type FlowRefetchableFragment$fragmentType: FlowRefetchableFragment$ref; +export type FlowRefetchableFragmentQueryVariables = {| + id: string +|}; +export type FlowRefetchableFragmentQueryResponse = {| + +node: ?{| + +$fragmentRefs: FlowRefetchableFragment$ref + |} +|}; +export type FlowRefetchableFragmentQuery = {| + variables: FlowRefetchableFragmentQueryVariables, + response: FlowRefetchableFragmentQueryResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +import { FlowRefetchableFragment$ref, FlowRefetchableFragment$fragmentType } from "FlowRefetchableFragmentQuery.graphql"; +export { FlowRefetchableFragment$ref, FlowRefetchableFragment$fragmentType }; +export type FlowRefetchableFragment = { + readonly id: string, + readonly name?: string, + readonly $refType: FlowRefetchableFragment$ref, +}; +export type FlowRefetchableFragment$data = FlowRefetchableFragment; +export type FlowRefetchableFragment$key = { + readonly $data?: FlowRefetchableFragment$data, + readonly $fragmentRefs: FlowRefetchableFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable.graphql new file mode 100644 index 0000000000000..2e998f05f3b20 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/refetchable.graphql @@ -0,0 +1,7 @@ +fragment FlowRefetchableFragment on Node + @refetchable(queryName: "FlowRefetchableFragmentQuery") { + id + ... on User { + name + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/relay-client-id-field.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/relay-client-id-field.expected new file mode 100644 index 0000000000000..39ec042b5bb8e --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/relay-client-id-field.expected @@ -0,0 +1,58 @@ +==================================== INPUT ==================================== +query RelayClientIDFieldQuery($id: ID!) { + __id # ok on query type + me { + __id # ok on object type with 'id' + __typename + id + } + node(id: $id) { + __id # ok on interface type + __typename + id + ... on Comment { + commentBody(supported: ["PlainCommentBody"]) { + __id # ok on union type + __typename + ... on PlainCommentBody { + __id # ok on object type w/o 'id' + text { + __id # ok on object type w/o 'id' + __typename + text + } + } + } + } + } +} +==================================== OUTPUT =================================== +export type RelayClientIDFieldQueryVariables = {| + id: string +|}; +export type RelayClientIDFieldQueryResponse = {| + +__id: string, + +me: ?{| + +__id: string, + +__typename: string, + +id: string, + |}, + +node: ?{| + +__id: string, + +__typename: string, + +id: string, + +commentBody?: ?{| + +__id: string, + +__typename: string, + +text?: ?{| + +__id: string, + +__typename: string, + +text: ?string, + |}, + |}, + |}, +|}; +export type RelayClientIDFieldQuery = {| + variables: RelayClientIDFieldQueryVariables, + response: RelayClientIDFieldQueryResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/relay-client-id-field.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/relay-client-id-field.graphql new file mode 100644 index 0000000000000..819f708b8bb40 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/relay-client-id-field.graphql @@ -0,0 +1,27 @@ +query RelayClientIDFieldQuery($id: ID!) { + __id # ok on query type + me { + __id # ok on object type with 'id' + __typename + id + } + node(id: $id) { + __id # ok on interface type + __typename + id + ... on Comment { + commentBody(supported: ["PlainCommentBody"]) { + __id # ok on union type + __typename + ... on PlainCommentBody { + __id # ok on object type w/o 'id' + text { + __id # ok on object type w/o 'id' + __typename + text + } + } + } + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.expected new file mode 100644 index 0000000000000..9014fb862cd06 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.expected @@ -0,0 +1,28 @@ +==================================== INPUT ==================================== +fragment Foo on Node { + __typename + ... on User { + ... on User { + name @required(action: LOG) + } + } +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type Foo$ref = FragmentReference & { _: "Foo$ref" }; +export type Foo$fragmentType = Foo$ref & { _: "Foo$fragmentType" }; +export type Foo = { + readonly __typename: "User", + readonly name: string, + readonly $refType: Foo$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + readonly $refType: Foo$ref, +} | null; +export type Foo$data = Foo; +export type Foo$key = { + readonly $data?: Foo$data, + readonly $fragmentRefs: Foo$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.graphql new file mode 100644 index 0000000000000..01304b96f8eff --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.graphql @@ -0,0 +1,8 @@ +fragment Foo on Node { + __typename + ... on User { + ... on User { + name @required(action: LOG) + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-fragment.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-fragment.expected new file mode 100644 index 0000000000000..ae418394b6861 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-fragment.expected @@ -0,0 +1,19 @@ +==================================== INPUT ==================================== +fragment NonNullFragment on User { + firstName + lastName @required(action: NONE) +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type NonNullFragment$ref = FragmentReference & { _: "NonNullFragment$ref" }; +export type NonNullFragment$fragmentType = NonNullFragment$ref & { _: "NonNullFragment$fragmentType" }; +export type NonNullFragment = { + readonly firstName?: string, + readonly lastName: string, + readonly $refType: NonNullFragment$ref, +} | null; +export type NonNullFragment$data = NonNullFragment; +export type NonNullFragment$key = { + readonly $data?: NonNullFragment$data, + readonly $fragmentRefs: NonNullFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-fragment.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-fragment.graphql new file mode 100644 index 0000000000000..e3e8c34964aba --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-fragment.graphql @@ -0,0 +1,4 @@ +fragment NonNullFragment on User { + firstName + lastName @required(action: NONE) +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.expected new file mode 100644 index 0000000000000..1921d176f4f76 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.expected @@ -0,0 +1,25 @@ +==================================== INPUT ==================================== +fragment NonNullFragment on User { + firstName + screennames { + name + service @required(action: LOG) + } +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type NonNullFragment$ref = FragmentReference & { _: "NonNullFragment$ref" }; +export type NonNullFragment$fragmentType = NonNullFragment$ref & { _: "NonNullFragment$fragmentType" }; +export type NonNullFragment = { + readonly firstName?: string, + readonly screennames?: ReadonlyArray<{ + readonly name?: string, + readonly service: string, + } | null>, + readonly $refType: NonNullFragment$ref, +}; +export type NonNullFragment$data = NonNullFragment; +export type NonNullFragment$key = { + readonly $data?: NonNullFragment$data, + readonly $fragmentRefs: NonNullFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.graphql new file mode 100644 index 0000000000000..2d3ee99486bcc --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.graphql @@ -0,0 +1,7 @@ +fragment NonNullFragment on User { + firstName + screennames { + name + service @required(action: LOG) + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-query.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-query.expected new file mode 100644 index 0000000000000..0369b06f93565 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-query.expected @@ -0,0 +1,19 @@ +==================================== INPUT ==================================== +query FooQuery { + me @required(action: LOG) { + firstName + lastName @required(action: LOG) + } +} +==================================== OUTPUT =================================== +export type FooQueryVariables = {||}; +export type FooQueryResponse = {| + +me: {| + +firstName: ?string, + +lastName: string, + |} +|}; +export type FooQuery = {| + variables: FooQueryVariables, + response: FooQueryResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-query.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-query.graphql new file mode 100644 index 0000000000000..c676f8c880d4c --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-to-query.graphql @@ -0,0 +1,6 @@ +query FooQuery { + me @required(action: LOG) { + firstName + lastName @required(action: LOG) + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-up-to-mutation-response.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-up-to-mutation-response.expected new file mode 100644 index 0000000000000..94e527f5b999f --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-up-to-mutation-response.expected @@ -0,0 +1,34 @@ +==================================== INPUT ==================================== +mutation CommentCreateMutation($input: CommentCreateInput!) { + commentCreate(input: $input) @required(action: LOG) { + comment @required(action: LOG) { + id @required(action: LOG) + } + } +} +==================================== OUTPUT =================================== +export type CommentCreateInput = {| + clientMutationId?: ?string, + feedbackId?: ?string, + feedback?: ?CommentfeedbackFeedback, +|}; +export type CommentfeedbackFeedback = {| + comment?: ?FeedbackcommentComment +|}; +export type FeedbackcommentComment = {| + feedback?: ?CommentfeedbackFeedback +|}; +export type CommentCreateMutationVariables = {| + input: CommentCreateInput +|}; +export type CommentCreateMutationResponse = {| + +commentCreate: {| + +comment: {| + +id: string + |} + |} +|}; +export type CommentCreateMutation = {| + variables: CommentCreateMutationVariables, + response: CommentCreateMutationResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-up-to-mutation-response.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-up-to-mutation-response.graphql new file mode 100644 index 0000000000000..d14254e7a0e38 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-bubbles-up-to-mutation-response.graphql @@ -0,0 +1,7 @@ +mutation CommentCreateMutation($input: CommentCreateInput!) { + commentCreate(input: $input) @required(action: LOG) { + comment @required(action: LOG) { + id @required(action: LOG) + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-isolates-concrete-inline-fragments.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-isolates-concrete-inline-fragments.expected new file mode 100644 index 0000000000000..5aa678fac57d5 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-isolates-concrete-inline-fragments.expected @@ -0,0 +1,70 @@ +==================================== INPUT ==================================== +fragment Bar on Node { + # Since __typename is omitted here, we will end up with a single object type + # rather than a union type. Even though `name` is @required, it will still be + # nullable in the collapsed object's type since the object may not match `User`. + ... on User { + name @required(action: LOG) + } + ... on Comment { + body { + text + } + } +} + +fragment Foo on Node { + __typename + ... on User { + # Ideally this would only cause the `__typename == 'User'` case to become + # nullable, but currently it just makes the entire union type nullable. Not + # ideal, but tollerable. + name @required(action: LOG) + } + ... on Comment { + body { + text + } + } +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type Bar$ref = FragmentReference & { _: "Bar$ref" }; +export type Bar$fragmentType = Bar$ref & { _: "Bar$fragmentType" }; +export type Bar = { + readonly name?: string, + readonly body?: { + readonly text?: string + }, + readonly $refType: Bar$ref, +} | null; +export type Bar$data = Bar; +export type Bar$key = { + readonly $data?: Bar$data, + readonly $fragmentRefs: Bar$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type Foo$ref = FragmentReference & { _: "Foo$ref" }; +export type Foo$fragmentType = Foo$ref & { _: "Foo$fragmentType" }; +export type Foo = { + readonly __typename: "User", + readonly name: string, + readonly $refType: Foo$ref, +} | { + readonly __typename: "Comment", + readonly body?: { + readonly text?: string + }, + readonly $refType: Foo$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + readonly $refType: Foo$ref, +} | null; +export type Foo$data = Foo; +export type Foo$key = { + readonly $data?: Foo$data, + readonly $fragmentRefs: Foo$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-isolates-concrete-inline-fragments.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-isolates-concrete-inline-fragments.graphql new file mode 100644 index 0000000000000..13845fba575d9 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-isolates-concrete-inline-fragments.graphql @@ -0,0 +1,28 @@ +fragment Bar on Node { + # Since __typename is omitted here, we will end up with a single object type + # rather than a union type. Even though `name` is @required, it will still be + # nullable in the collapsed object's type since the object may not match `User`. + ... on User { + name @required(action: LOG) + } + ... on Comment { + body { + text + } + } +} + +fragment Foo on Node { + __typename + ... on User { + # Ideally this would only cause the `__typename == 'User'` case to become + # nullable, but currently it just makes the entire union type nullable. Not + # ideal, but tollerable. + name @required(action: LOG) + } + ... on Comment { + body { + text + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-raw-response-type.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-raw-response-type.expected new file mode 100644 index 0000000000000..407e4601bce16 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-raw-response-type.expected @@ -0,0 +1,26 @@ +==================================== INPUT ==================================== +query MyQuery @raw_response_type { + me @required(action: LOG) { + id @required(action: LOG) + name @required(action: LOG) + } +} +==================================== OUTPUT =================================== +export type MyQueryVariables = {||}; +export type MyQueryResponse = {| + +me: {| + +id: string, + +name: string, + |} +|}; +export type MyQueryRawResponse = {| + +me: ?{| + +id: string, + +name: ?string, + |} +|}; +export type MyQuery = {| + variables: MyQueryVariables, + response: MyQueryResponse, + rawResponse: MyQueryRawResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-raw-response-type.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-raw-response-type.graphql new file mode 100644 index 0000000000000..3eba4dc11bdaa --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-raw-response-type.graphql @@ -0,0 +1,6 @@ +query MyQuery @raw_response_type { + me @required(action: LOG) { + id @required(action: LOG) + name @required(action: LOG) + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.expected new file mode 100644 index 0000000000000..fe959342ffea0 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.expected @@ -0,0 +1,19 @@ +==================================== INPUT ==================================== +fragment NonNullFragment on User { + firstName + lastName @required(action: THROW) +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type NonNullFragment$ref = FragmentReference & { _: "NonNullFragment$ref" }; +export type NonNullFragment$fragmentType = NonNullFragment$ref & { _: "NonNullFragment$fragmentType" }; +export type NonNullFragment = { + readonly firstName?: string, + readonly lastName: string, + readonly $refType: NonNullFragment$ref, +}; +export type NonNullFragment$data = NonNullFragment; +export type NonNullFragment$key = { + readonly $data?: NonNullFragment$data, + readonly $fragmentRefs: NonNullFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.graphql new file mode 100644 index 0000000000000..006e386b73e05 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.graphql @@ -0,0 +1,4 @@ +fragment NonNullFragment on User { + firstName + lastName @required(action: THROW) +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.expected new file mode 100644 index 0000000000000..2bbcbc3c1baa0 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.expected @@ -0,0 +1,19 @@ +==================================== INPUT ==================================== +query FooQuery { + me @required(action: THROW) { + firstName + lastName + } +} +==================================== OUTPUT =================================== +export type FooQueryVariables = {||}; +export type FooQueryResponse = {| + +me: {| + +firstName: ?string, + +lastName: ?string, + |} +|}; +export type FooQuery = {| + variables: FooQueryVariables, + response: FooQueryResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.graphql new file mode 100644 index 0000000000000..c24100ba1e99e --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.graphql @@ -0,0 +1,6 @@ +query FooQuery { + me @required(action: THROW) { + firstName + lastName + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throws-nested.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throws-nested.expected new file mode 100644 index 0000000000000..8b1ab03230fd8 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throws-nested.expected @@ -0,0 +1,19 @@ +==================================== INPUT ==================================== +query FooQuery { + me { + firstName + lastName @required(action: THROW) + } +} +==================================== OUTPUT =================================== +export type FooQueryVariables = {||}; +export type FooQueryResponse = {| + +me: ?{| + +firstName: ?string, + +lastName: string, + |} +|}; +export type FooQuery = {| + variables: FooQueryVariables, + response: FooQueryResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throws-nested.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throws-nested.graphql new file mode 100644 index 0000000000000..76e385db4810a --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required-throws-nested.graphql @@ -0,0 +1,6 @@ +query FooQuery { + me { + firstName + lastName @required(action: THROW) + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required.expected new file mode 100644 index 0000000000000..e82aac670513c --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required.expected @@ -0,0 +1,19 @@ +==================================== INPUT ==================================== +query FooQuery { + me { + firstName + lastName @required(action: LOG) + } +} +==================================== OUTPUT =================================== +export type FooQueryVariables = {||}; +export type FooQueryResponse = {| + +me: ?{| + +firstName: ?string, + +lastName: string, + |} +|}; +export type FooQuery = {| + variables: FooQueryVariables, + response: FooQueryResponse, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required.graphql new file mode 100644 index 0000000000000..4513dcf1562d1 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/required.graphql @@ -0,0 +1,6 @@ +query FooQuery { + me { + firstName + lastName @required(action: LOG) + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/roots.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/roots.expected new file mode 100644 index 0000000000000..fb45f9daf717e --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/roots.expected @@ -0,0 +1,97 @@ +==================================== INPUT ==================================== +query ExampleQuery($id: ID!) { + node(id: $id) { + id + } +} + +fragment ExampleFragment on User { + id +} + +mutation TestMutation($input: CommentCreateInput!) { + commentCreate(input: $input) { + comment { + id + } + } +} + +subscription TestSubscription($input: FeedbackLikeInput) { + feedbackLikeSubscribe(input: $input) { + feedback { + id + } + } +} +==================================== OUTPUT =================================== +export type ExampleQueryVariables = {| + id: string +|}; +export type ExampleQueryResponse = {| + +node: ?{| + +id: string + |} +|}; +export type ExampleQuery = {| + variables: ExampleQueryVariables, + response: ExampleQueryResponse, +|}; +------------------------------------------------------------------------------- +export type CommentCreateInput = {| + clientMutationId?: ?string, + feedbackId?: ?string, + feedback?: ?CommentfeedbackFeedback, +|}; +export type CommentfeedbackFeedback = {| + comment?: ?FeedbackcommentComment +|}; +export type FeedbackcommentComment = {| + feedback?: ?CommentfeedbackFeedback +|}; +export type TestMutationVariables = {| + input: CommentCreateInput +|}; +export type TestMutationResponse = {| + +commentCreate: ?{| + +comment: ?{| + +id: string + |} + |} +|}; +export type TestMutation = {| + variables: TestMutationVariables, + response: TestMutationResponse, +|}; +------------------------------------------------------------------------------- +export type FeedbackLikeInput = {| + clientMutationId?: ?string, + feedbackId?: ?string, +|}; +export type TestSubscriptionVariables = {| + input?: ?FeedbackLikeInput +|}; +export type TestSubscriptionResponse = {| + +feedbackLikeSubscribe: ?{| + +feedback: ?{| + +id: string + |} + |} +|}; +export type TestSubscription = {| + variables: TestSubscriptionVariables, + response: TestSubscriptionResponse, +|}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type ExampleFragment$ref = FragmentReference & { _: "ExampleFragment$ref" }; +export type ExampleFragment$fragmentType = ExampleFragment$ref & { _: "ExampleFragment$fragmentType" }; +export type ExampleFragment = { + readonly id: string, + readonly $refType: ExampleFragment$ref, +}; +export type ExampleFragment$data = ExampleFragment; +export type ExampleFragment$key = { + readonly $data?: ExampleFragment$data, + readonly $fragmentRefs: ExampleFragment$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/roots.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/roots.graphql new file mode 100644 index 0000000000000..541b61e8c9019 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/roots.graphql @@ -0,0 +1,25 @@ +query ExampleQuery($id: ID!) { + node(id: $id) { + id + } +} + +fragment ExampleFragment on User { + id +} + +mutation TestMutation($input: CommentCreateInput!) { + commentCreate(input: $input) { + comment { + id + } + } +} + +subscription TestSubscription($input: FeedbackLikeInput) { + feedbackLikeSubscribe(input: $input) { + feedback { + id + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-field.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-field.expected new file mode 100644 index 0000000000000..0c5859491d7d3 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-field.expected @@ -0,0 +1,38 @@ +==================================== INPUT ==================================== +fragment ScalarField on User { + id + name + websites + traits + aliasedLinkedField: birthdate { + aliasedField: year + } + screennames { + name + service + } +} +==================================== OUTPUT =================================== +export type PersonalityTraits = "CHEERFUL" | "DERISIVE" | "HELPFUL" | "SNARKY" | "%future added value"; +import { FragmentReference } from "relay-runtime"; +export type ScalarField$ref = FragmentReference & { _: "ScalarField$ref" }; +export type ScalarField$fragmentType = ScalarField$ref & { _: "ScalarField$fragmentType" }; +export type ScalarField = { + readonly id: string, + readonly name?: string, + readonly websites?: ReadonlyArray, + readonly traits?: ReadonlyArray, + readonly aliasedLinkedField?: { + readonly aliasedField?: number + }, + readonly screennames?: ReadonlyArray<{ + readonly name?: string, + readonly service?: string, + } | null>, + readonly $refType: ScalarField$ref, +}; +export type ScalarField$data = ScalarField; +export type ScalarField$key = { + readonly $data?: ScalarField$data, + readonly $fragmentRefs: ScalarField$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-field.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-field.graphql new file mode 100644 index 0000000000000..264c33d82b438 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-field.graphql @@ -0,0 +1,13 @@ +fragment ScalarField on User { + id + name + websites + traits + aliasedLinkedField: birthdate { + aliasedField: year + } + screennames { + name + service + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/simple.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/simple.expected new file mode 100644 index 0000000000000..c0577c4d17e4c --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/simple.expected @@ -0,0 +1,27 @@ +==================================== INPUT ==================================== +fragment LinkedField on User { + name + profilePicture { + uri + width + height + } +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type LinkedField$ref = FragmentReference & { _: "LinkedField$ref" }; +export type LinkedField$fragmentType = LinkedField$ref & { _: "LinkedField$fragmentType" }; +export type LinkedField = { + readonly name?: string, + readonly profilePicture?: { + readonly uri?: string, + readonly width?: number, + readonly height?: number, + }, + readonly $refType: LinkedField$ref, +}; +export type LinkedField$data = LinkedField; +export type LinkedField$key = { + readonly $data?: LinkedField$data, + readonly $fragmentRefs: LinkedField$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/simple.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/simple.graphql new file mode 100644 index 0000000000000..c002a2fcdd8e9 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/simple.graphql @@ -0,0 +1,8 @@ +fragment LinkedField on User { + name + profilePicture { + uri + width + height + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-inside-with-overlapping-fields.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-inside-with-overlapping-fields.expected new file mode 100644 index 0000000000000..7ae01881ea6ea --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-inside-with-overlapping-fields.expected @@ -0,0 +1,45 @@ +==================================== INPUT ==================================== +fragment TypenameInsideWithOverlappingFields on Viewer { + actor { + __typename + ... on Page { + id + name + } + ... on User { + id + name + profile_picture { + uri + } + } + } +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type TypenameInsideWithOverlappingFields$ref = FragmentReference & { _: "TypenameInsideWithOverlappingFields$ref" }; +export type TypenameInsideWithOverlappingFields$fragmentType = TypenameInsideWithOverlappingFields$ref & { _: "TypenameInsideWithOverlappingFields$fragmentType" }; +export type TypenameInsideWithOverlappingFields = { + readonly actor?: { + readonly __typename: "Page", + readonly id: string, + readonly name?: string, + } | { + readonly __typename: "User", + readonly id: string, + readonly name?: string, + readonly profile_picture?: { + readonly uri?: string + }, + } | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other" + }, + readonly $refType: TypenameInsideWithOverlappingFields$ref, +}; +export type TypenameInsideWithOverlappingFields$data = TypenameInsideWithOverlappingFields; +export type TypenameInsideWithOverlappingFields$key = { + readonly $data?: TypenameInsideWithOverlappingFields$data, + readonly $fragmentRefs: TypenameInsideWithOverlappingFields$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-inside-with-overlapping-fields.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-inside-with-overlapping-fields.graphql new file mode 100644 index 0000000000000..7c2b9d9cfacd7 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-inside-with-overlapping-fields.graphql @@ -0,0 +1,16 @@ +fragment TypenameInsideWithOverlappingFields on Viewer { + actor { + __typename + ... on Page { + id + name + } + ... on User { + id + name + profile_picture { + uri + } + } + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-on-union.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-on-union.expected new file mode 100644 index 0000000000000..3905ab0123b1e --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-on-union.expected @@ -0,0 +1,242 @@ +==================================== INPUT ==================================== +fragment TypenameInside on Actor { + ... on User { + __typename + firstName + } + ... on Page { + __typename + username + } +} + +fragment TypenameOutside on Actor { + __typename + ... on User { + firstName + } + ... on Page { + username + } +} + +fragment TypenameOutsideWithAbstractType on Node { + __typename + ... on User { + firstName + address { + street # only here + city # common + } + } + ... on Actor { + username + address { + city # common + country # only here + } + } +} + +fragment TypenameWithoutSpreads on User { + __typename + firstName +} + +fragment TypenameWithoutSpreadsAbstractType on Node { + __typename + id +} + +fragment TypenameWithCommonSelections on Actor { + __typename + name + ... on User { + firstName + } + ... on Page { + username + } +} + +fragment TypenameAlias on Actor { + _typeAlias: __typename + ... on User { + firstName + } + ... on Page { + username + } +} + +fragment TypenameAliases on Actor { + _typeAlias1: __typename + _typeAlias2: __typename + ... on User { + firstName + } + ... on Page { + username + } +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type TypenameAlias$ref = FragmentReference & { _: "TypenameAlias$ref" }; +export type TypenameAlias$fragmentType = TypenameAlias$ref & { _: "TypenameAlias$fragmentType" }; +export type TypenameAlias = { + readonly _typeAlias: "User", + readonly firstName?: string, + readonly $refType: TypenameAlias$ref, +} | { + readonly _typeAlias: "Page", + readonly username?: string, + readonly $refType: TypenameAlias$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly _typeAlias: "%other", + readonly $refType: TypenameAlias$ref, +}; +export type TypenameAlias$data = TypenameAlias; +export type TypenameAlias$key = { + readonly $data?: TypenameAlias$data, + readonly $fragmentRefs: TypenameAlias$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type TypenameAliases$ref = FragmentReference & { _: "TypenameAliases$ref" }; +export type TypenameAliases$fragmentType = TypenameAliases$ref & { _: "TypenameAliases$fragmentType" }; +export type TypenameAliases = { + readonly _typeAlias1: "User", + readonly _typeAlias2: "User", + readonly firstName?: string, + readonly $refType: TypenameAliases$ref, +} | { + readonly _typeAlias1: "Page", + readonly _typeAlias2: "Page", + readonly username?: string, + readonly $refType: TypenameAliases$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly _typeAlias1: "%other", + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly _typeAlias2: "%other", + readonly $refType: TypenameAliases$ref, +}; +export type TypenameAliases$data = TypenameAliases; +export type TypenameAliases$key = { + readonly $data?: TypenameAliases$data, + readonly $fragmentRefs: TypenameAliases$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type TypenameInside$ref = FragmentReference & { _: "TypenameInside$ref" }; +export type TypenameInside$fragmentType = TypenameInside$ref & { _: "TypenameInside$fragmentType" }; +export type TypenameInside = { + readonly __typename: "User", + readonly firstName?: string, + readonly $refType: TypenameInside$ref, +} | { + readonly __typename: "Page", + readonly username?: string, + readonly $refType: TypenameInside$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + readonly $refType: TypenameInside$ref, +}; +export type TypenameInside$data = TypenameInside; +export type TypenameInside$key = { + readonly $data?: TypenameInside$data, + readonly $fragmentRefs: TypenameInside$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type TypenameOutside$ref = FragmentReference & { _: "TypenameOutside$ref" }; +export type TypenameOutside$fragmentType = TypenameOutside$ref & { _: "TypenameOutside$fragmentType" }; +export type TypenameOutside = { + readonly __typename: "User", + readonly firstName?: string, + readonly $refType: TypenameOutside$ref, +} | { + readonly __typename: "Page", + readonly username?: string, + readonly $refType: TypenameOutside$ref, +} | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + readonly $refType: TypenameOutside$ref, +}; +export type TypenameOutside$data = TypenameOutside; +export type TypenameOutside$key = { + readonly $data?: TypenameOutside$data, + readonly $fragmentRefs: TypenameOutside$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type TypenameOutsideWithAbstractType$ref = FragmentReference & { _: "TypenameOutsideWithAbstractType$ref" }; +export type TypenameOutsideWithAbstractType$fragmentType = TypenameOutsideWithAbstractType$ref & { _: "TypenameOutsideWithAbstractType$fragmentType" }; +export type TypenameOutsideWithAbstractType = { + readonly __typename: string, + readonly username?: string, + readonly address?: { + readonly city?: string, + readonly country?: string, + readonly street?: string, + }, + readonly firstName?: string, + readonly $refType: TypenameOutsideWithAbstractType$ref, +}; +export type TypenameOutsideWithAbstractType$data = TypenameOutsideWithAbstractType; +export type TypenameOutsideWithAbstractType$key = { + readonly $data?: TypenameOutsideWithAbstractType$data, + readonly $fragmentRefs: TypenameOutsideWithAbstractType$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type TypenameWithCommonSelections$ref = FragmentReference & { _: "TypenameWithCommonSelections$ref" }; +export type TypenameWithCommonSelections$fragmentType = TypenameWithCommonSelections$ref & { _: "TypenameWithCommonSelections$fragmentType" }; +export type TypenameWithCommonSelections = { + readonly __typename: string, + readonly name?: string, + readonly firstName?: string, + readonly username?: string, + readonly $refType: TypenameWithCommonSelections$ref, +}; +export type TypenameWithCommonSelections$data = TypenameWithCommonSelections; +export type TypenameWithCommonSelections$key = { + readonly $data?: TypenameWithCommonSelections$data, + readonly $fragmentRefs: TypenameWithCommonSelections$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type TypenameWithoutSpreads$ref = FragmentReference & { _: "TypenameWithoutSpreads$ref" }; +export type TypenameWithoutSpreads$fragmentType = TypenameWithoutSpreads$ref & { _: "TypenameWithoutSpreads$fragmentType" }; +export type TypenameWithoutSpreads = { + readonly firstName?: string, + readonly __typename: "User", + readonly $refType: TypenameWithoutSpreads$ref, +}; +export type TypenameWithoutSpreads$data = TypenameWithoutSpreads; +export type TypenameWithoutSpreads$key = { + readonly $data?: TypenameWithoutSpreads$data, + readonly $fragmentRefs: TypenameWithoutSpreads$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type TypenameWithoutSpreadsAbstractType$ref = FragmentReference & { _: "TypenameWithoutSpreadsAbstractType$ref" }; +export type TypenameWithoutSpreadsAbstractType$fragmentType = TypenameWithoutSpreadsAbstractType$ref & { _: "TypenameWithoutSpreadsAbstractType$fragmentType" }; +export type TypenameWithoutSpreadsAbstractType = { + readonly __typename: string, + readonly id: string, + readonly $refType: TypenameWithoutSpreadsAbstractType$ref, +}; +export type TypenameWithoutSpreadsAbstractType$data = TypenameWithoutSpreadsAbstractType; +export type TypenameWithoutSpreadsAbstractType$key = { + readonly $data?: TypenameWithoutSpreadsAbstractType$data, + readonly $fragmentRefs: TypenameWithoutSpreadsAbstractType$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-on-union.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-on-union.graphql new file mode 100644 index 0000000000000..3c09d095aa67d --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/typename-on-union.graphql @@ -0,0 +1,80 @@ +fragment TypenameInside on Actor { + ... on User { + __typename + firstName + } + ... on Page { + __typename + username + } +} + +fragment TypenameOutside on Actor { + __typename + ... on User { + firstName + } + ... on Page { + username + } +} + +fragment TypenameOutsideWithAbstractType on Node { + __typename + ... on User { + firstName + address { + street # only here + city # common + } + } + ... on Actor { + username + address { + city # common + country # only here + } + } +} + +fragment TypenameWithoutSpreads on User { + __typename + firstName +} + +fragment TypenameWithoutSpreadsAbstractType on Node { + __typename + id +} + +fragment TypenameWithCommonSelections on Actor { + __typename + name + ... on User { + firstName + } + ... on Page { + username + } +} + +fragment TypenameAlias on Actor { + _typeAlias: __typename + ... on User { + firstName + } + ... on Page { + username + } +} + +fragment TypenameAliases on Actor { + _typeAlias1: __typename + _typeAlias2: __typename + ... on User { + firstName + } + ... on Page { + username + } +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/unmasked-fragment-spreads.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/unmasked-fragment-spreads.expected new file mode 100644 index 0000000000000..278de8553f0b0 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/unmasked-fragment-spreads.expected @@ -0,0 +1,87 @@ +==================================== INPUT ==================================== +fragment UserProfile on User { + profilePicture(size: $ProfilePicture_SIZE) { + ...PhotoFragment @relay(mask: false) + + # duplicated field should be merged + ...AnotherRecursiveFragment @relay(mask: false) + + # Compose child fragment + ...PhotoFragment + } +} + +fragment PhotoFragment on Image { + uri + ...RecursiveFragment @relay(mask: false) +} + +fragment RecursiveFragment on Image @relay(mask: false) { + uri + width +} + +fragment AnotherRecursiveFragment on Image { + uri + height +} +==================================== OUTPUT =================================== +import { FragmentReference } from "relay-runtime"; +export type AnotherRecursiveFragment$ref = FragmentReference & { _: "AnotherRecursiveFragment$ref" }; +export type AnotherRecursiveFragment$fragmentType = AnotherRecursiveFragment$ref & { _: "AnotherRecursiveFragment$fragmentType" }; +export type AnotherRecursiveFragment = { + readonly uri?: string, + readonly height?: number, + readonly $refType: AnotherRecursiveFragment$ref, +}; +export type AnotherRecursiveFragment$data = AnotherRecursiveFragment; +export type AnotherRecursiveFragment$key = { + readonly $data?: AnotherRecursiveFragment$data, + readonly $fragmentRefs: AnotherRecursiveFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type PhotoFragment$ref = FragmentReference & { _: "PhotoFragment$ref" }; +export type PhotoFragment$fragmentType = PhotoFragment$ref & { _: "PhotoFragment$fragmentType" }; +export type PhotoFragment = { + readonly uri?: string, + readonly width?: number, + readonly $refType: PhotoFragment$ref, +}; +export type PhotoFragment$data = PhotoFragment; +export type PhotoFragment$key = { + readonly $data?: PhotoFragment$data, + readonly $fragmentRefs: PhotoFragment$ref, +}; +------------------------------------------------------------------------------- +import { FragmentReference } from "relay-runtime"; +export type RecursiveFragment$ref = FragmentReference & { _: "RecursiveFragment$ref" }; +export type RecursiveFragment$fragmentType = RecursiveFragment$ref & { _: "RecursiveFragment$fragmentType" }; +export type RecursiveFragment = { + readonly uri?: string, + readonly width?: number, +}; +export type RecursiveFragment$data = RecursiveFragment; +export type RecursiveFragment$key = { + readonly $data?: RecursiveFragment$data, + readonly $fragmentRefs: RecursiveFragment$ref, +}; +------------------------------------------------------------------------------- +import type { PhotoFragment$ref } from "PhotoFragment.graphql"; +import { FragmentReference } from "relay-runtime"; +export type UserProfile$ref = FragmentReference & { _: "UserProfile$ref" }; +export type UserProfile$fragmentType = UserProfile$ref & { _: "UserProfile$fragmentType" }; +export type UserProfile = { + readonly profilePicture?: { + readonly uri?: string, + readonly width?: number, + readonly height?: number, + readonly $fragmentRefs: PhotoFragment$ref, + }, + readonly $refType: UserProfile$ref, +}; +export type UserProfile$data = UserProfile; +export type UserProfile$key = { + readonly $data?: UserProfile$data, + readonly $fragmentRefs: UserProfile$ref, +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/unmasked-fragment-spreads.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/unmasked-fragment-spreads.graphql new file mode 100644 index 0000000000000..0fb706bcbc9d0 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/unmasked-fragment-spreads.graphql @@ -0,0 +1,26 @@ +fragment UserProfile on User { + profilePicture(size: $ProfilePicture_SIZE) { + ...PhotoFragment @relay(mask: false) + + # duplicated field should be merged + ...AnotherRecursiveFragment @relay(mask: false) + + # Compose child fragment + ...PhotoFragment + } +} + +fragment PhotoFragment on Image { + uri + ...RecursiveFragment @relay(mask: false) +} + +fragment RecursiveFragment on Image @relay(mask: false) { + uri + width +} + +fragment AnotherRecursiveFragment on Image { + uri + height +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/mod.rs b/compiler/crates/relay-typegen/tests/generate_typescript/mod.rs new file mode 100644 index 0000000000000..f7df10147e51b --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/mod.rs @@ -0,0 +1,82 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +use common::{ConsoleLogger, SourceLocationKey}; +use fixture_tests::Fixture; +use fnv::FnvHashMap; +use graphql_ir::{build, Program}; +use graphql_syntax::parse_executable; +use graphql_transforms::{ConnectionInterface, FeatureFlags}; +use interner::Intern; +use relay_compiler::apply_transforms; +use relay_typegen::{self, TypegenConfig, TypegenLanguage}; +use std::sync::Arc; +use test_schema::{get_test_schema, get_test_schema_with_extensions}; + +pub fn transform_fixture(fixture: &Fixture<'_>) -> Result { + let parts = fixture.content.split("%extensions%").collect::>(); + let (source, schema) = match parts.as_slice() { + [source, extensions] => (source, get_test_schema_with_extensions(extensions)), + [source] => (source, get_test_schema()), + _ => panic!(), + }; + + let source_location = SourceLocationKey::standalone(fixture.file_name); + + let mut sources = FnvHashMap::default(); + sources.insert(source_location, source); + let ast = parse_executable(source, source_location).unwrap(); + let ir = build(&schema, &ast.definitions).unwrap(); + let program = Program::from_definitions(Arc::clone(&schema), ir); + let programs = apply_transforms( + "test".intern(), + Arc::new(program), + Default::default(), + &ConnectionInterface::default(), + Arc::new(FeatureFlags { + enable_flight_transform: false, + enable_required_transform_for_prefix: Some("".intern()), + }), + Arc::new(ConsoleLogger), + ) + .unwrap(); + + let mut operations: Vec<_> = programs.typegen.operations().collect(); + operations.sort_by_key(|op| op.name.item); + let operation_strings = operations.into_iter().map(|typegen_operation| { + let normalization_operation = programs + .normalization + .operation(typegen_operation.name.item) + .unwrap(); + relay_typegen::generate_operation_type( + typegen_operation, + normalization_operation, + &schema, + &TypegenConfig::default(), + ) + }); + + let mut fragments: Vec<_> = programs.typegen.fragments().collect(); + fragments.sort_by_key(|frag| frag.name.item); + let fragment_strings = fragments.into_iter().map(|frag| { + relay_typegen::generate_fragment_type( + frag, + &schema, + &TypegenConfig { + language: TypegenLanguage::TypeScript, + enum_module_suffix: None, + optional_input_fields: vec![], + custom_scalar_types: Default::default(), + }, + ) + }); + + let mut result: Vec = operation_strings.collect(); + result.extend(fragment_strings); + Ok(result + .join("-------------------------------------------------------------------------------\n")) +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript_test.rs b/compiler/crates/relay-typegen/tests/generate_typescript_test.rs new file mode 100644 index 0000000000000..1d2b2e0d1cfef --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript_test.rs @@ -0,0 +1,300 @@ +// @generated SignedSource<> + +mod generate_typescript; + +use generate_typescript::transform_fixture; +use fixture_tests::test_fixture; + +#[test] +fn conditional() { + let input = include_str!("generate_typescript/fixtures/conditional.graphql"); + let expected = include_str!("generate_typescript/fixtures/conditional.expected"); + test_fixture(transform_fixture, "conditional.graphql", "generate_typescript/fixtures/conditional.expected", input, expected); +} + +#[test] +fn fragment_spread() { + let input = include_str!("generate_typescript/fixtures/fragment-spread.graphql"); + let expected = include_str!("generate_typescript/fixtures/fragment-spread.expected"); + test_fixture(transform_fixture, "fragment-spread.graphql", "generate_typescript/fixtures/fragment-spread.expected", input, expected); +} + +#[test] +fn inline_fragment() { + let input = include_str!("generate_typescript/fixtures/inline-fragment.graphql"); + let expected = include_str!("generate_typescript/fixtures/inline-fragment.expected"); + test_fixture(transform_fixture, "inline-fragment.graphql", "generate_typescript/fixtures/inline-fragment.expected", input, expected); +} + +#[test] +fn linked_field() { + let input = include_str!("generate_typescript/fixtures/linked-field.graphql"); + let expected = include_str!("generate_typescript/fixtures/linked-field.expected"); + test_fixture(transform_fixture, "linked-field.graphql", "generate_typescript/fixtures/linked-field.expected", input, expected); +} + +#[test] +fn match_field() { + let input = include_str!("generate_typescript/fixtures/match-field.graphql"); + let expected = include_str!("generate_typescript/fixtures/match-field.expected"); + test_fixture(transform_fixture, "match-field.graphql", "generate_typescript/fixtures/match-field.expected", input, expected); +} + +#[test] +fn match_field_in_query() { + let input = include_str!("generate_typescript/fixtures/match-field-in-query.graphql"); + let expected = include_str!("generate_typescript/fixtures/match-field-in-query.expected"); + test_fixture(transform_fixture, "match-field-in-query.graphql", "generate_typescript/fixtures/match-field-in-query.expected", input, expected); +} + +#[test] +fn mutaion_with_client_extension() { + let input = include_str!("generate_typescript/fixtures/mutaion-with-client-extension.graphql"); + let expected = include_str!("generate_typescript/fixtures/mutaion-with-client-extension.expected"); + test_fixture(transform_fixture, "mutaion-with-client-extension.graphql", "generate_typescript/fixtures/mutaion-with-client-extension.expected", input, expected); +} + +#[test] +fn mutaion_with_response_on_inline_fragments() { + let input = include_str!("generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.graphql"); + let expected = include_str!("generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.expected"); + test_fixture(transform_fixture, "mutaion-with-response-on-inline-fragments.graphql", "generate_typescript/fixtures/mutaion-with-response-on-inline-fragments.expected", input, expected); +} + +#[test] +fn mutation() { + let input = include_str!("generate_typescript/fixtures/mutation.graphql"); + let expected = include_str!("generate_typescript/fixtures/mutation.expected"); + test_fixture(transform_fixture, "mutation.graphql", "generate_typescript/fixtures/mutation.expected", input, expected); +} + +#[test] +fn mutation_input_has_array() { + let input = include_str!("generate_typescript/fixtures/mutation-input-has-array.graphql"); + let expected = include_str!("generate_typescript/fixtures/mutation-input-has-array.expected"); + test_fixture(transform_fixture, "mutation-input-has-array.graphql", "generate_typescript/fixtures/mutation-input-has-array.expected", input, expected); +} + +#[test] +fn mutation_with_enums_on_fragment() { + let input = include_str!("generate_typescript/fixtures/mutation-with-enums-on-fragment.graphql"); + let expected = include_str!("generate_typescript/fixtures/mutation-with-enums-on-fragment.expected"); + test_fixture(transform_fixture, "mutation-with-enums-on-fragment.graphql", "generate_typescript/fixtures/mutation-with-enums-on-fragment.expected", input, expected); +} + +#[test] +fn mutation_with_nested_fragments() { + let input = include_str!("generate_typescript/fixtures/mutation-with-nested-fragments.graphql"); + let expected = include_str!("generate_typescript/fixtures/mutation-with-nested-fragments.expected"); + test_fixture(transform_fixture, "mutation-with-nested-fragments.graphql", "generate_typescript/fixtures/mutation-with-nested-fragments.expected", input, expected); +} + +#[test] +fn plural_fragment() { + let input = include_str!("generate_typescript/fixtures/plural-fragment.graphql"); + let expected = include_str!("generate_typescript/fixtures/plural-fragment.expected"); + test_fixture(transform_fixture, "plural-fragment.graphql", "generate_typescript/fixtures/plural-fragment.expected", input, expected); +} + +#[test] +fn query_with_handles() { + let input = include_str!("generate_typescript/fixtures/query-with-handles.graphql"); + let expected = include_str!("generate_typescript/fixtures/query-with-handles.expected"); + test_fixture(transform_fixture, "query-with-handles.graphql", "generate_typescript/fixtures/query-with-handles.expected", input, expected); +} + +#[test] +fn query_with_match_fields() { + let input = include_str!("generate_typescript/fixtures/query-with-match-fields.graphql"); + let expected = include_str!("generate_typescript/fixtures/query-with-match-fields.expected"); + test_fixture(transform_fixture, "query-with-match-fields.graphql", "generate_typescript/fixtures/query-with-match-fields.expected", input, expected); +} + +#[test] +fn query_with_module_field() { + let input = include_str!("generate_typescript/fixtures/query-with-module-field.graphql"); + let expected = include_str!("generate_typescript/fixtures/query-with-module-field.expected"); + test_fixture(transform_fixture, "query-with-module-field.graphql", "generate_typescript/fixtures/query-with-module-field.expected", input, expected); +} + +#[test] +fn query_with_multiple_match_fields() { + let input = include_str!("generate_typescript/fixtures/query-with-multiple-match-fields.graphql"); + let expected = include_str!("generate_typescript/fixtures/query-with-multiple-match-fields.expected"); + test_fixture(transform_fixture, "query-with-multiple-match-fields.graphql", "generate_typescript/fixtures/query-with-multiple-match-fields.expected", input, expected); +} + +#[test] +fn query_with_raw_response_on_conditional() { + let input = include_str!("generate_typescript/fixtures/query-with-raw-response-on-conditional.graphql"); + let expected = include_str!("generate_typescript/fixtures/query-with-raw-response-on-conditional.expected"); + test_fixture(transform_fixture, "query-with-raw-response-on-conditional.graphql", "generate_typescript/fixtures/query-with-raw-response-on-conditional.expected", input, expected); +} + +#[test] +fn query_with_raw_response_on_literal_conditional() { + let input = include_str!("generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.graphql"); + let expected = include_str!("generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.expected"); + test_fixture(transform_fixture, "query-with-raw-response-on-literal-conditional.graphql", "generate_typescript/fixtures/query-with-raw-response-on-literal-conditional.expected", input, expected); +} + +#[test] +fn query_with_stream() { + let input = include_str!("generate_typescript/fixtures/query-with-stream.graphql"); + let expected = include_str!("generate_typescript/fixtures/query-with-stream.expected"); + test_fixture(transform_fixture, "query-with-stream.graphql", "generate_typescript/fixtures/query-with-stream.expected", input, expected); +} + +#[test] +fn query_with_stream_connection() { + let input = include_str!("generate_typescript/fixtures/query-with-stream-connection.graphql"); + let expected = include_str!("generate_typescript/fixtures/query-with-stream-connection.expected"); + test_fixture(transform_fixture, "query-with-stream-connection.graphql", "generate_typescript/fixtures/query-with-stream-connection.expected", input, expected); +} + +#[test] +fn recursive_fragments() { + let input = include_str!("generate_typescript/fixtures/recursive-fragments.graphql"); + let expected = include_str!("generate_typescript/fixtures/recursive-fragments.expected"); + test_fixture(transform_fixture, "recursive-fragments.graphql", "generate_typescript/fixtures/recursive-fragments.expected", input, expected); +} + +#[test] +fn refetchable() { + let input = include_str!("generate_typescript/fixtures/refetchable.graphql"); + let expected = include_str!("generate_typescript/fixtures/refetchable.expected"); + test_fixture(transform_fixture, "refetchable.graphql", "generate_typescript/fixtures/refetchable.expected", input, expected); +} + +#[test] +fn refetchable_fragment() { + let input = include_str!("generate_typescript/fixtures/refetchable-fragment.graphql"); + let expected = include_str!("generate_typescript/fixtures/refetchable-fragment.expected"); + test_fixture(transform_fixture, "refetchable-fragment.graphql", "generate_typescript/fixtures/refetchable-fragment.expected", input, expected); +} + +#[test] +fn relay_client_id_field() { + let input = include_str!("generate_typescript/fixtures/relay-client-id-field.graphql"); + let expected = include_str!("generate_typescript/fixtures/relay-client-id-field.expected"); + test_fixture(transform_fixture, "relay-client-id-field.graphql", "generate_typescript/fixtures/relay-client-id-field.expected", input, expected); +} + +#[test] +fn required() { + let input = include_str!("generate_typescript/fixtures/required.graphql"); + let expected = include_str!("generate_typescript/fixtures/required.expected"); + test_fixture(transform_fixture, "required.graphql", "generate_typescript/fixtures/required.expected", input, expected); +} + +#[test] +fn required_bubbles_through_inline_fragments_to_fragment() { + let input = include_str!("generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.expected"); + test_fixture(transform_fixture, "required-bubbles-through-inline-fragments-to-fragment.graphql", "generate_typescript/fixtures/required-bubbles-through-inline-fragments-to-fragment.expected", input, expected); +} + +#[test] +fn required_bubbles_to_fragment() { + let input = include_str!("generate_typescript/fixtures/required-bubbles-to-fragment.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-bubbles-to-fragment.expected"); + test_fixture(transform_fixture, "required-bubbles-to-fragment.graphql", "generate_typescript/fixtures/required-bubbles-to-fragment.expected", input, expected); +} + +#[test] +fn required_bubbles_to_item_in_plural_field() { + let input = include_str!("generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.expected"); + test_fixture(transform_fixture, "required-bubbles-to-item-in-plural-field.graphql", "generate_typescript/fixtures/required-bubbles-to-item-in-plural-field.expected", input, expected); +} + +#[test] +fn required_bubbles_to_query() { + let input = include_str!("generate_typescript/fixtures/required-bubbles-to-query.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-bubbles-to-query.expected"); + test_fixture(transform_fixture, "required-bubbles-to-query.graphql", "generate_typescript/fixtures/required-bubbles-to-query.expected", input, expected); +} + +#[test] +fn required_bubbles_up_to_mutation_response() { + let input = include_str!("generate_typescript/fixtures/required-bubbles-up-to-mutation-response.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-bubbles-up-to-mutation-response.expected"); + test_fixture(transform_fixture, "required-bubbles-up-to-mutation-response.graphql", "generate_typescript/fixtures/required-bubbles-up-to-mutation-response.expected", input, expected); +} + +#[test] +fn required_isolates_concrete_inline_fragments() { + let input = include_str!("generate_typescript/fixtures/required-isolates-concrete-inline-fragments.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-isolates-concrete-inline-fragments.expected"); + test_fixture(transform_fixture, "required-isolates-concrete-inline-fragments.graphql", "generate_typescript/fixtures/required-isolates-concrete-inline-fragments.expected", input, expected); +} + +#[test] +fn required_raw_response_type() { + let input = include_str!("generate_typescript/fixtures/required-raw-response-type.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-raw-response-type.expected"); + test_fixture(transform_fixture, "required-raw-response-type.graphql", "generate_typescript/fixtures/required-raw-response-type.expected", input, expected); +} + +#[test] +fn required_throw_doesnt_bubbles_to_fragment() { + let input = include_str!("generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.expected"); + test_fixture(transform_fixture, "required-throw-doesnt-bubbles-to-fragment.graphql", "generate_typescript/fixtures/required-throw-doesnt-bubbles-to-fragment.expected", input, expected); +} + +#[test] +fn required_throw_doesnt_bubbles_to_query() { + let input = include_str!("generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.expected"); + test_fixture(transform_fixture, "required-throw-doesnt-bubbles-to-query.graphql", "generate_typescript/fixtures/required-throw-doesnt-bubbles-to-query.expected", input, expected); +} + +#[test] +fn required_throws_nested() { + let input = include_str!("generate_typescript/fixtures/required-throws-nested.graphql"); + let expected = include_str!("generate_typescript/fixtures/required-throws-nested.expected"); + test_fixture(transform_fixture, "required-throws-nested.graphql", "generate_typescript/fixtures/required-throws-nested.expected", input, expected); +} + +#[test] +fn roots() { + let input = include_str!("generate_typescript/fixtures/roots.graphql"); + let expected = include_str!("generate_typescript/fixtures/roots.expected"); + test_fixture(transform_fixture, "roots.graphql", "generate_typescript/fixtures/roots.expected", input, expected); +} + +#[test] +fn scalar_field() { + let input = include_str!("generate_typescript/fixtures/scalar-field.graphql"); + let expected = include_str!("generate_typescript/fixtures/scalar-field.expected"); + test_fixture(transform_fixture, "scalar-field.graphql", "generate_typescript/fixtures/scalar-field.expected", input, expected); +} + +#[test] +fn simple() { + let input = include_str!("generate_typescript/fixtures/simple.graphql"); + let expected = include_str!("generate_typescript/fixtures/simple.expected"); + test_fixture(transform_fixture, "simple.graphql", "generate_typescript/fixtures/simple.expected", input, expected); +} + +#[test] +fn typename_inside_with_overlapping_fields() { + let input = include_str!("generate_typescript/fixtures/typename-inside-with-overlapping-fields.graphql"); + let expected = include_str!("generate_typescript/fixtures/typename-inside-with-overlapping-fields.expected"); + test_fixture(transform_fixture, "typename-inside-with-overlapping-fields.graphql", "generate_typescript/fixtures/typename-inside-with-overlapping-fields.expected", input, expected); +} + +#[test] +fn typename_on_union() { + let input = include_str!("generate_typescript/fixtures/typename-on-union.graphql"); + let expected = include_str!("generate_typescript/fixtures/typename-on-union.expected"); + test_fixture(transform_fixture, "typename-on-union.graphql", "generate_typescript/fixtures/typename-on-union.expected", input, expected); +} + +#[test] +fn unmasked_fragment_spreads() { + let input = include_str!("generate_typescript/fixtures/unmasked-fragment-spreads.graphql"); + let expected = include_str!("generate_typescript/fixtures/unmasked-fragment-spreads.expected"); + test_fixture(transform_fixture, "unmasked-fragment-spreads.graphql", "generate_typescript/fixtures/unmasked-fragment-spreads.expected", input, expected); +}