cucumber-cucumber-expressions 8.3.1 → 19.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +2 -2
  3. data/lib/cucumber/cucumber_expressions/argument.rb +8 -4
  4. data/lib/cucumber/cucumber_expressions/ast.rb +166 -0
  5. data/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_factory.rb +6 -13
  6. data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +82 -80
  7. data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +14 -26
  8. data/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +203 -0
  9. data/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +90 -0
  10. data/lib/cucumber/cucumber_expressions/errors.rb +205 -13
  11. data/lib/cucumber/cucumber_expressions/expression_factory.rb +24 -0
  12. data/lib/cucumber/cucumber_expressions/generated_expression.rb +2 -0
  13. data/lib/cucumber/cucumber_expressions/group.rb +7 -1
  14. data/lib/cucumber/cucumber_expressions/group_builder.rb +9 -2
  15. data/lib/cucumber/cucumber_expressions/parameter_type.rb +14 -21
  16. data/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb +11 -9
  17. data/lib/cucumber/cucumber_expressions/parameter_type_registry.rb +28 -16
  18. data/lib/cucumber/cucumber_expressions/regular_expression.rb +3 -2
  19. data/lib/cucumber/cucumber_expressions/tree_regexp.rb +54 -46
  20. metadata +76 -77
  21. data/.github/ISSUE_TEMPLATE.md +0 -5
  22. data/.github/PULL_REQUEST_TEMPLATE.md +0 -5
  23. data/.rspec +0 -1
  24. data/.rsync +0 -4
  25. data/.subrepo +0 -1
  26. data/Gemfile +0 -3
  27. data/Makefile +0 -1
  28. data/README.md +0 -5
  29. data/Rakefile +0 -27
  30. data/cucumber-cucumber-expressions.gemspec +0 -33
  31. data/default.mk +0 -70
  32. data/examples.txt +0 -31
  33. data/scripts/update-gemspec +0 -32
  34. data/spec/capture_warnings.rb +0 -74
  35. data/spec/coverage.rb +0 -7
  36. data/spec/cucumber/cucumber_expressions/argument_spec.rb +0 -17
  37. data/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_test.rb +0 -43
  38. data/spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb +0 -231
  39. data/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb +0 -57
  40. data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +0 -212
  41. data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +0 -202
  42. data/spec/cucumber/cucumber_expressions/expression_examples_spec.rb +0 -30
  43. data/spec/cucumber/cucumber_expressions/parameter_type_registry_spec.rb +0 -86
  44. data/spec/cucumber/cucumber_expressions/parameter_type_spec.rb +0 -15
  45. data/spec/cucumber/cucumber_expressions/regular_expression_spec.rb +0 -80
  46. data/spec/cucumber/cucumber_expressions/tree_regexp_spec.rb +0 -133
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/cucumber_expressions/ast'
4
+ require 'cucumber/cucumber_expressions/errors'
5
+ require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer'
6
+
7
+ module Cucumber
8
+ module CucumberExpressions
9
+ class CucumberExpressionParser
10
+ def parse(expression)
11
+ # text := whitespace | ')' | '}' | .
12
+ parse_text = lambda do |_, tokens, current|
13
+ token = tokens[current]
14
+ case token.type
15
+ when TokenType::WHITE_SPACE, TokenType::TEXT, TokenType::END_PARAMETER, TokenType::END_OPTIONAL
16
+ return [1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]]
17
+ when TokenType::ALTERNATION
18
+ raise AlternationNotAllowedInOptional.new(expression, token)
19
+ when TokenType::BEGIN_PARAMETER, TokenType::START_OF_LINE, TokenType::END_OF_LINE, TokenType::BEGIN_OPTIONAL
20
+ else
21
+ # If configured correctly this will never happen
22
+ return [0, nil]
23
+ end
24
+ # If configured correctly this will never happen
25
+ return [0, nil]
26
+ end
27
+
28
+ # name := whitespace | .
29
+ parse_name = lambda do |_, tokens, current|
30
+ token = tokens[current]
31
+ case token.type
32
+ when TokenType::WHITE_SPACE, TokenType::TEXT
33
+ return [1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]]
34
+ when TokenType::BEGIN_PARAMETER, TokenType::END_PARAMETER, TokenType::BEGIN_OPTIONAL, TokenType::END_OPTIONAL, TokenType::ALTERNATION
35
+ raise InvalidParameterTypeNameInNode.new(expression, token)
36
+ when TokenType::START_OF_LINE, TokenType::END_OF_LINE
37
+ # If configured correctly this will never happen
38
+ return [0, nil]
39
+ else
40
+ # If configured correctly this will never happen
41
+ return [0, nil]
42
+ end
43
+ end
44
+
45
+ # parameter := '{' + name* + '}'
46
+ parse_parameter = parse_between(NodeType::PARAMETER, TokenType::BEGIN_PARAMETER, TokenType::END_PARAMETER, [parse_name])
47
+
48
+ # optional := '(' + option* + ')'
49
+ # option := optional | parameter | text
50
+ optional_sub_parsers = []
51
+ parse_optional = parse_between(NodeType::OPTIONAL, TokenType::BEGIN_OPTIONAL, TokenType::END_OPTIONAL, optional_sub_parsers)
52
+ optional_sub_parsers << parse_optional << parse_parameter << parse_text
53
+
54
+ # alternation := alternative* + ( '/' + alternative* )+
55
+ parse_alternative_separator = lambda do |_, tokens, current|
56
+ return [0, nil] unless looking_at(tokens, current, TokenType::ALTERNATION)
57
+
58
+ token = tokens[current]
59
+ return [1, [Node.new(NodeType::ALTERNATIVE, nil, token.text, token.start, token.end)]]
60
+ end
61
+
62
+ alternative_parsers = [
63
+ parse_alternative_separator,
64
+ parse_optional,
65
+ parse_parameter,
66
+ parse_text,
67
+ ]
68
+
69
+ # alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary)
70
+ # left-boundary := whitespace | } | ^
71
+ # right-boundary := whitespace | { | $
72
+ # alternative: = optional | parameter | text
73
+ parse_alternation = lambda do |expr, tokens, current|
74
+ previous = current - 1
75
+ return [0, nil] unless looking_at_any(tokens, previous, [TokenType::START_OF_LINE, TokenType::WHITE_SPACE, TokenType::END_PARAMETER])
76
+
77
+ consumed, ast = parse_tokens_until(expr, alternative_parsers, tokens, current, [TokenType::WHITE_SPACE, TokenType::END_OF_LINE, TokenType::BEGIN_PARAMETER])
78
+ sub_current = current + consumed
79
+ return [0, nil] unless ast.map { |ast_node| ast_node.type }.include? NodeType::ALTERNATIVE
80
+
81
+ start = tokens[current].start
82
+ _end = tokens[sub_current].start
83
+ # Does not consume right hand boundary token
84
+ return [consumed, [Node.new(NodeType::ALTERNATION, split_alternatives(start, _end, ast), nil, start, _end)]]
85
+ end
86
+
87
+ #
88
+ # cucumber-expression := ( alternation | optional | parameter | text )*
89
+ #
90
+ parse_cucumber_expression = parse_between(
91
+ NodeType::EXPRESSION,
92
+ TokenType::START_OF_LINE,
93
+ TokenType::END_OF_LINE,
94
+ [parse_alternation, parse_optional, parse_parameter, parse_text]
95
+ )
96
+
97
+ tokenizer = CucumberExpressionTokenizer.new
98
+ tokens = tokenizer.tokenize(expression)
99
+ _, ast = parse_cucumber_expression.call(expression, tokens, 0)
100
+ ast[0]
101
+ end
102
+
103
+ private
104
+
105
+ def parse_between(type, begin_token, end_token, parsers)
106
+ lambda do |expression, tokens, current|
107
+ return [0, nil] unless looking_at(tokens, current, begin_token)
108
+
109
+ sub_current = current + 1
110
+ consumed, ast = parse_tokens_until(expression, parsers, tokens, sub_current, [end_token, TokenType::END_OF_LINE])
111
+ sub_current += consumed
112
+
113
+ # endToken not found
114
+ raise MissingEndToken.new(expression, begin_token, end_token, tokens[current]) unless looking_at(tokens, sub_current, end_token)
115
+
116
+ # consumes endToken
117
+ start = tokens[current].start
118
+ _end = tokens[sub_current].end
119
+ consumed = sub_current + 1 - current
120
+ ast = [Node.new(type, ast, nil, start, _end)]
121
+ return [consumed, ast]
122
+ end
123
+ end
124
+
125
+ def parse_token(expression, parsers, tokens, start_at)
126
+ parsers.each do |parser|
127
+ consumed, ast = parser.call(expression, tokens, start_at)
128
+ return [consumed, ast] unless consumed == 0
129
+ end
130
+ # If configured correctly this will never happen
131
+ raise 'No eligible parsers for ' + tokens
132
+ end
133
+
134
+ def parse_tokens_until(expression, parsers, tokens, start_at, end_tokens)
135
+ current = start_at
136
+ size = tokens.length
137
+ ast = []
138
+ while current < size do
139
+ break if looking_at_any(tokens, current, end_tokens)
140
+
141
+ consumed, sub_ast = parse_token(expression, parsers, tokens, current)
142
+ if consumed == 0
143
+ # If configured correctly this will never happen
144
+ # Keep to avoid infinite loops
145
+ raise 'No eligible parsers for ' + tokens
146
+ end
147
+
148
+ current += consumed
149
+ ast += sub_ast
150
+ end
151
+ [current - start_at, ast]
152
+ end
153
+
154
+ def looking_at_any(tokens, at, token_types)
155
+ token_types.detect { |token_type| looking_at(tokens, at, token_type) }
156
+ end
157
+
158
+ def looking_at(tokens, at, token)
159
+ if at < 0
160
+ # If configured correctly this will never happen
161
+ # Keep for completeness
162
+ return token == TokenType::START_OF_LINE
163
+ end
164
+ return token == TokenType::END_OF_LINE if at >= tokens.length
165
+
166
+ tokens[at].type == token
167
+ end
168
+
169
+ def split_alternatives(start, _end, alternation)
170
+ separators = []
171
+ alternatives = []
172
+ alternative = []
173
+ alternation.each do |n|
174
+ if NodeType::ALTERNATIVE == n.type
175
+ separators.push(n)
176
+ alternatives.push(alternative)
177
+ alternative = []
178
+ else
179
+ alternative.push(n)
180
+ end
181
+ end
182
+ alternatives.push(alternative)
183
+ create_alternative_nodes(start, _end, separators, alternatives)
184
+ end
185
+
186
+ def create_alternative_nodes(start, _end, separators, alternatives)
187
+ alternatives.each_with_index.map do |n, i|
188
+ if i == 0
189
+ right_separator = separators[i]
190
+ Node.new(NodeType::ALTERNATIVE, n, nil, start, right_separator.start)
191
+ elsif i == alternatives.length - 1
192
+ left_separator = separators[i - 1]
193
+ Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, _end)
194
+ else
195
+ left_separator = separators[i - 1]
196
+ right_separator = separators[i]
197
+ Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, right_separator.start)
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/cucumber_expressions/ast'
4
+ require 'cucumber/cucumber_expressions/errors'
5
+
6
+ module Cucumber
7
+ module CucumberExpressions
8
+ class CucumberExpressionTokenizer
9
+ def tokenize(expression)
10
+ @expression = expression
11
+ tokens = []
12
+ @buffer = []
13
+ previous_token_type = TokenType::START_OF_LINE
14
+ treat_as_text = false
15
+ @escaped = 0
16
+ @buffer_start_index = 0
17
+
18
+ codepoints = expression.codepoints
19
+
20
+ tokens.push(Token.new(TokenType::START_OF_LINE, '', 0, 0)) if codepoints.empty?
21
+
22
+ codepoints.each do |codepoint|
23
+ if !treat_as_text && Token.is_escape_character(codepoint)
24
+ @escaped += 1
25
+ treat_as_text = true
26
+ next
27
+ end
28
+ current_token_type = token_type_of(codepoint, treat_as_text)
29
+ treat_as_text = false
30
+
31
+ if should_create_new_token?(previous_token_type, current_token_type)
32
+ token = convert_buffer_to_token(previous_token_type)
33
+ previous_token_type = current_token_type
34
+ @buffer.push(codepoint)
35
+ tokens.push(token)
36
+ else
37
+ previous_token_type = current_token_type
38
+ @buffer.push(codepoint)
39
+ end
40
+ end
41
+
42
+ if @buffer.length > 0
43
+ token = convert_buffer_to_token(previous_token_type)
44
+ tokens.push(token)
45
+ end
46
+
47
+ raise TheEndOfLineCannotBeEscaped.new(expression) if treat_as_text
48
+
49
+ tokens.push(Token.new(TokenType::END_OF_LINE, '', codepoints.length, codepoints.length))
50
+ tokens
51
+ end
52
+
53
+ private
54
+
55
+ # TODO: Make these lambdas
56
+
57
+ def convert_buffer_to_token(token_type)
58
+ escape_tokens = 0
59
+ if token_type == TokenType::TEXT
60
+ escape_tokens = @escaped
61
+ @escaped = 0
62
+ end
63
+
64
+ consumed_index = @buffer_start_index + @buffer.length + escape_tokens
65
+ t = Token.new(
66
+ token_type,
67
+ @buffer.map { |codepoint| codepoint.chr(Encoding::UTF_8) }.join(''),
68
+ @buffer_start_index,
69
+ consumed_index
70
+ )
71
+ @buffer = []
72
+ @buffer_start_index = consumed_index
73
+ t
74
+ end
75
+
76
+ def token_type_of(codepoint, treat_as_text)
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)
82
+ end
83
+
84
+ def should_create_new_token?(previous_token_type, current_token_type)
85
+ current_token_type != previous_token_type ||
86
+ (current_token_type != TokenType::WHITE_SPACE && current_token_type != TokenType::TEXT)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,39 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/cucumber_expressions/ast'
4
+
1
5
  module Cucumber
