diff --git a/compiler/crates/relay-schema-generation/src/lib.rs b/compiler/crates/relay-schema-generation/src/lib.rs index 3cd929af25a78..aa102ab75ed6a 100644 --- a/compiler/crates/relay-schema-generation/src/lib.rs +++ b/compiler/crates/relay-schema-generation/src/lib.rs @@ -124,8 +124,10 @@ pub struct RelayResolverExtractor { )] pub enum JSImportType { Default, - Namespace, Named(StringKey), + // Note that namespace imports cannot be used for resolver types. Anything namespace + // imported should be a "Named" import instead + Namespace, } impl fmt::Display for JSImportType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -286,6 +288,7 @@ impl RelayResolverExtractor { source_hash, source_module_path, description, + false, )? } } @@ -301,61 +304,65 @@ impl RelayResolverExtractor { self.unresolved_field_definitions .into_iter() .map(|(key, field)| { - if let Some(DocblockIr::Type(ResolverTypeDocblockIr::StrongObjectResolver( - object, - ))) = self.type_definitions.get(&key) - { - let arguments = if let Some(args) = field.arguments { - Some(flow_type_to_field_arguments(self.current_location, &args)?) - } else { - None - }; - let description_node = field.description.map(|desc| StringNode { - token: Token { - span: desc.location.span(), - kind: TokenKind::Empty, - }, - value: desc.item, - }); - let field_definition = FieldDefinition { - name: string_key_to_identifier(field.field_name), - type_: return_type_to_type_annotation( - self.current_location, - &field.return_type, - )?, - arguments, - directives: vec![], - description: description_node, - hack_source: None, - span: field.field_name.location.span(), - }; - let live = field - .is_live - .map(|loc| UnpopulatedIrField { key_location: loc }); - self.resolved_field_definitions.push(TerseRelayResolverIr { - field: field_definition, - type_: object + let entity = match self.type_definitions.get(&key) { + Some(DocblockIr::Type(ResolverTypeDocblockIr::StrongObjectResolver( + object, + ))) => Ok(object + .type_name + .name_with_location(SourceLocationKey::Generated)), + Some(DocblockIr::Type(ResolverTypeDocblockIr::WeakObjectType(object))) => { + Ok(object .type_name - .name_with_location(SourceLocationKey::Generated), - root_fragment: None, - location: field.field_name.location, - deprecated: None, - live, - fragment_arguments: None, - source_hash: field.source_hash, - semantic_non_null: None, - }); - Ok(()) - } else { - Err(vec![Diagnostic::error( + .name_with_location(SourceLocationKey::Generated)) + } + _ => Err(vec![Diagnostic::error( SchemaGenerationError::ModuleNotFound { entity_name: field.entity_name.item, export_type: key.import_type, module_name: key.module_name, }, field.entity_name.location, - )]) - } + )]), + }?; + let arguments = if let Some(args) = field.arguments { + Some(flow_type_to_field_arguments(self.current_location, &args)?) + } else { + None + }; + let description_node = field.description.map(|desc| StringNode { + token: Token { + span: desc.location.span(), + kind: TokenKind::Empty, + }, + value: desc.item, + }); + let field_definition = FieldDefinition { + name: string_key_to_identifier(field.field_name), + type_: return_type_to_type_annotation( + self.current_location, + &field.return_type, + )?, + arguments, + directives: vec![], + description: description_node, + hack_source: None, + span: field.field_name.location.span(), + }; + let live = field + .is_live + .map(|loc| UnpopulatedIrField { key_location: loc }); + self.resolved_field_definitions.push(TerseRelayResolverIr { + field: field_definition, + type_: entity, + root_fragment: None, + location: field.field_name.location, + deprecated: None, + live, + fragment_arguments: None, + source_hash: field.source_hash, + semantic_non_null: None, + }); + Ok(()) }), )?; Ok(( @@ -566,6 +573,7 @@ impl RelayResolverExtractor { source_hash: ResolverSourceHash, source_module_path: &str, description: Option>, + should_generate_fields: bool, ) -> DiagnosticsResult<()> { let weak_object = WeakObjectIr { type_name: string_key_to_identifier(name), @@ -586,55 +594,62 @@ impl RelayResolverExtractor { module_name: haste_module_name.intern(), import_type: JSImportType::Named(name.item), }; - // Add fields - if let FlowTypeAnnotation::ObjectTypeAnnotation(object_node) = type_alias { - let field_map = self.get_object_fields(&object_node)?; - if !field_map.is_empty() { - try_all(field_map.into_iter().map(|(field_name, field_type)| { - let field_definition = FieldDefinition { - name: string_key_to_identifier(field_name), - type_: return_type_to_type_annotation(self.current_location, field_type)?, - arguments: None, - directives: vec![], - description: None, - hack_source: None, - span: field_name.location.span(), - }; - self.resolved_field_definitions.push(TerseRelayResolverIr { - field: field_definition, - type_: weak_object - .type_name - .name_with_location(SourceLocationKey::Generated), - root_fragment: None, - location: field_name.location, - deprecated: None, - live: None, - fragment_arguments: None, - source_hash, - semantic_non_null: None, - }); - Ok(()) - }))?; + // TODO: this generates the IR but not the runtime JS + if should_generate_fields { + if let FlowTypeAnnotation::ObjectTypeAnnotation(object_node) = type_alias { + let field_map = self.get_object_fields(&object_node)?; + if !field_map.is_empty() { + try_all(field_map.into_iter().map(|(field_name, field_type)| { + let field_definition = FieldDefinition { + name: string_key_to_identifier(field_name), + type_: return_type_to_type_annotation( + self.current_location, + field_type, + )?, + arguments: None, + directives: vec![], + description: None, + hack_source: None, + span: field_name.location.span(), + }; - self.type_definitions.insert( - key.clone(), - DocblockIr::Type(ResolverTypeDocblockIr::WeakObjectType(weak_object)), - ); - Ok(()) + self.resolved_field_definitions.push(TerseRelayResolverIr { + field: field_definition, + type_: weak_object + .type_name + .name_with_location(SourceLocationKey::Generated), + root_fragment: None, + location: field_name.location, + deprecated: None, + live: None, + fragment_arguments: None, + source_hash, + semantic_non_null: None, + }); + Ok(()) + }))?; + } else { + let location = self.to_location(object_node.as_ref()); + return Err(vec![Diagnostic::error( + SchemaGenerationError::ExpectedWeakObjectToHaveFields, + location, + )]); + } } else { - let location = self.to_location(object_node.as_ref()); - Err(vec![Diagnostic::error( - SchemaGenerationError::ExpectedWeakObjectToHaveFields, - location, - )]) + return Err(vec![Diagnostic::error( + SchemaGenerationError::ExpectedTypeAliasToBeObject, + self.to_location(&type_alias), + )]); } - } else { - Err(vec![Diagnostic::error( - SchemaGenerationError::ExpectedTypeAliasToBeObject, - self.to_location(&type_alias), - )]) } + + // Add weak object + self.type_definitions.insert( + key.clone(), + DocblockIr::Type(ResolverTypeDocblockIr::WeakObjectType(weak_object)), + ); + Ok(()) } pub fn extract_function(&self, node: &Function) -> DiagnosticsResult { @@ -791,19 +806,13 @@ impl RelayResolverExtractor { } _ => Err(vec![Diagnostic::error( SchemaGenerationError::ExpectedFunctionOrTypeAlias, - Location::new( - self.current_location, - Span::new(range.start, range.end.into()), - ), + Location::new(self.current_location, Span::new(range.start, range.end)), )]), } } else { Err(vec![Diagnostic::error( SchemaGenerationError::ExpectedNamedExport, - Location::new( - self.current_location, - Span::new(range.start, range.end.into()), - ), + Location::new(self.current_location, Span::new(range.start, range.end)), )]) } } @@ -856,7 +865,7 @@ impl SchemaExtractor for RelayResolverExtractor { fn to_location(source_location: SourceLocationKey, node: &T) -> Location { let range = node.range(); - Location::new(source_location, Span::new(range.start, range.end.into())) + Location::new(source_location, Span::new(range.start, range.end)) } fn string_key_to_identifier(name: WithLocation) -> Identifier { diff --git a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/return-optional-weak-object.expected b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/return-optional-weak-object.expected index 88285bee6f960..784dbd984785f 100644 --- a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/return-optional-weak-object.expected +++ b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/return-optional-weak-object.expected @@ -29,126 +29,6 @@ export function fullName(cat: CatFlowType): ?FullName { return {first: "Mr", last: "Cat"}; } ==================================== OUTPUT =================================== -Field( - TerseRelayResolver( - TerseRelayResolverIr { - field: FieldDefinition { - name: Identifier { - span: 369:374, - token: Token { - span: 369:374, - kind: Identifier, - }, - value: "first", - }, - type_: NonNull( - NonNullTypeAnnotation { - span: 376:382, - type_: Named( - NamedTypeAnnotation { - name: Identifier { - span: 376:382, - token: Token { - span: 376:382, - kind: Identifier, - }, - value: "String", - }, - }, - ), - exclamation: Token { - span: 0:0, - kind: Empty, - }, - }, - ), - arguments: None, - directives: [], - description: None, - hack_source: None, - span: 369:374, - }, - type_: WithLocation { - location: :357:365, - item: "FullName", - }, - root_fragment: None, - deprecated: None, - semantic_non_null: None, - live: None, - location: module.js:369:374, - fragment_arguments: None, - source_hash: ResolverSourceHash( - "1b4f4f49bebe8b72971595382a3f9b57", - ), - }, - ), -) -extend type FullName { - first: String! @relay_resolver(fragment_name: "FullName____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", has_output_type: true, import_name: "first", import_path: "module.js") @resolver_source_hash(value: "1b4f4f49bebe8b72971595382a3f9b57") -} - - -Field( - TerseRelayResolver( - TerseRelayResolverIr { - field: FieldDefinition { - name: Identifier { - span: 384:388, - token: Token { - span: 384:388, - kind: Identifier, - }, - value: "last", - }, - type_: NonNull( - NonNullTypeAnnotation { - span: 390:396, - type_: Named( - NamedTypeAnnotation { - name: Identifier { - span: 390:396, - token: Token { - span: 390:396, - kind: Identifier, - }, - value: "String", - }, - }, - ), - exclamation: Token { - span: 0:0, - kind: Empty, - }, - }, - ), - arguments: None, - directives: [], - description: None, - hack_source: None, - span: 384:388, - }, - type_: WithLocation { - location: :357:365, - item: "FullName", - }, - root_fragment: None, - deprecated: None, - semantic_non_null: None, - live: None, - location: module.js:384:388, - fragment_arguments: None, - source_hash: ResolverSourceHash( - "1b4f4f49bebe8b72971595382a3f9b57", - ), - }, - ), -) -extend type FullName { - last: String! @relay_resolver(fragment_name: "FullName____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", has_output_type: true, import_name: "last", import_path: "module.js") @resolver_source_hash(value: "1b4f4f49bebe8b72971595382a3f9b57") -} - - Field( TerseRelayResolver( TerseRelayResolverIr { diff --git a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object-no-fields.expected b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object-no-fields.expected deleted file mode 100644 index 5cfc6c641d72b..0000000000000 --- a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object-no-fields.expected +++ /dev/null @@ -1,23 +0,0 @@ -==================================== INPUT ==================================== -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -//- module.js - -/** - * @RelayResolver - */ -export type FullName = {}; -==================================== OUTPUT =================================== - - -✖︎ Expected object definition to include fields - - module.js:12:24 - 11 │ */ - 12 │ export type FullName = {}; - │ ^^ diff --git a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object-no-fields.input b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object-no-fields.input deleted file mode 100644 index 980089e967982..0000000000000 --- a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object-no-fields.input +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -//- module.js - -/** - * @RelayResolver - */ -export type FullName = {}; diff --git a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object.expected b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object.expected index dd2c8159e9ed6..991f4269706cb 100644 --- a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object.expected +++ b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object.expected @@ -22,6 +22,21 @@ export function Cat(id: DataID): CatFlowType { */ export type FullName = {first: string, last: string}; + +/** + * @RelayResolver + */ +export function first(name: FullName): string { + return name.first; +} + +/** + * @RelayResolver + */ +export function last(name: FullName): string { + return name.last; +} + /** * @RelayResolver */ @@ -34,22 +49,22 @@ Field( TerseRelayResolverIr { field: FieldDefinition { name: Identifier { - span: 369:374, + span: 443:448, token: Token { - span: 369:374, + span: 443:448, kind: Identifier, }, value: "first", }, type_: NonNull( NonNullTypeAnnotation { - span: 376:382, + span: 466:472, type_: Named( NamedTypeAnnotation { name: Identifier { - span: 376:382, + span: 466:472, token: Token { - span: 376:382, + span: 466:472, kind: Identifier, }, value: "String", @@ -66,7 +81,7 @@ Field( directives: [], description: None, hack_source: None, - span: 369:374, + span: 443:448, }, type_: WithLocation { location: :357:365, @@ -76,16 +91,16 @@ Field( deprecated: None, semantic_non_null: None, live: None, - location: module.js:369:374, + location: module.js:443:448, fragment_arguments: None, source_hash: ResolverSourceHash( - "47aa9b99e97b427fbcade6680ebf096d", + "cbee4c20757f831a7a766d841ecacc1e", ), }, ), ) extend type FullName { - first: String! @relay_resolver(fragment_name: "FullName____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", has_output_type: true, import_name: "first", import_path: "module.js") @resolver_source_hash(value: "47aa9b99e97b427fbcade6680ebf096d") + first: String! @relay_resolver(fragment_name: "FullName____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", has_output_type: true, import_name: "first", import_path: "module.js") @resolver_source_hash(value: "cbee4c20757f831a7a766d841ecacc1e") } @@ -94,22 +109,22 @@ Field( TerseRelayResolverIr { field: FieldDefinition { name: Identifier { - span: 384:388, + span: 541:545, token: Token { - span: 384:388, + span: 541:545, kind: Identifier, }, value: "last", }, type_: NonNull( NonNullTypeAnnotation { - span: 390:396, + span: 563:569, type_: Named( NamedTypeAnnotation { name: Identifier { - span: 390:396, + span: 563:569, token: Token { - span: 390:396, + span: 563:569, kind: Identifier, }, value: "String", @@ -126,7 +141,7 @@ Field( directives: [], description: None, hack_source: None, - span: 384:388, + span: 541:545, }, type_: WithLocation { location: :357:365, @@ -136,16 +151,16 @@ Field( deprecated: None, semantic_non_null: None, live: None, - location: module.js:384:388, + location: module.js:541:545, fragment_arguments: None, source_hash: ResolverSourceHash( - "47aa9b99e97b427fbcade6680ebf096d", + "cbee4c20757f831a7a766d841ecacc1e", ), }, ), ) extend type FullName { - last: String! @relay_resolver(fragment_name: "FullName____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", has_output_type: true, import_name: "last", import_path: "module.js") @resolver_source_hash(value: "47aa9b99e97b427fbcade6680ebf096d") + last: String! @relay_resolver(fragment_name: "FullName____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", has_output_type: true, import_name: "last", import_path: "module.js") @resolver_source_hash(value: "cbee4c20757f831a7a766d841ecacc1e") } @@ -154,22 +169,22 @@ Field( TerseRelayResolverIr { field: FieldDefinition { name: Identifier { - span: 442:450, + span: 637:645, token: Token { - span: 442:450, + span: 637:645, kind: Identifier, }, value: "fullName", }, type_: NonNull( NonNullTypeAnnotation { - span: 470:478, + span: 665:673, type_: Named( NamedTypeAnnotation { name: Identifier { - span: 470:478, + span: 665:673, token: Token { - span: 470:478, + span: 665:673, kind: Identifier, }, value: "FullName", @@ -186,7 +201,7 @@ Field( directives: [], description: None, hack_source: None, - span: 442:450, + span: 637:645, }, type_: WithLocation { location: :272:275, @@ -196,16 +211,16 @@ Field( deprecated: None, semantic_non_null: None, live: None, - location: module.js:442:450, + location: module.js:637:645, fragment_arguments: None, source_hash: ResolverSourceHash( - "47aa9b99e97b427fbcade6680ebf096d", + "cbee4c20757f831a7a766d841ecacc1e", ), }, ), ) extend type Cat { - fullName: FullName! @relay_resolver(fragment_name: "Cat____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", has_output_type: true, import_name: "fullName", import_path: "module.js") @resolver_source_hash(value: "47aa9b99e97b427fbcade6680ebf096d") + fullName: FullName! @relay_resolver(fragment_name: "Cat____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", has_output_type: true, import_name: "fullName", import_path: "module.js") @resolver_source_hash(value: "cbee4c20757f831a7a766d841ecacc1e") } @@ -234,14 +249,14 @@ Type( location: module.js:272:275, implements_interfaces: [], source_hash: ResolverSourceHash( - "47aa9b99e97b427fbcade6680ebf096d", + "cbee4c20757f831a7a766d841ecacc1e", ), }, ), ) type Cat @__RelayResolverModel { id: ID! - __relay_model_instance: RelayResolverValue! @relay_resolver(generated_fragment: true, fragment_name: "Cat__id", import_name: "Cat", import_path: "module.js", inject_fragment_data: "id") @resolver_source_hash(value: "47aa9b99e97b427fbcade6680ebf096d") @unselectable(reason: "This field is intended only for Relay's internal use") + __relay_model_instance: RelayResolverValue! @relay_resolver(generated_fragment: true, fragment_name: "Cat__id", import_name: "Cat", import_path: "module.js", inject_fragment_data: "id") @resolver_source_hash(value: "cbee4c20757f831a7a766d841ecacc1e") @unselectable(reason: "This field is intended only for Relay's internal use") } @@ -263,7 +278,7 @@ Type( location: module.js:357:365, implements_interfaces: [], source_hash: ResolverSourceHash( - "47aa9b99e97b427fbcade6680ebf096d", + "cbee4c20757f831a7a766d841ecacc1e", ), }, ), @@ -272,5 +287,5 @@ scalar FullNameModel @__RelayCustomScalar(path: "module.js", export_name: "FullN type FullName @__RelayResolverModel @RelayOutputType @__RelayWeakObject { - __relay_model_instance: FullNameModel! @resolver_source_hash(value: "47aa9b99e97b427fbcade6680ebf096d") @unselectable(reason: "This field is intended only for Relay's internal use") + __relay_model_instance: FullNameModel! @resolver_source_hash(value: "cbee4c20757f831a7a766d841ecacc1e") @unselectable(reason: "This field is intended only for Relay's internal use") } diff --git a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object.input b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object.input index f1815167219cb..bf1cf1e17ee97 100644 --- a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object.input +++ b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-object.input @@ -21,6 +21,21 @@ export function Cat(id: DataID): CatFlowType { */ export type FullName = {first: string, last: string}; + +/** + * @RelayResolver + */ +export function first(name: FullName): string { + return name.first; +} + +/** + * @RelayResolver + */ +export function last(name: FullName): string { + return name.last; +} + /** * @RelayResolver */ diff --git a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-type-error.expected b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-type-error.expected deleted file mode 100644 index c0ac4e2c72dfe..0000000000000 --- a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-type-error.expected +++ /dev/null @@ -1,23 +0,0 @@ -==================================== INPUT ==================================== -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -//- module.js - -/** - * @RelayResolver - */ -export type FullName = string; -==================================== OUTPUT =================================== - - -✖︎ Type aliases in Relay resolvers are expected to be object types - - module.js:12:24 - 11 │ */ - 12 │ export type FullName = string; - │ ^^^^^^ diff --git a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-type-error.input b/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-type-error.input deleted file mode 100644 index 332a34ff571de..0000000000000 --- a/compiler/crates/relay-schema-generation/tests/docblock/fixtures/weak-type-error.input +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -//- module.js - -/** - * @RelayResolver - */ -export type FullName = string; diff --git a/compiler/crates/relay-schema-generation/tests/docblock_test.rs b/compiler/crates/relay-schema-generation/tests/docblock_test.rs index 4b4167b3b9d62..9016f6cf5f601 100644 --- a/compiler/crates/relay-schema-generation/tests/docblock_test.rs +++ b/compiler/crates/relay-schema-generation/tests/docblock_test.rs @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<9accc2fd2f37ff6320681c4e9f8918bb>> + * @generated SignedSource<<7f0997fb1dbf297de82340a1c311a2b0>> */ mod docblock; @@ -151,17 +151,3 @@ async fn weak_object() { let expected = include_str!("docblock/fixtures/weak-object.expected"); test_fixture(transform_fixture, file!(), "weak-object.input", "docblock/fixtures/weak-object.expected", input, expected).await; } - -#[tokio::test] -async fn weak_object_no_fields() { - let input = include_str!("docblock/fixtures/weak-object-no-fields.input"); - let expected = include_str!("docblock/fixtures/weak-object-no-fields.expected"); - test_fixture(transform_fixture, file!(), "weak-object-no-fields.input", "docblock/fixtures/weak-object-no-fields.expected", input, expected).await; -} - -#[tokio::test] -async fn weak_type_error() { - let input = include_str!("docblock/fixtures/weak-type-error.input"); - let expected = include_str!("docblock/fixtures/weak-type-error.expected"); - test_fixture(transform_fixture, file!(), "weak-type-error.input", "docblock/fixtures/weak-type-error.expected", input, expected).await; -}