Repository: apollographql/apollo-rs (rust)

Issue:
erroneous spans when an error is created with err_and_pop
Our error spans are inconsistent when an error is created with `err_and_pop`. 

A few examples.
GraphQL:
```graphql
cats
# comment
```
Document:
```
- DOCUMENT@0..10
    - WHITESPACE@0..1 "\n"
    - COMMENT@1..10 "# comment"
- ERROR@0:4 "expected definition" cats
```

GraphQL:
```graphql
cats

type Query {
    name: String
}
```
Document:
```
- DOCUMENT@0..33
    - WHITESPACE@0..2 "\n\n"
    - OBJECT_TYPE_DEFINITION@2..33
        - type_KW@2..6 "type"
        - WHITESPACE@6..7 " "
        - NAME@7..13
            - IDENT@7..12 "Query"
            - WHITESPACE@12..13 " "
        - FIELDS_DEFINITION@13..33
            - L_CURLY@13..14 "{"
            - WHITESPACE@14..19 "\n    "
            - FIELD_DEFINITION@19..32
                - NAME@19..23
                    - IDENT@19..23 "name"
                - COLON@23..24 ":"
                - WHITESPACE@24..25 " "
                - NAMED_TYPE@25..31
                    - NAME@25..31
                        - IDENT@25..31 "String"
                - WHITESPACE@31..32 "\n"
            - R_CURLY@32..33 "}"
- ERROR@0:4 "expected definition" cats
```

This no longer becomes the case when the error precedes a valid statement, so if we reverse the two statements above:

```graphql
type Query {
    name: String
}

cats
```
Our Document's spans are then correct:
```
- DOCUMENT@0..33
    - OBJECT_TYPE_DEFINITION@0..33
        - type_KW@0..4 "type"
        - WHITESPACE@4..5 " "
        - NAME@5..11
            - IDENT@5..10 "Query"
            - WHITESPACE@10..11 " "
        - FIELDS_DEFINITION@11..33
            - L_CURLY@11..12 "{"
            - WHITESPACE@12..17 "\n    "
            - FIELD_DEFINITION@17..30
                - NAME@17..21
                    - IDENT@17..21 "name"
                - COLON@21..22 ":"
                - WHITESPACE@22..23 " "
                - NAMED_TYPE@23..29
                    - NAME@23..29
                        - IDENT@23..29 "String"
                - WHITESPACE@29..30 "\n"
            - R_CURLY@30..31 "}"
            - WHITESPACE@31..33 "\n\n"
- ERROR@33:37 "expected definition" cats
```

Initially found in #324 

Pull request:
fix(parser): Fix spans after parsing unexpected tokens
When there is a parse error, we ignore the token that caused the error. Then we continue parsing. The offsets associated with parse nodes increase linearly as tokens are added, so if we skip any token, the spans for all future nodes will be wrong. This adds an `ERROR` syntax token kind so we can add error tokens to nodes, which advances the spans, and fixes the issue. The tree is also fully lossless now.

There could be other similar cases, where we pop a token and never add it to the tree, but I think we can address those cases as we find them

I'm not sure if the addition of a new type of token to the parse tree could have issues downstream, I don't think it should (as we just ignnore them when building the typed tree), but maybe you have a better idea @lrlna ?

Ref #325

Patch:
diff --git a/crates/apollo-compiler/README.md b/crates/apollo-compiler/README.md
index 7d73371..4ce8dde 100644
--- a/crates/apollo-compiler/README.md
+++ b/crates/apollo-compiler/README.md
@@ -299,7 +299,7 @@ let diagnostics = compiler.validate();
 for diagnostic in &diagnostics {
     println!("{}", diagnostic)
 }
