gherkin3 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: 0dc70f9552b102602504fff5b292d8eea53e0b4e
4
+ data.tar.gz: d802c01c8500f2bcc7e24f699aa4e3667b47e10f
5
+ SHA512:
6
+ metadata.gz: 5e8a7e4f1065ada8af02b0c10737689f0b7ec080a79a8698c39c3aa9b0e9955a38122061a5eac960225a61e0fa1a90a1dc22fbfdd8a41022c6b6211fd88845b4
7
+ data.tar.gz: d5ca28a5a800909e3d334cca859fa0a3d96e42ddef04da103d77e1be899bbb94972369e1bae14260927e1bc5e8744eda424173c790e0c8335c032ab02258aabf
@@ -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,63 @@
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 $< $@
60
+
61
+ release: .compared
62
+ bundle exec rake build release:guard_clean release:rubygem_push
63
+ .PHONY: release
@@ -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,24 @@
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.any? ? ARGV : (STDIN.tty? ? [] : [STDIN])
12
+ start_time = Time.now
13
+ files.each do |file|
14
+ scanner = Gherkin3::TokenScanner.new(file)
15
+ builder = Gherkin3::AstBuilder.new
16
+ begin
17
+ puts JSON.generate(parser.parse(scanner, builder, Gherkin3::TokenMatcher.new))
18
+ rescue Gherkin3::ParserError => e
19
+ STDERR.puts e.message
20
+ exit 1
21
+ end
22
+ end
23
+ end_time = Time.now
24
+ STDERR.puts (end_time - start_time)*1000 if ENV['GHERKIN_PERF']
@@ -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,211 @@
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 'token_matcher'
31
+ require_relative 'errors'
32
+
33
+ module Gherkin3
34
+
35
+ RULE_TYPE = [
36
+ :None,
37
+ @foreach(var rule in Model.RuleSet.Where(r => !r.TempRule))
38
+ {<text> :@rule.Name.Replace("#", "_"), # @rule.ToString(true)
39
+ </text>}
40
+ ]
41
+
42
+ class ParserContext
43
+ attr_reader :token_scanner, :ast_builder, :token_matcher, :token_queue, :errors
44
+
45
+ def initialize(token_scanner, ast_builder, token_matcher, token_queue, errors)
46
+ @@token_scanner = token_scanner
47
+ @@ast_builder = ast_builder
48
+ @@token_matcher = token_matcher
49
+ @@token_queue = token_queue
50
+ @@errors = errors
51
+ end
52
+ end
53
+
54
+ class @Model.ParserClassName
55
+ attr_accessor :stop_at_first_error
56
+
57
+ def parse(token_scanner, ast_builder=AstBuilder.new, token_matcher=TokenMatcher.new)
58
+ context = ParserContext.new(
59
+ token_scanner,
60
+ ast_builder,
61
+ token_matcher,
62
+ [],
63
+ []
64
+ )
65
+
66
+ start_rule(context, :@Model.RuleSet.StartRule.Name);
67
+ state = 0
68
+ token = nil
69
+ begin
70
+ token = read_token(context)
71
+ state = match_token(state, token, context)
72
+ end until(token.eof?)
73
+
74
+ end_rule(context, :@Model.RuleSet.StartRule.Name)
75
+
76
+ raise CompositeParserException.new(context.errors) if context.errors.any?
77
+
78
+ get_result(context)
79
+ end
80
+
81
+ def build(context, token)
82
+ handle_ast_error(context) do
83
+ context.ast_builder.build(token)
84
+ end
85
+ end
86
+
87
+ def add_error(context, error)
88
+ context.errors.push(error)
89
+ raise CompositeParserException, context.errors if context.errors.length > 10
90
+ end
91
+
92
+ def start_rule(context, rule_type)
93
+ handle_ast_error(context) do
94
+ context.ast_builder.start_rule(rule_type)
95
+ end
96
+ end
97
+
98
+ def end_rule(context, rule_type)
99
+ handle_ast_error(context) do
100
+ context.ast_builder.end_rule(rule_type)
101
+ end
102
+ end
103
+
104
+ def get_result(context)
105
+ context.ast_builder.get_result
106
+ end
107
+
108
+ def read_token(context)
109
+ context.token_queue.any? ? context.token_queue.shift : context.token_scanner.read
110
+ end
111
+
112
+ @foreach(var rule in Model.RuleSet.TokenRules)
113
+ {<text>
114
+ def match_@(rule.Name.Replace("#", ""))( context, token)
115
+ @if (rule.Name != "#EOF")
116
+ {
117
+ @:return false if token.eof?
118
+ }
119
+ return handle_external_error(context, false) do
120
+ context.token_matcher.match_@(rule.Name.Replace("#", ""))(token)
121
+ end
122
+ end</text>
123
+ }
124
+
125
+ def match_token(state, token, context)
126
+ case state
127
+ @foreach(var state in Model.States.Values.Where(s => !s.IsEndState))
128
+ {
129
+ @:when @state.Id
130
+ @:match_token_at_@(state.Id)(token, context)
131
+ }
132
+ else
133
+ raise InvalidOperationException, "Unknown state: #{state}"
134
+ end
135
+ end
136
+
137
+ @foreach(var state in Model.States.Values.Where(s => !s.IsEndState))
138
+ {<text>
139
+ # @Raw(state.Comment)
140
+ def match_token_at_@(state.Id)(token, context)
141
+ @foreach(var transition in state.Transitions)
142
+ {
143
+ @:if @MatchToken(transition.TokenType)
144
+ if (transition.LookAheadHint != null)
145
+ {
146
+ @:if lookahead_@(transition.LookAheadHint.Id)(context, token)
147
+ }
148
+ foreach(var production in transition.Productions)
149
+ {
150
+ @CallProduction(production)
151
+ }
152
+ @:return @transition.TargetState
153
+ if (transition.LookAheadHint != null)
154
+ {
155
+ @:end
156
+ }
157
+ @:end
158
+ }
159
+ @HandleParserError(state.Transitions.Select(t => "#" + t.TokenType.ToString()).Distinct(), state)
160
+ end</text>
161
+ }
162
+
163
+ @foreach(var lookAheadHint in Model.RuleSet.LookAheadHints)
164
+ {
165
+ <text>
166
+ def lookahead_@(lookAheadHint.Id)(context, currentToken)
167
+ currentToken.detach
168
+ token = nil
169
+ queue = []
170
+ match = false
171
+ loop do
172
+ token = read_token(context)
173
+ token.detach
174
+ queue.unshift(token)
175
+
176
+ if (false @foreach(var tokenType in lookAheadHint.ExpectedTokens) {<text>|| @MatchToken(tokenType)</text>})
177
+ match = true
178
+ break
179
+ end
180
+
181
+ break unless (false @foreach(var tokenType in lookAheadHint.Skip) {<text>|| @MatchToken(tokenType)</text>})
182
+ end
183
+
184
+ context.token_queue.concat(queue)
185
+
186
+ return match
187
+ end
188
+ </text>
189
+ }
190
+
191
+ private
192
+
193
+ def handle_ast_error(context, &action)
194
+ handle_external_error(context, true, &action)
195
+ end
196
+
197
+ def handle_external_error(context, default_value, &action)
198
+ return action.call if stop_at_first_error
199
+
200
+ begin
201
+ return action.call
202
+ rescue CompositeParserException => e
203
+ e.errors.each { |error| add_error(context, error) }
204
+ rescue ParserException => e
205
+ add_error(context, e)
206
+ end
207
+ default_value
208
+ end
209
+
210
+ end
211
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ Gem::Specification.new do |s|
3
+ s.name = 'gherkin3'
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'
15
+ s.add_development_dependency 'rake', '~> 10.4'
16
+ s.add_development_dependency 'rspec', '~> 3.3'
17
+
18
+ # For coverage reports
19
+ s.add_development_dependency 'coveralls', '~> 0.8'
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