Skip to content

Commit 1c02417

Browse files
evanyeungfacebook-github-bot
authored andcommitted
Add logic to find property lookup resolver docblocks
Summary: This diff builds on the previous one to add a new visitor that finds docblocks for property lookup resolvers. The visitor just looks at the location of the `gqlField` comment nodes and compares an object key to see if the object key should generate a property lookup resolver. For any object key that matches, a new `UnresolvedFieldDefinition` is added. Reviewed By: captbaritone Differential Revision: D67370142 fbshipit-source-id: b3abb2fa3b9ea6da77edcd0202c8887597303b68
1 parent 24951b9 commit 1c02417

File tree

6 files changed

+354
-4
lines changed

6 files changed

+354
-4
lines changed

compiler/crates/relay-schema-generation/src/errors.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ pub enum SchemaGenerationError {
9898
module_name: StringKey,
9999
import_type: JSImportType,
100100
},
101+
#[error(
102+
"The key of a property lookup resolver defined with @gqlField must be an identifier (not a string or computed value)."
103+
)]
104+
ExpectedPropertyLookupToBeIdentifer,
101105
#[error(
102106
"This field is a property lookup but has an import for a GraphQL fragment used in the resolver. This is not allowed."
103107
)]
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#![deny(warnings)]
9+
#![deny(clippy::all)]
10+
11+
use ::intern::string_key::Intern;
12+
use ::intern::string_key::StringKey;
13+
use common::Diagnostic;
14+
use common::Location;
15+
use common::SourceLocationKey;
16+
use common::Span;
17+
use common::WithLocation;
18+
use docblock_shared::ResolverSourceHash;
19+
use fnv::FnvHashSet;
20+
use hermes_estree::ObjectTypePropertyKey;
21+
use hermes_estree::Range;
22+
use hermes_estree::SourceRange;
23+
use hermes_estree::Visitor;
24+
25+
use crate::FieldDefinitionInfo;
26+
use crate::SchemaGenerationError;
27+
use crate::UnresolvedFieldDefinition;
28+
29+
fn source_range_to_span(source_range: SourceRange) -> Span {
30+
Span {
31+
start: source_range.start,
32+
end: source_range.end,
33+
}
34+
}
35+
36+
pub struct PropertyVisitor<'a> {
37+
pub location: SourceLocationKey,
38+
source_hash: ResolverSourceHash,
39+
pub errors: Vec<Diagnostic>,
40+
entity_name: WithLocation<StringKey>,
41+
resolver_node_ranges: &'a FnvHashSet<SourceRange>,
42+
pub field_definitions: Vec<UnresolvedFieldDefinition>,
43+
}
44+
45+
impl<'a> PropertyVisitor<'a> {
46+
pub fn new(
47+
source_module_path: &str,
48+
source_hash: ResolverSourceHash,
49+
entity_name: WithLocation<StringKey>,
50+
resolver_node_ranges: &'a FnvHashSet<SourceRange>,
51+
) -> Self {
52+
Self {
53+
location: SourceLocationKey::standalone(source_module_path),
54+
source_hash,
55+
errors: vec![],
56+
entity_name,
57+
resolver_node_ranges,
58+
field_definitions: vec![],
59+
}
60+
}
61+
}
62+
63+
impl Visitor<'_> for PropertyVisitor<'_> {
64+
fn visit_object_type_property(&mut self, ast: &'_ hermes_estree::ObjectTypeProperty) {
65+
if self.resolver_node_ranges.contains(&ast.range) {
66+
let field_name = match &ast.key {
67+
ObjectTypePropertyKey::Identifier(id) => WithLocation::from_span(
68+
self.location,
69+
source_range_to_span(id.range),
70+
id.name.clone().intern(),
71+
),
72+
ObjectTypePropertyKey::_Literal(lit) => {
73+
self.errors.push(Diagnostic::error(
74+
SchemaGenerationError::ExpectedPropertyLookupToBeIdentifer,
75+
Location::new(self.location, source_range_to_span(lit.range())),
76+
));
77+
return;
78+
}
79+
};
80+
let field_definition = UnresolvedFieldDefinition {
81+
field_name,
82+
entity_name: Some(self.entity_name),
83+
return_type: ast.value.clone(),
84+
source_hash: self.source_hash,
85+
description: None, // TODO
86+
deprecated: None, // TODO
87+
entity_type: None,
88+
field_info: FieldDefinitionInfo::PropertyLookupInfo,
89+
};
90+
self.field_definitions.push(field_definition);
91+
}
92+
}
93+
}

