diff --git a/modules/core/src/main/scala/parser.scala b/modules/core/src/main/scala/parser.scala index cf9e15b3..5349bfb1 100644 --- a/modules/core/src/main/scala/parser.scala +++ b/modules/core/src/main/scala/parser.scala @@ -348,7 +348,7 @@ object GraphQLParser { } def InlineFragment(n: Int): Parser[Ast.Selection.InlineFragment] = - ((TypeCondition.? ~ Directives).with1 ~ SelectionSetN(n)).map { + ((TypeCondition.backtrack.? ~ Directives).with1 ~ SelectionSetN(n)).map { case ((cond, dirs), sel) => Ast.Selection.InlineFragment(cond, dirs, sel) } @@ -371,7 +371,7 @@ object GraphQLParser { (Name <* punctuation(":")) ~ Value lazy val FragmentName: Parser[Ast.Name] = - not(string("on")).with1 *> Name + not(string("on") <* not(charIn(nameSubsequent))).with1 *> Name lazy val FragmentDefinition: Parser[Ast.FragmentDefinition] = ((keyword("fragment") *> FragmentName) ~ TypeCondition ~ Directives ~ SelectionSet).map { @@ -382,11 +382,12 @@ object GraphQLParser { keyword("on") *> NamedType lazy val NullValue: Parser[Ast.Value.NullValue.type] = - keyword("null").as(Ast.Value.NullValue) + keyword("null").backtrack.as(Ast.Value.NullValue) lazy val EnumValue: Parser[Ast.Value.EnumValue] = - (not(string("true") | string("false") | string("null")).with1 *> Name) - .map(Ast.Value.EnumValue.apply) + (not( + (string("true") | string("false") | string("null")) <* not( + charIn(nameSubsequent))).with1 *> Name).map(Ast.Value.EnumValue.apply) def ListValue(n: Int): Parser[Ast.Value.ListValue] = token( @@ -413,7 +414,8 @@ object GraphQLParser { } lazy val BooleanValue: Parser[Ast.Value.BooleanValue] = - token(booleanLiteral).map(Ast.Value.BooleanValue.apply) + token((booleanLiteral <* not(charIn(nameSubsequent))).backtrack) + .map(Ast.Value.BooleanValue.apply) def ObjectField(n: Int): Parser[(Ast.Name, Ast.Value)] = (Name <* punctuation(":")) ~ ValueN(n) diff --git a/modules/core/src/test/scala/parser/ParserSuite.scala b/modules/core/src/test/scala/parser/ParserSuite.scala index 7697f8aa..22357520 100644 --- a/modules/core/src/test/scala/parser/ParserSuite.scala +++ b/modules/core/src/test/scala/parser/ParserSuite.scala @@ -980,6 +980,85 @@ final class ParserSuite extends CatsEffectSuite { assertEquals(parser.parseText("query { # inner\n x } # a\n# b"), Result(expected)) } + test("fragment name that starts with 'on'") { + val query = """ + query { x { ...onlyFriends } } + fragment onlyFriends on X { name } + """ + + val expected = + List( + Operation( + Query, + None, + Nil, + Nil, + List( + Field(None, Name("x"), Nil, Nil, List(FragmentSpread(Name("onlyFriends"), Nil))))), + FragmentDefinition( + Name("onlyFriends"), + Named(Name("X")), + Nil, + List(Field(None, Name("name"), Nil, Nil, Nil))) + ) + + assertEquals(parser.parseText(query), Result(expected)) + } + + test("fragment name 'on' is still rejected") { + assert(parser.parseText("fragment on on X { name }").hasValue == false) + } + + test("enum values that start with 'true', 'false' or 'null'") { + val query = "query { x(a: trueStory, b: falseAlarm, c: nullable) }" + + val expected = + Operation( + Query, + None, + Nil, + Nil, + List( + Field( + None, + Name("x"), + List( + (Name("a"), EnumValue(Name("trueStory"))), + (Name("b"), EnumValue(Name("falseAlarm"))), + (Name("c"), EnumValue(Name("nullable"))) + ), + Nil, + Nil + )) + ) + + assertEquals(parser.parseText(query), Result(List(expected))) + } + + test("bare keywords are still literals, not enum values") { + val query = "query { x(a: true, b: false, c: null) }" + + val expected = + Operation( + Query, + None, + Nil, + Nil, + List( + Field( + None, + Name("x"), + List( + (Name("a"), BooleanValue(true)), + (Name("b"), BooleanValue(false)), + (Name("c"), NullValue) + ), + Nil, + Nil))) + + assertEquals(parser.parseText(query), Result(List(expected))) + } + def mkParser( maxSelectionDepth: Int = GraphQLParser.defaultConfig.maxSelectionDepth, maxSelectionWidth: Int = GraphQLParser.defaultConfig.maxSelectionWidth,