gherkin3-pre-alpha 3.0.0.alpha.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4b4620019eafdd1a5b88e0cb91fbddaec8dc3a7d
4
+ data.tar.gz: f9d2e1a7044759658aefa6a0d02ea3691c419bda
5
+ SHA512:
6
+ metadata.gz: 1e5d41da5cd9f1bf76bb829427cf823afb2c7e602471b9d63e8bf0d963e58071be0d966312b5d3e1c6ae8814e0a5d26ffbfc74cf1bf0734ff15a11d78192b3a2
7
+ data.tar.gz: 6dac43c4da02ce6503523cd1c5ad8b9790afd47dab83adb37112dd419940f287cf178f6ae74225e7ec465adc4a087d3ddc70b8d7a4a44af808cf5cb4fc35bb55
@@ -0,0 +1 @@
1
+ language: ruby
@@ -0,0 +1,16 @@
1
+ Please read [CONTRIBUTING](https://github.com/cucumber/gherkin3/blob/master/CONTRIBUTING.md) first.
2
+ You should clone the [cucumber/gherkin3](https://github.com/cucumber/gherkin3) repo if you want
3
+ to contribute.
4
+
5
+ ## Using make
6
+
7
+ Just run `make` from this directory.
8
+
9
+ Even if you prefer `make` - run `rake` occasionally, as it reports better warnings.
10
+
11
+ ## Using rake
12
+
13
+ Just run `rake` from this directory.
14
+
15
+ Keep in mind that this will only run unit tests. The acceptance tests are only
16
+ run when you build with `make`.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014-2015 Cucumber Ltd, Gaspar Nagy, TechTalk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ GOOD_FEATURE_FILES = $(shell find ../testdata/good -name "*.feature")
2
+ BAD_FEATURE_FILES = $(shell find ../testdata/bad -name "*.feature")
3
+
4
+ TOKENS = $(patsubst ../testdata/%.feature,acceptance/testdata/%.feature.tokens,$(GOOD_FEATURE_FILES))
5
+ ASTS = $(patsubst ../testdata/%.feature,acceptance/testdata/%.feature.ast.json,$(GOOD_FEATURE_FILES))
6
+ ERRORS = $(patsubst ../testdata/%.feature,acceptance/testdata/%.feature.errors,$(BAD_FEATURE_FILES))
7
+
8
+ RUBY_FILES = $(shell find . -name "*.rb")
9
+
10
+ all: .compared
11
+ .PHONY: all
12
+
13
+ .compared: .built $(TOKENS) $(ASTS) $(ERRORS)
14
+ touch $@
15
+
16
+ .built: show-version-info lib/gherkin3/parser.rb lib/gherkin3/gherkin-languages.json $(RUBY_FILES) Gemfile.lock LICENSE
17
+ bundle exec rspec --color
18
+ touch $@
19
+
20
+ show-version-info:
21
+ ruby --version
22
+ .PHONY: show-version-info
23
+
24
+ acceptance/testdata/%.feature.tokens: ../testdata/%.feature ../testdata/%.feature.tokens .built
25
+ mkdir -p `dirname $@`
26
+ bin/gherkin-generate-tokens $< > $@
27
+ diff --unified $<.tokens $@
28
+ .DELETE_ON_ERROR: acceptance/testdata/%.feature.tokens
29
+
30
+ acceptance/testdata/%.feature.ast.json: ../testdata/%.feature ../testdata/%.feature.ast.json .built
31
+ mkdir -p `dirname $@`
32
+ bin/gherkin-generate-ast $< | jq --sort-keys "." > $@
33
+ diff --unified $<.ast.json $@
34
+ .DELETE_ON_ERROR: acceptance/testdata/%.feature.ast.json
35
+
36
+ acceptance/testdata/%.feature.errors: ../testdata/%.feature ../testdata/%.feature.errors .built
37
+ mkdir -p `dirname $@`
38
+ ! bin/gherkin-generate-ast $< 2> $@
39
+ diff --unified $<.errors $@
40
+ .DELETE_ON_ERROR: acceptance/testdata/%.feature.errors
41
+
42
+ lib/gherkin3/gherkin-languages.json: ../gherkin-languages.json
43
+ cp $^ $@
44
+
45
+ clean:
46
+ rm -rf .compared .built acceptance lib/gherkin3/parser.rb lib/gherkin3/gherkin-languages.json coverage
47
+ .PHONY: clean
48
+
49
+ lib/gherkin3/parser.rb: ../gherkin.berp gherkin-ruby.razor ../bin/berp.exe
50
+ mono ../bin/berp.exe -g ../gherkin.berp -t gherkin-ruby.razor -o $@
51
+ # Remove BOM
52
+ tail -c +4 $@ > $@.nobom
53
+ mv $@.nobom $@
54
+
55
+ Gemfile.lock: Gemfile
56
+ bundle install
57
+
58
+ LICENSE: ../LICENSE
59
+ cp $< $@
@@ -0,0 +1,3 @@
1
+ [![Build Status](https://secure.travis-ci.org/cucumber/gherkin-ruby.png)](http://travis-ci.org/cucumber/gherkin-ruby)
2
+
3
+ Gherkin parser/compiler for Ruby. Please see [Gherkin3](https://github.com/cucumber/gherkin3) for details.
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ $:.unshift File.expand_path("../lib", __FILE__)
7
+
8
+ require "rspec/core/rake_task"
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.ruby_opts = %w[-r./spec/coverage -w]
11
+ t.rspec_opts = %w[--color]
12
+ end
13
+
14
+ require_relative 'spec/capture_warnings'
15
+ include CaptureWarnings
16
+ namespace :spec do
17
+ task :warnings do
18
+ report_warnings do
19
+ Rake::Task['spec'].invoke
20
+ end
21
+ end
22
+ end
23
+
24
+ task default: ['spec:warnings']
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"../lib"))
3
+ require 'gherkin3/parser'
4
+ require 'gherkin3/token_scanner'
5
+ require 'gherkin3/token_matcher'
6
+ require 'gherkin3/ast_builder'
7
+ require 'json'
8
+
9
+ parser = Gherkin3::Parser.new
10
+ parser.stop_at_first_error = false
11
+ files = ARGV + (STDIN.tty? ? [] : [STDIN])
12
+ files.each do |file|
13
+ scanner = Gherkin3::TokenScanner.new(file)
14
+ builder = Gherkin3::AstBuilder.new
15
+ begin
16
+ puts JSON.generate(parser.parse(scanner, builder, Gherkin3::TokenMatcher.new))
17
+ rescue Gherkin3::ParserError => e
18
+ STDERR.puts e.message
19
+ exit 1
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"../lib"))
3
+ require 'gherkin3/parser'
4
+ require 'gherkin3/token_scanner'
5
+ require 'gherkin3/token_formatter_builder'
6
+ require 'gherkin3/token_matcher'
7
+
8
+ parser = Gherkin3::Parser.new
9
+ files = ARGV + (STDIN.tty? ? [] : [STDIN])
10
+ files.each do |file|
11
+ scanner = Gherkin3::TokenScanner.new(file)
12
+ builder = Gherkin3::TokenFormatterBuilder.new
13
+ print parser.parse(scanner, builder, Gherkin3::TokenMatcher.new)
14
+ end
@@ -0,0 +1,210 @@
1
+ @using Berp;
2
+ @helper CallProduction(ProductionRule production)
3
+ {
4
+ switch(production.Type)
5
+ {
6
+ case ProductionRuleType.Start:
7
+ @: start_rule(context, :@production.RuleName);
8
+ break;
9
+ case ProductionRuleType.End:
10
+ @: end_rule(context, :@production.RuleName);
11
+ break;
12
+ case ProductionRuleType.Process:
13
+ @: build(context, token);
14
+ break;
15
+ }
16
+ }
17
+ @helper HandleParserError(IEnumerable<string> expectedTokens, State state)
18
+ {<text>
19
+ state_comment = "State: @state.Id - @Raw(state.Comment)"
20
+ token.detach
21
+ expected_tokens = ["@Raw(string.Join("\", \"", expectedTokens))"]
22
+ error = token.eof? ? UnexpectedEOFException.new(token, expected_tokens, state_comment) : UnexpectedTokenException.new(token, expected_tokens, state_comment)
23
+ raise error if (stop_at_first_error)
24
+ add_error(context, error)
25
+ return @state.Id</text>}
26
+ @helper MatchToken(TokenType tokenType)
27
+ {<text>match_@(tokenType)(context, token)</text>}
28
+ # This file is generated. Do not edit! Edit gherkin-ruby.razor instead.
29
+ require_relative 'ast_builder'
30
+ require_relative 'errors'
31
+
32
+ module Gherkin3
33
+
34
+ RULE_TYPE = [
35
+ :None,
36
+ @foreach(var rule in Model.RuleSet.Where(r => !r.TempRule))
37
+ {<text> :@rule.Name.Replace("#", "_"), # @rule.ToString(true)
38
+ </text>}
39
+ ]
40
+
41
+ class ParserContext
42
+ attr_reader :token_scanner, :ast_builder, :token_matcher, :token_queue, :errors
43
+
44
+ def initialize(token_scanner, ast_builder, token_matcher, token_queue, errors)
45
+ @@token_scanner = token_scanner
46
+ @@ast_builder = ast_builder
47
+ @@token_matcher = token_matcher
48
+ @@token_queue = token_queue
49
+ @@errors = errors
50
+ end
51
+ end
52
+
53
+ class @Model.ParserClassName
54
+ attr_accessor :stop_at_first_error
55
+
56
+ def parse(token_scanner, ast_builder, token_matcher)
57
+ context = ParserContext.new(
58
+ token_scanner,
59
+ ast_builder,
60
+ token_matcher,
61
+ [],
62
+ []
63
+ )
64
+
65
+ start_rule(context, :@Model.RuleSet.StartRule.Name);
66
+ state = 0
67
+ token = nil
68
+ begin
69
+ token = read_token(context)
70
+ state = match_token(state, token, context)
71
+ end until(token.eof?)
72
+
73
+ end_rule(context, :@Model.RuleSet.StartRule.Name)
74
+
75
+ raise CompositeParserException.new(context.errors) if context.errors.any?
76
+
77
+ get_result(context)
78
+ end
79
+
80
+ def build(context, token)
81
+ handle_ast_error(context) do
82
+ context.ast_builder.build(token)
83
+ end
84
+ end
85
+
86
+ def add_error(context, error)
87
+ context.errors.push(error)
88
+ raise CompositeParserException, context.errors if context.errors.length > 10
89
+ end
90
+
91
+ def start_rule(context, rule_type)
92
+ handle_ast_error(context) do
93
+ context.ast_builder.start_rule(rule_type)
94
+ end
95
+ end
96
+
97
+ def end_rule(context, rule_type)
98
+ handle_ast_error(context) do
99
+ context.ast_builder.end_rule(rule_type)
100
+ end
101
+ end
102
+
103
+ def get_result(context)
104
+ context.ast_builder.get_result
105
+ end
106
+
107
+ def read_token(context)
108
+ context.token_queue.any? ? context.token_queue.shift : context.token_scanner.read
109
+ end
110
+
111
+ @foreach(var rule in Model.RuleSet.TokenRules)
112
+ {<text>
113
+ def match_@(rule.Name.Replace("#", ""))( context, token)
114
+ @if (rule.Name != "#EOF")
115
+ {
116
+ @:return false if token.eof?
117
+ }
118
+ return handle_external_error(context, false) do
119
+ context.token_matcher.match_@(rule.Name.Replace("#", ""))(token)
120
+ end
121
+ end</text>
122
+ }
123
+
124
+ def match_token(state, token, context)
125
+ case state
126
+ @foreach(var state in Model.States.Values.Where(s => !s.IsEndState))
127
+ {
128
+ @:when @state.Id
129
+ @:match_token_at_@(state.Id)(token, context)
130
+ }
131
+ else
132
+ raise InvalidOperationException, "Unknown state: #{state}"
133
+ end
134
+ end
135
+
136
+ @foreach(var state in Model.States.Values.Where(s => !s.IsEndState))
137
+ {<text>
138
+ # @Raw(state.Comment)
139
+ def match_token_at_@(state.Id)(token, context)
140
+ @foreach(var transition in state.Transitions)
141
+ {
142
+ @:if @MatchToken(transition.TokenType)
143
+ if (transition.LookAheadHint != null)
144
+ {
145
+ @:if lookahead_@(transition.LookAheadHint.Id)(context, token)
146
+ }
147
+ foreach(var production in transition.Productions)
148
+ {
149
+ @CallProduction(production)
150
+ }
151
+ @:return @transition.TargetState
152
+ if (transition.LookAheadHint != null)
153
+ {
154
+ @:end
155
+ }
156
+ @:end
157
+ }
158
+ @HandleParserError(state.Transitions.Select(t => "#" + t.TokenType.ToString()).Distinct(), state)
159
+ end</text>
160
+ }
161
+
162
+ @foreach(var lookAheadHint in Model.RuleSet.LookAheadHints)
163
+ {
164
+ <text>
165
+ def lookahead_@(lookAheadHint.Id)(context, currentToken)
166
+ currentToken.detach
167
+ token = nil
168
+ queue = []
169
+ match = false
170
+ loop do
171
+ token = read_token(context)
172
+ token.detach
173
+ queue.unshift(token)
174
+
175
+ if (false @foreach(var tokenType in lookAheadHint.ExpectedTokens) {<text>|| @MatchToken(tokenType)</text>})
176
+ match = true
177
+ break
178
+ end
179
+
180
+ break unless (false @foreach(var tokenType in lookAheadHint.Skip) {<text>|| @MatchToken(tokenType)</text>})
181
+ end
182
+
183
+ context.token_queue.concat(queue)
184
+
185
+ return match
186
+ end
187
+ </text>
188
+ }
189
+
190
+ private
191
+
192
+ def handle_ast_error(context, &action)
193
+ handle_external_error(context, true, &action)
194
+ end
195
+
196
+ def handle_external_error(context, default_value, &action)
197
+ return action.call if stop_at_first_error
198
+
199
+ begin
200
+ return action.call
201
+ rescue CompositeParserException => e
202
+ e.errors.each { |error| add_error(context, error) }
203
+ rescue ParserException => e
204
+ add_error(context, e)
205
+ end
206
+ default_value
207
+ end
208
+
209
+ end
210
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ Gem::Specification.new do |s|
3
+ s.name = 'gherkin3-pre-alpha'
4
+ s.version = '3.0.0.alpha.1'
5
+ s.authors = ["Gáspár Nagy", "Aslak Hellesøy", "Steve Tooke"]
6
+ s.description = 'Gherkin parser'
7
+ s.summary = "gherkin-#{s.version}"
8
+ s.email = 'cukes@googlegroups.com'
9
+ s.homepage = "https://github.com/cucumber/gherkin3"
10
+ s.platform = Gem::Platform::RUBY
11
+ s.license = "MIT"
12
+ s.required_ruby_version = ">= 1.9.3"
13
+
14
+ s.add_development_dependency 'bundler', '>= 1.7.6'
15
+ s.add_development_dependency 'rake', '>= 10.4.2'
16
+ s.add_development_dependency 'rspec', '>= 3.2.0'
17
+
18
+ # For coverage reports
19
+ s.add_development_dependency 'coveralls', '>= 0.8.1'
20
+
21
+ s.rubygems_version = ">= 1.6.1"
22
+ s.files = `git ls-files`.split("\n").reject {|path| path =~ /\.gitignore$/ }
23
+ s.test_files = `git ls-files -- spec/*`.split("\n")
24
+ s.rdoc_options = ["--charset=UTF-8"]
25
+ s.require_path = "lib"
26
+ end
@@ -0,0 +1,239 @@
1
+ require_relative 'ast_node'
2
+
3
+ module Gherkin3
4
+ class AstBuilder
5
+ def initialize
6
+ @stack = [AstNode.new(:None)]
7
+ @comments = []
8
+ end
9
+
10
+ def start_rule(rule_type)
11
+ @stack.push AstNode.new(rule_type)
12
+ end
13
+
14
+ def end_rule(rule_type)
15
+ node = @stack.pop
16
+ current_node.add(node.rule_type, transform_node(node))
17
+ end
18
+
19
+ def build(token)
20
+ if token.matched_type == :Comment
21
+ @comments.push({
22
+ type: 'Comment',
23
+ location: get_location(token),
24
+ text: token.matched_text
25
+ })
26
+ else
27
+ current_node.add(token.matched_type, token)
28
+ end
29
+ end
30
+
31
+ def get_result
32
+ current_node.get_single(:Feature)
33
+ end
34
+
35
+ def current_node
36
+ @stack.last
37
+ end
38
+
39
+ def get_location(token, column=nil)
40
+ # TODO: translated from JS... is it right?
41
+ (column.nil? || column.zero?) ? token.location : {line: token.location[:line], column: column}
42
+ end
43
+
44
+ def get_tags(node)
45
+ tags = []
46
+ tags_node = node.get_single(:Tags)
47
+ return tags unless tags_node
48
+
49
+ tags_node.get_tokens(:TagLine).each do |token|
50
+ token.matched_items.each do |tag_item|
51
+ tags.push({
52
+ type: 'Tag',
53
+ location: get_location(token, tag_item.column),
54
+ name: tag_item.text
55
+ })
56
+ end
57
+ end
58
+
59
+ tags
60
+ end
61
+
62
+ def get_table_rows(node)
63
+ rows = node.get_tokens(:TableRow).map do |token|
64
+ {
65
+ type: 'TableRow',
66
+ location: get_location(token),
67
+ cells: get_cells(token)
68
+ }
69
+ end
70
+ ensure_cell_count(rows);
71
+ rows
72
+ end
73
+
74
+ def ensure_cell_count(rows)
75
+ return if rows.empty?
76
+ cell_count = rows[0][:cells].length
77
+ rows.each do |row|
78
+ if (row[:cells].length != cell_count)
79
+ raise AstBuilderException.new("inconsistent cell count within the table", row[:location]);
80
+ end
81
+ end
82
+ end
83
+
84
+ def get_cells(table_row_token)
85
+ table_row_token.matched_items.map do |cell_item|
86
+ {
87
+ type: 'TableCell',
88
+ location: get_location(table_row_token, cell_item.column),
89
+ value: cell_item.text
90
+ }
91
+ end
92
+ end
93
+
94
+ def get_description(node)
95
+ node.get_single(:Description)
96
+ end
97
+
98
+ def get_steps(node)
99
+ node.get_items(:Step)
100
+ end
101
+
102
+ def transform_node(node)
103
+ case node.rule_type
104
+ when :Step
105
+ step_line = node.get_token(:StepLine)
106
+ step_argument = node.get_single(:DataTable) || node.get_single(:DocString) || nil
107
+
108
+ reject_nils(
109
+ type: node.rule_type,
110
+ location: get_location(step_line),
111
+ keyword: step_line.matched_keyword,
112
+ text: step_line.matched_text,
113
+ argument: step_argument
114
+ )
115
+ when :DocString
116
+ separator_token = node.get_tokens(:DocStringSeparator)[0]
117
+ content_type = separator_token.matched_text
118
+ line_tokens = node.get_tokens(:Other)
119
+ content = line_tokens.map { |t| t.matched_text }.join("\n")
120
+
121
+ reject_nils(
122
+ type: node.rule_type,
123
+ location: get_location(separator_token),
124
+ contentType: content_type,
125
+ content: content
126
+ )
127
+ when :DataTable
128
+ rows = get_table_rows(node)
129
+ reject_nils(
130
+ type: node.rule_type,
131
+ location: rows[0][:location],
132
+ rows: rows,
133
+ )
134
+ when :Background
135
+ background_line = node.get_token(:BackgroundLine)
136
+ description = get_description(node)
137
+ steps = get_steps(node)
138
+
139
+ reject_nils(
140
+ type: node.rule_type,
141
+ location: get_location(background_line),
142
+ keyword: background_line.matched_keyword,
143
+ name: background_line.matched_text,
144
+ description: description,
145
+ steps: steps
146
+ )
147
+ when :Scenario_Definition
148
+ tags = get_tags(node)
149
+ scenario_node = node.get_single(:Scenario)
150
+ if(scenario_node)
151
+ scenario_line = scenario_node.get_token(:ScenarioLine)
152
+ description = get_description(scenario_node)
153
+ steps = get_steps(scenario_node)
154
+
155
+ reject_nils(
156
+ type: scenario_node.rule_type,
157
+ tags: tags,
158
+ location: get_location(scenario_line),
159
+ keyword: scenario_line.matched_keyword,
160
+ name: scenario_line.matched_text,
161
+ description: description,
162
+ steps: steps
163
+ )
164
+ else
165
+ scenario_outline_node = node.get_single(:ScenarioOutline)
166
+ raise 'Internal grammar error' unless scenario_outline_node
167
+
168
+ scenario_outline_line = scenario_outline_node.get_token(:ScenarioOutlineLine)
169
+ description = get_description(scenario_outline_node)
170
+ steps = get_steps(scenario_outline_node)
171
+ examples = scenario_outline_node.get_items(:Examples_Definition)
172
+
173
+ reject_nils(
174
+ type: scenario_outline_node.rule_type,
175
+ tags: tags,
176
+ location: get_location(scenario_outline_line),
177
+ keyword: scenario_outline_line.matched_keyword,
178
+ name: scenario_outline_line.matched_text,
179
+ description: description,
180
+ steps: steps,
181
+ examples: examples
182
+ )
183
+ end
184
+ when :Examples_Definition
185
+ tags = get_tags(node)
186
+ examples_node = node.get_single(:Examples)
187
+ examples_line = examples_node.get_token(:ExamplesLine)
188
+ description = get_description(examples_node)
189
+ rows = get_table_rows(examples_node)
190
+
191
+ reject_nils(
192
+ type: examples_node.rule_type,
193
+ tags: tags,
194
+ location: get_location(examples_line),
195
+ keyword: examples_line.matched_keyword,
196
+ name: examples_line.matched_text,
197
+ description: description,
198
+ tableHeader: rows.first,
199
+ tableBody: rows[1..-1]
200
+ )
201
+ when :Description
202
+ line_tokens = node.get_tokens(:Other)
203
+ # Trim trailing empty lines
204
+ last_non_empty = line_tokens.rindex { |token| !token.line.trimmed_line_text.empty? }
205
+ description = line_tokens[0..last_non_empty].map { |token| token.matched_text }.join("\n")
206
+ return description
207
+ when :Feature
208
+ header = node.get_single(:Feature_Header)
209
+ return unless header
210
+ tags = get_tags(header)
211
+ feature_line = header.get_token(:FeatureLine)
212
+ return unless feature_line
213
+ background = node.get_single(:Background)
214
+ scenario_definitions = node.get_items(:Scenario_Definition)
215
+ description = get_description(header)
216
+ language = feature_line.matched_gherkin_dialect
217
+
218
+ reject_nils(
219
+ type: node.rule_type,
220
+ tags: tags,
221
+ location: get_location(feature_line),
222
+ language: language,
223
+ keyword: feature_line.matched_keyword,
224
+ name: feature_line.matched_text,
225
+ description: description,
226
+ background: background,
227
+ scenarioDefinitions: scenario_definitions,
228
+ comments: @comments
229
+ )
230
+ else
231
+ return node
232
+ end
233
+ end
234
+
235
+ def reject_nils(values)
236
+ values.reject { |k,v| v.nil? }
237
+ end
238
+ end
239
+ end