From da7e0236173076ab567f1e67d21c751a9515a698 Mon Sep 17 00:00:00 2001 From: Matthew Stanton Date: Wed, 3 Jun 2026 13:44:14 -0400 Subject: [PATCH 1/6] Escape generated snippet apostrophes Signed-off-by: Matthew Stanton --- lib/cucumber/glue/snippet.rb | 6 +++++- spec/cucumber/glue/snippet_spec.rb | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/cucumber/glue/snippet.rb b/lib/cucumber/glue/snippet.rb index f3995c1647..fc23f1db6d 100644 --- a/lib/cucumber/glue/snippet.rb +++ b/lib/cucumber/glue/snippet.rb @@ -91,7 +91,7 @@ def typed_pattern def to_s header = generated_expressions.each_with_index.map do |expr, i| prefix = i.zero? ? '' : '# ' - "#{prefix}#{code_keyword}('#{expr.source}') do#{parameters(expr)}" + "#{prefix}#{code_keyword}('#{escaped_expression_source(expr)}') do#{parameters(expr)}" end.join("\n") body = <<~DOC.chomp @@ -108,6 +108,10 @@ def parameters(expr) parameter_names.empty? ? '' : " |#{parameter_names.join(', ')}|" end + def escaped_expression_source(expr) + expr.source.gsub(/['\\]/) { |char| "\\#{char}" } + end + def self.description 'Cucumber Expressions' end diff --git a/spec/cucumber/glue/snippet_spec.rb b/spec/cucumber/glue/snippet_spec.rb index 651426bfa3..df662bcac7 100644 --- a/spec/cucumber/glue/snippet_spec.rb +++ b/spec/cucumber/glue/snippet_spec.rb @@ -180,6 +180,17 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end + + it 'escapes apostrophes in generated cucumber expression snippets' do + @step_text = "Lucy hears Sean's message" + cucumber_output = <<~CUKE.chomp + Given('Lucy hears Sean\\'s message') do + pending # Write code here that turns the phrase above into concrete actions + end + CUKE + + expect(snippet.to_s).to eq(cucumber_output) + end end end end From b07fbea3d4e32316bf69cf4b18c966cdf8418140 Mon Sep 17 00:00:00 2001 From: Matthew Stanton Date: Wed, 3 Jun 2026 14:14:38 -0400 Subject: [PATCH 2/6] Use double quotes for apostrophe snippets Signed-off-by: Matthew Stanton --- lib/cucumber/glue/snippet.rb | 9 ++++++--- spec/cucumber/glue/snippet_spec.rb | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/cucumber/glue/snippet.rb b/lib/cucumber/glue/snippet.rb index fc23f1db6d..b15df99893 100644 --- a/lib/cucumber/glue/snippet.rb +++ b/lib/cucumber/glue/snippet.rb @@ -91,7 +91,7 @@ def typed_pattern def to_s header = generated_expressions.each_with_index.map do |expr, i| prefix = i.zero? ? '' : '# ' - "#{prefix}#{code_keyword}('#{escaped_expression_source(expr)}') do#{parameters(expr)}" + "#{prefix}#{code_keyword}(#{quoted_expression_source(expr)}) do#{parameters(expr)}" end.join("\n") body = <<~DOC.chomp @@ -108,8 +108,11 @@ def parameters(expr) parameter_names.empty? ? '' : " |#{parameter_names.join(', ')}|" end - def escaped_expression_source(expr) - expr.source.gsub(/['\\]/) { |char| "\\#{char}" } + def quoted_expression_source(expr) + source = expr.source + return "'#{source.gsub(/['\\]/) { |char| "\\#{char}" }}'" unless source.include?("'") + + "\"#{source.gsub(/["\\]/) { |char| "\\#{char}" }}\"" end def self.description diff --git a/spec/cucumber/glue/snippet_spec.rb b/spec/cucumber/glue/snippet_spec.rb index df662bcac7..f183876fc1 100644 --- a/spec/cucumber/glue/snippet_spec.rb +++ b/spec/cucumber/glue/snippet_spec.rb @@ -181,10 +181,10 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end - it 'escapes apostrophes in generated cucumber expression snippets' do + it 'uses double quotes when generated cucumber expression snippets contain apostrophes' do @step_text = "Lucy hears Sean's message" cucumber_output = <<~CUKE.chomp - Given('Lucy hears Sean\\'s message') do + Given("Lucy hears Sean's message") do pending # Write code here that turns the phrase above into concrete actions end CUKE From a05700c408f7181dad43cff6fc72193de4e8fae2 Mon Sep 17 00:00:00 2001 From: Matthew Stanton Date: Wed, 3 Jun 2026 14:20:12 -0400 Subject: [PATCH 3/6] Harden double-quoted snippet escaping Signed-off-by: Matthew Stanton --- lib/cucumber/glue/snippet.rb | 6 ++++-- spec/cucumber/glue/snippet_spec.rb | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/cucumber/glue/snippet.rb b/lib/cucumber/glue/snippet.rb index b15df99893..31bff76c14 100644 --- a/lib/cucumber/glue/snippet.rb +++ b/lib/cucumber/glue/snippet.rb @@ -110,9 +110,11 @@ def parameters(expr) def quoted_expression_source(expr) source = expr.source - return "'#{source.gsub(/['\\]/) { |char| "\\#{char}" }}'" unless source.include?("'") - "\"#{source.gsub(/["\\]/) { |char| "\\#{char}" }}\"" + return "'#{source}'" unless source.include?("'") + + escaped_source = source.gsub(/["\\#]/) { |char| "\\#{char}" } + "\"#{escaped_source}\"" end def self.description diff --git a/spec/cucumber/glue/snippet_spec.rb b/spec/cucumber/glue/snippet_spec.rb index f183876fc1..3c7cf76873 100644 --- a/spec/cucumber/glue/snippet_spec.rb +++ b/spec/cucumber/glue/snippet_spec.rb @@ -181,7 +181,7 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end - it 'uses double quotes when generated cucumber expression snippets contain apostrophes' do + it 'uses double quotes for generated cucumber expression snippets with apostrophes' do @step_text = "Lucy hears Sean's message" cucumber_output = <<~CUKE.chomp Given("Lucy hears Sean's message") do @@ -191,6 +191,17 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end + + it 'escapes interpolation markers in double quoted cucumber expression snippets' do + @step_text = 'Lucy hears Sean\'s #@message' + cucumber_output = <<~'CUKE'.chomp + Given("Lucy hears Sean's \#@message") do + pending # Write code here that turns the phrase above into concrete actions + end + CUKE + + expect(snippet.to_s).to eq(cucumber_output) + end end end end From 542b1e709379cc4710013ccd1a1d1fb5c63e9ce0 Mon Sep 17 00:00:00 2001 From: Matthew Stanton Date: Wed, 3 Jun 2026 14:46:43 -0400 Subject: [PATCH 4/6] Preserve backslashes in generated snippets Signed-off-by: Matthew Stanton --- lib/cucumber/glue/snippet.rb | 2 +- spec/cucumber/glue/snippet_spec.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/cucumber/glue/snippet.rb b/lib/cucumber/glue/snippet.rb index 31bff76c14..c61d0e47a5 100644 --- a/lib/cucumber/glue/snippet.rb +++ b/lib/cucumber/glue/snippet.rb @@ -111,7 +111,7 @@ def parameters(expr) def quoted_expression_source(expr) source = expr.source - return "'#{source}'" unless source.include?("'") + return "'#{source.gsub(/\\/) { |char| "\\#{char}" }}'" unless source.include?("'") escaped_source = source.gsub(/["\\#]/) { |char| "\\#{char}" } "\"#{escaped_source}\"" diff --git a/spec/cucumber/glue/snippet_spec.rb b/spec/cucumber/glue/snippet_spec.rb index 3c7cf76873..ccf177b033 100644 --- a/spec/cucumber/glue/snippet_spec.rb +++ b/spec/cucumber/glue/snippet_spec.rb @@ -192,6 +192,17 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end + it 'escapes backslashes in single quoted cucumber expression snippets' do + @step_text = 'Lucy hears trailing \\' + cucumber_output = <<~'CUKE'.chomp + Given('Lucy hears trailing \\') do + pending # Write code here that turns the phrase above into concrete actions + end + CUKE + + expect(snippet.to_s).to eq(cucumber_output) + end + it 'escapes interpolation markers in double quoted cucumber expression snippets' do @step_text = 'Lucy hears Sean\'s #@message' cucumber_output = <<~'CUKE'.chomp From 06591c7e2dc99cbed6114edcab71ffebc3a5bc22 Mon Sep 17 00:00:00 2001 From: Matthew Stanton Date: Fri, 5 Jun 2026 08:08:01 -0400 Subject: [PATCH 5/6] Clarify generated snippet specs --- spec/cucumber/glue/snippet_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/cucumber/glue/snippet_spec.rb b/spec/cucumber/glue/snippet_spec.rb index ccf177b033..fb27bf017b 100644 --- a/spec/cucumber/glue/snippet_spec.rb +++ b/spec/cucumber/glue/snippet_spec.rb @@ -181,7 +181,7 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end - it 'uses double quotes for generated cucumber expression snippets with apostrophes' do + it 'uses double quotes for generated snippets with apostrophes' do @step_text = "Lucy hears Sean's message" cucumber_output = <<~CUKE.chomp Given("Lucy hears Sean's message") do @@ -192,7 +192,7 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end - it 'escapes backslashes in single quoted cucumber expression snippets' do + it 'escapes backslashes in single quoted snippets' do @step_text = 'Lucy hears trailing \\' cucumber_output = <<~'CUKE'.chomp Given('Lucy hears trailing \\') do @@ -203,7 +203,7 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end - it 'escapes interpolation markers in double quoted cucumber expression snippets' do + it 'escapes interpolation markers in double quoted snippets' do @step_text = 'Lucy hears Sean\'s #@message' cucumber_output = <<~'CUKE'.chomp Given("Lucy hears Sean's \#@message") do From ab0dc3b4f9bbc67ef04d6a763fbe556a94295924 Mon Sep 17 00:00:00 2001 From: Luke Hill <20105237+luke-hill@users.noreply.github.com> Date: Tue, 18 Aug 2026 10:33:13 +0100 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Luke Hill <20105237+luke-hill@users.noreply.github.com> --- spec/cucumber/glue/snippet_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/cucumber/glue/snippet_spec.rb b/spec/cucumber/glue/snippet_spec.rb index fb27bf017b..2ea32dc281 100644 --- a/spec/cucumber/glue/snippet_spec.rb +++ b/spec/cucumber/glue/snippet_spec.rb @@ -181,7 +181,7 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end - it 'uses double quotes for generated snippets with apostrophes' do + it 'uses double quotes for generated snippets that contain apostrophes' do @step_text = "Lucy hears Sean's message" cucumber_output = <<~CUKE.chomp Given("Lucy hears Sean's message") do @@ -192,7 +192,7 @@ module Glue expect(snippet.to_s).to eq(cucumber_output) end - it 'escapes backslashes in single quoted snippets' do + it 'escapes backslashes when generating snippets' do @step_text = 'Lucy hears trailing \\' cucumber_output = <<~'CUKE'.chomp Given('Lucy hears trailing \\') do