-assert_eq!(diagnostics.len(), 5)
+assert_eq!(diagnostics.len(), 9)
 ```
 
 ## License
diff --git a/crates/apollo-compiler/src/database/hir.rs b/crates/apollo-compiler/src/database/hir.rs
index c658130..6c5ceeb 100644
--- a/crates/apollo-compiler/src/database/hir.rs
+++ b/crates/apollo-compiler/src/database/hir.rs
@@ -130,6 +130,17 @@ impl TypeDefinition {
             _ => None,
         }
     }
+
+    pub fn loc(&self) -> Option<HirNodeLocation> {
+        match self {
+            Self::ObjectTypeDefinition(def) => Some(def.loc()),
+            Self::InterfaceTypeDefinition(def) => Some(def.loc()),
+            Self::UnionTypeDefinition(def) => Some(def.loc()),
+            Self::EnumTypeDefinition(def) => Some(def.loc()),
+            Self::InputObjectTypeDefinition(def) => Some(def.loc()),
+            Self::ScalarTypeDefinition(def) => def.loc(),
+        }
+    }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
diff --git a/crates/apollo-compiler/src/validation/field.rs b/crates/apollo-compiler/src/validation/field.rs
index fa35da5..cfb0575 100644
--- a/crates/apollo-compiler/src/validation/field.rs
+++ b/crates/apollo-compiler/src/validation/field.rs
@@ -14,6 +14,44 @@ pub fn validate_field(
         db.validate_directives(field.directives().to_vec(), hir::DirectiveLocation::Field);
     diagnostics.extend(db.validate_arguments(field.arguments().to_vec()));
 
+    let field_type = field.ty(db.upcast());
+    if field_type.is_some() {
+        diagnostics.extend(db.validate_selection_set(field.selection_set().clone()));
+    } else {
+        let help = format!(
+            "`{}` is not defined on `{}` type",
+            field.name(),
+            field.parent_obj.as_ref().unwrap(),
+        );
+        let field_name = field.name();
+        let diagnostic = ApolloDiagnostic::new(
+            db,
+            field.loc().into(),
+            DiagnosticData::UndefinedField {
+                field: field_name.into(),
+            },
+        )
+        .label(Label::new(
+            field.loc(),
+            format!("`{field_name}` field is not defined"),
+        ))
+        .help(help);
+
+        let parent_type_loc = db
+            .find_type_definition_by_name(field.parent_obj.clone().unwrap())
+            .and_then(|type_def| type_def.loc());
+
+        let diagnostic = if let Some(parent_type_loc) = parent_type_loc {
+            diagnostic.label(Label::new(
+                parent_type_loc,
+                format!("`{}` declared here", field.parent_obj.as_ref().unwrap()),
+            ))
+        } else {
+            diagnostic
+        };
+        diagnostics.push(diagnostic);
+    }
+
     diagnostics
 }
 
@@ -61,13 +99,13 @@ pub fn validate_field_definitions(
                         field: field_name.into(),
                         original_definition: original_definition.into(),
                         redefined_definition: redefined_definition.into(),
-                    }
+                    },
                 )
-                .labels([
-                    Label::new(original_definition, format!("previous definition of `{field_name}` here")),
-                    Label::new(redefined_definition, format!("`{field_name}` redefined here")),
-                ])
-                .help(format!("`{field_name}` field must only be defined once in this input object definition."))
+                    .labels([
+                        Label::new(original_definition, format!("previous definition of `{field_name}` here")),
+                        Label::new(redefined_definition, format!("`{field_name}` redefined here")),
+                    ])
+                    .help(format!("`{field_name}` field must only be defined once in this input object definition."))
             );
         } else {
             seen.insert(field_name, field);
@@ -81,8 +119,8 @@ pub fn validate_field_definitions(
                         name: field.name().into(),
                         ty: field_ty.kind(),
                     })
-                    .label(Label::new(field.loc(), format!("this is of `{}` type", field_ty.kind())))
-                    .help(format!("Scalars, Objects, Interfaces, Unions and Enums are output types. Change `{}` field to return one of these output types.", field.name())),
+                        .label(Label::new(field.loc(), format!("this is of `{}` type", field_ty.kind())))
+                        .help(format!("Scalars, Objects, Interfaces, Unions and Enums are output types. Change `{}` field to return one of these output types.", field.name())),
                 );
             }
         } else if let Some(field_ty_loc) = field.ty().loc() {
diff --git a/crates/apollo-compiler/src/validation/fragment.rs b/crates/apollo-compiler/src/validation/fragment.rs
index e36ac9c..6022673 100644
--- a/crates/apollo-compiler/src/validation/fragment.rs
+++ b/crates/apollo-compiler/src/validation/fragment.rs
@@ -10,8 +10,48 @@ pub fn validate_fragment_definitions(
             def.directives().to_vec(),
             DirectiveLocation::FragmentDefinition,
         ));
-        diagnostics.extend(db.validate_selection_set(def.selection_set().clone()));
+
+        let fragment_type_def = db.find_type_definition_by_name(def.type_condition().to_string());
+        // Make sure the fragment type exists in the schema
+        if fragment_type_def.is_some() {
+            // TODO handle cases where the type does not support fragments (Enum, Scalar...)
+            diagnostics.extend(db.validate_selection_set(def.selection_set().clone()));
+        }
     }
 
     diagnostics
 }
+
+#[cfg(test)]
+mod test {
+    use crate::ApolloCompiler;
+
+    #[test]
+    fn it_validates_fields_in_fragment_definitions() {
+        let input = r#"
+type Query {
+  name: String
+  topProducts: Product
+}
+
+type Product {
+  inStock: Boolean
+  name: String
+}
+
+fragment XY on Product {
+  notExistingField
+}
+"#;
+
+        let mut compiler = ApolloCompiler::new();
+        compiler.add_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
+        for diagnostic in &diagnostics {
+            println!("{diagnostic}");
+        }
+
+        assert_eq!(diagnostics.len(), 1)
+    }
+}
diff --git a/crates/apollo-compiler/src/validation/operation.rs b/crates/apollo-compiler/src/validation/operation.rs
index d3e25c4..006557d 100644
--- a/crates/apollo-compiler/src/validation/operation.rs
+++ b/crates/apollo-compiler/src/validation/operation.rs
@@ -16,7 +16,12 @@ pub fn validate_operation_definitions(
         diagnostics
             .extend(db.validate_directives(def.directives().to_vec(), def.operation_ty().into()));
         diagnostics.extend(db.validate_variable_definitions(def.variables.as_ref().clone()));
-        diagnostics.extend(db.validate_selection_set(def.selection_set().clone()));
+
+        // Validate the Selection Set recursively
+        // Check that the root type exists
+        if def.object_type(db.upcast()).is_some() {
+            diagnostics.extend(db.validate_selection_set(def.selection_set().clone()));
+        }
     }
 
     let subscription_operations = db.upcast().subscription_operations(file_id);
@@ -93,50 +98,6 @@ pub fn validate_operation_definitions(
         }
     }
 
-    // Fields must exist on the type being queried.
-    for op in operations.iter() {
-        for selection in op.selection_set().selection() {
-            let obj_type = op.object_type(db.upcast());
-            let obj_name = obj_type.as_ref().map(|obj| obj.name().to_owned());
-            if let hir::Selection::Field(field) = selection {
-                if field.ty(db.upcast()).is_none() {
-                    let field_name = field.name();
-                    let help = if let Some(actual_name) = obj_name {
-                        format!("`{field_name}` is not defined on `{actual_name}` type")
-                    } else {
-                        format!(
-                            "`{}` is not defined on the current {} root operation type.",
-                            field_name,
-                            op.operation_ty()
-                        )
-                    };
-                    let diagnostic = ApolloDiagnostic::new(
-                        db,
-                        field.loc().into(),
-                        DiagnosticData::UndefinedField {
-                            field: field_name.into(),
-                        },
-                    )
-                    .label(Label::new(
-                        field.loc(),
-                        format!("`{field_name}` field is not defined"),
-                    ))
-                    .help(help);
-
-                    let diagnostic = if let Some(ty) = obj_type {
-                        diagnostic.label(Label::new(
-                            ty.loc(),
-                            format!("`{}` declared here", ty.name()),
-                        ))
-                    } else {
-                        diagnostic
-                    };
-                    diagnostics.push(diagnostic)
-                }
-            }
-        }
-    }
-
     diagnostics
 }
 
@@ -317,51 +278,25 @@ query {
 
 query getPet {
   cat {
-    owner {
-      name
-    }
+    name
   }
 }
 
-query getPet {
+query getOtherPet {
   cat {
-    treat
-  }
-}
-
-subscription sub {
-  newMessage {
-    body
-    sender
+    nickname
   }
-  disallowedSecondRootField
 }
 
 type Query {
-  cat: Pet
+  cat: Cat
 }
 
-type Subscription {
-  newMessage: Result
-}
-
-interface Pet {
-  name: String
-}
-
-type Dog implements Pet {
-  name: String
-  nickname: String
-  barkVolume: Int
-}
-
-type Cat implements Pet {
+type Cat {
   name: String
   nickname: String
   meowVolume: Int
 }
-
-union CatOrDog = Cat | Dog
 "#;
         let mut compiler = ApolloCompiler::new();
         compiler.add_document(input, "schema.graphql");
@@ -370,7 +305,7 @@ union CatOrDog = Cat | Dog
         for diagnostic in &diagnostics {
             println!("{diagnostic}")
         }
-        assert_eq!(diagnostics.len(), 5)
+        assert_eq!(diagnostics.len(), 1)
     }
 
     #[test]
@@ -384,9 +319,8 @@ query getName {
 
 query getName {
   cat {
-    owner {
-      name
-    }
+    name
+    nickname
   }
 }
 
@@ -398,6 +332,7 @@ union CatOrDog = Cat | Dog
 
 interface Pet {
   name: String
+  nickname: String
 }
 
 type Dog implements Pet {
@@ -431,11 +366,9 @@ query getCatName {
   }
 }
 
-query getOwnerName {
+query getPetNickname {
   cat {
-    owner {
-      name
-    }
+    nickname
   }
 }
 
@@ -447,6 +380,7 @@ union CatOrDog = Cat | Dog
 
 interface Pet {
   name: String
+  nickname: String
 }
 
 type Dog implements Pet {
@@ -465,6 +399,9 @@ type Cat implements Pet {
         compiler.add_document(input, "schema.graphql");
 
         let diagnostics = compiler.validate();
+        for diagnostic in &diagnostics {
+            println!("{diagnostic}")
+        }
         assert!(diagnostics.is_empty());
     }
 
@@ -508,15 +445,19 @@ type Cat implements Pet {
             println!("{diagnostic}")
         }
 
-        assert_eq!(diagnostics.len(), 2)
+        assert_eq!(diagnostics.len(), 1)
     }
 
     #[test]
     fn it_validates_fields_in_operations() {
         let input = r#"
 query getProduct {
-  size
-  weight
+  name
+  noName
+  topProducts {
+    inStock
+    price
+  }
 }
 
 type Query {
@@ -525,11 +466,9 @@ type Query {
 }
 
 type Product {
-  inStock: Boolean @join__field(graph: INVENTORY)
-  name: String @join__field(graph: PRODUCTS)
+  inStock: Boolean
+  name: String
 }
-
-directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet) on FIELD_DEFINITION
 "#;
 
         let mut compiler = ApolloCompiler::new();
diff --git a/crates/apollo-compiler/src/validation/selection.rs b/crates/apollo-compiler/src/validation/selection.rs
index 80f4dfc..311018e 100644
--- a/crates/apollo-compiler/src/validation/selection.rs
+++ b/crates/apollo-compiler/src/validation/selection.rs
@@ -11,12 +11,10 @@ pub fn validate_selection(
     for sel in selection.iter() {
         match sel {
             hir::Selection::Field(field) => {
-                if !field.selection_set().selection().is_empty() {
-                    diagnostics
-                        .extend(db.validate_selection(field.selection_set().selection.clone()))
-                }
                 diagnostics.extend(db.validate_field(field.clone()));
             }
+
+            // TODO handle fragment spreads on invalid parent types
             hir::Selection::FragmentSpread(frag) => diagnostics.extend(db.validate_directives(
                 frag.directives().to_vec(),
                 hir::DirectiveLocation::FragmentSpread,
@@ -35,5 +33,11 @@ pub fn validate_selection_set(
     db: &dyn ValidationDatabase,
     selection_set: hir::SelectionSet,
 ) -> Vec<ApolloDiagnostic> {
-    db.validate_selection(selection_set.selection)
+    let mut diagnostics = Vec::new();
+
+    // TODO handle Enums, Scalar (no selection set content allowed), Unions
+
+    diagnostics.extend(db.validate_selection(selection_set.selection));
+
+    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/variable.rs b/crates/apollo-compiler/src/validation/variable.rs
index 4071200..205b46f 100644
--- a/crates/apollo-compiler/src/validation/variable.rs
+++ b/crates/apollo-compiler/src/validation/variable.rs
@@ -187,6 +187,7 @@ fragment fragmentOne on Query {
 
 type Query {
   topProducts: Product
+  profilePic(size: Int): String
 }
 
 type Product {
diff --git a/crates/apollo-parser/src/parser/generated/syntax_kind.rs b/crates/apollo-parser/src/parser/generated/syntax_kind.rs
index 763190d..9cd0b9e 100644
--- a/crates/apollo-parser/src/parser/generated/syntax_kind.rs
+++ b/crates/apollo-parser/src/parser/generated/syntax_kind.rs
@@ -76,6 +76,7 @@ pub enum SyntaxKind {
     IDENT,
     WHITESPACE,
     COMMENT,
+    ERROR,
     NAME,
     INTEGER_PART,
     NEGATIVE_SIGN,
diff --git a/crates/apollo-parser/src/parser/mod.rs b/crates/apollo-parser/src/parser/mod.rs
index 68b6025..1af9de6 100644
--- a/crates/apollo-parser/src/parser/mod.rs
+++ b/crates/apollo-parser/src/parser/mod.rs
@@ -181,7 +181,7 @@ impl<'a> Parser<'a> {
         }
 
         let token = self.pop();
-        self.builder.borrow_mut().token(kind, token.data());
+        self.push_ast(kind, token);
     }
 
     /// Create a parser limit error and push it into the error vector.
@@ -235,16 +235,20 @@ impl<'a> Parser<'a> {
         }
 
         let current = self.pop();
-        // we usually bump ignored after we pop a token, so make sure we also do
-        // this when we create an error and pop.
-        self.skip_ignored();
         let err = if current.kind == TokenKind::Eof {
             Error::eof(message, current.index())
         } else {
             // this needs to be the computed location
             Error::with_loc(message, current.data().to_string(), current.index())
         };
+
+        // Keep the error in the parse tree for position information
+        self.push_ast(SyntaxKind::ERROR, current);
         self.push_err(err);
+
+        // we usually skip ignored tokens after we pop each token, so make sure we also do
+        // this when we create an error and pop.
+        self.skip_ignored();
     }
 
     /// Consume the next token if it is `kind` or emit an error
@@ -561,4 +565,28 @@ mod tests {
         "##]];
         tree.assert_eq(&format!("{:#?}", ast.document().syntax));
     }
+
+    #[test]
+    fn tree_with_syntax_errors() {
+        use crate::ast::Definition;
+
+        // Some arbitrary token spam in incorrect places--this test uses
+        // valid tokens only
+        let source = r#"
+            garbage type Query implements X {
+                field(arg: Int): Int
+            } garbage :,, (|) interface X {}
+        "#;
+        let ast = Parser::new(source).parse();
+
+        let mut definitions = ast.document().definitions();
+        let query_def = definitions.next().unwrap();
+        let interface_def = definitions.next().unwrap();
+        assert_eq!(definitions.next(), None);
+        assert!(matches!(query_def, Definition::ObjectTypeDefinition(_)));
+        assert!(matches!(
+            interface_def,
+            Definition::InterfaceTypeDefinition(_)
+        ));
+    }
 }
diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs
index bb2c1b2..0b0025c 100644
--- a/xtask/src/ast_src.rs
+++ b/xtask/src/ast_src.rs
@@ -65,7 +65,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
         "INPUT_FIELD_DEFINITION",
     ],
     literals: &["INT", "FLOAT", "STRING"],
-    tokens: &["IDENT", "WHITESPACE", "COMMENT"],
+    tokens: &["IDENT", "WHITESPACE", "COMMENT", "ERROR"],
     // These are all the "DOCUMENT" items defined in the GraphQL spec --
     // https://spec.graphql.org/October2021/#sec-Appendix-Grammar-Summary.Document,
     // as well as items listed in the ungrammar file in the root directory.


Test patch:
diff --git a/crates/apollo-compiler/test_data/diagnostics/0001_duplicate_operatoin_names.graphql b/crates/apollo-compiler/test_data/diagnostics/0001_duplicate_operatoin_names.graphql
index b1ac364..dcc364e 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0001_duplicate_operatoin_names.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0001_duplicate_operatoin_names.graphql
@@ -1,15 +1,9 @@
 query getName {
-  cat {
-    name
-  }
+  cat
 }
 
 query getName {
-  cat {
-    owner {
-      name
-    }
-  }
+  cat
 }
 
 type Query {
diff --git a/crates/apollo-compiler/test_data/diagnostics/0001_duplicate_operatoin_names.txt b/crates/apollo-compiler/test_data/diagnostics/0001_duplicate_operatoin_names.txt
index aee8cd1..cf46bf3 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0001_duplicate_operatoin_names.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0001_duplicate_operatoin_names.txt
@@ -7,7 +7,7 @@
             file_id: FileId {
                 id: 0,
             },
-            offset: 46,
+            offset: 31,
             length: 7,
         },
         labels: [
@@ -26,7 +26,7 @@
                     file_id: FileId {
                         id: 0,
                     },
-                    offset: 46,
+                    offset: 31,
                     length: 7,
                 },
                 text: "`getName` redefined here",
@@ -49,7 +49,7 @@
                 file_id: FileId {
                     id: 0,
                 },
-                offset: 46,
+                offset: 31,
                 length: 7,
             },
         },
diff --git a/crates/apollo-compiler/test_data/diagnostics/0002_multiple_anonymous_operations.graphql b/crates/apollo-compiler/test_data/diagnostics/0002_multiple_anonymous_operations.graphql
index ee5985f..546049b 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0002_multiple_anonymous_operations.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0002_multiple_anonymous_operations.graphql
@@ -1,14 +1,10 @@
 query {
-  cat {
-    name
-  }
+  cat
 }
 
 mutation {
   addPet {
-    owner {
-      name
-    }
+    response
   }
 }
 
diff --git a/crates/apollo-compiler/test_data/diagnostics/0002_multiple_anonymous_operations.txt b/crates/apollo-compiler/test_data/diagnostics/0002_multiple_anonymous_operations.txt
index cffe8f0..8724923 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0002_multiple_anonymous_operations.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0002_multiple_anonymous_operations.txt
@@ -8,7 +8,7 @@
                 id: 1,
             },
             offset: 0,
-            length: 30,
+            length: 15,
         },
         labels: [
             Label {
@@ -17,7 +17,7 @@
                         id: 1,
                     },
                     offset: 0,
-                    length: 30,
+                    length: 15,
                 },
                 text: "provide a name for this definition",
             },
@@ -35,8 +35,8 @@
             file_id: FileId {
                 id: 1,
             },
-            offset: 32,
-            length: 56,
+            offset: 17,
+            length: 40,
         },
         labels: [
             Label {
@@ -44,8 +44,8 @@
                     file_id: FileId {
                         id: 1,
                     },
-                    offset: 32,
-                    length: 56,
+                    offset: 17,
+                    length: 40,
                 },
                 text: "provide a name for this definition",
             },
diff --git a/crates/apollo-compiler/test_data/diagnostics/0003_anonymous_and_named_operation.graphql b/crates/apollo-compiler/test_data/diagnostics/0003_anonymous_and_named_operation.graphql
index 80354f4..8d6bd73 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0003_anonymous_and_named_operation.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0003_anonymous_and_named_operation.graphql
@@ -1,14 +1,10 @@
 query {
-  cat {
-    name
-  }
+  cat
 }
 
 mutation getName {
   addPet {
-    owner {
-      name
-    }
+    response
   }
 }
 
diff --git a/crates/apollo-compiler/test_data/diagnostics/0003_anonymous_and_named_operation.txt b/crates/apollo-compiler/test_data/diagnostics/0003_anonymous_and_named_operation.txt
index 7cac0ce..517969b 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0003_anonymous_and_named_operation.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0003_anonymous_and_named_operation.txt
@@ -8,7 +8,7 @@
                 id: 2,
             },
             offset: 0,
-            length: 30,
+            length: 15,
         },
         labels: [
             Label {
@@ -17,7 +17,7 @@
                         id: 2,
                     },
                     offset: 0,
-                    length: 30,
+                    length: 15,
                 },
                 text: "provide a name for this definition",
             },
diff --git a/crates/apollo-compiler/test_data/diagnostics/0004_subscription_with_multiple_root_fields.graphql b/crates/apollo-compiler/test_data/diagnostics/0004_subscription_with_multiple_root_fields.graphql
index c5cf392..e8aef3d 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0004_subscription_with_multiple_root_fields.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0004_subscription_with_multiple_root_fields.graphql
@@ -8,6 +8,7 @@ subscription sub {
 
 type Subscription {
   newMessage: Result
+  disallowedSecondRootField: String
 }
 
 type Result {
diff --git a/crates/apollo-compiler/test_data/diagnostics/0004_subscription_with_multiple_root_fields.txt b/crates/apollo-compiler/test_data/diagnostics/0004_subscription_with_multiple_root_fields.txt
index 156cb65..e151cff 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0004_subscription_with_multiple_root_fields.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0004_subscription_with_multiple_root_fields.txt
@@ -36,44 +36,4 @@
             },
         },
     },
-    ApolloDiagnostic {
-        cache: {
-            3: "0004_subscription_with_multiple_root_fields.graphql",
-        },
-        location: DiagnosticLocation {
-            file_id: FileId {
-                id: 3,
-            },
-            offset: 60,
-            length: 25,
-        },
-        labels: [
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 3,
-                    },
-                    offset: 60,
-                    length: 25,
-                },
-                text: "`disallowedSecondRootField` field is not defined",
-            },
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 3,
-                    },
-                    offset: 89,
-                    length: 42,
-                },
-                text: "`Subscription` declared here",
-            },
-        ],
-        help: Some(
-            "`disallowedSecondRootField` is not defined on `Subscription` type",
-        ),
-        data: UndefinedField {
-            field: "disallowedSecondRootField",
-        },
-    },
 ]
diff --git a/crates/apollo-compiler/test_data/diagnostics/0005_subscription_with_multiple_root_fields_in_fragment_spreads.graphql b/crates/apollo-compiler/test_data/diagnostics/0005_subscription_with_multiple_root_fields_in_fragment_spreads.graphql
index a4e608a..c0d8464 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0005_subscription_with_multiple_root_fields_in_fragment_spreads.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0005_subscription_with_multiple_root_fields_in_fragment_spreads.graphql
@@ -12,6 +12,7 @@ fragment multipleSubscriptions on Subscription {
 
 type Subscription {
   newMessage: Result
+  disallowedSecondRootField: String
 }
 
 type Result {
diff --git a/crates/apollo-compiler/test_data/diagnostics/0011_syntactic_errors_from_parser.graphql b/crates/apollo-compiler/test_data/diagnostics/0011_syntactic_errors_from_parser.graphql
index 36c714e..207c226 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0011_syntactic_errors_from_parser.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0011_syntactic_errors_from_parser.graphql
@@ -11,4 +11,5 @@ type Query {
 
 type Product {
   name: String
+  birthday: String
 }
\ No newline at end of file
diff --git a/crates/apollo-compiler/test_data/diagnostics/0014_subscription_operation_without_subscription_schema_operation_type.txt b/crates/apollo-compiler/test_data/diagnostics/0014_subscription_operation_without_subscription_schema_operation_type.txt
index 97cf048..0a4438f 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0014_subscription_operation_without_subscription_schema_operation_type.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0014_subscription_operation_without_subscription_schema_operation_type.txt
@@ -37,34 +37,4 @@
             ty: "subscription",
         },
     },
-    ApolloDiagnostic {
-        cache: {
-            13: "0014_subscription_operation_without_subscription_schema_operation_type.graphql",
-        },
-        location: DiagnosticLocation {
-            file_id: FileId {
-                id: 13,
-            },
-            offset: 37,
-            length: 36,
-        },
-        labels: [
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 13,
-                    },
-                    offset: 37,
-                    length: 36,
-                },
-                text: "`newMessage` field is not defined",
-            },
-        ],
-        help: Some(
-            "`newMessage` is not defined on the current Subscription root operation type.",
-        ),
-        data: UndefinedField {
-            field: "newMessage",
-        },
-    },
 ]
diff --git a/crates/apollo-compiler/test_data/diagnostics/0015_mutation_operation_without_mutation_schema_definition.txt b/crates/apollo-compiler/test_data/diagnostics/0015_mutation_operation_without_mutation_schema_definition.txt
index d95a0f4..7567792 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0015_mutation_operation_without_mutation_schema_definition.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0015_mutation_operation_without_mutation_schema_definition.txt
@@ -29,34 +29,4 @@
             ty: "mutation",
         },
     },
-    ApolloDiagnostic {
-        cache: {
-            14: "0015_mutation_operation_without_mutation_schema_definition.graphql",
-        },
-        location: DiagnosticLocation {
-            file_id: FileId {
-                id: 14,
-            },
-            offset: 31,
-            length: 41,
-        },
-        labels: [
-            Label {
-                location: DiagnosticLocation {
-                    file_id: FileId {
-                        id: 14,
-                    },
-                    offset: 31,
-                    length: 41,
-                },
-                text: "`addPet` field is not defined",
-            },
-        ],
-        help: Some(
-            "`addPet` is not defined on the current Mutation root operation type.",
-        ),
-        data: UndefinedField {
-            field: "addPet",
-        },
-    },
 ]
diff --git a/crates/apollo-parser/test_data/parser/err/0024_document_with_incorrect_definition.txt b/crates/apollo-parser/test_data/parser/err/0024_document_with_incorrect_definition.txt
index a55c06d..bce5220 100644
--- a/crates/apollo-parser/test_data/parser/err/0024_document_with_incorrect_definition.txt
+++ b/crates/apollo-parser/test_data/parser/err/0024_document_with_incorrect_definition.txt
@@ -1,3 +1,4 @@
-- DOCUMENT@0..0
+- DOCUMENT@0..21
+    - ERROR@0..21 "awsas8d2934213hkj0987"
 - ERROR@0:21 "expected definition" awsas8d2934213hkj0987
 recursion limit: 4096, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0025_document_with_incorrect_definition_and_selection_set.txt b/crates/apollo-parser/test_data/parser/err/0025_document_with_incorrect_definition_and_selection_set.txt
index 2fc7c67..a7cc237 100644
--- a/crates/apollo-parser/test_data/parser/err/0025_document_with_incorrect_definition_and_selection_set.txt
+++ b/crates/apollo-parser/test_data/parser/err/0025_document_with_incorrect_definition_and_selection_set.txt
@@ -1,17 +1,18 @@
-- DOCUMENT@0..27
-    - WHITESPACE@0..2 "\n\n"
-    - OPERATION_DEFINITION@2..27
-        - SELECTION_SET@2..27
-            - L_CURLY@2..3 "{"
-            - WHITESPACE@3..8 "\n    "
-            - FIELD@8..11
-                - NAME@8..11
-                    - IDENT@8..11 "pet"
-            - WHITESPACE@11..16 "\n    "
-            - FIELD@16..25
-                - NAME@16..25
-                    - IDENT@16..25 "faveSnack"
-            - WHITESPACE@25..26 "\n"
-            - R_CURLY@26..27 "}"
+- DOCUMENT@0..41
+    - ERROR@0..14 "uasdf21230jkdw"
+    - WHITESPACE@14..16 "\n\n"
+    - OPERATION_DEFINITION@16..41
+        - SELECTION_SET@16..41
+            - L_CURLY@16..17 "{"
+            - WHITESPACE@17..22 "\n    "
+            - FIELD@22..25
+                - NAME@22..25
+                    - IDENT@22..25 "pet"
+            - WHITESPACE@25..30 "\n    "
+            - FIELD@30..39
+                - NAME@30..39
+                    - IDENT@30..39 "faveSnack"
+            - WHITESPACE@39..40 "\n"
+            - R_CURLY@40..41 "}"
 - ERROR@0:14 "expected definition" uasdf21230jkdw
 recursion limit: 4096, high: 3
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0026_invalid_definition_squished_between_two_valid_definitions.txt b/crates/apollo-parser/test_data/parser/err/0026_invalid_definition_squished_between_two_valid_definitions.txt
index b03b322..0ac9a05 100644
--- a/crates/apollo-parser/test_data/parser/err/0026_invalid_definition_squished_between_two_valid_definitions.txt
+++ b/crates/apollo-parser/test_data/parser/err/0026_invalid_definition_squished_between_two_valid_definitions.txt
@@ -1,4 +1,4 @@
-- DOCUMENT@0..126
+- DOCUMENT@0..140
     - ENUM_TYPE_DEFINITION@0..97
         - enum_KW@0..4 "enum"
         - WHITESPACE@4..5 " "
@@ -40,19 +40,20 @@
             - WHITESPACE@95..96 "\n"
             - R_CURLY@96..97 "}"
     - WHITESPACE@97..99 "\n\n"
-    - WHITESPACE@99..101 "\n\n"
-    - OPERATION_DEFINITION@101..126
-        - SELECTION_SET@101..126
-            - L_CURLY@101..102 "{"
-            - WHITESPACE@102..107 "\n    "
-            - FIELD@107..110
-                - NAME@107..110
-                    - IDENT@107..110 "pet"
-            - WHITESPACE@110..115 "\n    "
-            - FIELD@115..124
-                - NAME@115..124
-                    - IDENT@115..124 "faveSnack"
-            - WHITESPACE@124..125 "\n"
-            - R_CURLY@125..126 "}"
+    - ERROR@99..113 "uasdf21230jkdw"
+    - WHITESPACE@113..115 "\n\n"
+    - OPERATION_DEFINITION@115..140
+        - SELECTION_SET@115..140
+            - L_CURLY@115..116 "{"
+            - WHITESPACE@116..121 "\n    "
+            - FIELD@121..124
+                - NAME@121..124
+                    - IDENT@121..124 "pet"
+            - WHITESPACE@124..129 "\n    "
+            - FIELD@129..138
+                - NAME@129..138
+                    - IDENT@129..138 "faveSnack"
+            - WHITESPACE@138..139 "\n"
+            - R_CURLY@139..140 "}"
 - ERROR@99:113 "expected definition" uasdf21230jkdw
 recursion limit: 4096, high: 3
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.txt b/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.txt
index 2e826ac..b9ec4b4 100644
--- a/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.txt
+++ b/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.txt
@@ -1,5 +1,7 @@
-- DOCUMENT@0..1
-    - WHITESPACE@0..1 " "
+- DOCUMENT@0..10
+    - ERROR@0..6 "extend"
+    - WHITESPACE@6..7 " "
+    - ERROR@7..10 "Cat"
 - ERROR@0:6 "Invalid Type System Extension. This extension cannot be applied." extend
 - ERROR@7:10 "expected definition" Cat
 recursion limit: 4096, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.txt b/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.txt
index 715bbb5..d8aa085 100644
--- a/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.txt
+++ b/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.txt
@@ -1,27 +1,29 @@
-- DOCUMENT@0..52
-    - WHITESPACE@0..1 " "
-    - WHITESPACE@1..3 "\n\n"
-    - INTERFACE_TYPE_EXTENSION@3..52
-        - extend_KW@3..9 "extend"
-        - WHITESPACE@9..10 " "
-        - interface_KW@10..19 "interface"
-        - WHITESPACE@19..20 " "
-        - NAME@20..31
-            - IDENT@20..31 "NamedEntity"
-        - WHITESPACE@31..32 " "
-        - FIELDS_DEFINITION@32..52
-            - L_CURLY@32..33 "{"
-            - WHITESPACE@33..38 "\n    "
-            - FIELD_DEFINITION@38..50
-                - NAME@38..42
-                    - IDENT@38..42 "name"
-                - COLON@42..43 ":"
-                - WHITESPACE@43..44 " "
-                - NAMED_TYPE@44..50
-                    - NAME@44..50
-                        - IDENT@44..50 "String"
-            - WHITESPACE@50..51 "\n"
-            - R_CURLY@51..52 "}"
+- DOCUMENT@0..61
+    - ERROR@0..6 "extend"
+    - WHITESPACE@6..7 " "
+    - ERROR@7..10 "Cat"
+    - WHITESPACE@10..12 "\n\n"
+    - INTERFACE_TYPE_EXTENSION@12..61
+        - extend_KW@12..18 "extend"
+        - WHITESPACE@18..19 " "
+        - interface_KW@19..28 "interface"
+        - WHITESPACE@28..29 " "
+        - NAME@29..40
+            - IDENT@29..40 "NamedEntity"
+        - WHITESPACE@40..41 " "
+        - FIELDS_DEFINITION@41..61
+            - L_CURLY@41..42 "{"
+            - WHITESPACE@42..47 "\n    "
+            - FIELD_DEFINITION@47..59
+                - NAME@47..51
+                    - IDENT@47..51 "name"
+                - COLON@51..52 ":"
+                - WHITESPACE@52..53 " "
+                - NAMED_TYPE@53..59
+                    - NAME@53..59
+                        - IDENT@53..59 "String"
+            - WHITESPACE@59..60 "\n"
+            - R_CURLY@60..61 "}"
 - ERROR@0:6 "Invalid Type System Extension. This extension cannot be applied." extend
 - ERROR@7:10 "expected definition" Cat
 recursion limit: 4096, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0030_operation_definition_with_description.txt b/crates/apollo-parser/test_data/parser/err/0030_operation_definition_with_description.txt
index 9fb8e06..c8c635a 100644
--- a/crates/apollo-parser/test_data/parser/err/0030_operation_definition_with_description.txt
+++ b/crates/apollo-parser/test_data/parser/err/0030_operation_definition_with_description.txt
@@ -1,15 +1,16 @@
-- DOCUMENT@0..15
-    - WHITESPACE@0..1 "\n"
-    - OPERATION_DEFINITION@1..15
-        - OPERATION_TYPE@1..6
-            - query_KW@1..6 "query"
-        - WHITESPACE@6..7 " "
-        - NAME@7..12
-            - IDENT@7..12 "empty"
-        - WHITESPACE@12..13 " "
-        - SELECTION_SET@13..15
-            - L_CURLY@13..14 "{"
-            - R_CURLY@14..15 "}"
+- DOCUMENT@0..108
+    - ERROR@0..93 "\"after this PR this should not be an issue: https://github.com/graphql/graphql-spec/pull/892\""
+    - WHITESPACE@93..94 "\n"
+    - OPERATION_DEFINITION@94..108
+        - OPERATION_TYPE@94..99
+            - query_KW@94..99 "query"
+        - WHITESPACE@99..100 " "
+        - NAME@100..105
+            - IDENT@100..105 "empty"
+        - WHITESPACE@105..106 " "
+        - SELECTION_SET@106..108
+            - L_CURLY@106..107 "{"
+            - R_CURLY@107..108 "}"
 - ERROR@0:93 "expected an Operation Type or a Selection Set" "after this PR this should not be an issue: https://github.com/graphql/graphql-spec/pull/892"
 - ERROR@107:108 "expected at least one Selection in Selection Set" }
 recursion limit: 4096, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0040_operation_definition_missing_selection_set.txt b/crates/apollo-parser/test_data/parser/err/0040_operation_definition_missing_selection_set.txt
index 2bf2066..d710be0 100644
--- a/crates/apollo-parser/test_data/parser/err/0040_operation_definition_missing_selection_set.txt
+++ b/crates/apollo-parser/test_data/parser/err/0040_operation_definition_missing_selection_set.txt
@@ -1,10 +1,11 @@
-- DOCUMENT@0..17
-    - OPERATION_DEFINITION@0..17
+- DOCUMENT@0..18
+    - OPERATION_DEFINITION@0..18
         - OPERATION_TYPE@0..5
             - query_KW@0..5 "query"
         - WHITESPACE@5..6 " "
         - NAME@6..16
             - IDENT@6..16 "__typename"
         - WHITESPACE@16..17 " "
+        - ERROR@17..18 "}"
 - ERROR@17:18 "expected a Selection Set" }
 recursion limit: 4096, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0041_operation_definition_with_missing_selection_set.txt b/crates/apollo-parser/test_data/parser/err/0041_operation_definition_with_missing_selection_set.txt
index b145bfb..4913f56 100644
--- a/crates/apollo-parser/test_data/parser/err/0041_operation_definition_with_missing_selection_set.txt
+++ b/crates/apollo-parser/test_data/parser/err/0041_operation_definition_with_missing_selection_set.txt
@@ -1,9 +1,10 @@
-- DOCUMENT@0..16
-    - OPERATION_DEFINITION@0..16
+- DOCUMENT@0..19
+    - OPERATION_DEFINITION@0..19
         - OPERATION_TYPE@0..5
             - query_KW@0..5 "query"
         - WHITESPACE@5..6 " "
         - NAME@6..16
             - IDENT@6..16 "__typename"
+        - ERROR@16..19 "EOF"
 - ERROR@16:16 "expected a Selection Set" EOF
 recursion limit: 4096, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0042_document_with_incorrect_token.txt b/crates/apollo-parser/test_data/parser/err/0042_document_with_incorrect_token.txt
index b025b34..99349b9 100644
--- a/crates/apollo-parser/test_data/parser/err/0042_document_with_incorrect_token.txt
+++ b/crates/apollo-parser/test_data/parser/err/0042_document_with_incorrect_token.txt
@@ -1,43 +1,45 @@
-- DOCUMENT@0..81
-    - WHITESPACE@0..1 "\n"
-    - COMMENT@1..10 "# comment"
-    - WHITESPACE@10..11 "\n"
-    - OPERATION_DEFINITION@11..36
-        - SELECTION_SET@11..36
-            - L_CURLY@11..12 "{"
-            - WHITESPACE@12..17 "\n    "
-            - FIELD@17..20
-                - NAME@17..20
-                    - IDENT@17..20 "pet"
-            - WHITESPACE@20..25 "\n    "
-            - FIELD@25..34
-                - NAME@25..34
-                    - IDENT@25..34 "faveSnack"
-            - WHITESPACE@34..35 "\n"
-            - R_CURLY@35..36 "}"
-    - WHITESPACE@36..38 "\n\n"
-    - COMMENT@38..47 "# comment"
-    - WHITESPACE@47..48 "\n"
-    - WHITESPACE@48..50 "\n\n"
-    - OBJECT_TYPE_DEFINITION@50..81
-        - type_KW@50..54 "type"
-        - WHITESPACE@54..55 " "
-        - NAME@55..60
-            - IDENT@55..60 "Query"
-        - WHITESPACE@60..61 " "
-        - FIELDS_DEFINITION@61..81
-            - L_CURLY@61..62 "{"
-            - WHITESPACE@62..67 "\n    "
-            - FIELD_DEFINITION@67..79
-                - NAME@67..71
-                    - IDENT@67..71 "name"
-                - COLON@71..72 ":"
-                - WHITESPACE@72..73 " "
-                - NAMED_TYPE@73..79
-                    - NAME@73..79
-                        - IDENT@73..79 "String"
-            - WHITESPACE@79..80 "\n"
-            - R_CURLY@80..81 "}"
+- DOCUMENT@0..83
+    - ERROR@0..1 "@"
+    - WHITESPACE@1..2 "\n"
+    - COMMENT@2..11 "# comment"
+    - WHITESPACE@11..12 "\n"
+    - OPERATION_DEFINITION@12..37
+        - SELECTION_SET@12..37
+            - L_CURLY@12..13 "{"
+            - WHITESPACE@13..18 "\n    "
+            - FIELD@18..21
+                - NAME@18..21
+                    - IDENT@18..21 "pet"
+            - WHITESPACE@21..26 "\n    "
+            - FIELD@26..35
+                - NAME@26..35
+                    - IDENT@26..35 "faveSnack"
+            - WHITESPACE@35..36 "\n"
+            - R_CURLY@36..37 "}"
+    - WHITESPACE@37..39 "\n\n"
+    - COMMENT@39..48 "# comment"
+    - WHITESPACE@48..49 "\n"
+    - ERROR@49..50 "}"
+    - WHITESPACE@50..52 "\n\n"
+    - OBJECT_TYPE_DEFINITION@52..83
+        - type_KW@52..56 "type"
+        - WHITESPACE@56..57 " "
+        - NAME@57..62
+            - IDENT@57..62 "Query"
+        - WHITESPACE@62..63 " "
+        - FIELDS_DEFINITION@63..83
+            - L_CURLY@63..64 "{"
+            - WHITESPACE@64..69 "\n    "
+            - FIELD_DEFINITION@69..81
+                - NAME@69..73
+                    - IDENT@69..73 "name"
+                - COLON@73..74 ":"
+                - WHITESPACE@74..75 " "
+                - NAMED_TYPE@75..81
+                    - NAME@75..81
+                        - IDENT@75..81 "String"
+            - WHITESPACE@81..82 "\n"
+            - R_CURLY@82..83 "}"
 - ERROR@0:1 "expected a StringValue, Name or OperationDefinition" @
 - ERROR@49:50 "expected a StringValue, Name or OperationDefinition" }
 recursion limit: 4096, high: 3
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.txt b/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.txt
index 0e307d6..1b5d513 100644
--- a/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.txt
+++ b/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.txt
@@ -1,4 +1,4 @@
-- DOCUMENT@0..65
+- DOCUMENT@0..69
     - OBJECT_TYPE_DEFINITION@0..63
         - type_KW@0..4 "type"
         - WHITESPACE@4..5 " "
@@ -40,8 +40,12 @@
             - FIELD_DEFINITION@59..63
                 - NAME@59..63
                     - IDENT@59..63 "Type"
-    - WHITESPACE@63..64 "\n"
-    - WHITESPACE@64..65 "\n"
+    - ERROR@63..64 "]"
+    - ERROR@64..65 "]"
+    - ERROR@65..66 "]"
+    - WHITESPACE@66..67 "\n"
+    - ERROR@67..68 "}"
+    - WHITESPACE@68..69 "\n"
 - ERROR@22:23 "expected item type" ]
 - ERROR@64:65 "expected a type" ]
 - ERROR@64:65 "expected R_CURLY, got ]" ]
diff --git a/crates/apollo-parser/test_data/parser/err/0045_ignored_token_spans.graphql b/crates/apollo-parser/test_data/parser/err/0045_ignored_token_spans.graphql
new file mode 100644
index 0000000..03ac94f
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0045_ignored_token_spans.graphql
@@ -0,0 +1,9 @@
+cats
+# https://github.com/apollographql/apollo-rs/issues/325
+
+interface X {}
+
+cats
+type Query {
+    name: String
+}
diff --git a/crates/apollo-parser/test_data/parser/err/0045_ignored_token_spans.txt b/crates/apollo-parser/test_data/parser/err/0045_ignored_token_spans.txt
new file mode 100644
index 0000000..e0823e7
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0045_ignored_token_spans.txt
@@ -0,0 +1,40 @@
+- DOCUMENT@0..115
+    - ERROR@0..4 "cats"
+    - WHITESPACE@4..5 "\n"
+    - COMMENT@5..60 "# https://github.com/apollographql/apollo-rs/issues/325"
+    - WHITESPACE@60..62 "\n\n"
+    - INTERFACE_TYPE_DEFINITION@62..76
+        - interface_KW@62..71 "interface"
+        - WHITESPACE@71..72 " "
+        - NAME@72..73
+            - IDENT@72..73 "X"
+        - WHITESPACE@73..74 " "
+        - FIELDS_DEFINITION@74..76
+            - L_CURLY@74..75 "{"
+            - R_CURLY@75..76 "}"
+    - WHITESPACE@76..78 "\n\n"
+    - ERROR@78..82 "cats"
+    - WHITESPACE@82..83 "\n"
+    - OBJECT_TYPE_DEFINITION@83..114
+        - type_KW@83..87 "type"
+        - WHITESPACE@87..88 " "
+        - NAME@88..93
+            - IDENT@88..93 "Query"
+        - WHITESPACE@93..94 " "
+        - FIELDS_DEFINITION@94..114
+            - L_CURLY@94..95 "{"
+            - WHITESPACE@95..100 "\n    "
+            - FIELD_DEFINITION@100..112
+                - NAME@100..104
+                    - IDENT@100..104 "name"
+                - COLON@104..105 ":"
+                - WHITESPACE@105..106 " "
+                - NAMED_TYPE@106..112
+                    - NAME@106..112
+                        - IDENT@106..112 "String"
+            - WHITESPACE@112..113 "\n"
+            - R_CURLY@113..114 "}"
+    - WHITESPACE@114..115 "\n"
+- ERROR@0:4 "expected definition" cats
+- ERROR@78:82 "expected definition" cats
+recursion limit: 4096, high: 0
\ No newline at end of file