Skip to main content

apollo_parser/parser/grammar/
ty.rs

1use crate::parser::grammar::name;
2use crate::Parser;
3use crate::SyntaxKind;
4use crate::Token;
5use crate::TokenKind;
6use crate::S;
7use crate::T;
8
9/// See: https://spec.graphql.org/October2021/#InputValueDefinition
10///
11/// *Type*:
12///     NamedType
13///     ListType
14///         **[** Type **]**
15///     NonNullType
16///         NamedType **!**
17///         ListType **!**
18//
19// NOTE(lrlna): Because Type cannot be parsed in a typical LR fashion, the
20// following parsing rule does not follow the same pattern as all other parsing
21// rules in this library. The parent node type is determined based on what its
22// last possible NonNullType.
23//
24// To make this work, we first collect all types in a double ended queue, and
25// unwrap them once the last possible child has been parsed. Nodes are then
26// created in the processing stage of this parsing rule.
27pub(crate) fn ty(p: &mut Parser) {
28    match parse(p) {
29        Ok(_) => (),
30        Err(Some(token)) => p.err_at_token(&token, "expected a type"),
31        Err(None) => p.err("expected a type"),
32    }
33}
34
35/// Returns the type on success, or the TokenKind that caused an error.
36///
37/// When errors occur deeper inside nested types like lists, this function
38/// pushes errors *inside* the list to the parser, and returns an Ok() with
39/// an incomplete type.
40fn parse<'a>(p: &mut Parser<'a>) -> Result<(), Option<Token<'a>>> {
41    let checkpoint = p.checkpoint_node();
42    match p.peek() {
43        Some(T!['[']) => {
44            let _guard = p.start_node(SyntaxKind::LIST_TYPE);
45            p.bump(S!['[']);
46
47            if p.recursion_limit.check_and_increment() {
48                p.limit_err("parser recursion limit reached");
49                return Ok(()); // TODO: is this right?
50            }
51            let result = parse(p);
52            p.recursion_limit.decrement();
53
54            if let Err(Some(token)) = result {
55                // TODO(@goto-bus-stop) ideally the span here would point to the entire list
56                // type, so both opening and closing brackets `[]`.
57                p.err_at_token(&token, "expected item type");
58            }
59            p.expect(T![']'], S![']']);
60        }
61        Some(TokenKind::Name) => {
62            let _guard = p.start_node(SyntaxKind::NAMED_TYPE);
63            let _name_node_guard = p.start_node(SyntaxKind::NAME);
64
65            let token = p.pop();
66            name::validate_name(token.data(), p);
67            p.push_token(SyntaxKind::IDENT, token);
68        }
69        Some(_) => return Err(Some(p.pop())),
70        None => return Err(None),
71    };
72
73    // There may be whitespace inside a list node or between the type and the non-null `!`.
74    p.skip_ignored();
75
76    // Deal with nullable types
77    if let Some(T![!]) = p.peek() {
78        let _guard = checkpoint.wrap_node(SyntaxKind::NON_NULL_TYPE);
79
80        p.eat(S![!]);
81    }
82
83    // Handle post-node commas, whitespace, comments
84    // TODO(@goto-bus-stop) This should maybe be done further up the parse tree? the type node is
85    // parsed completely at this point.
86    p.skip_ignored();
87
88    Ok(())
89}
90
91/// See: https://spec.graphql.org/October2021/#NamedType
92///
93/// *NamedType*:
94///     Name
95pub(crate) fn named_type(p: &mut Parser) {
96    // TODO(@goto-bus-stop) can we make this error instead if no name is found?
97    if let Some(TokenKind::Name) = p.peek() {
98        let _g = p.start_node(SyntaxKind::NAMED_TYPE);
99        name::name(p);
100    }
101}
102
103#[cfg(test)]
104mod test {
105    use crate::cst;
106    use crate::cst::CstNode;
107    use crate::Parser;
108
109    #[test]
110    fn it_parses_nested_wrapped_types_in_op_def_and_returns_matching_stringified_doc() {
111        let mutation = r#"
112mutation MyMutation($custId: [Int!]!) {
113  myMutation(custId: $custId)
114}"#;
115        let parser = Parser::new(mutation);
116        let cst = parser.parse();
117        assert!(cst.errors.is_empty());
118
119        let doc = cst.document();
120        assert_eq!(&mutation, &doc.source_string());
121
122        for definition in doc.definitions() {
123            if let cst::Definition::OperationDefinition(op_type) = definition {
124                for var in op_type
125                    .variable_definitions()
126                    .unwrap()
127                    .variable_definitions()
128                {
129                    if let cst::Type::NamedType(name) = var.ty().unwrap() {
130                        assert_eq!(name.source_string(), "[Int!]!")
131                    }
132                }
133            }
134        }
135    }
136
137    #[test]
138    fn stringified_cst_matches_input_with_deeply_nested_wrapped_types() {
139        let mutation = r#"
140mutation MyMutation($a: Int $b: [Int] $c: String! $d: [Int!]!
141
142    $e: String
143    $f: [String]
144    $g: String!
145    $h: [String!]!
146) {
147  myMutation(custId: $a)
148}"#;
149        let parser = Parser::new(mutation);
150        let cst = parser.parse();
151
152        let doc = cst.document();
153        assert_eq!(&mutation, &doc.source_string());
154    }
155
156    #[test]
157    fn stringified_cst_matches_input_with_deeply_nested_wrapped_types_with_commas() {
158        let mutation = r#"
159mutation MyMutation($a: Int, $b: [Int], $c: String!, $d: [Int!]!,
160
161    $e: String,
162    $f: [String],
163    $g: String!,
164    $h: [String!]!,
165) {
166  myMutation(custId: $a)
167}"#;
168        let parser = Parser::new(mutation);
169        let cst = parser.parse();
170
171        let doc = cst.document();
172        assert_eq!(&mutation, &doc.source_string());
173    }
174}