chutney 1.6.3 → 2.0.0.rc1
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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +3 -3
- data/README.md +44 -30
- data/Rakefile +10 -24
- data/chutney.gemspec +13 -10
- data/config/{default.yml → chutney.yml} +6 -3
- data/docs/.keep +0 -0
- data/exe/chutney +28 -22
- data/img/chutney.svg +852 -0
- data/img/formatters.png +0 -0
- data/lib/chutney.rb +61 -85
- data/lib/chutney/configuration.rb +6 -7
- data/lib/chutney/formatter.rb +21 -0
- data/lib/chutney/formatter/json_formatter.rb +8 -0
- data/lib/chutney/formatter/pie_formatter.rb +78 -0
- data/lib/chutney/formatter/rainbow_formatter.rb +47 -0
- data/lib/chutney/linter.rb +145 -113
- data/lib/chutney/linter/avoid_full_stop.rb +12 -0
- data/lib/chutney/linter/avoid_outline_for_single_example.rb +3 -6
- data/lib/chutney/linter/avoid_scripting.rb +10 -15
- data/lib/chutney/linter/background_does_more_than_setup.rb +7 -10
- data/lib/chutney/linter/background_requires_multiple_scenarios.rb +2 -3
- data/lib/chutney/linter/bad_scenario_name.rb +4 -11
- data/lib/chutney/linter/file_name_differs_feature_name.rb +5 -10
- data/lib/chutney/linter/givens_after_background.rb +17 -0
- data/lib/chutney/linter/invalid_file_name.rb +14 -6
- data/lib/chutney/linter/invalid_step_flow.rb +18 -18
- data/lib/chutney/linter/missing_example_name.rb +13 -11
- data/lib/chutney/linter/missing_feature_description.rb +2 -9
- data/lib/chutney/linter/missing_feature_name.rb +3 -12
- data/lib/chutney/linter/missing_scenario_name.rb +3 -7
- data/lib/chutney/linter/missing_test_action.rb +3 -6
- data/lib/chutney/linter/missing_verification.rb +3 -4
- data/lib/chutney/linter/required_tags_starts_with.rb +20 -5
- data/lib/chutney/linter/same_tag_for_all_scenarios.rb +24 -28
- data/lib/chutney/linter/scenario_names_match.rb +6 -8
- data/lib/chutney/linter/tag_used_multiple_times.rb +6 -13
- data/lib/chutney/linter/too_clumsy.rb +4 -4
- data/lib/chutney/linter/too_long_step.rb +8 -18
- data/lib/chutney/linter/too_many_different_tags.rb +13 -34
- data/lib/chutney/linter/too_many_steps.rb +10 -6
- data/lib/chutney/linter/too_many_tags.rb +11 -10
- data/lib/chutney/linter/unique_scenario_names.rb +19 -13
- data/lib/chutney/linter/unknown_variable.rb +5 -6
- data/lib/chutney/linter/unused_variable.rb +6 -7
- data/lib/chutney/linter/use_background.rb +11 -29
- data/lib/chutney/linter/use_outline.rb +21 -15
- data/lib/chutney/version.rb +1 -1
- data/lib/config/locales/en.yml +93 -0
- data/spec/chutney_spec.rb +54 -62
- data/spec/spec_helper.rb +103 -0
- metadata +75 -44
- data/Guardfile +0 -3
- data/lib/chutney/linter/avoid_period.rb +0 -19
- data/lib/chutney/linter/be_declarative.rb +0 -49
- data/lib/chutney/linter/tag_collector.rb +0 -10
- data/lib/chutney/linter/tag_constraint.rb +0 -35
- data/spec/configuration_spec.rb +0 -58
- data/spec/required_tags_starts_with_spec.rb +0 -74
- data/spec/shared_contexts/file_exists.rb +0 -12
- data/spec/shared_contexts/gherkin_linter.rb +0 -14
data/img/formatters.png
ADDED
Binary file
|
data/lib/chutney.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
require '
|
1
|
+
require 'amatch'
|
2
|
+
require 'chutney/configuration'
|
2
3
|
require 'chutney/linter'
|
4
|
+
require 'chutney/linter/avoid_full_stop'
|
3
5
|
require 'chutney/linter/avoid_outline_for_single_example'
|
4
|
-
require 'chutney/linter/avoid_period'
|
5
6
|
require 'chutney/linter/avoid_scripting'
|
6
7
|
require 'chutney/linter/background_does_more_than_setup'
|
7
8
|
require 'chutney/linter/background_requires_multiple_scenarios'
|
8
9
|
require 'chutney/linter/bad_scenario_name'
|
9
|
-
require 'chutney/linter/be_declarative'
|
10
10
|
require 'chutney/linter/file_name_differs_feature_name'
|
11
|
+
require 'chutney/linter/givens_after_background'
|
11
12
|
require 'chutney/linter/invalid_file_name'
|
12
13
|
require 'chutney/linter/invalid_step_flow'
|
13
14
|
require 'chutney/linter/missing_example_name'
|
@@ -30,103 +31,78 @@ require 'chutney/linter/unknown_variable'
|
|
30
31
|
require 'chutney/linter/unused_variable'
|
31
32
|
require 'chutney/linter/use_background'
|
32
33
|
require 'chutney/linter/use_outline'
|
33
|
-
require '
|
34
|
+
require 'forwardable'
|
35
|
+
require 'gherkin/dialect'
|
36
|
+
require 'gherkin/parser'
|
37
|
+
require 'i18n'
|
34
38
|
require 'set'
|
35
|
-
require '
|
39
|
+
require 'yaml'
|
36
40
|
|
37
41
|
module Chutney
|
38
42
|
# gherkin linter
|
39
43
|
class ChutneyLint
|
44
|
+
extend Forwardable
|
40
45
|
attr_accessor :verbose
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@
|
48
|
-
@
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
@config.config[linter_name]['Enabled'] = value if @config.config.key? linter_name
|
46
|
+
attr_reader :files
|
47
|
+
attr_reader :results
|
48
|
+
|
49
|
+
def_delegators :@files, :<<, :clear, :delete, :include?
|
50
|
+
|
51
|
+
def initialize(*files)
|
52
|
+
@files = files
|
53
|
+
@results = Hash.new { |h, k| h[k] = [] }
|
54
|
+
i18n_paths = Dir[File.expand_path(File.join(__dir__, 'config/locales')) + '/*.yml']
|
55
|
+
return if I18n.load_path.include?(i18n_paths)
|
56
|
+
|
57
|
+
I18n.load_path << i18n_paths
|
54
58
|
end
|
55
|
-
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
def disable(disabled_linters)
|
63
|
-
disabled_linters.each do |linter|
|
64
|
-
enabled linter, false
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# Testing feature
|
69
|
-
def disable_all
|
70
|
-
@config.config.each do |member|
|
71
|
-
@config.config[member[0]]['Enabled'] = false
|
59
|
+
|
60
|
+
def configuration
|
61
|
+
unless @config
|
62
|
+
default_file = [File.expand_path('..', __dir__), '**/config', 'chutney.yml']
|
63
|
+
config_file = Dir.glob(File.join(default_file)).first.freeze
|
64
|
+
@config = Configuration.new(config_file)
|
72
65
|
end
|
66
|
+
@config
|
73
67
|
end
|
74
|
-
|
75
|
-
def
|
76
|
-
@
|
77
|
-
LINTER.each do |linter|
|
78
|
-
new_linter = linter.new
|
79
|
-
linter_enabled = @config.config[new_linter.class.name.split('::').last]['Enabled']
|
80
|
-
evaluate_members(new_linter) if linter_enabled
|
81
|
-
@linter.push new_linter if linter_enabled
|
82
|
-
end
|
68
|
+
|
69
|
+
def configuration=(config)
|
70
|
+
@config = config
|
83
71
|
end
|
84
|
-
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
member = member.downcase.to_sym
|
90
|
-
raise 'Member not found! Check the YAML' unless linter.respond_to? member
|
91
|
-
|
92
|
-
linter.public_send(member, value)
|
72
|
+
|
73
|
+
def analyse
|
74
|
+
files.each do |f|
|
75
|
+
lint(f)
|
93
76
|
end
|
77
|
+
@results
|
94
78
|
end
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
to_json File.read(file)
|
102
|
-
end
|
103
|
-
|
104
|
-
def report
|
105
|
-
issues = @linter.map do |linter|
|
106
|
-
linter.lint_files(@files, disable_tags)
|
107
|
-
linter.issues
|
108
|
-
end.flatten
|
109
|
-
|
110
|
-
print issues
|
111
|
-
return 0 if issues.select { |issue| issue.class == Error }.empty?
|
112
|
-
|
113
|
-
-1
|
79
|
+
# alias for non-british English
|
80
|
+
# https://dictionary.cambridge.org/dictionary/english/analyse
|
81
|
+
alias analyze analyse
|
82
|
+
|
83
|
+
def linters
|
84
|
+
@linters ||= Linter.descendants.filter { |l| configuration.dig(l.linter_name, 'Enabled') }
|
114
85
|
end
|
115
|
-
|
116
|
-
def
|
117
|
-
|
86
|
+
|
87
|
+
def linters=(*linters)
|
88
|
+
@linters = linters
|
118
89
|
end
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def parse(text)
|
94
|
+
@parser ||= Gherkin::Parser.new
|
95
|
+
scanner = Gherkin::TokenScanner.new(text)
|
96
|
+
@parser.parse(scanner)
|
125
97
|
end
|
126
|
-
|
127
|
-
def
|
128
|
-
|
129
|
-
|
98
|
+
|
99
|
+
def lint(file)
|
100
|
+
parsed = parse(File.read(file))
|
101
|
+
linters.each do |linter_class|
|
102
|
+
linter = linter_class.new(file, parsed, configuration[linter_class.linter_name])
|
103
|
+
linter.lint
|
104
|
+
@results[file] << { linter: linter.linter_name, issues: linter.issues }
|
105
|
+
end
|
130
106
|
end
|
131
107
|
end
|
132
108
|
end
|
@@ -1,13 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
module Chutney
|
1
|
+
require 'delegate'
|
2
|
+
module Chutney
|
3
3
|
# gherkin_lint configuration object
|
4
|
-
class Configuration
|
5
|
-
attr_reader :config
|
6
|
-
|
4
|
+
class Configuration < SimpleDelegator
|
7
5
|
def initialize(path)
|
8
6
|
@path = path
|
9
|
-
@config = load_configuration ||
|
7
|
+
@config = load_configuration || {}
|
10
8
|
load_user_configuration
|
9
|
+
super(@config)
|
11
10
|
end
|
12
11
|
|
13
12
|
def configuration_path
|
@@ -15,7 +14,7 @@ module Chutney
|
|
15
14
|
end
|
16
15
|
|
17
16
|
def load_configuration
|
18
|
-
YAML.load_file configuration_path || '' if
|
17
|
+
YAML.load_file configuration_path || '' if configuration_path
|
19
18
|
end
|
20
19
|
|
21
20
|
def load_user_configuration
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Chutney
|
2
|
+
# base class for all formatters
|
3
|
+
class Formatter
|
4
|
+
attr_accessor :results
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@results = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def format(results); end
|
11
|
+
|
12
|
+
def files
|
13
|
+
results.map { |k, _v| k }
|
14
|
+
end
|
15
|
+
|
16
|
+
def files_with_issues
|
17
|
+
results.filter { |_k, v| v.any? { |r| r[:issues].count.positive? } }
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'pastel'
|
2
|
+
require 'tty/pie'
|
3
|
+
|
4
|
+
module Chutney
|
5
|
+
# format results as pie charts
|
6
|
+
class PieFormatter < Formatter
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def format
|
12
|
+
data = top_offences.map do |offence|
|
13
|
+
{
|
14
|
+
name: offence.first,
|
15
|
+
value: offence.last,
|
16
|
+
color: colour_loop,
|
17
|
+
fill: char_loop
|
18
|
+
}
|
19
|
+
end
|
20
|
+
print_report(data)
|
21
|
+
end
|
22
|
+
|
23
|
+
def print_report(data)
|
24
|
+
unless data.empty?
|
25
|
+
print TTY::Pie.new(data: data, radius: 8, legend: { format: '%<label>s %<name>s %<value>i' })
|
26
|
+
puts
|
27
|
+
end
|
28
|
+
# put_summary
|
29
|
+
end
|
30
|
+
|
31
|
+
def top_offences
|
32
|
+
offence = Hash.new(0)
|
33
|
+
files_with_issues.each do |_file, linter|
|
34
|
+
linter.each do |lint|
|
35
|
+
offence[lint[:linter]] += lint[:issues].count
|
36
|
+
end
|
37
|
+
end
|
38
|
+
offence.reject { |_k, v| v.zero? }.sort_by { |_linter, count| -count }
|
39
|
+
end
|
40
|
+
|
41
|
+
def char_loop
|
42
|
+
@char_looper ||= Fiber.new do
|
43
|
+
chars = %w[• x + @ * / -]
|
44
|
+
current = 0
|
45
|
+
loop do
|
46
|
+
current = 0 if current >= chars.count
|
47
|
+
Fiber.yield chars[current]
|
48
|
+
current += 1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@char_looper.resume
|
52
|
+
end
|
53
|
+
|
54
|
+
def colour_loop
|
55
|
+
@colour_looper ||= Fiber.new do
|
56
|
+
colours = %i[bright_cyan bright_magenta bright_yellow bright_green]
|
57
|
+
current = 0
|
58
|
+
loop do
|
59
|
+
current = 0 if current >= colours.count
|
60
|
+
Fiber.yield colours[current]
|
61
|
+
current += 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
@colour_looper.resume
|
65
|
+
end
|
66
|
+
|
67
|
+
def put_summary
|
68
|
+
pastel = Pastel.new
|
69
|
+
print "#{files.count} features inspected, "
|
70
|
+
if files_with_issues.count.zero?
|
71
|
+
puts pastel.green('all taste delicious')
|
72
|
+
else
|
73
|
+
puts pastel.red("#{files_with_issues.count} taste nasty")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'pastel'
|
2
|
+
|
3
|
+
module Chutney
|
4
|
+
# pretty formatter
|
5
|
+
class RainbowFormatter < Formatter
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
|
10
|
+
@pastel = Pastel.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def format
|
14
|
+
files_with_issues.each do |file, linter|
|
15
|
+
put_file(file)
|
16
|
+
linter.filter { |l| !l[:issues].empty? }.each do |linter_with_issues|
|
17
|
+
|
18
|
+
put_linter(linter_with_issues)
|
19
|
+
linter_with_issues[:issues].each { |i| put_issue(i) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
put_summary
|
23
|
+
end
|
24
|
+
|
25
|
+
def put_file(file)
|
26
|
+
puts @pastel.cyan(file.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
def put_linter(linter)
|
30
|
+
puts @pastel.red(" #{linter[:linter]}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def put_issue(issue)
|
34
|
+
puts " #{@pastel.dim(issue.dig(:location, :line))} #{issue[:message]}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def put_summary
|
38
|
+
print "#{files.count} features inspected, "
|
39
|
+
if files_with_issues.count.zero?
|
40
|
+
puts @pastel.green('all taste delicious')
|
41
|
+
else
|
42
|
+
puts @pastel.red("#{files_with_issues.count} taste nasty")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/chutney/linter.rb
CHANGED
@@ -1,149 +1,181 @@
|
|
1
|
-
require 'chutney/issue'
|
2
|
-
|
3
1
|
# gherkin utilities
|
4
2
|
module Chutney
|
5
3
|
# base class for all linters
|
6
4
|
class Linter
|
7
|
-
|
5
|
+
attr_accessor :issues
|
6
|
+
attr_reader :filename
|
7
|
+
attr_reader :configuration
|
8
|
+
|
9
|
+
Lint = Struct.new(:message, :gherkin_type, :location, :feature, :scenario, :step, keyword_init: true)
|
8
10
|
|
9
11
|
def self.descendants
|
10
12
|
ObjectSpace.each_object(::Class).select { |klass| klass < self }
|
11
13
|
end
|
12
14
|
|
13
|
-
def initialize
|
15
|
+
def initialize(filename, content, configuration)
|
16
|
+
@content = content
|
17
|
+
@filename = filename
|
14
18
|
@issues = []
|
15
|
-
@
|
19
|
+
@configuration = configuration
|
20
|
+
language = @content.dig(:feature, :language) || 'en'
|
21
|
+
@dialect = Gherkin::Dialect.for(language)
|
16
22
|
end
|
17
23
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
def lint
|
25
|
+
raise 'not implemented'
|
26
|
+
end
|
27
|
+
|
28
|
+
def and_word?(word)
|
29
|
+
@dialect.and_keywords.include?(word)
|
30
|
+
end
|
31
|
+
|
32
|
+
def background_word?(word)
|
33
|
+
@dialect.background_keywords.include?(word)
|
34
|
+
end
|
35
|
+
|
36
|
+
def but_word?(word)
|
37
|
+
@dialect.but_keywords.include?(word)
|
38
|
+
end
|
39
|
+
|
40
|
+
def examples_word?(word)
|
41
|
+
@dialect.example_keywords.include?(word)
|
42
|
+
end
|
43
|
+
|
44
|
+
def feature_word?(word)
|
45
|
+
@dialect.feature_keywords.include?(word)
|
46
|
+
end
|
47
|
+
|
48
|
+
def given_word?(word)
|
49
|
+
@dialect.given_keywords.include?(word)
|
50
|
+
end
|
51
|
+
|
52
|
+
def scenario_outline_word?(word)
|
53
|
+
@dialect.scenario_outline_keywords.include?(word)
|
54
|
+
end
|
55
|
+
|
56
|
+
def then_word?(word)
|
57
|
+
@dialect.then_keywords.include?(word)
|
58
|
+
end
|
59
|
+
|
60
|
+
def when_word?(word)
|
61
|
+
@dialect.when_keywords.include?(word)
|
62
|
+
end
|
63
|
+
|
64
|
+
def tags_for(element)
|
65
|
+
return [] unless element.include? :tags
|
66
|
+
|
67
|
+
element[:tags].map { |tag| tag[:name][1..-1] }
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_issue(message, feature, scenario = nil, step = nil)
|
71
|
+
issues << Lint.new(
|
72
|
+
message: message,
|
73
|
+
gherkin_type: type(feature, scenario, step),
|
74
|
+
location: location(feature, scenario, step),
|
75
|
+
feature: feature[:name],
|
76
|
+
scenario: scenario ? scenario[:name] : nil,
|
77
|
+
step: step ? step[:text] : nil
|
78
|
+
).to_h
|
79
|
+
end
|
80
|
+
|
81
|
+
def location(feature, scenario, step)
|
82
|
+
if step
|
83
|
+
step[:location]
|
84
|
+
else
|
85
|
+
scenario ? scenario[:location] : feature[:location]
|
24
86
|
end
|
25
87
|
end
|
26
|
-
|
27
|
-
def
|
28
|
-
|
88
|
+
|
89
|
+
def type(_feature, scenario, step)
|
90
|
+
if step
|
91
|
+
:step
|
92
|
+
else
|
93
|
+
scenario ? :scenario : :feature
|
94
|
+
end
|
29
95
|
end
|
30
96
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
97
|
+
def feature
|
98
|
+
if block_given?
|
99
|
+
yield(@content[:feature]) if @content[:feature]
|
100
|
+
else
|
101
|
+
@content[:feature]
|
36
102
|
end
|
37
103
|
end
|
38
|
-
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
104
|
+
|
105
|
+
def elements
|
106
|
+
if block_given?
|
107
|
+
feature[:children].each do |child|
|
108
|
+
next if off_switch?(child)
|
43
109
|
|
44
|
-
|
110
|
+
yield(feature, child)
|
111
|
+
end
|
112
|
+
else
|
113
|
+
feature[:children]
|
45
114
|
end
|
46
115
|
end
|
47
|
-
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
116
|
+
|
117
|
+
def off_switch?(element = feature)
|
118
|
+
off_switch = element[:tags]
|
119
|
+
.then { |tags| tags || [] }
|
120
|
+
.filter { |tag| tag[:type] == :Tag }
|
121
|
+
.filter { |tag| tag[:name] == "@disable#{linter_name}" }
|
122
|
+
.count
|
123
|
+
.positive?
|
124
|
+
off_switch ||= off_switch?(feature) unless element == feature
|
125
|
+
off_switch
|
126
|
+
end
|
127
|
+
|
128
|
+
def background
|
129
|
+
if block_given?
|
130
|
+
elements do |feature, child|
|
131
|
+
next unless child[:type] == :Background
|
132
|
+
|
133
|
+
yield(feature, child)
|
134
|
+
end
|
135
|
+
else
|
136
|
+
elements.filter { |child| child[:type] == :Background }
|
53
137
|
end
|
54
138
|
end
|
55
|
-
|
56
|
-
def
|
57
|
-
|
58
|
-
|
139
|
+
|
140
|
+
def scenarios
|
141
|
+
if block_given?
|
142
|
+
elements do |feature, child|
|
143
|
+
next unless %i[ScenarioOutline Scenario].include? child[:type]
|
59
144
|
|
60
|
-
|
145
|
+
yield(feature, child)
|
146
|
+
end
|
147
|
+
else
|
148
|
+
elements.filter { |child| %i[ScenarioOutline Scenario].include? child[:type] }
|
61
149
|
end
|
62
150
|
end
|
63
|
-
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
151
|
+
|
152
|
+
def filled_scenarios
|
153
|
+
if block_given?
|
154
|
+
scenarios do |feature, scenario|
|
155
|
+
next unless scenario.include? :steps
|
156
|
+
next if scenario[:steps].empty?
|
69
157
|
|
70
|
-
|
71
|
-
yield(file, feature, scenario)
|
158
|
+
yield(feature, scenario)
|
72
159
|
end
|
160
|
+
else
|
161
|
+
scenarios.filter { |s| !s[:steps].empty? }
|
73
162
|
end
|
74
163
|
end
|
75
|
-
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
@files = files
|
82
|
-
@files = filter_tag(@files, "disable#{name}")
|
83
|
-
@files = suppress_tags(@files, tags_to_suppress)
|
84
|
-
lint
|
85
|
-
end
|
86
|
-
|
87
|
-
def filter_tag(data, tag)
|
88
|
-
return data.reject { |item| tag?(item, tag) }.map { |item| filter_tag(item, tag) } if data.class == Array
|
89
|
-
return {} if (data.class == Hash) && (data.include? :feature) && tag?(data[:feature], tag)
|
90
|
-
return data unless data.respond_to? :each_pair
|
91
|
-
|
92
|
-
result = {}
|
93
|
-
data.each_pair { |key, value| result[key] = filter_tag(value, tag) }
|
94
|
-
result
|
95
|
-
end
|
96
|
-
|
97
|
-
def tag?(data, tag)
|
98
|
-
return false if data.class != Hash
|
99
|
-
return false unless data.include? :tags
|
100
|
-
|
101
|
-
data[:tags].map { |item| item[:name] }.include? "@#{tag}"
|
102
|
-
end
|
103
|
-
|
104
|
-
def suppress_tags(data, tags)
|
105
|
-
return data.map { |item| suppress_tags(item, tags) } if data.class == Array
|
106
|
-
return data unless data.class == Hash
|
107
|
-
|
108
|
-
result = {}
|
109
|
-
|
110
|
-
data.each_pair do |key, value|
|
111
|
-
value = suppress(value, tags) if key == :tags
|
112
|
-
result[key] = suppress_tags(value, tags)
|
164
|
+
|
165
|
+
def steps
|
166
|
+
elements do |feature, child|
|
167
|
+
next unless child.include? :steps
|
168
|
+
|
169
|
+
child[:steps].each { |step| yield(feature, child, step) }
|
113
170
|
end
|
114
|
-
result
|
115
171
|
end
|
116
172
|
|
117
|
-
def
|
118
|
-
|
173
|
+
def self.linter_name
|
174
|
+
name.split('::').last
|
119
175
|
end
|
120
|
-
|
121
|
-
def
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
|
-
def reference(file, feature = nil, scenario = nil, step = nil)
|
126
|
-
return file if feature.nil? || feature[:name].empty?
|
127
|
-
|
128
|
-
result = "#{file} (#{line(feature, scenario, step)}): #{feature[:name]}"
|
129
|
-
result += ".#{scenario[:name]}" unless scenario.nil? || scenario[:name].empty?
|
130
|
-
result += " step: #{step[:text]}" unless step.nil?
|
131
|
-
result
|
132
|
-
end
|
133
|
-
|
134
|
-
def line(feature, scenario, step)
|
135
|
-
line = feature.nil? ? nil : feature[:location][:line]
|
136
|
-
line = scenario[:location][:line] unless scenario.nil?
|
137
|
-
line = step[:location][:line] unless step.nil?
|
138
|
-
line
|
139
|
-
end
|
140
|
-
|
141
|
-
def add_error(references, description = nil)
|
142
|
-
@issues.push Error.new(name, references, description)
|
143
|
-
end
|
144
|
-
|
145
|
-
def add_warning(references, description = nil)
|
146
|
-
@issues.push Warning.new(name, references, description)
|
176
|
+
|
177
|
+
def linter_name
|
178
|
+
self.class.linter_name
|
147
179
|
end
|
148
180
|
|
149
181
|
def render_step(step)
|
@@ -151,7 +183,7 @@ module Chutney
|
|
151
183
|
value += render_step_argument step[:argument] if step.include? :argument
|
152
184
|
value
|
153
185
|
end
|
154
|
-
|
186
|
+
|
155
187
|
def render_step_argument(argument)
|
156
188
|
return "\n#{argument[:content]}" if argument[:type] == :DocString
|
157
189
|
|