2
6
  module CucumberExpressions
3
7
  class CucumberExpressionError < StandardError
8
+ def build_message(index, expression, pointer, problem, solution)
9
+ m = <<~ERROR
10
+ This Cucumber Expression has a problem at column #{index + 1}:
11
+
12
+ #{expression}
13
+ #{pointer}
14
+ #{problem}.
15
+ #{solution}
16
+ ERROR
17
+ m.strip
18
+ end
19
+
20
+ def point_at(index)
21
+ ' ' * index + '^'
22
+ end
23
+
24
+ def point_at_located(node)
25
+ pointer = [point_at(node.start)]
26
+ if node.start + 1 < node.end
27
+ for _ in node.start + 1...node.end - 1
28
+ pointer.push('-')
29
+ end
30
+ pointer.push('^')
31
+ end
32
+ pointer.join('')
33
+ end
4
34
  end
5
35
 
6
- class UndefinedParameterTypeError < CucumberExpressionError
36
+ class AlternativeMayNotExclusivelyContainOptionals < CucumberExpressionError
37
+ def initialize(node, expression)
38
+ super(
39
+ build_message(
40
+ node.start,
41
+ expression,
42
+ point_at_located(node),
43
+ 'An alternative may not exclusively contain optionals',
44
+ "If you did not mean to use an optional you can use '\\(' to escape the '('"
45
+ )
46
+ )
47
+ end
48
+ end
49
+
50
+ class AlternativeMayNotBeEmpty < CucumberExpressionError
51
+ def initialize(node, expression)
52
+ super(
53
+ build_message(
54
+ node.start,
55
+ expression,
56
+ point_at_located(node),
57
+ 'Alternative may not be empty',
58
+ "If you did not mean to use an alternative you can use '\\/' to escape the '/'"
59
+ )
60
+ )
61
+ end
62
+ end
63
+
64
+ class CantEscape < CucumberExpressionError
65
+ def initialize(expression, index)
66
+ super(
67
+ build_message(
68
+ index,
69
+ expression,
70
+ point_at(index),
71
+ "Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped",
72
+ "If you did mean to use an '\\' you can use '\\\\' to escape it"
73
+ )
74
+ )
75
+ end
76
+ end
77
+
78
+ class OptionalMayNotBeEmpty < CucumberExpressionError
79
+ def initialize(node, expression)
80
+ super(
81
+ build_message(
82
+ node.start,
83
+ expression,
84
+ point_at_located(node),
85
+ 'An optional must contain some text',
86
+ "If you did not mean to use an optional you can use '\\(' to escape the '('"
87
+ )
88
+ )
89
+ end
90
+ end
91
+
92
+ class ParameterIsNotAllowedInOptional < CucumberExpressionError
93
+ def initialize(node, expression)
94
+ super(
95
+ build_message(
96
+ node.start,
97
+ expression,
98
+ point_at_located(node),
99
+ 'An optional may not contain a parameter type',
100
+ "If you did not mean to use an parameter type you can use '\\{' to escape the '{'"
101
+ )
102
+ )
103
+ end
104
+ end
105
+
106
+ class OptionalIsNotAllowedInOptional < CucumberExpressionError
107
+ def initialize(node, expression)
108
+ super(
109
+ build_message(
110
+ node.start,
111
+ expression,
112
+ point_at_located(node),
113
+ 'An optional may not contain an other optional',
114
+ "If you did not mean to use an optional type you can use '\\(' to escape the '('. For more complicated expressions consider using a regular expression instead."
115
+ )
116
+ )
117
+ end
118
+ end
119
+
120
+ class TheEndOfLineCannotBeEscaped < CucumberExpressionError
121
+ def initialize(expression)
122
+ index = expression.codepoints.length - 1
123
+ super(
124
+ build_message(
125
+ index,
126
+ expression,
127
+ point_at(index),
128
+ 'The end of line can not be escaped',
129
+ "You can use '\\\\' to escape the '\\'"
130
+ )
131
+ )
132
+ end
133
+ end
134
+
135
+ class MissingEndToken < CucumberExpressionError
136
+ def initialize(expression, begin_token, end_token, current)
137
+ begin_symbol = Token::symbol_of(begin_token)
138
+ end_symbol = Token::symbol_of(end_token)
139
+ purpose = Token::purpose_of(begin_token)
140
+
141
+ super(
142
+ build_message(
143
+ current.start,
144
+ expression,
145
+ point_at_located(current),
146
+ "The '#{begin_symbol}' does not have a matching '#{end_symbol}'",
147
+ "If you did not intend to use #{purpose} you can use '\\#{begin_symbol}' to escape the #{purpose}"
148
+ )
149
+ )
150
+ end
151
+ end
152
+
153
+ class AlternationNotAllowedInOptional < CucumberExpressionError
154
+ def initialize(expression, current)
155
+ super(
156
+ build_message(
157
+ current.start,
158
+ expression,
159
+ point_at_located(current),
160
+ 'An alternation can not be used inside an optional',
161
+ "If you did not mean to use an alternation you can use '\\/' to escape the '/'. Otherwise rephrase your expression or consider using a regular expression instead."
162
+ )
163
+ )
164
+ end
165
+ end
166
+
167
+ class InvalidParameterTypeName < CucumberExpressionError
7
168
  def initialize(type_name)
