Skip to content

Commit

Permalink
Support defining @weak types in docblocks
Browse files Browse the repository at this point in the history
Reviewed By: alunyov

Differential Revision: D40321983

fbshipit-source-id: 55e6df96947b2f8b5aa20ca5f85adec10e8c7299
  • Loading branch information
captbaritone authored and facebook-github-bot committed Oct 13, 2022
1 parent 317c21c commit 2bd9788
Show file tree
Hide file tree
Showing 16 changed files with 407 additions and 33 deletions.
1 change: 1 addition & 0 deletions compiler/crates/relay-docblock/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ graphql-ir = { path = "../graphql-ir" }
graphql-syntax = { path = "../graphql-syntax" }
intern = { path = "../intern" }
lazy_static = "1.4"
relay-schema = { path = "../relay-schema" }
schema = { path = "../schema" }
thiserror = "1.0.36"

Expand Down
163 changes: 149 additions & 14 deletions compiler/crates/relay-docblock/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use graphql_syntax::NamedTypeAnnotation;
use graphql_syntax::NonNullTypeAnnotation;
use graphql_syntax::ObjectTypeDefinition;
use graphql_syntax::ObjectTypeExtension;
use graphql_syntax::ScalarTypeDefinition;
use graphql_syntax::SchemaDocument;
use graphql_syntax::StringNode;
use graphql_syntax::Token;
Expand All @@ -41,6 +42,9 @@ use graphql_syntax::TypeSystemDefinition;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use lazy_static::lazy_static;
use relay_schema::CUSTOM_SCALAR_DIRECTIVE_NAME;
use relay_schema::EXPORT_NAME_CUSTOM_SCALAR_ARGUMENT_NAME;
use relay_schema::PATH_CUSTOM_SCALAR_ARGUMENT_NAME;
use schema::suggestion_list::GraphQLSuggestions;
use schema::InterfaceID;
use schema::ObjectID;
Expand Down Expand Up @@ -69,20 +73,25 @@ lazy_static! {
ArgumentName("has_output_type".intern());
pub(crate) static ref ID_FIELD_NAME: StringKey = "id".intern();
static ref RESOLVER_MODEL_INSTANCE_FIELD_NAME: StringKey = "__relay_model_instance".intern();
static ref MODEL_CUSTOM_SCALAR_TYPE_PREFIX: StringKey = "Model".intern();
}

#[derive(Debug, PartialEq)]
pub enum DocblockIr {
RelayResolver(RelayResolverIr),
StrongObjectResolver(StrongObjectIr),
WeakObjectType(WeakObjectIr),
}

