Skip to content

Commit 35434b3

Browse files
committed
feat: implement description parsing for executables
1 parent c4fdbf6 commit 35434b3

File tree

3 files changed

+83
-20
lines changed

3 files changed

+83
-20
lines changed

crates/apollo-parser/src/parser/grammar/fragment.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::parser::grammar::description;
12
use crate::parser::grammar::directive;
23
use crate::parser::grammar::name;
34
use crate::parser::grammar::selection;
@@ -9,12 +10,18 @@ use crate::TokenKind;
910
use crate::S;
1011
use crate::T;
1112

12-
/// See: https://spec.graphql.org/October2021/#FragmentDefinition
13+
/// See: https://spec.graphql.org/September2025/#sec-Language.Fragments
1314
///
1415
/// *FragmentDefinition*:
15-
/// **fragment** FragmentName TypeCondition Directives? SelectionSet
16+
/// Description? **fragment** FragmentName TypeCondition Directives? SelectionSet
1617
pub(crate) fn fragment_definition(p: &mut Parser) {
1718
let _g = p.start_node(SyntaxKind::FRAGMENT_DEFINITION);
19+
20+
// Check for optional description
21+
if let Some(TokenKind::StringValue) = p.peek() {
22+
description::description(p);
23+
}
24+
1825
p.bump(SyntaxKind::fragment_KW);
1926

2027
fragment_name(p);
@@ -30,7 +37,7 @@ pub(crate) fn fragment_definition(p: &mut Parser) {
3037
}
3138
}
3239

33-
/// See: https://spec.graphql.org/October2021/#FragmentName
40+
/// See: https://spec.graphql.org/September2025/#sec-Language.Fragments
3441
///
3542
/// *FragmentName*:
3643
/// Name *but not* **on**
@@ -47,7 +54,7 @@ pub(crate) fn fragment_name(p: &mut Parser) {
4754
}
4855
}
4956

50-
/// See: https://spec.graphql.org/October2021/#TypeCondition
57+
/// See: https://spec.graphql.org/September2025/#sec-Language.Fragments
5158
///
5259
/// *TypeCondition*:
5360
/// **on** NamedType
@@ -71,7 +78,7 @@ pub(crate) fn type_condition(p: &mut Parser) {
7178
}
7279
}
7380

74-
/// See: https://spec.graphql.org/October2021/#InlineFragment
81+
/// See: https://spec.graphql.org/September2025/#sec-Language.Fragments
7582
///
7683
/// *InlineFragment*:
7784
/// **...** TypeCondition? Directives? SelectionSet
@@ -93,7 +100,7 @@ pub(crate) fn inline_fragment(p: &mut Parser) {
93100
}
94101
}
95102

96-
/// See: https://spec.graphql.org/October2021/#FragmentSpread
103+
/// See: https://spec.graphql.org/September2025/#sec-Language.Fragments
97104
///
98105
/// *FragmentSpread*:
99106
/// **...** FragmentName Directives?

crates/apollo-parser/src/parser/grammar/operation.rs

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::parser::grammar::description;
12
use crate::parser::grammar::directive;
23
use crate::parser::grammar::name;
34
use crate::parser::grammar::selection;
@@ -8,13 +9,43 @@ use crate::SyntaxKind;
89
use crate::TokenKind;
910
use crate::T;
1011