compiler/crates/relay-schema-generation/src/lib.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#![deny(clippy::all)]
1111

1212
mod errors;
13+
mod find_property_lookup_resolvers;
1314
mod find_resolver_imports;
1415

1516
use std::collections::hash_map::Entry;
@@ -41,6 +42,7 @@ use find_resolver_imports::JSImportType;
4142
use find_resolver_imports::ModuleResolution;
4243
use find_resolver_imports::ModuleResolutionKey;
4344
use fnv::FnvBuildHasher;
45+
use fnv::FnvHashSet;
4446
use graphql_ir::FragmentDefinitionName;
4547
use graphql_syntax::ConstantArgument;
4648
use graphql_syntax::ConstantDirective;
@@ -72,6 +74,7 @@ use hermes_estree::Range;
7274
use hermes_estree::SourceRange;
7375
use hermes_estree::TypeAlias;
7476
use hermes_estree::TypeAnnotationEnum;
77+
use hermes_estree::Visitor;
7578
use hermes_parser::parse;
7679
use hermes_parser::ParseResult;
7780
use hermes_parser::ParserDialect;
@@ -92,6 +95,8 @@ use relay_docblock::WeakObjectIr;
9295
use rustc_hash::FxHashMap;
9396
use schema_extractor::SchemaExtractor;
9497

98+
use crate::find_property_lookup_resolvers::PropertyVisitor;
99+
95100
pub static LIVE_FLOW_TYPE_NAME: &str = "LiveState";
96101