impl DocblockIr {
pub fn to_sdl_string(&self, schema: &SDLSchema) -> DiagnosticsResult<String> {
match self {
DocblockIr::RelayResolver(relay_resolver) => relay_resolver.to_sdl_string(schema),
DocblockIr::StrongObjectResolver(strong_object) => strong_object.to_sdl_string(schema),
}
Ok(self
.to_graphql_schema_ast(schema)?
.definitions
.iter()
.map(|definition| format!("{}", definition))
.collect::<Vec<String>>()
.join("\n\n"))
}
pub fn to_graphql_schema_ast(&self, schema: &SDLSchema) -> DiagnosticsResult<SchemaDocument> {
match self {
Expand All @@ -92,6 +101,7 @@ impl DocblockIr {
DocblockIr::StrongObjectResolver(strong_object) => {
strong_object.to_graphql_schema_ast(schema)
}
DocblockIr::WeakObjectType(weak_object) => weak_object.to_graphql_schema_ast(schema),
}
}
}
Expand Down Expand Up @@ -151,16 +161,6 @@ trait ResolverIr {
fn live(&self) -> Option<IrField>;
fn named_import(&self) -> Option<StringKey>;

fn to_sdl_string(&self, schema: &SDLSchema) -> DiagnosticsResult<String> {
Ok(self
.to_graphql_schema_ast(schema)?
.definitions
.iter()
.map(|definition| format!("{}", definition))
.collect::<Vec<String>>()
.join("\n\n"))
}

fn to_graphql_schema_ast(&self, schema: &SDLSchema) -> DiagnosticsResult<SchemaDocument> {
Ok(SchemaDocument {
location: self.location(),
Expand Down Expand Up @@ -605,6 +605,141 @@ impl ResolverIr for StrongObjectIr {
}
}

/// Relay Resolver docblock representing a "model" type for a weak object
#[derive(Debug, PartialEq)]
pub struct WeakObjectIr {
pub type_name: PopulatedIrField,
pub description: Option<WithLocation<StringKey>>,
pub deprecated: Option<IrField>,
pub location: Location,
}

impl WeakObjectIr {
// Generate the named GraphQL type (with an __relay_model_instance field).
fn type_definition(&self) -> TypeSystemDefinition {
let span = self.type_name.value.location.span();

let mut directives = vec![ConstantDirective {
span: span.clone(),
at: dummy_token(&span),
name: string_key_as_identifier(RELAY_RESOLVER_MODEL_DIRECTIVE_NAME.0),
arguments: None,
}];
if let Some(deprecated) = self.deprecated {
directives.push(ConstantDirective {
span: span.clone(),
at: dummy_token(span),
name: string_key_as_identifier(DEPRECATED_RESOLVER_DIRECTIVE_NAME.0),
arguments: deprecated.value.map(|value| {
List::generated(vec![string_argument(
DEPRECATED_REASON_ARGUMENT_NAME.0,
value,
)])
}),
})
}
TypeSystemDefinition::ObjectTypeDefinition(ObjectTypeDefinition {
name: as_identifier(self.type_name.value),
interfaces: vec![],
directives,
fields: Some(List::generated(vec![FieldDefinition {
name: string_key_as_identifier(*RESOLVER_MODEL_INSTANCE_FIELD_NAME),
type_: TypeAnnotation::Named(NamedTypeAnnotation {
name: string_key_as_identifier(self.model_type_name()),
}),
arguments: None,
directives: vec![],
description: self.description.map(as_string_node),
}])),
})
}

// Genete a custom sclar definition based on the exported type.
fn instance_scalar_type_definition(&self) -> TypeSystemDefinition {
let span = self.type_name.value.location.span();
TypeSystemDefinition::ScalarTypeDefinition(ScalarTypeDefinition {
name: Identifier {
span: *span,
token: dummy_token(span),
value: self.model_type_name(),
},
directives: vec![ConstantDirective {
span: *span,
at: dummy_token(span),
name: as_identifier(WithLocation::generated(*CUSTOM_SCALAR_DIRECTIVE_NAME)),
arguments: Some(List::generated(vec![
ConstantArgument {
span: *span,
name: as_identifier(WithLocation::generated(
*PATH_CUSTOM_SCALAR_ARGUMENT_NAME,
)),
colon: dummy_token(span),
value: ConstantValue::String(StringNode {
token: dummy_token(span),
value: self.location.source_location().path().intern(),
}),
},
ConstantArgument {
span: *span,
name: as_identifier(WithLocation::generated(
*EXPORT_NAME_CUSTOM_SCALAR_ARGUMENT_NAME,
)),
colon: dummy_token(span),
value: ConstantValue::String(StringNode {
token: dummy_token(span),
value: self.type_name.value.item,
}),
},
])),
}],
})
}

// Derive a typename for the custom scalar that will be used as this type's
// `__relay_model_instance` model.
fn model_type_name(&self) -> StringKey {
// TODO: Ensure this type does not already exist?
format!(
"{}{}",
self.type_name.value.item, *MODEL_CUSTOM_SCALAR_TYPE_PREFIX
)
.intern()
}
}

impl ResolverIr for WeakObjectIr {
fn definitions(&self, _schema: &SDLSchema) -> DiagnosticsResult<Vec<TypeSystemDefinition>> {
Ok(vec![
self.instance_scalar_type_definition(),
self.type_definition(),
])
}

fn location(&self) -> Location {
self.location
}

fn root_fragment(&self) -> Option<WithLocation<FragmentDefinitionName>> {
None
}

fn output_type(&self) -> Option<&OutputType> {
None
}

fn deprecated(&self) -> Option<IrField> {
self.deprecated
}

fn live(&self) -> Option<IrField> {
None
}

fn named_import(&self) -> Option<StringKey> {
None
}
}

fn string_argument(name: StringKey, value: WithLocation<StringKey>) -> ConstantArgument {
let span = value.location.span();
ConstantArgument {
Expand Down
37 changes: 29 additions & 8 deletions compiler/crates/relay-docblock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use ir::OutputType;
use ir::PopulatedIrField;
pub use ir::RelayResolverIr;
use ir::StrongObjectIr;
use ir::WeakObjectIr;
use lazy_static::lazy_static;

use crate::errors::ErrorMessages;
Expand All @@ -60,6 +61,7 @@ lazy_static! {
static ref LIVE_FIELD: StringKey = "live".intern();
static ref ROOT_FRAGMENT_FIELD: StringKey = "rootFragment".intern();
static ref OUTPUT_TYPE_FIELD: StringKey = "outputType".intern();
static ref WEAK_FIELD: StringKey = "weak".intern();
static ref EMPTY_STRING: StringKey = "".intern();
static ref ARGUMENT_DEFINITIONS: DirectiveName = DirectiveName("argumentDefinitions".intern());
static ref ARGUMENT_TYPE: StringKey = "type".intern();
Expand Down Expand Up @@ -107,6 +109,7 @@ impl RelayResolverParser {
*DEPRECATED_FIELD,
*LIVE_FIELD,
*OUTPUT_TYPE_FIELD,
*WEAK_FIELD,
],
options,
}
Expand Down Expand Up @@ -161,14 +164,18 @@ impl RelayResolverParser {
return Err(());
}

self.parse_strong_object(
ast.location,
PopulatedIrField {
key_location: relay_resolver.key_location,
value: type_name,
},
)
.map(DocblockIr::StrongObjectResolver)
let type_ = PopulatedIrField {
key_location: relay_resolver.key_location,
value: type_name,
};

if self.fields.get(&WEAK_FIELD).is_some() {
self.parse_weak_type(ast.location, type_)
.map(DocblockIr::WeakObjectType)
} else {
self.parse_strong_object(ast.location, type_)
.map(DocblockIr::StrongObjectResolver)
}
} else {
self.parse_relay_resolver(ast.location, definitions_in_file)
.map(DocblockIr::RelayResolver)
Expand Down Expand Up @@ -606,4 +613,18 @@ impl RelayResolverParser {
named_import: self.options.use_named_imports.then_some(type_.value.item),
})
}

fn parse_weak_type(
&self,
ast_location: Location,
type_: PopulatedIrField,
) -> ParseResult<WeakObjectIr> {
// TODO: Validate that no incompatible docblock fields are used.
Ok(WeakObjectIr {
type_name: type_,
description: self.description,
deprecated: self.fields.get(&DEPRECATED_FIELD).copied(),
location: ast_location,
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
==================================== 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.
*/

/**
* @RelayResolver MyClientType
*
* @weak
* @deprecated Don't use this any more
*
* Check out this awesome client type!
*/

export type MyClientType = {
name: string
}
==================================== OUTPUT ===================================
scalar MyClientTypeModel @__RelayCustomScalar(path: "/path/to/test/fixture/weak-type.js", export_name: "MyClientType")


type MyClientType @__RelayResolverModel @deprecated(reason: "Don't use this any more") {
__relay_model_instance: MyClientTypeModel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* 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.
*/

/**
* @RelayResolver MyClientType
*
* @weak
* @deprecated Don't use this any more
*
* Check out this awesome client type!
*/

export type MyClientType = {
name: string
}
9 changes: 8 additions & 1 deletion compiler/crates/relay-docblock/tests/to_schema_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<<83c48009350539bc419375d2dd6ef433>>
* @generated SignedSource<<ece51c14bd366613961436d5bbea1465>>
*/

mod to_schema;
Expand Down Expand Up @@ -151,3 +151,10 @@ fn relay_resolver_with_output_type() {
let expected = include_str!("to_schema/fixtures/relay-resolver-with-output-type.expected");
test_fixture(transform_fixture, "relay-resolver-with-output-type.js", "to_schema/fixtures/relay-resolver-with-output-type.expected", input, expected);
}

#[test]
fn weak_type() {
let input = include_str!("to_schema/fixtures/weak-type.js");
let expected = include_str!("to_schema/fixtures/weak-type.expected");
test_fixture(transform_fixture, "weak-type.js", "to_schema/fixtures/weak-type.expected", input, expected);
}
Loading

0 comments on commit 2bd9788

Please sign in to comment.