Skip to content

Commit 1182ca6

Browse files
Arseniy Panfilovfacebook-github-bot
authored andcommitted
Support enquoted field alias
Summary: ## Context GraphQL Server team has a usecase for field aliases represented by string literals, e.g.: https://www.internalfb.com/code/fbsource/[6eebb1a1e581]/www/flib/__generated__/GraphQLHackClientMeerkatStep/single_source/GraphQLIGClipsProfileQueryExperiment/GraphQLIGClipsProfileInWWWQueryIGQuery.php?lines=47 More context on "why": [workplace post](https://fb.workplace.com/groups/606728000165364/permalink/1863505397820945/) This is already supported in WWW and IG parsers. In this stack we're adding an *opt-in* support for literal aliases for Relay's parser. ## This diff Parse field aliases when the feature is enabled. Adds support for field aliases specified as string literals, e.g.: ``` { "1$alias": name } ``` Reviewed By: captbaritone Differential Revision: D73563288 fbshipit-source-id: 8e46aea98b9427bf0edd2c0b1b769ede42392b28
1 parent fb9f4d7 commit 1182ca6

19 files changed

+409
-14
lines changed

compiler/crates/graphql-syntax/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# @generated by autocargo from //relay/oss/crates/graphql-syntax:[advance_schema_document_test,graphql-syntax,graphql-syntax_print_test,parse_document_test,parse_document_with_features_test,parse_executable_document_test,parse_executable_document_with_error_recovery_test,parse_schema_document_test]
1+
# @generated by autocargo from //relay/oss/crates/graphql-syntax:[advance_schema_document_test,graphql-syntax,graphql-syntax_print_test,parse_document_test,parse_document_with_enquoted_alias_test,parse_document_with_features_test,parse_executable_document_test,parse_executable_document_with_error_recovery_test,parse_schema_document_test]
22

33
[package]
44
name = "graphql-syntax"

compiler/crates/graphql-syntax/src/node/executable.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ impl fmt::Display for Alias {
160160
}
161161
}
162162

