apollo_parser/parser/grammar/
directive.rs

1use crate::parser::grammar::argument;
2use crate::parser::grammar::description;
3use crate::parser::grammar::input;
4use crate::parser::grammar::name;
5use crate::parser::grammar::value::Constness;
6use crate::Parser;
7use crate::SyntaxKind;
8use crate::TokenKind;
9use crate::S;
10use crate::T;
11use std::ops::ControlFlow;
12
13/// See: https://spec.graphql.org/October2021/#DirectiveDefinition
14///
15/// *DirectiveDefinition*:
16///     Description? **directive @** Name ArgumentsDefinition? **repeatable**? **on** DirectiveLocations
17pub(crate) fn directive_definition(p: &mut Parser) {
18    let _g = p.start_node(SyntaxKind::DIRECTIVE_DEFINITION);
19
20    if let Some(TokenKind::StringValue) = p.peek() {
21        description::description(p);
22    }
23
24    if let Some("directive") = p.peek_data() {
25        p.bump(SyntaxKind::directive_KW);
26    }
27
28    match p.peek() {
29        Some(T![@]) => p.bump(S![@]),
30        _ => p.err("expected @ symbol"),
31    }
32    name::name(p);
33
34    if let Some(T!['(']) = p.peek() {
35        let _g = p.start_node(SyntaxKind::ARGUMENTS_DEFINITION);
36        p.bump(S!['(']);
37        if let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
38            input::input_value_definition(p);
39        } else {
40            p.err("expected an Argument Definition");
41        }
42        p.peek_while(|p, kind| match kind {
43            TokenKind::Name | TokenKind::StringValue => {
44                input::input_value_definition(p);
45                ControlFlow::Continue(())
46            }
47            _ => ControlFlow::Break(()),
48        });
49        p.expect(T![')'], S![')']);
50    }
51
52    if let Some(node) = p.peek_data() {
53        if node == "repeatable" {
54            p.bump(SyntaxKind::repeatable_KW);
55        }
56    }
57
58    if let Some(node) = p.peek_data() {
59        match node {
60            "on" => p.bump(SyntaxKind::on_KW),
61            _ => p.err("expected Directive Locations"),
62        }
63    }
64
65    if let Some(TokenKind::Name | T![|]) = p.peek() {
66        let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATIONS);
67        directive_locations(p);
68    } else {
69        p.err("expected valid Directive Location");
70    }
71}
72
73/// https://spec.graphql.org/October2021/#DirectiveLocation
74///
75/// *DirectiveLocation*:
76///     *ExecutableDirectiveLocation*
77///     *TypeSystemDirectiveLocation*
78///
79/// (This function does not distinguish between the two groups of
80/// locations.)
81fn directive_location(p: &mut Parser) {
82    let Some(token) = p.peek_token() else {
83        return;
84    };
85
86    if token.kind == TokenKind::Name {
87        match token.data {
88            "QUERY" => {
89                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
90                p.bump(SyntaxKind::QUERY_KW);
91            }
92            "MUTATION" => {
93                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
94                p.bump(SyntaxKind::MUTATION_KW);
95            }
96            "SUBSCRIPTION" => {
97                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
98                p.bump(SyntaxKind::SUBSCRIPTION_KW);
99            }
100            "FIELD" => {
101                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
102                p.bump(SyntaxKind::FIELD_KW);
103            }
104            "FRAGMENT_DEFINITION" => {
105                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
106                p.bump(SyntaxKind::FRAGMENT_DEFINITION_KW);
107            }
108            "FRAGMENT_SPREAD" => {
109                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
110                p.bump(SyntaxKind::FRAGMENT_SPREAD_KW);
111            }
112            "INLINE_FRAGMENT" => {
113                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
114                p.bump(SyntaxKind::INLINE_FRAGMENT_KW);
115            }
116            "VARIABLE_DEFINITION" => {
117                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
118                p.bump(SyntaxKind::VARIABLE_DEFINITION_KW);
119            }
120            "SCHEMA" => {
121                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
122                p.bump(SyntaxKind::SCHEMA_KW);
123            }
124            "SCALAR" => {
125                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
126                p.bump(SyntaxKind::SCALAR_KW);
127            }
128            "OBJECT" => {
129                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
130                p.bump(SyntaxKind::OBJECT_KW);
131            }
132            "FIELD_DEFINITION" => {
133                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
134                p.bump(SyntaxKind::FIELD_DEFINITION_KW);
135            }
136            "ARGUMENT_DEFINITION" => {
137                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
138                p.bump(SyntaxKind::ARGUMENT_DEFINITION_KW);
139            }
140            "INTERFACE" => {
141                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
142                p.bump(SyntaxKind::INTERFACE_KW);
143            }
144            "UNION" => {
145                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
146                p.bump(SyntaxKind::UNION_KW);
147            }
148            "ENUM" => {
149                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
150                p.bump(SyntaxKind::ENUM_KW);
151            }
152            "ENUM_VALUE" => {
153                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
154                p.bump(SyntaxKind::ENUM_VALUE_KW);
155            }
156            "INPUT_OBJECT" => {
157                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
158                p.bump(SyntaxKind::INPUT_OBJECT_KW);
159            }
160            "INPUT_FIELD_DEFINITION" => {
161                let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
162                p.bump(SyntaxKind::INPUT_FIELD_DEFINITION_KW);
163            }
164            _ => {
165                p.err("expected valid Directive Location");
166            }
167        }
168    } else {
169        p.err("expected Directive Location");
170    }
171}
172
173/// See: https://spec.graphql.org/October2021/#DirectiveLocations
174///
175/// *DirectiveLocations*:
176///     DirectiveLocations **|** DirectiveLocation
177///     **|**? DirectiveLocation
178pub(crate) fn directive_locations(p: &mut Parser) {
179    p.parse_separated_list(T![|], S![|], directive_location);
180}
181
182/// See: https://spec.graphql.org/October2021/#Directive
183///
184/// *Directive[Const]*:
185///     **@** Name Arguments[?Const]?
186pub(crate) fn directive(p: &mut Parser, constness: Constness) {
187    let _g = p.start_node(SyntaxKind::DIRECTIVE);
188
189    p.expect(T![@], S![@]);
190    name::name(p);
191
192    if let Some(T!['(']) = p.peek() {
193        argument::arguments(p, constness);
194    }
195}
196
197/// See: https://spec.graphql.org/October2021/#Directives
198///
199/// *Directives[Const]*:
200///     Directive[?Const]*
201pub(crate) fn directives(p: &mut Parser, constness: Constness) {
202    let _g = p.start_node(SyntaxKind::DIRECTIVES);
203    p.peek_while_kind(T![@], |p| {
204        directive(p, constness);
205    });
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use crate::cst;
212
213    #[test]
214    fn it_can_access_repeatable_kw_on_directive_definition() {
215        let schema = r#"
216directive @example(isTreat: Boolean, treatKind: String) repeatable on FIELD | MUTATION
217        "#;
218        let parser = Parser::new(schema);
219        let cst = parser.parse();
220
221        assert!(cst.errors.is_empty());
222
223        let document = cst.document();
224        for definition in document.definitions() {
225            if let cst::Definition::DirectiveDefinition(dir_def) = definition {
226                assert_eq!(
227                    dir_def.repeatable_token().unwrap().kind(),
228                    SyntaxKind::repeatable_KW
229                );
230                return;
231            }
232        }
233        panic!("Expected CST to have a Directive Definition");
234    }
235
236    #[test]
237    fn it_can_access_directive_location_on_directive_definition() {
238        let schema = r#"
239directive @example on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
240        "#;
241        let parser = Parser::new(schema);
242        let cst = parser.parse();
243        assert!(cst.errors.is_empty());
244
245        let document = cst.document();
246        for definition in document.definitions() {
247            if let cst::Definition::DirectiveDefinition(dir_def) = definition {
248                let dir_locations: Vec<String> = dir_def
249                    .directive_locations()
250                    .unwrap()
251                    .directive_locations()
252                    .map(|loc| loc.text().unwrap().to_string())
253                    .collect();
254                assert_eq!(
255                    dir_locations,
256                    ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"]
257                );
258                return;
259            }
260        }
261        panic!("Expected CST to have a Directive Definition");
262    }
263
264    #[test]
265    fn it_can_access_multiline_directive_location_on_directive_definition() {
266        let schema = r#"
267directive @example on
268| FIELD
269| FRAGMENT_SPREAD
270| INLINE_FRAGMENT
271        "#;
272        let parser = Parser::new(schema);
273        let cst = parser.parse();
274        assert!(cst.errors.is_empty());
275
276        let document = cst.document();
277        for definition in document.definitions() {
278            if let cst::Definition::DirectiveDefinition(dir_def) = definition {
279                let dir_locations: Vec<String> = dir_def
280                    .directive_locations()
281                    .unwrap()
282                    .directive_locations()
283                    .map(|loc| loc.text().unwrap().to_string())
284                    .collect();
285                assert_eq!(
286                    dir_locations,
287                    ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"]
288                );
289                return;
290            }
291        }
292        panic!("Expected CST to have a Directive Definition");
293    }
294
295    #[test]
296    fn it_can_access_multiline_directive_location_on_directive_definition_with_error() {
297        let schema = r#"
298directive @example on
299| FIELD
300| FRAGMENT_SPREAD
301| INLINE_FRAGMENT |
302        "#;
303        let parser = Parser::new(schema);
304        let cst = parser.parse();
305        assert!(!cst.errors.is_empty());
306
307        let schema = r#"
308directive @example on
309|| FIELD
310| FRAGMENT_SPREAD
311| INLINE_FRAGMENT
312        "#;
313        let parser = Parser::new(schema);
314        let cst = parser.parse();
315        assert!(!cst.errors.is_empty());
316
317        let schema = r#"
318directive @example on
319| FIELD
320|| FRAGMENT_SPREAD
321| INLINE_FRAGMENT
322        "#;
323        let parser = Parser::new(schema);
324        let cst = parser.parse();
325        assert!(!cst.errors.is_empty());
326    }
327}