cucumber-cucumber-expressions 16.1.2 → 17.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +27 -0
- data/.rubocop_todo.yml +524 -0
- data/Gemfile +2 -1
- data/Rakefile +4 -17
- data/VERSION +1 -1
- data/cucumber-cucumber-expressions.gemspec +11 -9
- data/lib/cucumber/cucumber_expressions/argument.rb +6 -3
- data/lib/cucumber/cucumber_expressions/ast.rb +24 -59
- data/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_factory.rb +6 -13
- data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +9 -11
- data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +7 -11
- data/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +37 -53
- data/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +13 -18
- data/lib/cucumber/cucumber_expressions/errors.rb +119 -101
- data/lib/cucumber/cucumber_expressions/expression_factory.rb +2 -0
- data/lib/cucumber/cucumber_expressions/generated_expression.rb +2 -0
- data/lib/cucumber/cucumber_expressions/group.rb +2 -0
- data/lib/cucumber/cucumber_expressions/group_builder.rb +2 -0
- data/lib/cucumber/cucumber_expressions/parameter_type.rb +13 -23
- data/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb +8 -6
- data/lib/cucumber/cucumber_expressions/parameter_type_registry.rb +23 -20
- data/lib/cucumber/cucumber_expressions/regular_expression.rb +3 -2
- data/lib/cucumber/cucumber_expressions/tree_regexp.rb +5 -4
- data/spec/cucumber/cucumber_expressions/argument_spec.rb +4 -2
- data/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_test.rb +7 -6
- data/spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb +104 -101
- data/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb +2 -0
- data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +84 -87
- data/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb +3 -1
- data/spec/cucumber/cucumber_expressions/cucumber_expression_transformation_spec.rb +2 -0
- data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +79 -60
- data/spec/cucumber/cucumber_expressions/expression_factory_spec.rb +2 -0
- data/spec/cucumber/cucumber_expressions/parameter_type_registry_spec.rb +43 -48
- data/spec/cucumber/cucumber_expressions/parameter_type_spec.rb +3 -1
- data/spec/cucumber/cucumber_expressions/regular_expression_spec.rb +37 -28
- data/spec/cucumber/cucumber_expressions/tree_regexp_spec.rb +23 -22
- metadata +21 -9
- data/.rspec +0 -1
- data/scripts/update-gemspec +0 -32
- data/spec/capture_warnings.rb +0 -74
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Cucumber
         | 
| 2 4 | 
             
              module CucumberExpressions
         | 
| 3 5 | 
             
                ESCAPE_CHARACTER = '\\'
         | 
| @@ -8,55 +10,31 @@ module Cucumber | |
| 8 10 | 
             
                END_OPTIONAL_CHARACTER = ')'
         | 
| 9 11 |  | 
| 10 12 | 
             
                class Node
         | 
| 11 | 
            -
                   | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
                     | 
| 13 | 
            +
                  attr_reader :type, :nodes, :token, :start, :end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def initialize(type, nodes, token, start, ending)
         | 
| 16 | 
            +
                    raise 'Either nodes or token must be defined' if nodes.nil? && token.nil?
         | 
| 17 | 
            +
             | 
| 15 18 | 
             
                    @type = type
         | 
| 16 19 | 
             
                    @nodes = nodes
         | 
| 17 20 | 
             
                    @token = token
         | 
| 18 21 | 
             
                    @start = start
         | 
| 19 | 
            -
                    @end =  | 
| 20 | 
            -
                  end
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                  def type
         | 
| 23 | 
            -
                    @type
         | 
| 24 | 
            -
                  end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                  def nodes
         | 
| 27 | 
            -
                    @nodes
         | 
| 28 | 
            -
                  end
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                  def token
         | 
| 31 | 
            -
                    @token
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                  def start
         | 
| 35 | 
            -
                    @start
         | 
| 36 | 
            -
                  end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                  def end
         | 
| 39 | 
            -
                    @end
         | 
| 22 | 
            +
                    @end = ending
         | 
| 40 23 | 
             
                  end
         | 
| 41 24 |  | 
| 42 25 | 
             
                  def text
         | 
| 43 | 
            -
                    if @token.nil?
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                    end
         | 
| 26 | 
            +
                    return @nodes.map { |value| value.text }.join('') if @token.nil?
         | 
| 27 | 
            +
             | 
| 46 28 | 
             
                    @token
         | 
| 47 29 | 
             
                  end
         | 
| 48 30 |  | 
| 49 31 | 
             
                  def to_hash
         | 
| 50 32 | 
             
                    hash = Hash.new
         | 
