cucumber-cucumber-expressions 10.2.0 → 11.0.1

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rsync +1 -0
  3. data/VERSION +1 -1
  4. data/cucumber-cucumber-expressions.gemspec +2 -2
  5. data/default.mk +12 -0
  6. data/examples.txt +13 -1
  7. data/lib/cucumber/cucumber_expressions/ast.rb +201 -0
  8. data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +80 -76
  9. data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +4 -8
  10. data/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +219 -0
  11. data/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +95 -0
  12. data/lib/cucumber/cucumber_expressions/errors.rb +175 -4
  13. data/lib/cucumber/cucumber_expressions/group.rb +1 -1
  14. data/lib/cucumber/cucumber_expressions/parameter_type.rb +12 -7
  15. data/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb +2 -2
  16. data/lib/cucumber/cucumber_expressions/parameter_type_registry.rb +1 -1
  17. data/lib/cucumber/cucumber_expressions/tree_regexp.rb +53 -46
  18. data/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb +24 -0
  19. data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +73 -124
  20. data/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb +24 -0
  21. data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +1 -1
  22. data/spec/cucumber/cucumber_expressions/expression_examples_spec.rb +1 -1
  23. data/spec/cucumber/cucumber_expressions/expression_factory_spec.rb +0 -4
  24. data/spec/cucumber/cucumber_expressions/tree_regexp_spec.rb +58 -5
  25. data/testdata/ast/alternation-followed-by-optional.yaml +17 -0
  26. data/testdata/ast/alternation-phrase.yaml +16 -0
  27. data/testdata/ast/alternation-with-parameter.yaml +27 -0
  28. data/testdata/ast/alternation-with-unused-end-optional.yaml +15 -0
  29. data/testdata/ast/alternation-with-unused-start-optional.yaml +8 -0
  30. data/testdata/ast/alternation-with-white-space.yaml +12 -0
  31. data/testdata/ast/alternation.yaml +12 -0
  32. data/testdata/ast/anonymous-parameter.yaml +5 -0
  33. data/testdata/ast/closing-brace.yaml +5 -0
  34. data/testdata/ast/closing-parenthesis.yaml +5 -0
  35. data/testdata/ast/empty-alternation.yaml +8 -0
  36. data/testdata/ast/empty-alternations.yaml +9 -0
  37. data/testdata/ast/empty-string.yaml +3 -0
  38. data/testdata/ast/escaped-alternation.yaml +5 -0
  39. data/testdata/ast/escaped-backslash.yaml +5 -0
  40. data/testdata/ast/escaped-opening-parenthesis.yaml +5 -0
  41. data/testdata/ast/escaped-optional-followed-by-optional.yaml +15 -0
  42. data/testdata/ast/escaped-optional-phrase.yaml +10 -0
  43. data/testdata/ast/escaped-optional.yaml +7 -0
  44. data/testdata/ast/opening-brace.yaml +8 -0
  45. data/testdata/ast/opening-parenthesis.yaml +8 -0
  46. data/testdata/ast/optional-containing-nested-optional.yaml +15 -0
  47. data/testdata/ast/optional-phrase.yaml +12 -0
  48. data/testdata/ast/optional.yaml +7 -0
  49. data/testdata/ast/parameter.yaml +7 -0
  50. data/testdata/ast/phrase.yaml +9 -0
  51. data/testdata/ast/unfinished-parameter.yaml +8 -0
  52. data/testdata/expression/allows-escaped-optional-parameter-types.yaml +4 -0
  53. data/testdata/expression/allows-parameter-type-in-alternation-1.yaml +4 -0
  54. data/testdata/expression/allows-parameter-type-in-alternation-2.yaml +4 -0
  55. data/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml +5 -0
  56. data/testdata/expression/does-not-allow-alternation-in-optional.yaml +9 -0
  57. data/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml +10 -0
  58. data/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml +9 -0
  59. data/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml +9 -0
  60. data/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml +9 -0
  61. data/testdata/expression/does-not-allow-empty-optional.yaml +9 -0
  62. data/testdata/expression/does-not-allow-nested-optional.yaml +8 -0
  63. data/testdata/expression/does-not-allow-optional-parameter-types.yaml +9 -0
  64. data/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml +10 -0
  65. data/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml +8 -0
  66. data/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml +8 -0
  67. data/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml +8 -0
  68. data/testdata/expression/does-not-match-misquoted-string.yaml +4 -0
  69. data/testdata/expression/doesnt-match-float-as-int.yaml +5 -0
  70. data/testdata/expression/matches-alternation.yaml +4 -0
  71. data/testdata/expression/matches-anonymous-parameter-type.yaml +5 -0
  72. data/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml +4 -0
  73. data/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml +4 -0
  74. data/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml +4 -0
  75. data/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml +4 -0
  76. data/testdata/expression/matches-double-quoted-string.yaml +4 -0
  77. data/testdata/expression/matches-doubly-escaped-parenthesis.yaml +4 -0
  78. data/testdata/expression/matches-doubly-escaped-slash-1.yaml +4 -0
  79. data/testdata/expression/matches-doubly-escaped-slash-2.yaml +4 -0
  80. data/testdata/expression/matches-escaped-parenthesis-1.yaml +4 -0
  81. data/testdata/expression/matches-escaped-parenthesis-2.yaml +4 -0
  82. data/testdata/expression/matches-escaped-parenthesis-3.yaml +4 -0
  83. data/testdata/expression/matches-escaped-slash.yaml +4 -0
  84. data/testdata/expression/matches-float-1.yaml +5 -0
  85. data/testdata/expression/matches-float-2.yaml +5 -0
  86. data/testdata/expression/matches-int.yaml +5 -0
  87. data/testdata/expression/matches-multiple-double-quoted-strings.yaml +4 -0
  88. data/testdata/expression/matches-multiple-single-quoted-strings.yaml +4 -0
  89. data/testdata/expression/matches-optional-before-alternation-1.yaml +4 -0
  90. data/testdata/expression/matches-optional-before-alternation-2.yaml +4 -0
  91. data/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml +4 -0
  92. data/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml +4 -0
  93. data/testdata/expression/matches-optional-in-alternation-1.yaml +5 -0
  94. data/testdata/expression/matches-optional-in-alternation-2.yaml +5 -0
  95. data/testdata/expression/matches-optional-in-alternation-3.yaml +5 -0
  96. data/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml +4 -0
  97. data/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml +4 -0
  98. data/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml +4 -0
  99. data/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml +4 -0
  100. data/testdata/expression/matches-single-quoted-string.yaml +4 -0
  101. data/testdata/expression/matches-word.yaml +4 -0
  102. data/testdata/expression/throws-unknown-parameter-type.yaml +10 -0
  103. data/testdata/regex/alternation-with-optional.yaml +2 -0
  104. data/testdata/regex/alternation.yaml +2 -0
  105. data/testdata/regex/empty.yaml +2 -0
  106. data/testdata/regex/escape-regex-characters.yaml +2 -0
  107. data/testdata/regex/optional.yaml +2 -0
  108. data/testdata/regex/parameter.yaml +2 -0
  109. data/testdata/regex/text.yaml +2 -0
  110. data/testdata/regex/unicode.yaml +2 -0
  111. data/testdata/tokens/alternation-phrase.yaml +13 -0
  112. data/testdata/tokens/alternation.yaml +9 -0
  113. data/testdata/tokens/empty-string.yaml +6 -0
  114. data/testdata/tokens/escape-non-reserved-character.yaml +8 -0
  115. data/testdata/tokens/escaped-alternation.yaml +9 -0
  116. data/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml +9 -0
  117. data/testdata/tokens/escaped-end-of-line.yaml +8 -0
  118. data/testdata/tokens/escaped-optional.yaml +7 -0
  119. data/testdata/tokens/escaped-parameter.yaml +7 -0
  120. data/testdata/tokens/escaped-space.yaml +7 -0
  121. data/testdata/tokens/optional-phrase.yaml +13 -0
  122. data/testdata/tokens/optional.yaml +9 -0
  123. data/testdata/tokens/parameter-phrase.yaml +13 -0
  124. data/testdata/tokens/parameter.yaml +9 -0
  125. data/testdata/tokens/phrase.yaml +11 -0
  126. metadata +118 -13
  127. data/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb +0 -57