8
- super("Undefined parameter type {#{type_name}}")
169
+ super("Illegal character in parameter name {#{type_name}}. Parameter names may not contain '{', '}', '(', ')', '\\' or '/'")
170
+ end
171
+ end
172
+
173
+ class InvalidParameterTypeNameInNode < CucumberExpressionError
174
+ def initialize(expression, token)
175
+ super(
176
+ build_message(
177
+ token.start,
178
+ expression,
179
+ point_at_located(token),
180
+ "Parameter names may not contain '{', '}', '(', ')', '\\' or '/'",
181
+ 'Did you mean to use a regular expression?'
182
+ )
183
+ )
184
+ end
185
+ end
186
+
187
+ class UndefinedParameterTypeError < CucumberExpressionError
188
+ attr_reader :undefined_parameter_type_name
189
+
190
+ def initialize(node, expression, undefined_parameter_type_name)
191
+ super(
192
+ build_message(
193
+ node.start,
194
+ expression,
195
+ point_at_located(node),
196
+ "Undefined parameter type '#{undefined_parameter_type_name}'",
197
+ "Please register a ParameterType for '#{undefined_parameter_type_name}'"
198
+ )
199
+ )
200
+ @undefined_parameter_type_name = undefined_parameter_type_name
9
201
  end
10
202
  end