| 51 | 
            -
                    hash[ | 
| 52 | 
            -
                    unless @nodes.nil?
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                     | 
| 55 | 
            -
                     | 
| 56 | 
            -
                      hash["token"] = @token
         | 
| 57 | 
            -
                    end
         | 
| 58 | 
            -
                    hash["start"] = @start
         | 
| 59 | 
            -
                    hash["end"] = @end
         | 
| 33 | 
            +
                    hash['type'] = @type
         | 
| 34 | 
            +
                    hash['nodes'] = @nodes.map { |node| node.to_hash } unless @nodes.nil?
         | 
| 35 | 
            +
                    hash['token'] = @token unless @token.nil?
         | 
| 36 | 
            +
                    hash['start'] = @start
         | 
| 37 | 
            +
                    hash['end'] = @end
         | 
| 60 38 | 
             
                    hash
         | 
| 61 39 | 
             
                  end
         | 
| 62 40 | 
             
                end
         | 
| @@ -70,26 +48,11 @@ module Cucumber | |
| 70 48 | 
             
                  EXPRESSION = 'EXPRESSION_NODE'
         | 
| 71 49 | 
             
                end
         | 
| 72 50 |  | 
| 73 | 
            -
             | 
| 74 51 | 
             
                class Token
         | 
| 75 | 
            -
                   | 
| 76 | 
            -
                    @type, @text, @start, @end = type, text, start, _end
         | 
| 77 | 
            -
                  end
         | 
| 52 | 
            +
                  attr_reader :type, :text, :start, :end
         | 
| 78 53 |  | 
| 79 | 
            -
                  def type
         | 
| 80 | 
            -
                    @type
         | 
| 81 | 
            -
                  end
         | 
| 82 | 
            -
             | 
| 83 | 
            -
                  def text
         | 
| 84 | 
            -
                    @text
         | 
| 85 | 
            -
                  end
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                  def start
         | 
| 88 | 
            -
                    @start
         | 
| 89 | 
            -
                  end
         | 
| 90 | 
            -
             | 
| 91 | 
            -
                  def end
         | 
| 92 | 
            -
                    @end
         | 
| 54 | 
            +
                  def initialize(type, text, start, ending)
         | 
| 55 | 
            +
                    @type, @text, @start, @end = type, text, start, ending
         | 
| 93 56 | 
             
                  end
         | 
| 94 57 |  | 
| 95 58 | 
             
                  def self.is_escape_character(codepoint)
         | 
| @@ -102,6 +65,7 @@ module Cucumber | |
| 102 65 | 
             
                      # TODO: Unicode whitespace?
         | 
| 103 66 | 
             
                      return true
         | 
| 104 67 | 
             
                    end
         | 
| 68 | 
            +
             | 
| 105 69 | 
             
                    case c
         | 
| 106 70 | 
             
                    when ESCAPE_CHARACTER
         | 
| 107 71 | 
             
                      true
         | 
| @@ -126,6 +90,7 @@ module Cucumber | |
| 126 90 | 
             
                      # TODO: Unicode whitespace?
         | 
| 127 91 | 
             
                      return TokenType::WHITE_SPACE
         | 
| 128 92 | 
             
                    end
         | 
| 93 | 
            +
             | 
| 129 94 | 
             
                    case c
         | 
| 130 95 | 
             
                    when ALTERNATION_CHARACTER
         | 
| 131 96 | 
             
                      TokenType::ALTERNATION
         | 
| @@ -178,10 +143,10 @@ module Cucumber | |
| 178 143 |  | 
| 179 144 | 
             
                  def to_hash
         | 
| 180 145 | 
             
                    {
         | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 184 | 
            -
             | 
| 146 | 
            +
                      'type' => @type,
         | 
| 147 | 
            +
                      'text' => @text,
         | 
| 148 | 
            +
                      'start' => @start,
         | 
| 149 | 
            +
                      'end' => @end
         | 
| 185 150 | 
             
                    }
         | 
| 186 151 | 
             
                  end
         | 
| 187 152 | 
             
                end
         | 
| @@ -1,8 +1,9 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require('cucumber/cucumber_expressions/generated_expression')
         | 
| 2 4 |  | 
| 3 5 | 
             
            module Cucumber
         | 
| 4 6 | 
             
              module CucumberExpressions
         | 
| 5 | 
            -
             | 
| 6 7 | 
             
                class CombinatorialGeneratedExpressionFactory
         | 
| 7 8 | 
             
                  def initialize(expression_template, parameter_type_combinations)
         | 
| 8 9 | 
             
                    @expression_template = expression_template
         | 
| @@ -19,9 +20,7 @@ module Cucumber | |
| 19 20 | 
             
                  MAX_EXPRESSIONS = 256
         | 
| 20 21 |  | 
| 21 22 | 
             
                  def generate_permutations(generated_expressions, depth, current_parameter_types)
         | 
| 22 | 
            -
                    if generated_expressions.length >= MAX_EXPRESSIONS
         | 
| 23 | 
            -
                      return
         | 
| 24 | 
            -
                    end
         | 
| 23 | 
            +
                    return if generated_expressions.length >= MAX_EXPRESSIONS
         | 
| 25 24 |  | 
| 26 25 | 
             
                    if depth == @parameter_type_combinations.length
         | 
| 27 26 | 
             
                      generated_expression = GeneratedExpression.new(@expression_template, current_parameter_types)
         | 
| @@ -31,19 +30,13 @@ module Cucumber | |
| 31 30 |  | 
| 32 31 | 
             
                    (0...@parameter_type_combinations[depth].length).each do |i|
         | 
| 33 32 | 
             
                      # Avoid recursion if no elements can be added.
         | 
| 34 | 
            -
                      if generated_expressions.length >= MAX_EXPRESSIONS
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                      end
         | 
| 33 | 
            +
                      return if generated_expressions.length >= MAX_EXPRESSIONS
         | 
| 34 | 
            +
             | 
| 37 35 | 
             
                      new_current_parameter_types = current_parameter_types.dup # clone
         | 
| 38 36 | 
             
                      new_current_parameter_types.push(@parameter_type_combinations[depth][i])
         | 
| 39 | 
            -
                      generate_permutations(
         | 
| 40 | 
            -
                          generated_expressions,
         | 
| 41 | 
            -
                          depth + 1,
         | 
| 42 | 
            -
                          new_current_parameter_types
         | 
| 43 | 
            -
                      )
         | 
| 37 | 
            +
                      generate_permutations(generated_expressions, depth + 1, new_current_parameter_types)
         | 
| 44 38 | 
             
                    end
         | 
| 45 39 | 
             
                  end
         | 
| 46 40 | 
             
                end
         | 
| 47 | 
            -
             | 
| 48 41 | 
             
              end
         | 
| 49 42 | 
             
            end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'cucumber/cucumber_expressions/argument'
         | 
| 2 4 | 
             
            require 'cucumber/cucumber_expressions/tree_regexp'
         | 
| 3 5 | 
             
            require 'cucumber/cucumber_expressions/errors'
         | 
| @@ -6,8 +8,7 @@ require 'cucumber/cucumber_expressions/cucumber_expression_parser' | |
| 6 8 | 
             
            module Cucumber
         | 
| 7 9 | 
             
              module CucumberExpressions
         | 
| 8 10 | 
             
                class CucumberExpression
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                  ESCAPE_PATTERN = /([\\^\[({$.|?*+})\]])/
         | 
| 11 | 
            +
                  ESCAPE_PATTERN = /([\\^\[({$.|?*+})\]])/.freeze
         | 
| 11 12 |  | 
| 12 13 | 
             
                  def initialize(expression, parameter_type_registry)
         | 
| 13 14 | 
             
                    @expression = expression
         | 
| @@ -72,9 +73,8 @@ module Cucumber | |
| 72 73 | 
             
                  def rewrite_alternation(node)
         | 
| 73 74 | 
             
                    # Make sure the alternative parts aren't empty and don't contain parameter types
         | 
| 74 75 | 
             
                    node.nodes.each { |alternative|
         | 
| 75 | 
            -
                      if alternative.nodes.length == 0
         | 
| 76 | 
            -
             | 
| 77 | 
            -
                      end
         | 
| 76 | 
            +
                      raise AlternativeMayNotBeEmpty.new(alternative, @expression) if alternative.nodes.length == 0
         | 
| 77 | 
            +
             | 
| 78 78 | 
             
                      assert_not_empty(alternative) { |astNode| raise AlternativeMayNotExclusivelyContainOptionals.new(astNode, @expression) }
         | 
| 79 79 | 
             
                    }
         | 
| 80 80 | 
             
                    regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('|')
         | 
| @@ -88,14 +88,12 @@ module Cucumber | |
| 88 88 | 
             
                  def rewrite_parameter(node)
         | 
| 89 89 | 
             
                    name = node.text
         | 
| 90 90 | 
             
                    parameter_type = @parameter_type_registry.lookup_by_type_name(name)
         | 
| 91 | 
            -
                    if parameter_type.nil?
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                    end
         | 
| 91 | 
            +
                    raise UndefinedParameterTypeError.new(node, @expression, name) if parameter_type.nil?
         | 
| 92 | 
            +
             | 
| 94 93 | 
             
                    @parameter_types.push(parameter_type)
         | 
| 95 94 | 
             
                    regexps = parameter_type.regexps
         | 
| 96 | 
            -
                    if regexps.length == 1
         | 
| 97 | 
            -
             | 
| 98 | 
            -
                    end
         | 
| 95 | 
            +
                    return "(#{regexps[0]})" if regexps.length == 1
         | 
| 96 | 
            +
             | 
| 99 97 | 
             
                    "((?:#{regexps.join(')|(?:')}))"
         | 
| 100 98 | 
             
                  end
         | 
| 101 99 |  | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'cucumber/cucumber_expressions/parameter_type_matcher'
         | 
| 2 4 | 
             
            require 'cucumber/cucumber_expressions/generated_expression'
         | 
| 3 5 | 
             
            require 'cucumber/cucumber_expressions/combinatorial_generated_expression_factory'
         | 
| @@ -12,16 +14,14 @@ module Cucumber | |
| 12 14 | 
             
                  def generate_expressions(text)
         | 
| 13 15 | 
             
                    parameter_type_combinations = []
         | 
| 14 16 | 
             
                    parameter_type_matchers = create_parameter_type_matchers(text)
         | 
| 15 | 
            -
                    expression_template =  | 
| 17 | 
            +
                    expression_template = +''
         | 
| 16 18 | 
             
                    pos = 0
         | 
| 17 19 |  | 
| 18 20 | 
             
                    loop do
         | 
| 19 21 | 
             
                      matching_parameter_type_matchers = []
         | 
| 20 22 | 
             
                      parameter_type_matchers.each do |parameter_type_matcher|
         | 
| 21 23 | 
             
                        advanced_parameter_type_matcher = parameter_type_matcher.advance_to(pos)
         | 
| 22 | 
            -
                        if advanced_parameter_type_matcher.find
         | 
| 23 | 
            -
                          matching_parameter_type_matchers.push(advanced_parameter_type_matcher)
         | 
| 24 | 
            -
                        end
         | 
| 24 | 
            +
                        matching_parameter_type_matchers.push(advanced_parameter_type_matcher) if advanced_parameter_type_matcher.find
         | 
| 25 25 | 
             
                      end
         | 
| 26 26 |  | 
| 27 27 | 
             
                      if matching_parameter_type_matchers.any?
         | 
| @@ -39,16 +39,14 @@ module Cucumber | |
| 39 39 | 
             
                        # Users are most likely to want these, so they should be listed at the top.
         | 
| 40 40 | 
             
                        parameter_types = []
         | 
| 41 41 | 
             
                        best_parameter_type_matchers.each do |parameter_type_matcher|
         | 
| 42 | 
            -
                          unless parameter_types.include?(parameter_type_matcher.parameter_type)
         | 
| 43 | 
            -
                            parameter_types.push(parameter_type_matcher.parameter_type)
         | 
| 44 | 
            -
                          end
         | 
| 42 | 
            +
                          parameter_types.push(parameter_type_matcher.parameter_type) unless parameter_types.include?(parameter_type_matcher.parameter_type)
         | 
| 45 43 | 
             
                        end
         | 
| 46 44 | 
             
                        parameter_types.sort!
         | 
| 47 45 |  | 
| 48 46 | 
             
                        parameter_type_combinations.push(parameter_types)
         | 
| 49 47 |  | 
| 50 48 | 
             
                        expression_template += escape(text.slice(pos...best_parameter_type_matcher.start))
         | 
| 51 | 
            -
                        expression_template +=  | 
| 49 | 
            +
                        expression_template += '{%s}'
         | 
| 52 50 |  | 
| 53 51 | 
             
                        pos = best_parameter_type_matcher.start + best_parameter_type_matcher.group.length
         | 
| 54 52 | 
             
                      else
         | 
| @@ -71,9 +69,7 @@ module Cucumber | |
| 71 69 | 
             
                  def create_parameter_type_matchers(text)
         | 
| 72 70 | 
             
                    parameter_matchers = []
         | 
| 73 71 | 
             
                    @parameter_type_registry.parameter_types.each do |parameter_type|
         | 
| 74 | 
            -
                      if parameter_type.use_for_snippets | 
| 75 | 
            -
                        parameter_matchers += create_parameter_type_matchers2(parameter_type, text)
         | 
| 76 | 
            -
                      end
         | 
| 72 | 
            +
                      parameter_matchers += create_parameter_type_matchers2(parameter_type, text) if parameter_type.use_for_snippets
         | 
| 77 73 | 
             
                    end
         | 
| 78 74 | 
             
                    parameter_matchers
         | 
| 79 75 | 
             
                  end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'cucumber/cucumber_expressions/ast'
         | 
| 2 4 | 
             
            require 'cucumber/cucumber_expressions/errors'
         | 
| 3 5 | 
             
            require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer'
         | 
| @@ -11,16 +13,16 @@ module Cucumber | |
| 11 13 | 
             
                      token = tokens[current]
         | 
| 12 14 | 
             
                      case token.type
         | 
| 13 15 | 
             
                      when TokenType::WHITE_SPACE, TokenType::TEXT, TokenType::END_PARAMETER, TokenType::END_OPTIONAL
         | 
| 14 | 
            -
                        return 1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]
         | 