11-
/// See: https://spec.graphql.org/October2021/#OperationDefinition
12+
/// See: https://spec.graphql.org/September2025/#sec-Language.Operations
1213
///
1314
/// *OperationDefinition*:
14-
/// OperationType Name? VariableDefinitions? Directives? SelectionSet
15+
/// Description? OperationType Name? VariableDefinitions? Directives? SelectionSet
1516
/// SelectionSet
1617
pub(crate) fn operation_definition(p: &mut Parser) {
1718
match p.peek() {
19+
Some(TokenKind::StringValue) => {
20+
// Description found - must be full operation definition, not shorthand
21+
let _g = p.start_node(SyntaxKind::OPERATION_DEFINITION);
22+
23+
description::description(p);
24+
25+
// After description, we must have an operation type
26+
if let Some(TokenKind::Name) = p.peek() {
27+
operation_type(p);
28+
} else {
29+
return p.err_and_pop("expected an Operation Type after description");
30+
}
31+
32+
if let Some(TokenKind::Name) = p.peek() {
33+
name::name(p);
34+
}
35+
36+
if let Some(T!['(']) = p.peek() {
37+
variable::variable_definitions(p)
38+
}
39+
40+
if let Some(T![@]) = p.peek() {
41+
directive::directives(p, Constness::NotConst);
42+
}
43+
44+
match p.peek() {
45+
Some(T!['{']) => selection::selection_set(p),
46+
_ => p.err_and_pop("expected a Selection Set"),
47+
}
48+
}
1849
Some(TokenKind::Name) => {
1950
let _g = p.start_node(SyntaxKind::OPERATION_DEFINITION);
2051

@@ -46,7 +77,7 @@ pub(crate) fn operation_definition(p: &mut Parser) {
4677
}
4778
}
4879

49-
/// See: https://spec.graphql.org/October2021/#OperationType
80+
/// See: https://spec.graphql.org/September2025/#sec-Language.Operations
5081
///
5182
/// *OperationType*: one of
5283
/// **query** **mutation** **subscription**
@@ -66,15 +97,25 @@ pub(crate) fn operation_type(p: &mut Parser) {
6697
mod test {
6798
use crate::Parser;
6899

69-
// NOTE @lrlna: related PR to the spec to avoid this issue:
70-
// https://github.com/graphql/graphql-spec/pull/892
100+
// NOTE @lrlna: Descriptions on operations are now supported in September 2025 spec
101+
// https://github.com/graphql/graphql-spec/pull/1170
71102
#[test]
72-
fn it_continues_parsing_when_operation_definition_starts_with_description() {
73-
let input = "\"description\"{}";
103+
fn it_parses_operation_definition_with_description() {
104+
let input = "\"A test query\" query Test { field }";
74105
let parser = Parser::new(input);
75106
let cst = parser.parse();
76107

77-
assert_eq!(cst.errors().len(), 2);
108+
assert_eq!(cst.errors().len(), 0);
78109
assert_eq!(cst.document().definitions().count(), 1);
79110
}
111+
112+
#[test]
113+
fn it_errors_on_shorthand_query_with_description() {
114+
let input = "\"description\"{}";
115+
let parser = Parser::new(input);
116+
let cst = parser.parse();
117+
118+
// Should error because descriptions are not allowed on shorthand queries
119+
assert!(cst.errors().len() > 0);
120+
}
80121
}

crates/apollo-parser/src/parser/grammar/variable.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::parser::grammar::description;
12
use crate::parser::grammar::directive;
23
use crate::parser::grammar::name;
34
use crate::parser::grammar::ty;
@@ -9,30 +10,44 @@ use crate::TokenKind;
910
use crate::S;
1011
use crate::T;
1112

12-
/// See: https://spec.graphql.org/October2021/#VariableDefinitions
13+
/// See: https://spec.graphql.org/September2025/#sec-Language.Variables
1314
///
1415
/// *VariableDefinitions*:
1516
/// **(** VariableDefinition* **)**
1617
pub(crate) fn variable_definitions(p: &mut Parser) {
1718
let _g = p.start_node(SyntaxKind::VARIABLE_DEFINITIONS);
1819
p.bump(S!['(']);
1920

20-
if let Some(T![$]) = p.peek() {
21+
// Variable definitions can start with a description (string) or $ (variable)
22+
if let Some(TokenKind::StringValue | T![$]) = p.peek() {
2123
variable_definition(p);
2224
} else {
2325
p.err("expected a Variable Definition")
2426
}
25-
p.peek_while_kind(T![$], variable_definition);
27+
28+
// Continue parsing while we see descriptions or variables
29+
loop {
30+
match p.peek() {
31+
Some(TokenKind::StringValue | T![$]) => variable_definition(p),
32+
_ => break,
33+
}
34+
}
2635

2736
p.expect(T![')'], S![')']);
2837
}
2938

30-
/// See: https://spec.graphql.org/October2021/#VariableDefinition
39+
/// See: https://spec.graphql.org/September2025/#sec-Language.Variables
3140
///
3241
/// *VariableDefinition*:
33-
/// Variable **:** Type DefaultValue? Directives[Const]?
42+
/// Description? Variable **:** Type DefaultValue? Directives[Const]?
3443
pub(crate) fn variable_definition(p: &mut Parser) {
3544
let _guard = p.start_node(SyntaxKind::VARIABLE_DEFINITION);
45+
46+
// Check for optional description
47+
if let Some(TokenKind::StringValue) = p.peek() {
48+
description::description(p);
49+
}
50+
3651
variable(p);
3752

3853
if let Some(T![:]) = p.peek() {
@@ -53,7 +68,7 @@ pub(crate) fn variable_definition(p: &mut Parser) {
5368
}
5469
}
5570

56-
/// See: https://spec.graphql.org/October2021/#Variable
71+
/// See: https://spec.graphql.org/September2025/#sec-Language.Variables
5772
///
5873
/// *Variable*:
5974
/// **$** Name

0 commit comments

Comments
 (0)