11
203
 
12
204
  class AmbiguousParameterTypeError < CucumberExpressionError
13
205
  def initialize(parameter_type_regexp, expression_regexp, parameter_types, generated_expressions)
14
- super(<<-EOM)
15
- Your Regular Expression /#{expression_regexp.source}/
16
- matches multiple parameter types with regexp /#{parameter_type_regexp}/:
17
- #{parameter_type_names(parameter_types)}
206
+ super(<<~ERROR)
207
+ Your Regular Expression /#{expression_regexp.source}/
208
+ matches multiple parameter types with regexp /#{parameter_type_regexp}/:
209
+ #{parameter_type_names(parameter_types)}
18
210
 
19
- I couldn't decide which one to use. You have two options:
211
+ I couldn't decide which one to use. You have two options:
20
212
 
21
- 1) Use a Cucumber Expression instead of a Regular Expression. Try one of these:
22
- #{expressions(generated_expressions)}
213
+ 1) Use a Cucumber Expression instead of a Regular Expression. Try one of these:
214
+ #{expressions(generated_expressions)}
23
215
 
24
- 2) Make one of the parameter types preferential and continue to use a Regular Expression.
216
+ 2) Make one of the parameter types preferential and continue to use a Regular Expression.
25
217
 
26
- EOM
218
+ ERROR
27
219
  end
