gherkin3 3.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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