| 16 | 
            +
                        return [1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]]
         | 
| 15 17 | 
             
                      when TokenType::ALTERNATION
         | 
| 16 18 | 
             
                        raise AlternationNotAllowedInOptional.new(expression, token)
         | 
| 17 19 | 
             
                      when TokenType::BEGIN_PARAMETER, TokenType::START_OF_LINE, TokenType::END_OF_LINE, TokenType::BEGIN_OPTIONAL
         | 
| 18 20 | 
             
                      else
         | 
| 19 21 | 
             
                        # If configured correctly this will never happen
         | 
| 20 | 
            -
                        return 0, nil
         | 
| 22 | 
            +
                        return [0, nil]
         | 
| 21 23 | 
             
                      end
         | 
| 22 24 | 
             
                      # If configured correctly this will never happen
         | 
| 23 | 
            -
                      return 0, nil
         | 
| 25 | 
            +
                      return [0, nil]
         | 
| 24 26 | 
             
                    end
         | 
| 25 27 |  | 
| 26 28 | 
             
                    # name := whitespace | .
         | 
| @@ -28,51 +30,40 @@ module Cucumber | |
| 28 30 | 
             
                      token = tokens[current]
         | 
| 29 31 | 
             
                      case token.type
         | 
| 30 32 | 
             
                      when TokenType::WHITE_SPACE, TokenType::TEXT
         | 