28
220
 
29
221
  private
30
222
 
31
223
  def parameter_type_names(parameter_types)
32
- parameter_types.map{|p| "{#{p.name}}"}.join("\n ")
224
+ parameter_types.map { |p| "{#{p.name}}" }.join("\n ")
33
225
  end
34
226
 
35
227
  def expressions(generated_expressions)
36
- generated_expressions.map{|ge| ge.source}.join("\n ")
228
+ generated_expressions.map { |ge| ge.source }.join("\n ")
37
229
  end
38
230
  end
39
231
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/cucumber_expressions/errors'
4
+ require 'cucumber/cucumber_expressions/cucumber_expression'
5
+ require 'cucumber/cucumber_expressions/regular_expression'
6
+
7
+ module Cucumber
8
+ module CucumberExpressions
9
+ class ExpressionFactory
10
+ def initialize(parameter_type_registry)
11
+ @parameter_type_registry = parameter_type_registry
12
+ end
13
+
14
+ def create_expression(string_or_regexp)
15
+ case string_or_regexp
16
+ when String then CucumberExpression.new(string_or_regexp, @parameter_type_registry)
17
+ when Regexp then RegularExpression.new(string_or_regexp, @parameter_type_registry)
18
+ else
19
+ raise CucumberExpressionError.new("Can't create an expression from #{string_or_regexp.inspect}")
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cucumber
2
4
  module CucumberExpressions
