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); +}