@@ -59,9 +59,7 @@ module Cucumber
59
59
  break
60
60
  end
61
61
 
62
- if pos >= text.length
63
- break
64
- end
62
+ break if pos >= text.length
65
63
  end
66
64
 
67
65
  expression_template += escape(text.slice(pos..-1))
@@ -85,19 +83,17 @@ module Cucumber
85
83
  end
86
84
 
87
85
  def create_parameter_type_matchers2(parameter_type, text)
88
- result = []
89
86
  regexps = parameter_type.regexps
90
- regexps.each do |regexp|
87
+ regexps.map do |regexp|
91
88
  regexp = Regexp.new("(#{regexp})")
92
- result.push(ParameterTypeMatcher.new(parameter_type, regexp, text, 0))
89
+ ParameterTypeMatcher.new(parameter_type, regexp, text, 0)
93
90
  end
94
- result
95
91
  end
96
92
 
97
93
  def escape(s)
98
94
  s.gsub(/%/, '%%')
99
95
  .gsub(/\(/, '\\(')
100
- .gsub(/\{/, '\\{')
96
+ .gsub(/{/, '\\{')
101
97
  .gsub(/\//, '\\/')
102
98
  end
103
99
  end
@@ -0,0 +1,219 @@
1
+ require 'cucumber/cucumber_expressions/ast'
2
+ require 'cucumber/cucumber_expressions/errors'
3
+ require 'cucumber/cucumber_expressions/cucumber_expression_tokenizer'
4
+
5
+ module Cucumber
6
+ module CucumberExpressions
7
+ class CucumberExpressionParser
8
+ def parse(expression)
9
+ # text := whitespace | ')' | '}' | .
10
+ parse_text = lambda do |_, tokens, current|
11
+ token = tokens[current]
12
+ case token.type
13
+ 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)]
15
+ when TokenType::ALTERNATION
16
+ raise AlternationNotAllowedInOptional.new(expression, token)
17
+ when TokenType::BEGIN_PARAMETER, TokenType::START_OF_LINE, TokenType::END_OF_LINE, TokenType::BEGIN_OPTIONAL
18
+ else
19
+ # If configured correctly this will never happen
20
+ return 0, nil
21
+ end
22
+ # If configured correctly this will never happen
23
+ return 0, nil
24
+ end
25
+
26
+ # name := whitespace | .
27
+ parse_name = lambda do |_, tokens, current|
28
+ token = tokens[current]
29
+ case token.type
30
+ when TokenType::WHITE_SPACE, TokenType::TEXT
31
+ return 1, [Node.new(NodeType::TEXT, nil, token.text, token.start, token.end)]
32
+ when TokenType::BEGIN_PARAMETER, TokenType::END_PARAMETER, TokenType::BEGIN_OPTIONAL, TokenType::END_OPTIONAL, TokenType::ALTERNATION
33
+ raise InvalidParameterTypeNameInNode.new(expression, token)
34
+ when TokenType::START_OF_LINE, TokenType::END_OF_LINE
35
+ # If configured correctly this will never happen
36
+ return 0, nil
37
+ else
38
+ # If configured correctly this will never happen
39
+ return 0, nil
40
+ end
41
+ end
42
+
43
+ # parameter := '{' + name* + '}'
44
+ parse_parameter = parse_between(
45
+ NodeType::PARAMETER,
46
+ TokenType::BEGIN_PARAMETER,
47
+ TokenType::END_PARAMETER,
48
+ [parse_name]
49
+ )
50
+
51
+ # optional := '(' + option* + ')'
52
+ # option := optional | parameter | text
53
+ optional_sub_parsers = []
54
+ parse_optional = parse_between(
55
+ NodeType::OPTIONAL,
56
+ TokenType::BEGIN_OPTIONAL,
57
+ TokenType::END_OPTIONAL,
58
+ optional_sub_parsers
59
+ )
60
+ optional_sub_parsers << parse_optional << parse_parameter << parse_text
61
+
62
+ # alternation := alternative* + ( '/' + alternative* )+
63
+ parse_alternative_separator = lambda do |_, tokens, current|
64
+ unless looking_at(tokens, current, TokenType::ALTERNATION)
65
+ return 0, nil
66
+ end
67
+ token = tokens[current]
68
+ return 1, [Node.new(NodeType::ALTERNATIVE, nil, token.text, token.start, token.end)]
69
+ end
70
+
71
+ alternative_parsers = [
72
+ parse_alternative_separator,
73
+ parse_optional,
74
+ parse_parameter,
75
+ parse_text,
76
+ ]
77
+
78
+ # alternation := (?<=left-boundary) + alternative* + ( '/' + alternative* )+ + (?=right-boundary)
79
+ # left-boundary := whitespace | } | ^
80
+ # right-boundary := whitespace | { | $
81
+ # alternative: = optional | parameter | text
82
+ parse_alternation = lambda do |expr, tokens, current|
83
+ 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
87
+
88
+ consumed, ast = parse_tokens_until(expr, alternative_parsers, tokens, current, [TokenType::WHITE_SPACE, TokenType::END_OF_LINE, TokenType::BEGIN_PARAMETER])
89
+ sub_current = current + consumed
90
+ unless ast.map { |astNode| astNode.type }.include? NodeType::ALTERNATIVE
91
+ return 0, nil
92
+ end
93
+
94
+ start = tokens[current].start
95
+ _end = tokens[sub_current].start
96
+ # Does not consume right hand boundary token
97
+ return consumed, [Node.new(NodeType::ALTERNATION, split_alternatives(start, _end, ast), nil, start, _end)]
98
+ end
99
+
100
+ #
101
+ # cucumber-expression := ( alternation | optional | parameter | text )*
102
+ #
103
+ parse_cucumber_expression = parse_between(
104
+ NodeType::EXPRESSION,
105
+ TokenType::START_OF_LINE,
106
+ TokenType::END_OF_LINE,
107
+ [parse_alternation, parse_optional, parse_parameter, parse_text]
108
+ )
109
+
110
+ tokenizer = CucumberExpressionTokenizer.new
111
+ tokens = tokenizer.tokenize(expression)
112
+ _, ast = parse_cucumber_expression.call(expression, tokens, 0)
113
+ ast[0]
114
+ end
115
+
116
+ private
117
+
118
+ def parse_between(type, begin_token, end_token, parsers)
119
+ lambda do |expression, tokens, current|
120
+ unless looking_at(tokens, current, begin_token)
121
+ return 0, nil
122
+ end
123
+ sub_current = current + 1
124
+ consumed, ast = parse_tokens_until(expression, parsers, tokens, sub_current, [end_token, TokenType::END_OF_LINE])
125
+ sub_current += consumed
126
+
127
+ # endToken not found
128
+ unless looking_at(tokens, sub_current, end_token)
129
+ raise MissingEndToken.new(expression, begin_token, end_token, tokens[current])
130
+ end
131
+ # consumes endToken
132
+ start = tokens[current].start
133
+ _end = tokens[sub_current].end
134
+ consumed = sub_current + 1 - current
135
+ ast = [Node.new(type, ast, nil, start, _end)]
136
+ return consumed, ast
137
+ end
138
+ end
139
+
140
+ def parse_token(expression, parsers, tokens, start_at)
141
+ parsers.each do |parser|
142
+ consumed, ast = parser.call(expression, tokens, start_at)
143
+ return consumed, ast unless consumed == 0
144
+ end
145
+ # If configured correctly this will never happen
146
+ raise 'No eligible parsers for ' + tokens
147
+ end
148
+
149
+ def parse_tokens_until(expression, parsers, tokens, start_at, end_tokens)
150
+ current = start_at
151
+ size = tokens.length
152
+ ast = []
153
+ while current < size do
154
+ if looking_at_any(tokens, current, end_tokens)
155
+ break
156
+ end
157
+ consumed, sub_ast = parse_token(expression, parsers, tokens, current)
158
+ if consumed == 0
159
+ # If configured correctly this will never happen
160
+ # Keep to avoid infinite loops
161
+ raise 'No eligible parsers for ' + tokens
162
+ end
163
+ current += consumed
164
+ ast += sub_ast
165
+ end
166
+ [current - start_at, ast]
167
+ end
168
+
169
+ def looking_at_any(tokens, at, token_types)
170
+ token_types.detect { |token_type| looking_at(tokens, at, token_type) }
171
+ end
172
+
173
+ def looking_at(tokens, at, token)
174
+ if at < 0
175
+ # If configured correctly this will never happen
176
+ # Keep for completeness
177
+ return token == TokenType::START_OF_LINE
178
+ end
179
+ if at >= tokens.length
180
+ return token == TokenType::END_OF_LINE
181
+ end
182
+ tokens[at].type == token
183
+ end
184
+
185
+ def split_alternatives(start, _end, alternation)
186
+ separators = []
187
+ alternatives = []
188
+ alternative = []
189
+ alternation.each { |n|
190
+ if NodeType::ALTERNATIVE == n.type
191
+ separators.push(n)
192
+ alternatives.push(alternative)
193
+ alternative = []
194
+ else
195
+ alternative.push(n)
196
+ end
197
+ }
198
+ alternatives.push(alternative)
199
+ create_alternative_nodes(start, _end, separators, alternatives)
200
+ end
201
+
202
+ def create_alternative_nodes(start, _end, separators, alternatives)
203
+ alternatives.each_with_index.map do |n, i|
204
+ if i == 0
205
+ right_separator = separators[i]
206
+ Node.new(NodeType::ALTERNATIVE, n, nil, start, right_separator.start)
207
+ elsif i == alternatives.length - 1
208
+ left_separator = separators[i - 1]
209
+ Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, _end)
210
+ else
211
+ left_separator = separators[i - 1]
212
+ right_separator = separators[i]
213
+ Node.new(NodeType::ALTERNATIVE, n, nil, left_separator.end, right_separator.start)
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,95 @@
1
+ require 'cucumber/cucumber_expressions/ast'
2
+ require 'cucumber/cucumber_expressions/errors'
3
+
4
+ module Cucumber
5
+ module CucumberExpressions
6
+ class CucumberExpressionTokenizer
7
+ def tokenize(expression)
8
+ @expression = expression
9
+ tokens = []
10
+ @buffer = []
11
+ previous_token_type = TokenType::START_OF_LINE
12
+ treat_as_text = false
13
+ @escaped = 0
14
+ @buffer_start_index = 0
15
+
16
+ codepoints = expression.codepoints
17
+
18
+ if codepoints.empty?
19
+ tokens.push(Token.new(TokenType::START_OF_LINE, '', 0, 0))
20
+ end
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
+ unless treat_as_text
78
+ return Token.type_of(codepoint)
79
+ end
80
+ if Token.can_escape(codepoint)
81
+ return TokenType::TEXT
82
+ end
83
+ raise CantEscape.new(
84
+ @expression,
85
+ @buffer_start_index + @buffer.length + @escaped
86
+ )
87
+ end
88
+
89
+ def should_create_new_token?(previous_token_type, current_token_type)
90
+ current_token_type != previous_token_type ||
91
+ (current_token_type != TokenType::WHITE_SPACE && current_token_type != TokenType::TEXT)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,11 +1,182 @@
1
+ require 'cucumber/cucumber_expressions/ast'
2
+
1
3
  module Cucumber
2
4
  module CucumberExpressions
3
5
  class CucumberExpressionError < StandardError
6
+
7
+ def build_message(
8
+ index,
9
+ expression,
10
+ pointer,
11
+ problem,
12
+ solution
13
+ )
14
+ m = <<-EOF
15
+ This Cucumber Expression has a problem at column #{index + 1}:
16
+
17
+ #{expression}
18
+ #{pointer}
19
+ #{problem}.
20
+ #{solution}
21
+ EOF
22
+ m.strip
23
+ end
24
+
25
+ def point_at(index)
26
+ ' ' * index + '^'
27
+ end
28
+
29
+ def point_at_located(node)
30
+ pointer = [point_at(node.start)]
31
+ if node.start + 1 < node.end
32
+ for _ in node.start + 1...node.end - 1
33
+ pointer.push('-')
34
+ end
35
+ pointer.push('^')
36
+ end
37
+ pointer.join('')
38
+ end
4
39
  end
5
40
 
6
- class UndefinedParameterTypeError < CucumberExpressionError
41
+ class AlternativeMayNotExclusivelyContainOptionals < CucumberExpressionError
42
+ def initialize(node, expression)
43
+ super(build_message(
44
+ node.start,
45
+ expression,
46
+ point_at_located(node),
47
+ 'An alternative may not exclusively contain optionals',
48
+ "If you did not mean to use an optional you can use '\\(' to escape the the '('"
49
+ ))
50
+ end
51
+ end
52
+
53
+ class AlternativeMayNotBeEmpty < CucumberExpressionError
54
+ def initialize(node, expression)
55
+ super(build_message(
56
+ node.start,
57
+ expression,
58
+ point_at_located(node),
59
+ 'Alternative may not be empty',
60
+ "If you did not mean to use an alternative you can use '\\/' to escape the the '/'"
61
+ ))
62
+ end
63
+ end
64
+
65
+ class CantEscape < CucumberExpressionError
66
+ def initialize(expression, index)
67
+ super(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
+ end
75
+ end
76
+
77
+ class OptionalMayNotBeEmpty < CucumberExpressionError
78
+ def initialize(node, expression)
79
+ super(build_message(
80
+ node.start,
81
+ expression,
82
+ point_at_located(node),
83
+ 'An optional must contain some text',
84
+ "If you did not mean to use an optional you can use '\\(' to escape the the '('"
85
+ ))
86
+ end
87
+ end
88
+
89
+ class ParameterIsNotAllowedInOptional < CucumberExpressionError
90
+ def initialize(node, expression)
91
+ super(build_message(
92
+ node.start,
93
+ expression,
94
+ point_at_located(node),
95
+ 'An optional may not contain a parameter type',
96
+ "If you did not mean to use an parameter type you can use '\\{' to escape the the '{'"
97
+ ))
98
+ end
99
+ end
100
+
101
+ class OptionalIsNotAllowedInOptional < CucumberExpressionError
102
+ def initialize(node, expression)
103
+ super(build_message(
104
+ node.start,
105
+ expression,
106
+ point_at_located(node),
107
+ 'An optional may not contain an other optional',
108
+ "If you did not mean to use an optional type you can use '\\(' to escape the the '('. For more complicated expressions consider using a regular expression instead."
109
+ ))
110
+ end
111
+ end
112
+
113
+ class TheEndOfLineCannotBeEscaped < CucumberExpressionError
114
+ def initialize(expression)
115
+ index = expression.codepoints.length - 1
116
+ super(build_message(
117
+ index,
118
+ expression,
119
+ point_at(index),
120
+ 'The end of line can not be escaped',
121
+ "You can use '\\\\' to escape the the '\\'"
122
+ ))
123
+ end
124
+ end
125
+
126
+ class MissingEndToken < CucumberExpressionError
127
+ def initialize(expression, begin_token, end_token, current)
128
+ begin_symbol = Token::symbol_of(begin_token)
129
+ end_symbol = Token::symbol_of(end_token)
130
+ purpose = Token::purpose_of(begin_token)
131
+ super(build_message(
132
+ current.start,
133
+ expression,
134
+ point_at_located(current),
135
+ "The '#{begin_symbol}' does not have a matching '#{end_symbol}'",
136
+ "If you did not intend to use #{purpose} you can use '\\#{begin_symbol}' to escape the #{purpose}"
137
+ ))
138
+ end
139
+ end
140
+
141
+ class AlternationNotAllowedInOptional < CucumberExpressionError
142
+ def initialize(expression, current)
143
+ super(build_message(
144
+ current.start,
145
+ expression,
146
+ point_at_located(current),
147
+ "An alternation can not be used inside an optional",
148
+ "You can use '\\/' to escape the the '/'"
149
+ ))
150
+ end
151
+ end
152
+
153
+ class InvalidParameterTypeName < CucumberExpressionError
7
154
  def initialize(type_name)
8
- super("Undefined parameter type {#{type_name}}")
155
+ super("Illegal character in parameter name {#{type_name}}. " +
156
+ "Parameter names may not contain '{', '}', '(', ')', '\\' or '/'")
157
+ end
158
+ end
159
+
160
+
161
+ class InvalidParameterTypeNameInNode < CucumberExpressionError
162
+ def initialize(expression, token)
163
+ super(build_message(
164
+ token.start,
165
+ expression,
166
+ point_at_located(token),
167
+ "Parameter names may not contain '{', '}', '(', ')', '\\' or '/'",
168
+ "Did you mean to use a regular expression?"
169
+ ))
170
+ end
171
+ end
172
+
173
+ class UndefinedParameterTypeError < CucumberExpressionError
174
+ def initialize(node, expression, parameter_type_name)
175
+ super(build_message(node.start,
176
+ expression,
177
+ point_at_located(node),
178
+ "Undefined parameter type '#{parameter_type_name}'",
179
+ "Please register a ParameterType for '#{parameter_type_name}'"))
9
180
  end
10
181
  end
11
182
 
@@ -29,11 +200,11 @@ I couldn't decide which one to use. You have two options:
29
200
  private
30
201
 
31
202
  def parameter_type_names(parameter_types)
32
- parameter_types.map{|p| "{#{p.name}}"}.join("\n ")
203
+ parameter_types.map { |p| "{#{p.name}}" }.join("\n ")
33
204
  end
34
205
 
35
206
  def expressions(generated_expressions)
36
- generated_expressions.map{|ge| ge.source}.join("\n ")
207
+ generated_expressions.map { |ge| ge.source }.join("\n ")
37
208
  end
38
209
  end
39
210
  end