cucumber-cucumber-expressions 10.3.0 → 11.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/.rsync +1 -0
  3. data/VERSION +1 -1
  4. data/cucumber-cucumber-expressions.gemspec +1 -1
  5. data/examples.txt +5 -1
  6. data/lib/cucumber/cucumber_expressions/ast.rb +201 -0
  7. data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +80 -76
  8. data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +4 -8
  9. data/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +219 -0
  10. data/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +95 -0
  11. data/lib/cucumber/cucumber_expressions/errors.rb +175 -4
  12. data/lib/cucumber/cucumber_expressions/parameter_type.rb +12 -7
  13. data/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb +24 -0
  14. data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +62 -140
  15. data/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb +24 -0
  16. data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +1 -1
  17. data/spec/cucumber/cucumber_expressions/expression_examples_spec.rb +1 -1
  18. data/spec/cucumber/cucumber_expressions/expression_factory_spec.rb +0 -4
  19. data/testdata/ast/alternation-followed-by-optional.yaml +17 -0
  20. data/testdata/ast/alternation-phrase.yaml +16 -0
  21. data/testdata/ast/alternation-with-parameter.yaml +27 -0
  22. data/testdata/ast/alternation-with-unused-end-optional.yaml +15 -0
  23. data/testdata/ast/alternation-with-unused-start-optional.yaml +8 -0
  24. data/testdata/ast/alternation-with-white-space.yaml +12 -0
  25. data/testdata/ast/alternation.yaml +12 -0
  26. data/testdata/ast/anonymous-parameter.yaml +5 -0
  27. data/testdata/ast/closing-brace.yaml +5 -0
  28. data/testdata/ast/closing-parenthesis.yaml +5 -0
  29. data/testdata/ast/empty-alternation.yaml +8 -0
  30. data/testdata/ast/empty-alternations.yaml +9 -0
  31. data/testdata/ast/empty-string.yaml +3 -0
  32. data/testdata/ast/escaped-alternation.yaml +5 -0
  33. data/testdata/ast/escaped-backslash.yaml +5 -0
  34. data/testdata/ast/escaped-opening-parenthesis.yaml +5 -0
  35. data/testdata/ast/escaped-optional-followed-by-optional.yaml +15 -0
  36. data/testdata/ast/escaped-optional-phrase.yaml +10 -0
  37. data/testdata/ast/escaped-optional.yaml +7 -0
  38. data/testdata/ast/opening-brace.yaml +8 -0
  39. data/testdata/ast/opening-parenthesis.yaml +8 -0
  40. data/testdata/ast/optional-containing-nested-optional.yaml +15 -0
  41. data/testdata/ast/optional-phrase.yaml +12 -0
  42. data/testdata/ast/optional.yaml +7 -0
  43. data/testdata/ast/parameter.yaml +7 -0
  44. data/testdata/ast/phrase.yaml +9 -0
  45. data/testdata/ast/unfinished-parameter.yaml +8 -0
  46. data/testdata/expression/allows-escaped-optional-parameter-types.yaml +4 -0
  47. data/testdata/expression/allows-parameter-type-in-alternation-1.yaml +4 -0
  48. data/testdata/expression/allows-parameter-type-in-alternation-2.yaml +4 -0
  49. data/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml +5 -0
  50. data/testdata/expression/does-not-allow-alternation-in-optional.yaml +9 -0
  51. data/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml +10 -0
  52. data/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml +9 -0
  53. data/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml +9 -0
  54. data/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml +9 -0
  55. data/testdata/expression/does-not-allow-empty-optional.yaml +9 -0
  56. data/testdata/expression/does-not-allow-nested-optional.yaml +8 -0
  57. data/testdata/expression/does-not-allow-optional-parameter-types.yaml +9 -0
  58. data/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml +10 -0
  59. data/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml +8 -0
  60. data/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml +8 -0
  61. data/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml +8 -0
  62. data/testdata/expression/does-not-match-misquoted-string.yaml +4 -0
  63. data/testdata/expression/doesnt-match-float-as-int.yaml +5 -0
  64. data/testdata/expression/matches-alternation.yaml +4 -0
  65. data/testdata/expression/matches-anonymous-parameter-type.yaml +5 -0
  66. data/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml +4 -0
  67. data/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml +4 -0
  68. data/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml +4 -0
  69. data/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml +4 -0
  70. data/testdata/expression/matches-double-quoted-string.yaml +4 -0
  71. data/testdata/expression/matches-doubly-escaped-parenthesis.yaml +4 -0
  72. data/testdata/expression/matches-doubly-escaped-slash-1.yaml +4 -0
  73. data/testdata/expression/matches-doubly-escaped-slash-2.yaml +4 -0
  74. data/testdata/expression/matches-escaped-parenthesis-1.yaml +4 -0
  75. data/testdata/expression/matches-escaped-parenthesis-2.yaml +4 -0
  76. data/testdata/expression/matches-escaped-parenthesis-3.yaml +4 -0
  77. data/testdata/expression/matches-escaped-slash.yaml +4 -0
  78. data/testdata/expression/matches-float-1.yaml +5 -0
  79. data/testdata/expression/matches-float-2.yaml +5 -0
  80. data/testdata/expression/matches-int.yaml +5 -0
  81. data/testdata/expression/matches-multiple-double-quoted-strings.yaml +4 -0
  82. data/testdata/expression/matches-multiple-single-quoted-strings.yaml +4 -0
  83. data/testdata/expression/matches-optional-before-alternation-1.yaml +4 -0
  84. data/testdata/expression/matches-optional-before-alternation-2.yaml +4 -0
  85. data/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml +4 -0
  86. data/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml +4 -0
  87. data/testdata/expression/matches-optional-in-alternation-1.yaml +5 -0
  88. data/testdata/expression/matches-optional-in-alternation-2.yaml +5 -0
  89. data/testdata/expression/matches-optional-in-alternation-3.yaml +5 -0
  90. data/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml +4 -0
  91. data/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml +4 -0
  92. data/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml +4 -0
  93. data/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml +4 -0
  94. data/testdata/expression/matches-single-quoted-string.yaml +4 -0
  95. data/testdata/expression/matches-word.yaml +4 -0
  96. data/testdata/expression/throws-unknown-parameter-type.yaml +10 -0
  97. data/testdata/regex/alternation-with-optional.yaml +2 -0
  98. data/testdata/regex/alternation.yaml +2 -0
  99. data/testdata/regex/empty.yaml +2 -0
  100. data/testdata/regex/escape-regex-characters.yaml +2 -0
  101. data/testdata/regex/optional.yaml +2 -0
  102. data/testdata/regex/parameter.yaml +2 -0
  103. data/testdata/regex/text.yaml +2 -0
  104. data/testdata/regex/unicode.yaml +2 -0
  105. data/testdata/tokens/alternation-phrase.yaml +13 -0
  106. data/testdata/tokens/alternation.yaml +9 -0
  107. data/testdata/tokens/empty-string.yaml +6 -0
  108. data/testdata/tokens/escape-non-reserved-character.yaml +8 -0
  109. data/testdata/tokens/escaped-alternation.yaml +9 -0
  110. data/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml +9 -0
  111. data/testdata/tokens/escaped-end-of-line.yaml +8 -0
  112. data/testdata/tokens/escaped-optional.yaml +7 -0
  113. data/testdata/tokens/escaped-parameter.yaml +7 -0
  114. data/testdata/tokens/escaped-space.yaml +7 -0
  115. data/testdata/tokens/optional-phrase.yaml +13 -0
  116. data/testdata/tokens/optional.yaml +9 -0
  117. data/testdata/tokens/parameter-phrase.yaml +13 -0
  118. data/testdata/tokens/parameter.yaml +9 -0
  119. data/testdata/tokens/phrase.yaml +11 -0
  120. metadata +115 -9
  121. data/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb +0 -57
@@ -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