From a0b2de04b1ecec70a5b9e1c8e749405d5bc80710 Mon Sep 17 00:00:00 2001 From: Daniel Pepper Date: Fri, 10 Jul 2026 15:32:49 -0700 Subject: [PATCH] Fix inline fragments without a space before "on" `...on Type` (no space) is valid GraphQL for an inline fragment, but Client#parse's fragment-spread regex matched it as a spread referencing a Ruby constant named `On`, since it only excluded whitespace between the dots and the name, not the specific "on" keyword. Adds a negative lookahead: GraphQL forbids naming a fragment "on" (FragmentName : Name but not "on"), so "...on" followed by a word boundary is always an inline fragment, never ambiguous with a constant reference. --- lib/graphql/client.rb | 8 ++++++-- test/test_client.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/graphql/client.rb b/lib/graphql/client.rb index dcc4b0c..7cd3014 100644 --- a/lib/graphql/client.rb +++ b/lib/graphql/client.rb @@ -153,8 +153,12 @@ def parse(str, filename = nil, lineno = nil) # Replace Ruby constant reference with GraphQL fragment names, # while populating `definition_dependencies` with - # GraphQL Fragment ASTs which this operation depends on - str = str.gsub(/\.\.\.([a-zA-Z0-9_]+(::[a-zA-Z0-9_]+)*)/) do + # GraphQL Fragment ASTs which this operation depends on. + # + # GraphQL forbids naming a fragment "on", so "...on" followed by a word + # boundary is unambiguously an inline fragment, never a spread + # referencing constant "On". + str = str.gsub(/\.\.\.(?!on\b)([a-zA-Z0-9_]+(::[a-zA-Z0-9_]+)*)/) do match = Regexp.last_match const_name = match[1] diff --git a/test/test_client.rb b/test/test_client.rb index da38ff7..d879048 100644 --- a/test/test_client.rb +++ b/test/test_client.rb @@ -159,6 +159,40 @@ def test_client_parse_anonymous_query assert_equal(query_string, Temp::UserQuery.document.to_query_string) end + def test_client_parse_inline_fragment_without_space_before_on + Temp.const_set :NodeQuery, @client.parse(<<-'GRAPHQL') + query { + node(id: "1") { + id + ...on User { + name + } + ... on Organization { + name + } + } + } + GRAPHQL + + query_string = <<-'GRAPHQL'.gsub(/^ /, "").chomp + query TestClient__Temp__NodeQuery { + node(id: "1") { + __typename + id + ... on User { + name + } + ... on Organization { + name + } + } + } + GRAPHQL + + assert_equal(query_string, @client.document.to_query_string) + assert_equal(query_string, Temp::NodeQuery.document.to_query_string) + end + def test_client_parse_query_document Temp.const_set :UserDocument, @client.parse(<<-'GRAPHQL') query GetUser {