97102
type FnvIndexMap<K, V> = IndexMap<K, V, FnvBuildHasher>;
@@ -241,12 +246,17 @@ impl RelayResolverExtractor {
241246
let module_resolution = import_export_visitor.get_module_resolution(&ast)?;
242247

243248
let attached_comments = find_nodes_after_comments(&ast, &comments);
244-
// TODO: use the gqlField comments
245-
let (_gql_field_comments, attached_comments): (AttachedComments<'_>, AttachedComments<'_>) =
249+
let (gql_field_comments, attached_comments): (AttachedComments<'_>, AttachedComments<'_>) =
246250
attached_comments
247251
.into_iter()
248252
.partition(|(comment, _, _, _)| comment.contains("@gqlField"));
249253

254+
let gql_comments = FnvHashSet::from_iter(
255+
gql_field_comments
256+
.into_iter()
257+
.map(|(_, _, _, node_range)| node_range),
258+
);
259+
250260
let result = try_all(
251261
attached_comments
252262
.into_iter()
@@ -320,14 +330,34 @@ impl RelayResolverExtractor {
320330
type_alias,
321331
}) => {
322332
let name = resolver_value.field_value.unwrap_or(field_name);
333+
let mut prop_visitor = PropertyVisitor::new(
334+
source_module_path,
335+
source_hash,
336+
name,
337+
&gql_comments,
338+
);
339+
prop_visitor.visit_flow_type_annotation(&type_alias);
340+
if !prop_visitor.errors.is_empty() {
341+
return Err(prop_visitor.errors);
342+
}
343+
let field_definitions: Vec<(
344+
UnresolvedFieldDefinition,
345+
SourceLocationKey,
346+
)> = prop_visitor
347+
.field_definitions
348+
.into_iter()
349+
.map(|def| (def, prop_visitor.location))
350+
.collect();
351+
323352
self.add_weak_type_definition(
324353
name,
325354
type_alias,
326355
source_hash,
327356
source_module_path,
328357
description,
329358
false,
330-
)?
359+
)?;
360+
self.unresolved_field_definitions.extend(field_definitions);
331361
}
332362
}
333363
Ok(())
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
==================================== INPUT ====================================
2+
/**
3+
* Copyright (c) Meta Platforms, Inc. and affiliates.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
//- module.js
10+
11+
/**
12+
* @RelayResolver
13+
*/
14+
export type Cat = {
15+
/**
16+
* @gqlField
17+
*/
18+
name: string,
19+
/**
20+
* @gqlField
21+
*/
22+
color: string,
23+
};
24+
==================================== OUTPUT ===================================
25+
Field(
26+
TerseRelayResolver(
27+
TerseRelayResolverIr {
28+
field: FieldDefinition {
29+
name: Identifier {
30+
span: 268:272,
31+
token: Token {
32+
span: 268:272,
33+
kind: Identifier,
34+
},
35+
value: "name",
36+
},
37+
type_: Named(
38+
NamedTypeAnnotation {
39+
name: Identifier {
40+
span: 274:280,
41+
token: Token {
42+
span: 274:280,
43+
kind: Identifier,
44+
},
45+
value: "String",
46+
},
47+
},
48+
),
49+
arguments: None,
50+
directives: [],
51+
description: None,
52+
hack_source: None,
53+
span: 268:272,
54+
},
55+
type_: WithLocation {
56+
location: module.js:231:234,
57+
item: "Cat",
58+
},
59+
root_fragment: None,
60+
deprecated: None,
61+
semantic_non_null: Some(
62+
ConstantDirective {
63+
span: 268:272,
64+
at: Token {
65+
span: 0:0,
66+
kind: Empty,
67+
},
68+
name: Identifier {
69+
span: 268:272,
70+
token: Token {
71+
span: 0:0,
72+
kind: Empty,
73+
},
74+
value: "semanticNonNull",
75+
},
76+
arguments: None,
77+
},
78+
),
79+
live: None,
80+
location: module.js:268:272,
81+
fragment_arguments: None,
82+
source_hash: ResolverSourceHash(
83+
"028500824c2efa57e633f83759af8a99",
84+
),
85+
type_confirmed: true,
86+
is_property_lookup: true,
87+
},
88+
),
89+
)
90+
extend type Cat {
91+
name: String @relay_resolver(fragment_name: "Cat____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", is_property_lookup: true, type_confirmed: true, has_output_type: true, import_name: "name", import_path: "module.js") @resolver_source_hash(value: "028500824c2efa57e633f83759af8a99") @semanticNonNull
92+
}
93+
94+
95+
Field(
96+
TerseRelayResolver(
97+
TerseRelayResolverIr {
98+
field: FieldDefinition {
99+
name: Identifier {
100+
span: 312:317,
101+
token: Token {
102+
span: 312:317,
103+
kind: Identifier,
104+
},
105+
value: "color",
106+
},
107+
type_: Named(
108+
NamedTypeAnnotation {
109+
name: Identifier {
110+
span: 319:325,
111+
token: Token {
112+
span: 319:325,
113+
kind: Identifier,
114+
},
115+
value: "String",
116+
},
117+
},
118+
),
119+
arguments: None,
120+
directives: [],
121+
description: None,
122+
hack_source: None,
123+
span: 312:317,
124+
},
125+
type_: WithLocation {
126+
location: module.js:231:234,
127+
item: "Cat",
128+
},
129+
root_fragment: None,
130+
deprecated: None,
131+
semantic_non_null: Some(
132+
ConstantDirective {
133+
span: 312:317,
134+
at: Token {
135+
span: 0:0,
136+
kind: Empty,
137+
},
138+
name: Identifier {
139+
span: 312:317,
140+
token: Token {
141+
span: 0:0,
142+
kind: Empty,
143+
},
144+
value: "semanticNonNull",
145+
},
146+
arguments: None,
147+
},
148+
),
149+
live: None,
150+
location: module.js:312:317,
151+
fragment_arguments: None,
152+
source_hash: ResolverSourceHash(
153+
"028500824c2efa57e633f83759af8a99",
154+
),
155+
type_confirmed: true,
156+
is_property_lookup: true,
157+
},
158+
),
159+
)
160+
extend type Cat {
161+
color: String @relay_resolver(fragment_name: "Cat____relay_model_instance", generated_fragment: true, inject_fragment_data: "__relay_model_instance", is_property_lookup: true, type_confirmed: true, has_output_type: true, import_name: "color", import_path: "module.js") @resolver_source_hash(value: "028500824c2efa57e633f83759af8a99") @semanticNonNull
162+
}
163+
164+
165+
Type(
166+
WeakObjectType(
167+
WeakObjectIr {
168+
type_name: Identifier {
169+
span: 231:234,
170+
token: Token {
171+
span: 231:234,
172+
kind: Identifier,
173+
},
174+
value: "Cat",
175+
},
176+
rhs_location: module.js:231:234,
177+
description: None,
178+
hack_source: None,
179+
deprecated: None,
180+
location: module.js:231:234,
181+
implements_interfaces: [],
182+
source_hash: ResolverSourceHash(
183+
"028500824c2efa57e633f83759af8a99",
184+
),
185+
type_confirmed: true,
186+
},
187+
),
188+
)
189+
scalar CatModel @__RelayCustomScalar(path: "module.js", export_name: "Cat")
190+
191+
192+
type Cat @__RelayResolverModel @RelayOutputType @__RelayWeakObject {
193+
__relay_model_instance: CatModel! @resolver_source_hash(value: "028500824c2efa57e633f83759af8a99") @unselectable(reason: "This field is intended only for Relay's internal use")
194+
}

0 commit comments

Comments
 (0)