3
5
  class GeneratedExpression
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cucumber
2
4
  module CucumberExpressions
3
5
  class Group
@@ -11,7 +13,11 @@ module Cucumber
11
13
  end
12
14
 
13
15
  def values
14
- (children.empty? ? [self] : children).map(&:value).compact
16
+ if children.nil?
17
+ [self.value]
18
+ else
19
+ children.map(&:value)
20
+ end
15
21
  end
16
22
  end
17
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cucumber/cucumber_expressions/group'
2
4
 
3
5
  module Cucumber
@@ -16,8 +18,13 @@ module Cucumber
16
18
 
17
19
  def build(match, group_indices)
18
20
  group_index = group_indices.next
19
- children = @group_builders.map {|gb| gb.build(match, group_indices)}
20
- Group.new(match[group_index], match.offset(group_index)[0], match.offset(group_index)[1], children)
21
+ children = @group_builders.map { |gb| gb.build(match, group_indices) }
22
+ Group.new(
23
+ match[group_index],
24
+ match.offset(group_index)[0],
25
+ match.offset(group_index)[1],
26
+ children.empty? ? nil : children
27
+ )
21
28
  end
22
29
 
23
30
  def set_non_capturing!
@@ -1,28 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cucumber/cucumber_expressions/errors'
2
4
 