| 31 | 
            -
                        return 1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]
         | 
| 33 | 
            +
                        return [1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]]
         | 
| 32 34 | 
             
                      when TokenType::BEGIN_PARAMETER, TokenType::END_PARAMETER, TokenType::BEGIN_OPTIONAL, TokenType::END_OPTIONAL, TokenType::ALTERNATION
         | 
| 33 35 | 
             
                        raise InvalidParameterTypeNameInNode.new(expression, token)
         | 
| 34 36 | 
             
                      when TokenType::START_OF_LINE, TokenType::END_OF_LINE
         | 
| 35 37 | 
             
                        # If configured correctly this will never happen
         | 
| 36 | 
            -
                        return 0, nil
         | 
| 38 | 
            +
                        return [0, nil]
         | 
| 37 39 | 
             
                      else
         | 
| 38 40 | 
             
                        # If configured correctly this will never happen
         | 
| 39 | 
            -
                        return 0, nil
         | 
| 41 | 
            +
                        return [0, nil]
         | 
| 40 42 | 
             
                      end
         | 
| 41 43 | 
             
                    end
         | 
| 42 44 |  | 
| 43 45 | 
             
                    # parameter := '{' + name* + '}'
         | 
| 44 | 
            -
                    parse_parameter = parse_between(
         | 
| 45 | 
            -
                        NodeType::PARAMETER,
         | 
| 46 | 
            -
                        TokenType::BEGIN_PARAMETER,
         | 
| 47 | 
            -
                        TokenType::END_PARAMETER,
         | 
| 48 | 
            -
                        [parse_name]
         | 
| 49 | 
            -
                    )
         | 