163+
pub enum IdentifierOrString {
164+
Identifier(Identifier),
165+
StringNode(StringNode),
166+
}
167+
163168
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
164169
pub struct TypeCondition {
165170
pub span: Span,

compiler/crates/graphql-syntax/src/parser.rs

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1908,7 +1908,7 @@ impl<'a> Parser<'a> {
19081908
let token = self.peek();
19091909
match token.kind {
19101910
TokenKind::Spread => self.parse_spread(),
1911-
TokenKind::Identifier => self.parse_field(),
1911+
TokenKind::Identifier | TokenKind::StringLiteral => self.parse_field(),
19121912
// hint for invalid spreads
19131913
TokenKind::Period | TokenKind::PeriodPeriod => {
19141914
let error = Diagnostic::error(
@@ -1929,13 +1929,49 @@ impl<'a> Parser<'a> {
19291929
}
19301930
}
19311931

1932+
fn parse_name_or_string_literal(&mut self) -> ParseResult<IdentifierOrString> {
1933+
let token_kind = self.peek_token_kind();
1934+
if token_kind == TokenKind::Identifier {
1935+
return Ok(IdentifierOrString::Identifier(self.parse_identifier()?));
1936+
}
1937+
1938+
let token = self.parse_token();
1939+
if !self.features.allow_string_literal_alias {
1940+
self.record_error(Diagnostic::error(
1941+
SyntaxError::Expected(TokenKind::Identifier),
1942+
Location::new(self.source_location, token.span),
1943+
));
1944+
return Err(());
1945+
}
1946+
1947+
match token.kind {
1948+
TokenKind::StringLiteral => Ok(IdentifierOrString::StringNode(
1949+
self.parse_string_literal(token, self.source(&token)),
1950+
)),
1951+
_ => {
1952+
self.record_error(Diagnostic::error(
1953+
SyntaxError::Expected(TokenKind::StringLiteral),
1954+
Location::new(self.source_location, token.span),
1955+
));
1956+
Err(())
1957+
}
1958+
}
1959+
}
1960+
19321961
/// Field : Alias? Name Arguments? Directives? SelectionSet?
19331962
fn parse_field(&mut self) -> ParseResult<Selection> {
19341963
let start = self.index();
1935-
let name = self.parse_identifier()?;
1964+
let maybe_alias = self.parse_name_or_string_literal()?;
19361965
let (name, alias) = if self.peek_token_kind() == TokenKind::Colon {
19371966
let colon = self.parse_kind(TokenKind::Colon)?;
1938-
let alias = name;
1967+
let alias = match maybe_alias {
1968+
IdentifierOrString::Identifier(node) => node,
1969+
IdentifierOrString::StringNode(node) => Identifier {
1970+
span: node.token.span,
1971+
token: node.token,
1972+
value: node.value,
1973+
},
1974+
};
19391975
let name = {
19401976
match self.peek_token_kind() {
19411977
TokenKind::Identifier => self.parse_identifier()?,
@@ -1962,8 +1998,18 @@ impl<'a> Parser<'a> {
19621998
}),
19631999
)
19642000
} else {
1965-
(name, None)
2001+
match maybe_alias {
2002+
IdentifierOrString::StringNode(node) => {
2003+
self.record_error(Diagnostic::error(
2004+
SyntaxError::Expected(TokenKind::Identifier),
2005+
Location::new(self.source_location, node.token.span),
2006+
));
2007+
(self.empty_identifier(), None)
2008+
}
2009+
IdentifierOrString::Identifier(node) => (node, None),
2010+
}
19662011
};
2012+
19672013
let arguments = self.parse_optional_arguments()?;
19682014
let directives = self.parse_directives()?;
19692015
if self.peek_token_kind() == TokenKind::OpenBrace {
@@ -2389,13 +2435,9 @@ impl<'a> Parser<'a> {
23892435
let token = self.parse_token();
23902436
let source = self.source(&token);
23912437
match &token.kind {
2392-
TokenKind::StringLiteral => {
2393-
let value = source[1..source.len() - 1].to_string();
2394-
Ok(ConstantValue::String(StringNode {
2395-
token,
2396-
value: value.intern(),
2397-
}))
2398-
}
2438+
TokenKind::StringLiteral => Ok(ConstantValue::String(
2439+
self.parse_string_literal(token, source),
2440+
)),
23992441
TokenKind::BlockStringLiteral => {
24002442
let value = clean_block_string_literal(source);
24012443
Ok(ConstantValue::String(StringNode {
@@ -2499,6 +2541,14 @@ impl<'a> Parser<'a> {
24992541
}
25002542
}
25012543

2544+
fn parse_string_literal(&self, token: Token, source: &str) -> StringNode {
2545+
let value = source[1..source.len() - 1].to_string();
2546+
StringNode {
2547+
token,
2548+
value: value.intern(),
2549+
}
2550+
}
2551+
25022552
/// IntValue
25032553
/// FloatValue
25042554
/// StringValue
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
use common::Diagnostic;
9+
use common::SourceLocationKey;
10+
use common::TextSource;
11+
use fixture_tests::Fixture;
12+
use graphql_cli::DiagnosticPrinter;
13+
use graphql_syntax::ParserFeatures;
14+
use graphql_syntax::parse_executable_with_features;
15+
16+
pub async fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
17+
parse_executable_with_features(
18+
fixture.content,
19+
SourceLocationKey::standalone(fixture.file_name),
20+
ParserFeatures {
21+
allow_string_literal_alias: true,
22+
..Default::default()
23+
},
24+
)
25+
.map(|x| format!("{:#?}", x))
26+
.map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))
27+
}
28+
29+
// NOTE: copied from graphql-test-helpers to avoid cyclic dependency breaking Rust Analyzer
30+
fn diagnostics_to_sorted_string(source: &str, diagnostics: &[Diagnostic]) -> String {
31+
let printer =
32+
DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(source.to_string())));
33+
let mut printed = diagnostics
34+
.iter()
35+
.map(|diagnostic| printer.diagnostic_to_string(diagnostic))
36+
.collect::<Vec<_>>();
37+
printed.sort();
38+
printed.join("\n\n")
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
==================================== INPUT ====================================
2+
# expected-to-throw
3+
4+
query EnquotedAliasWithEnquotedFieldName {
5+
"alias": "field_name"
6+
}
7+
==================================== ERROR ====================================
8+
✖︎ Expected a non-variable identifier (e.g. 'x' or 'Foo')
9+
10+
enquoted_alias_with_enquoted_field.invalid.graphql:4:12
11+
3 │ query EnquotedAliasWithEnquotedFieldName {
12+
4 │ "alias": "field_name"
13+
│ ^^^^^^^^^^^^
14+
5 │ }
15+
16+
17+
✖︎ Incomplete field alias, expected non-variable identifier (e.g. 'x' or 'Foo') but found string literal (e.g. '"..."')
18+
19+
enquoted_alias_with_enquoted_field.invalid.graphql:4:3
20+
3 │ query EnquotedAliasWithEnquotedFieldName {
21+
4 │ "alias": "field_name"
22+
│ ^^^^^^^^
23+
5 │ }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# expected-to-throw
2+
3+
query EnquotedAliasWithEnquotedFieldName {
4+
"alias": "field_name"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
==================================== INPUT ====================================
2+
query EnquotedAlias {
3+
"1$ali\"as": escaped_quote_alias
4+
}
5+
==================================== OUTPUT ===================================
6+
ExecutableDocument {
7+
span: 0:59,
8+
definitions: [
9+
OperationDefinition {
10+
location: enquoted_alias_with_escape.graphql:0:58,
11+
operation: Some(
12+
(
13+
Token {
14+
span: 0:5,
15+
kind: Identifier,
16+
},
17+
Query,
18+
),
19+
),
20+
name: Some(
21+
Identifier {
22+
span: 6:19,
23+
token: Token {
24+
span: 6:19,
25+
kind: Identifier,
26+
},
27+
value: "EnquotedAlias",
28+
},
29+
),
30+
variable_definitions: None,
31+
directives: [],
32+
selections: List {
33+
span: 20:58,
34+
start: Token {
35+
span: 20:21,
36+
kind: OpenBrace,
37+
},
38+
items: [
39+
ScalarField {
40+
span: 24:56,
41+
alias: Some(
42+
Alias {
43+
span: 24:56,
44+
alias: Identifier {
45+
span: 24:35,
46+
token: Token {
47+
span: 24:35,
48+
kind: StringLiteral,
49+
},
50+
value: "1$ali\\\"as",
51+
},
52+
colon: Token {
53+
span: 35:36,
54+
kind: Colon,
55+
},
56+
},
57+
),
58+
name: Identifier {
59+
span: 37:56,
60+
token: Token {
61+
span: 37:56,
62+
kind: Identifier,
63+
},
64+
value: "escaped_quote_alias",
65+
},
66+
arguments: None,
67+
directives: [],
68+
},
69+
],
70+
end: Token {
71+
span: 57:58,
72+
kind: CloseBrace,
73+
},
74+
},
75+
},
76+
],
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
query EnquotedAlias {
2+
"1$ali\"as": escaped_quote_alias
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
==================================== INPUT ====================================
2+
# expected-to-throw
3+
4+
query EnquotedFieldName {
5+
"field_name"
6+
}
7+
==================================== ERROR ====================================
8+
✖︎ Expected a non-variable identifier (e.g. 'x' or 'Foo')
9+
10+
enquoted_field_name.invalid.graphql:4:3
11+
3 │ query EnquotedFieldName {
12+
4 │ "field_name"
13+
│ ^^^^^^^^^^^^
14+
5 │ }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# expected-to-throw
2+
3+
query EnquotedFieldName {
4+
"field_name"
5+
}

0 commit comments

Comments
 (0)