3
5
  module Cucumber
4
6
  module CucumberExpressions
5
7
  class ParameterType
6
- ILLEGAL_PARAMETER_NAME_PATTERN = /([\[\]()$.|?*+])/
7
- UNESCAPE_PATTERN = /(\\([\[$.|?*+\]]))/
8
+ ILLEGAL_PARAMETER_NAME_PATTERN = /([\[\]()$.|?*+])/.freeze
9
+ UNESCAPE_PATTERN = /(\\([\[$.|?*+\]]))/.freeze
8
10
 
9
- attr_reader :name, :type, :regexps
11
+ attr_reader :name, :type, :transformer, :use_for_snippets, :prefer_for_regexp_match, :regexps
10
12
 
11
13
  def self.check_parameter_type_name(type_name)
12
- unescaped_type_name = type_name.gsub(UNESCAPE_PATTERN) do
13
- $2
14
- end
15
- if ILLEGAL_PARAMETER_NAME_PATTERN =~ unescaped_type_name
16
- raise CucumberExpressionError.new("Illegal character '#{$1}' in parameter name {#{unescaped_type_name}}")
17
- end
14
+ raise CucumberExpressionError.new("Illegal character in parameter name {#{type_name}}. Parameter names may not contain '[]()$.|?*+'") unless is_valid_parameter_type_name(type_name)
18
15
  end
19
16
 
20
- def prefer_for_regexp_match?
21
- @prefer_for_regexp_match
22
- end
23
-
24
- def use_for_snippets?
25
- @use_for_snippets
17
+ def self.is_valid_parameter_type_name(type_name)
18
+ unescaped_type_name = type_name.gsub(UNESCAPE_PATTERN) { Regexp.last_match(2) }
19
+ !(ILLEGAL_PARAMETER_NAME_PATTERN =~ unescaped_type_name)
26
20
  end
27
21
 
28
22
  # Create a new Parameter
@@ -51,8 +45,9 @@ module Cucumber
51
45
  end
52
46
 
53
47
  def <=>(other)
54
- return -1 if prefer_for_regexp_match? && !other.prefer_for_regexp_match?
55
- return 1 if other.prefer_for_regexp_match? && !prefer_for_regexp_match?
48
+ return -1 if prefer_for_regexp_match && !other.prefer_for_regexp_match
49
+ return 1 if other.prefer_for_regexp_match && !prefer_for_regexp_match
50
+
56
51
  return name <=> other.name
57
52
  end
58
53
 
@@ -60,7 +55,7 @@ module Cucumber
60
55
 
61
56
  def string_array(regexps)
62
57
  array = regexps.is_a?(Array) ? regexps : [regexps]
63
- array.map {|regexp| regexp.is_a?(String) ? regexp : regexp_source(regexp)}
58
+ array.map { |regexp| regexp.is_a?(String) ? regexp : regexp_source(regexp) }
64
59
  end
65
60
 
66
61
  def regexp_source(regexp)
@@ -70,9 +65,7 @@ module Cucumber
70
65
  'MULTILINE'
71
66
  ].each do |option_name|
72
67
  option = Regexp.const_get(option_name)
73
- if regexp.options & option != 0
74
- raise CucumberExpressionError.new("ParameterType Regexps can't use option Regexp::#{option_name}")
75
- end
68
+ raise CucumberExpressionError.new("ParameterType Regexps can't use option Regexp::#{option_name}") if regexp.options & option != 0
76
69
  end
77
70
  regexp.source
78
71
  end