| 46 | 
            +
                    parse_parameter = parse_between(NodeType::PARAMETER, TokenType::BEGIN_PARAMETER, TokenType::END_PARAMETER, [parse_name])
         | 
| 50 47 |  | 
| 51 48 | 
             
                    # optional := '(' + option* + ')'
         | 
| 52 49 | 
             
                    # option := optional | parameter | text
         | 
| 53 50 | 
             
                    optional_sub_parsers = []
         | 
| 54 | 
            -
                    parse_optional = parse_between(
         | 
| 55 | 
            -
                        NodeType::OPTIONAL,
         | 
| 56 | 
            -
                        TokenType::BEGIN_OPTIONAL,
         | 
| 57 | 
            -
                        TokenType::END_OPTIONAL,
         | 
| 58 | 
            -
                        optional_sub_parsers
         | 
| 59 | 
            -
                    )
         | 
| 51 | 
            +
                    parse_optional = parse_between(NodeType::OPTIONAL, TokenType::BEGIN_OPTIONAL, TokenType::END_OPTIONAL, optional_sub_parsers)
         | 
| 60 52 | 
             
                    optional_sub_parsers << parse_optional << parse_parameter << parse_text
         | 
| 61 53 |  | 
| 62 54 | 
             
                    # alternation := alternative* + ( '/' + alternative* )+
         | 
| 63 55 | 
             
                    parse_alternative_separator = lambda do |_, tokens, current|
         | 
| 64 | 
            -
                      unless looking_at(tokens, current, TokenType::ALTERNATION)
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                      end
         | 
| 56 | 
            +
                      return [0, nil] unless looking_at(tokens, current, TokenType::ALTERNATION)
         | 
| 57 | 
            +
             | 
| 67 58 | 
             
                      token = tokens[current]
         | 
| 68 | 
            -
                      return 1, [Node.new(NodeType::ALTERNATIVE, nil, token.text, token.start, token.end)]
         | 
| 59 | 
            +
                      return [1, [Node.new(NodeType::ALTERNATIVE, nil, token.text, token.start, token.end)]]
         | 
| 69 60 | 
             
                    end
         | 
| 70 61 |  | 
| 71 62 | 
             
                    alternative_parsers = [
         | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 63 | 
            +
                      parse_alternative_separator,
         | 
| 64 | 
            +
                      parse_optional,
         | 
| 65 | 
            +
                      parse_parameter,
         | 
| 66 | 
            +
                      parse_text,
         | 
| 76 67 | 
             
                    ]
         | 
| 77 68 |  | 
| 78 69 | 
             
                    # alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary)
         | 
| @@ -81,30 +72,26 @@ module Cucumber | |
| 81 72 | 
             
                    # alternative: = optional | parameter | text
         | 
| 82 73 | 
             
                    parse_alternation = lambda do |expr, tokens, current|
         | 
| 83 74 | 
             
                      previous = current - 1
         | 
| 84 | 
            -
                      unless looking_at_any(tokens, previous, [TokenType::START_OF_LINE, TokenType::WHITE_SPACE, TokenType::END_PARAMETER])
         | 
| 85 | 
            -
                        return 0, nil
         | 
| 86 | 
            -
                      end
         | 
| 75 | 
            +
                      return [0, nil] unless looking_at_any(tokens, previous, [TokenType::START_OF_LINE, TokenType::WHITE_SPACE, TokenType::END_PARAMETER])
         | 
| 87 76 |  | 
| 88 77 | 
             
                      consumed, ast = parse_tokens_until(expr, alternative_parsers, tokens, current, [TokenType::WHITE_SPACE, TokenType::END_OF_LINE, TokenType::BEGIN_PARAMETER])
         | 
| 89 78 | 
             
                      sub_current = current + consumed
         | 
| 90 | 
            -
                      unless ast.map { |astNode| astNode.type }.include? NodeType::ALTERNATIVE
         | 
| 91 | 
            -
                        return 0, nil
         | 
| 92 | 
            -
                      end
         | 
| 79 | 
            +
                      return [0, nil] unless ast.map { |astNode| astNode.type }.include? NodeType::ALTERNATIVE
         | 
| 93 80 |  | 
| 94 81 | 
             
                      start = tokens[current].start
         | 
| 95 82 | 
             
                      _end = tokens[sub_current].start
         | 
| 96 83 | 
             
                      # Does not consume right hand boundary token
         | 
| 97 | 
            -
                      return consumed, [Node.new(NodeType::ALTERNATION, split_alternatives(start, _end, ast), nil, start, _end)]
         | 
| 84 | 
            +
                      return [consumed, [Node.new(NodeType::ALTERNATION, split_alternatives(start, _end, ast), nil, start, _end)]]
         | 
| 98 85 | 
             
                    end
         | 
| 99 86 |  | 
| 100 87 | 
             
                    #
         | 
| 101 88 | 
             
                    # cucumber-expression :=  ( alternation | optional | parameter | text )*
         | 
| 102 89 | 
             
                    #
         | 
| 103 90 | 
             
                    parse_cucumber_expression = parse_between(
         | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 91 | 
            +
                      NodeType::EXPRESSION,
         | 
| 92 | 
            +
                      TokenType::START_OF_LINE,
         | 
| 93 | 
            +
                      TokenType::END_OF_LINE,
         | 
| 94 | 
            +
                      [parse_alternation, parse_optional, parse_parameter, parse_text]
         | 
| 108 95 | 
             
                    )
         | 
| 109 96 |  | 
| 110 97 | 
             
                    tokenizer = CucumberExpressionTokenizer.new
         | 
| @@ -117,30 +104,28 @@ module Cucumber | |
| 117 104 |  | 
| 118 105 | 
             
                  def parse_between(type, begin_token, end_token, parsers)
         | 
| 119 106 | 
             
                    lambda do |expression, tokens, current|
         | 
| 120 | 
            -
                      unless looking_at(tokens, current, begin_token)
         | 
| 121 | 
            -
             | 
| 122 | 
            -
                      end
         | 
| 107 | 
            +
                      return [0, nil] unless looking_at(tokens, current, begin_token)
         | 
| 108 | 
            +
             | 
| 123 109 | 
             
                      sub_current = current + 1
         | 
| 124 110 | 
             
                      consumed, ast = parse_tokens_until(expression, parsers, tokens, sub_current, [end_token, TokenType::END_OF_LINE])
         | 
| 125 111 | 
             
                      sub_current += consumed
         | 
| 126 112 |  | 
| 127 113 | 
             
                      # endToken not found
         | 
| 128 | 
            -
                      unless looking_at(tokens, sub_current, end_token)
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                      end
         | 
| 114 | 
            +
                      raise MissingEndToken.new(expression, begin_token, end_token, tokens[current]) unless looking_at(tokens, sub_current, end_token)
         | 
| 115 | 
            +
             | 
| 131 116 | 
             
                      # consumes endToken
         | 
| 132 117 | 
             
                      start = tokens[current].start
         | 
| 133 118 | 
             
                      _end = tokens[sub_current].end
         | 
| 134 119 | 
             
                      consumed = sub_current + 1 - current
         | 
| 135 120 | 
             
                      ast = [Node.new(type, ast, nil, start, _end)]
         | 
| 136 | 
            -
                      return consumed, ast
         | 
| 121 | 
            +
                      return [consumed, ast]
         | 
| 137 122 | 
             
                    end
         | 
| 138 123 | 
             
                  end
         | 
| 139 124 |  | 
| 140 125 | 
             
                  def parse_token(expression, parsers, tokens, start_at)
         | 
| 141 126 | 
             
                    parsers.each do |parser|
         | 
| 142 127 | 
             
                      consumed, ast = parser.call(expression, tokens, start_at)
         | 
| 143 | 
            -
                      return consumed, ast unless consumed == 0
         | 
| 128 | 
            +
                      return [consumed, ast] unless consumed == 0
         | 
| 144 129 | 
             
                    end
         | 
| 145 130 | 
             
                    # If configured correctly this will never happen
         | 
| 146 131 | 
             
                    raise 'No eligible parsers for ' + tokens
         | 
| @@ -151,15 +136,15 @@ module Cucumber | |
| 151 136 | 
             
                    size = tokens.length
         | 
| 152 137 | 
             
                    ast = []
         | 
| 153 138 | 
             
                    while current < size do
         | 
| 154 | 
            -
                      if looking_at_any(tokens, current, end_tokens)
         | 
| 155 | 
            -
             | 
| 156 | 
            -
                      end
         | 
| 139 | 
            +
                      break if looking_at_any(tokens, current, end_tokens)
         | 
| 140 | 
            +
             | 
| 157 141 | 
             
                      consumed, sub_ast = parse_token(expression, parsers, tokens, current)
         | 
| 158 142 | 
             
                      if consumed == 0
         | 
| 159 143 | 
             
                        # If configured correctly this will never happen
         | 
| 160 144 | 
             
                        # Keep to avoid infinite loops
         | 
| 161 145 | 
             
                        raise 'No eligible parsers for ' + tokens
         | 
| 162 146 | 
             
                      end
         | 
| 147 | 
            +
             | 
| 163 148 | 
             
                      current += consumed
         | 
| 164 149 | 
             
                      ast += sub_ast
         | 
| 165 150 | 
             
                    end
         | 
| @@ -176,9 +161,8 @@ module Cucumber | |
| 176 161 | 
             
                      # Keep for completeness
         | 
| 177 162 | 
             
                      return token == TokenType::START_OF_LINE
         | 
| 178 163 | 
             
                    end
         | 
| 179 | 
            -
                    if at >= tokens.length
         | 
| 180 | 
            -
             | 
| 181 | 
            -
                    end
         | 
| 164 | 
            +
                    return token == TokenType::END_OF_LINE if at >= tokens.length
         | 
| 165 | 
            +
             | 
| 182 166 | 
             
                    tokens[at].type == token
         | 
| 183 167 | 
             
                  end
         | 
| 184 168 |  | 
| @@ -186,7 +170,7 @@ module Cucumber | |
| 186 170 | 
             
                    separators = []
         | 
| 187 171 | 
             
                    alternatives = []
         | 
| 188 172 | 
             
                    alternative = []
         | 
| 189 | 
            -
                    alternation.each  | 
| 173 | 
            +
                    alternation.each do |n|
         | 
| 190 174 | 
             
                      if NodeType::ALTERNATIVE == n.type
         | 
| 191 175 | 
             
                        separators.push(n)
         | 
| 192 176 | 
             
                        alternatives.push(alternative)
         | 
| @@ -194,7 +178,7 @@ module Cucumber | |
| 194 178 | 
             
                      else
         | 
| 195 179 | 
             
                        alternative.push(n)
         | 
| 196 180 | 
             
                      end
         | 
| 197 | 
            -
                     | 
| 181 | 
            +
                    end
         | 
| 198 182 | 
             
                    alternatives.push(alternative)
         | 
| 199 183 | 
             
                    create_alternative_nodes(start, _end, separators, alternatives)
         | 
| 200 184 | 
             
                  end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'cucumber/cucumber_expressions/ast'
         | 
| 2 4 | 
             
            require 'cucumber/cucumber_expressions/errors'
         | 
| 3 5 |  | 
| @@ -15,9 +17,7 @@ module Cucumber | |
| 15 17 |  | 
| 16 18 | 
             
                    codepoints = expression.codepoints
         | 
| 17 19 |  | 
| 18 | 
            -
                    if codepoints.empty?
         | 
| 19 | 
            -
                      tokens.push(Token.new(TokenType::START_OF_LINE, '', 0, 0))
         | 
| 20 | 
            -
                    end
         | 
| 20 | 
            +
                    tokens.push(Token.new(TokenType::START_OF_LINE, '', 0, 0)) if codepoints.empty?
         | 
| 21 21 |  | 
| 22 22 | 
             
                    codepoints.each do |codepoint|
         | 
| 23 23 | 
             
                      if !treat_as_text && Token.is_escape_character(codepoint)
         | 
| @@ -63,10 +63,10 @@ module Cucumber | |
| 63 63 |  | 
| 64 64 | 
             
                    consumed_index = @buffer_start_index + @buffer.length + escape_tokens
         | 
| 65 65 | 
             
                    t = Token.new(
         | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 66 | 
            +
                      token_type,
         | 
| 67 | 
            +
                      @buffer.map { |codepoint| codepoint.chr(Encoding::UTF_8) }.join(''),
         | 
| 68 | 
            +
                      @buffer_start_index,
         | 
| 69 | 
            +
                      consumed_index
         | 
| 70 70 | 
             
                    )
         | 
| 71 71 | 
             
                    @buffer = []
         | 
| 72 72 | 
             
                    @buffer_start_index = consumed_index
         | 
| @@ -74,21 +74,16 @@ module Cucumber | |
| 74 74 | 
             
                  end
         | 
| 75 75 |  | 
| 76 76 | 
             
                  def token_type_of(codepoint, treat_as_text)
         | 
| 77 | 
            -
                    unless treat_as_text
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                     | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
                    end
         | 
| 83 | 
            -
                    raise CantEscape.new(
         | 
| 84 | 
            -
                        @expression,
         | 
| 85 | 
            -
                        @buffer_start_index + @buffer.length + @escaped
         | 
| 86 | 
            -
                    )
         | 
| 77 | 
            +
                    return Token.type_of(codepoint) unless treat_as_text
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    return TokenType::TEXT if Token.can_escape(codepoint)
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    raise CantEscape.new(@expression, @buffer_start_index + @buffer.length + @escaped)
         | 
| 87 82 | 
             
                  end
         | 
| 88 83 |  | 
| 89 84 | 
             
                  def should_create_new_token?(previous_token_type, current_token_type)
         | 
| 90 85 | 
             
                    current_token_type != previous_token_type ||
         | 
| 91 | 
            -
             | 
| 86 | 
            +
                      (current_token_type != TokenType::WHITE_SPACE && current_token_type != TokenType::TEXT)
         | 
| 92 87 | 
             
                  end
         | 
| 93 88 | 
             
                end
         | 
| 94 89 | 
             
              end
         |