chutney 2.2.1 → 3.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -1
- data/Gemfile +2 -0
- data/Rakefile +2 -0
- data/chutney.gemspec +8 -4
- data/config/cucumber.yml +1 -0
- data/exe/chutney +2 -0
- data/lib/chutney.rb +9 -11
- data/lib/chutney/configuration.rb +2 -0
- data/lib/chutney/formatter.rb +2 -0
- data/lib/chutney/formatter/json_formatter.rb +2 -0
- data/lib/chutney/formatter/pie_formatter.rb +2 -0
- data/lib/chutney/formatter/rainbow_formatter.rb +2 -0
- data/lib/chutney/issue.rb +2 -0
- data/lib/chutney/linter.rb +58 -59
- data/lib/chutney/linter/avoid_full_stop.rb +3 -1
- data/lib/chutney/linter/avoid_outline_for_single_example.rb +8 -6
- data/lib/chutney/linter/avoid_scripting.rb +6 -4
- data/lib/chutney/linter/avoid_typographers_quotes.rb +16 -14
- data/lib/chutney/linter/background_does_more_than_setup.rb +8 -6
- data/lib/chutney/linter/background_requires_multiple_scenarios.rb +6 -3
- data/lib/chutney/linter/bad_scenario_name.rb +4 -2
- data/lib/chutney/linter/empty_feature_file.rb +2 -0
- data/lib/chutney/linter/file_name_differs_feature_name.rb +4 -2
- data/lib/chutney/linter/givens_after_background.rb +5 -6
- data/lib/chutney/linter/invalid_file_name.rb +2 -0
- data/lib/chutney/linter/invalid_step_flow.rb +7 -7
- data/lib/chutney/linter/missing_example_name.rb +7 -5
- data/lib/chutney/linter/missing_feature_description.rb +4 -3
- data/lib/chutney/linter/missing_feature_name.rb +3 -2
- data/lib/chutney/linter/missing_scenario_name.rb +3 -4
- data/lib/chutney/linter/missing_test_action.rb +3 -1
- data/lib/chutney/linter/missing_verification.rb +3 -1
- data/lib/chutney/linter/required_tags_starts_with.rb +2 -0
- data/lib/chutney/linter/same_tag_for_all_scenarios.rb +11 -10
- data/lib/chutney/linter/scenario_names_match.rb +4 -3
- data/lib/chutney/linter/tag_used_multiple_times.rb +2 -0
- data/lib/chutney/linter/too_clumsy.rb +3 -1
- data/lib/chutney/linter/too_long_step.rb +4 -2
- data/lib/chutney/linter/too_many_different_tags.rb +4 -2
- data/lib/chutney/linter/too_many_steps.rb +4 -2
- data/lib/chutney/linter/too_many_tags.rb +2 -0
- data/lib/chutney/linter/unique_scenario_names.rb +3 -3
- data/lib/chutney/linter/unknown_variable.rb +13 -13
- data/lib/chutney/linter/unused_variable.rb +13 -13
- data/lib/chutney/linter/use_background.rb +17 -16
- data/lib/chutney/linter/use_outline.rb +8 -7
- data/lib/chutney/version.rb +3 -1
- data/spec/chutney_spec.rb +2 -0
- data/spec/spec_helper.rb +2 -0
- metadata +33 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3eb2bbfa506f0fde529c0365064ffe21e6f102e37d3790e138e09c972c07319d
|
4
|
+
data.tar.gz: 8951672afdbf61863d949ebce24862f203395a931228bd98a0fe1d401ad1544f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d41e8f3694940cb023fd03192b973a3778366e2745b544e8003f8d2f02c65285de6c7e9b8e254e89ce714732474c0837db8487da0eefbaba77ed405a02cf4f57
|
7
|
+
data.tar.gz: 1a3f433eb45190171bad88ca533e65f533f59ff5d2c2654a3da41b4f585b3abab3a6814824b0925ea0973ba76e331ce2bb9ab9345cd2f73ae1d242f64641eaff
|
data/.rubocop.yml
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
# Offense count: 1
|
10
10
|
Metrics/AbcSize:
|
11
|
-
|
11
|
+
Enabled: false
|
12
12
|
|
13
13
|
# Offense count: 1
|
14
14
|
# Configuration parameters: CountComments.
|
@@ -55,4 +55,10 @@ Layout/TrailingWhitespace:
|
|
55
55
|
Enabled: false
|
56
56
|
|
57
57
|
Style/FrozenStringLiteralComment:
|
58
|
+
Enabled: true
|
59
|
+
|
60
|
+
Style/StringConcatenation:
|
58
61
|
Enabled: false
|
62
|
+
|
63
|
+
AllCops:
|
64
|
+
NewCops: enable
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/chutney.gemspec
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Disable rubocop checks for the .gemspec
|
2
4
|
# I'll take the output from 'bundle gem new' to be authoritative
|
3
5
|
# rubocop:disable all
|
@@ -12,9 +14,9 @@ Gem::Specification.new do |spec|
|
|
12
14
|
spec.authors = ['Nigel Brookes-Thomas', 'Stefan Rohe', 'Nishtha Argawal', 'John Gluck']
|
13
15
|
spec.email = ['nigel@brookes-thomas.co.uk']
|
14
16
|
|
15
|
-
spec.summary = 'A linter for
|
17
|
+
spec.summary = 'A linter for multi-lingual Gherkin'
|
16
18
|
spec.description = 'A linter for your Cucumber features. ' \
|
17
|
-
'It supports any spoken language Cucumber
|
19
|
+
'It supports any spoken language Cucumber supports.'
|
18
20
|
|
19
21
|
spec.homepage = 'https://billyruffian.github.io/chutney/'
|
20
22
|
spec.license = 'MIT'
|
@@ -43,14 +45,15 @@ Gem::Specification.new do |spec|
|
|
43
45
|
spec.require_paths = ['lib']
|
44
46
|
|
45
47
|
spec.add_runtime_dependency 'amatch', '~> 0.4.0'
|
46
|
-
spec.add_runtime_dependency '
|
48
|
+
spec.add_runtime_dependency 'cuke_modeler', '~> 3.3'
|
49
|
+
spec.add_runtime_dependency 'gherkin', '>= 5.1.0', '< 9.1'
|
47
50
|
spec.add_runtime_dependency 'i18n', '~> 1.8.2'
|
48
51
|
spec.add_runtime_dependency 'pastel', '~> 0.7'
|
49
52
|
spec.add_runtime_dependency 'tty-pie', '~> 0.3'
|
50
53
|
|
51
54
|
|
52
55
|
spec.add_development_dependency 'coveralls', '~> 0.8'
|
53
|
-
spec.add_development_dependency 'cucumber', '~>
|
56
|
+
spec.add_development_dependency 'cucumber', '~> 5.1'
|
54
57
|
spec.add_development_dependency 'pry-byebug', '~> 3.0'
|
55
58
|
spec.add_development_dependency 'rake', '~> 13.0'
|
56
59
|
spec.add_development_dependency 'rerun', '~> 0.13'
|
@@ -58,4 +61,5 @@ Gem::Specification.new do |spec|
|
|
58
61
|
spec.add_development_dependency 'rubocop', '~> 0.89.0'
|
59
62
|
spec.add_development_dependency 'rspec', '~> 3.8'
|
60
63
|
|
64
|
+
spec.required_ruby_version = '~> 2.6'
|
61
65
|
end
|
data/config/cucumber.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
default: --publish-quiet
|
data/exe/chutney
CHANGED
data/lib/chutney.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'amatch'
|
4
|
+
|
2
5
|
require 'chutney/configuration'
|
3
6
|
require 'chutney/linter'
|
4
7
|
require 'chutney/linter/avoid_full_stop'
|
@@ -33,9 +36,11 @@ require 'chutney/linter/unknown_variable'
|
|
33
36
|
require 'chutney/linter/unused_variable'
|
34
37
|
require 'chutney/linter/use_background'
|
35
38
|
require 'chutney/linter/use_outline'
|
39
|
+
|
40
|
+
require 'cuke_modeler'
|
36
41
|
require 'forwardable'
|
37
|
-
require 'gherkin/dialect'
|
38
|
-
require 'gherkin/parser'
|
42
|
+
# require 'gherkin/dialect'
|
43
|
+
# require 'gherkin/parser'
|
39
44
|
require 'i18n'
|
40
45
|
require 'set'
|
41
46
|
require 'yaml'
|
@@ -45,8 +50,7 @@ module Chutney
|
|
45
50
|
class ChutneyLint
|
46
51
|
extend Forwardable
|
47
52
|
attr_accessor :verbose
|
48
|
-
attr_reader :files
|
49
|
-
attr_reader :results
|
53
|
+
attr_reader :files, :results
|
50
54
|
|
51
55
|
def_delegators :@files, :<<, :clear, :delete, :include?
|
52
56
|
|
@@ -92,14 +96,8 @@ module Chutney
|
|
92
96
|
|
93
97
|
private
|
94
98
|
|
95
|
-
def parse(text)
|
96
|
-
@parser ||= Gherkin::Parser.new
|
97
|
-
scanner = Gherkin::TokenScanner.new(text)
|
98
|
-
@parser.parse(scanner)
|
99
|
-
end
|
100
|
-
|
101
99
|
def lint(file)
|
102
|
-
parsed =
|
100
|
+
parsed = CukeModeler::FeatureFile.new(file)
|
103
101
|
linters.each do |linter_class|
|
104
102
|
linter = linter_class.new(file, parsed, configuration[linter_class.linter_name])
|
105
103
|
linter.lint
|
data/lib/chutney/formatter.rb
CHANGED
data/lib/chutney/issue.rb
CHANGED
data/lib/chutney/linter.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# gherkin utilities
|
2
4
|
|
3
5
|
module Chutney
|
4
6
|
# base class for all linters
|
5
7
|
class Linter
|
6
8
|
attr_accessor :issues
|
7
|
-
attr_reader :filename
|
8
|
-
attr_reader :configuration
|
9
|
+
attr_reader :filename, :configuration
|
9
10
|
|
10
11
|
Lint = Struct.new(:message, :gherkin_type, :location, :feature, :scenario, :step, keyword_init: true)
|
11
12
|
|
@@ -18,8 +19,8 @@ module Chutney
|
|
18
19
|
@filename = filename
|
19
20
|
@issues = []
|
20
21
|
@configuration = configuration
|
21
|
-
language = @content.dig(:feature, :language) || 'en'
|
22
|
-
@dialect = Gherkin::Dialect.for(language)
|
22
|
+
# language = @content.dig(:feature, :language) || 'en'
|
23
|
+
# @dialect = Gherkin::Dialect.for(language)
|
23
24
|
end
|
24
25
|
|
25
26
|
def lint
|
@@ -27,65 +28,71 @@ module Chutney
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def and_word?(word)
|
30
|
-
|
31
|
+
dialect_word(:and).include?(word)
|
31
32
|
end
|
32
33
|
|
33
34
|
def background_word?(word)
|
34
|
-
|
35
|
+
dialect_word(:background).include?(word)
|
35
36
|
end
|
36
37
|
|
37
38
|
def but_word?(word)
|
38
|
-
|
39
|
+
dialect_word(:but).include?(word)
|
39
40
|
end
|
40
41
|
|
41
42
|
def examples_word?(word)
|
42
|
-
|
43
|
+
dialect_word(:examples).include?(word)
|
43
44
|
end
|
44
45
|
|
45
46
|
def feature_word?(word)
|
46
|
-
|
47
|
+
dialect_word(:feature).include?(word)
|
47
48
|
end
|
48
49
|
|
49
50
|
def given_word?(word)
|
50
|
-
|
51
|
+
dialect_word(:given).include?(word)
|
51
52
|
end
|
52
53
|
|
53
54
|
def scenario_outline_word?(word)
|
54
|
-
|
55
|
+
dialect_word(:scenarioOutline).include?(word)
|
55
56
|
end
|
56
57
|
|
57
58
|
def then_word?(word)
|
58
|
-
|
59
|
+
dialect_word(:then).include?(word)
|
59
60
|
end
|
60
61
|
|
61
62
|
def when_word?(word)
|
62
|
-
|
63
|
+
dialect_word(:when).include?(word)
|
64
|
+
end
|
65
|
+
|
66
|
+
def dialect_word(word)
|
67
|
+
CukeModeler::Parsing.dialects[dialect][word.to_s].map(&:strip)
|
68
|
+
end
|
69
|
+
|
70
|
+
def dialect
|
71
|
+
@content.feature&.parsing_data&.dig(:language) || 'en'
|
63
72
|
end
|
64
73
|
|
65
74
|
def tags_for(element)
|
66
|
-
|
67
|
-
|
68
|
-
element[:tags].map { |tag| tag[:name][1..-1] }
|
75
|
+
element.tags.map { |tag| tag.name[1..-1] }
|
69
76
|
end
|
70
77
|
|
71
|
-
def add_issue(message, feature = nil, scenario = nil,
|
78
|
+
def add_issue(message, feature = nil, scenario = nil, item = nil)
|
72
79
|
issues << Lint.new(
|
73
80
|
message: message,
|
74
|
-
gherkin_type: type(feature, scenario,
|
75
|
-
location: location(feature, scenario,
|
76
|
-
feature: feature
|
77
|
-
scenario: scenario
|
78
|
-
step:
|
81
|
+
gherkin_type: type(feature, scenario, item),
|
82
|
+
location: location(feature, scenario, item),
|
83
|
+
feature: feature&.name,
|
84
|
+
scenario: scenario&.name,
|
85
|
+
step: item&.parsing_data&.dig(:name)
|
79
86
|
).to_h
|
80
87
|
end
|
81
88
|
|
82
89
|
def location(feature, scenario, step)
|
83
90
|
if step
|
84
|
-
step[:location]
|
91
|
+
step.parsing_data[:location]
|
85
92
|
elsif scenario
|
86
|
-
scenario
|
93
|
+
scenario.parsing_data.dig(:scenario, :location) || scenario.parsing_data.dig(:background, :location)
|
87
94
|
else
|
88
|
-
feature ? feature[:location] : 0
|
95
|
+
feature ? feature.parsing_data[:location] : 0
|
89
96
|
end
|
90
97
|
end
|
91
98
|
|
@@ -99,9 +106,9 @@ module Chutney
|
|
99
106
|
|
100
107
|
def feature
|
101
108
|
if block_given?
|
102
|
-
yield(@content
|
109
|
+
yield(@content.feature) if @content.feature
|
103
110
|
else
|
104
|
-
@content
|
111
|
+
@content.feature
|
105
112
|
end
|
106
113
|
end
|
107
114
|
|
@@ -109,69 +116,61 @@ module Chutney
|
|
109
116
|
return [] unless feature
|
110
117
|
|
111
118
|
if block_given?
|
112
|
-
feature
|
119
|
+
feature.children.each do |child|
|
113
120
|
next if off_switch?(child)
|
114
121
|
|
115
122
|
yield(feature, child)
|
116
123
|
end
|
117
124
|
else
|
118
|
-
feature
|
125
|
+
feature.children
|
119
126
|
end
|
120
127
|
end
|
121
128
|
|
122
129
|
def off_switch?(element = feature)
|
123
|
-
off_switch = element
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
130
|
+
off_switch = element.tags
|
131
|
+
.then { |tags| tags || [] }
|
132
|
+
.filter { |tag| tag[:type] == :Tag }
|
133
|
+
.filter { |tag| tag[:name] == "@disable#{linter_name}" }
|
134
|
+
.count
|
135
|
+
.positive?
|
129
136
|
off_switch ||= off_switch?(feature) unless element == feature
|
130
137
|
off_switch
|
131
138
|
end
|
132
139
|
|
133
140
|
def background
|
134
|
-
if block_given?
|
135
|
-
|
136
|
-
next unless child[:type] == :Background
|
137
|
-
|
138
|
-
yield(feature, child)
|
139
|
-
end
|
141
|
+
if block_given?
|
142
|
+
yield(feature, feature&.background)
|
140
143
|
else
|
141
|
-
|
144
|
+
feature&.background
|
142
145
|
end
|
143
146
|
end
|
144
147
|
|
145
148
|
def scenarios
|
146
149
|
if block_given?
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
yield(feature, child)
|
150
|
+
feature&.tests&.each do |test|
|
151
|
+
yield(feature, test)
|
151
152
|
end
|
153
|
+
|
152
154
|
else
|
153
|
-
|
155
|
+
feature&.tests
|
154
156
|
end
|
155
157
|
end
|
156
158
|
|
157
159
|
def filled_scenarios
|
158
160
|
if block_given?
|
159
161
|
scenarios do |feature, scenario|
|
160
|
-
next
|
161
|
-
next if scenario[:steps].empty?
|
162
|
+
next if scenario.steps.empty?
|
162
163
|
|
163
164
|
yield(feature, scenario)
|
164
165
|
end
|
165
166
|
else
|
166
|
-
scenarios.filter { |s| !s
|
167
|
+
scenarios ? scenarios.filter { |s| !s.steps.empty? } : []
|
167
168
|
end
|
168
169
|
end
|
169
170
|
|
170
171
|
def steps
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
child[:steps].each { |step| yield(feature, child, step) }
|
172
|
+
feature&.tests&.each do |t|
|
173
|
+
t.steps.each { |s| yield(feature, t, s) }
|
175
174
|
end
|
176
175
|
end
|
177
176
|
|
@@ -184,16 +183,16 @@ module Chutney
|
|
184
183
|
end
|
185
184
|
|
186
185
|
def render_step(step)
|
187
|
-
value = "#{step
|
188
|
-
value += render_step_argument
|
186
|
+
value = "#{step.keyword} #{step.text}"
|
187
|
+
value += render_step_argument(step.block) if step.block
|
189
188
|
value
|
190
189
|
end
|
191
190
|
|
192
191
|
def render_step_argument(argument)
|
193
|
-
return "\n#{argument
|
192
|
+
return "\n#{argument.content}" if argument.is_a?(CukeModeler::DocString)
|
194
193
|
|
195
|
-
result = argument
|
196
|
-
"|#{row
|
194
|
+
result = argument.rows.map do |row|
|
195
|
+
"|#{row.cells.map(&:value).join '|'}|"
|
197
196
|
end.join "\n"
|
198
197
|
"\n#{result}"
|
199
198
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Chutney
|
2
4
|
# service class to lint for avoiding periods
|
3
5
|
class AvoidFullStop < Linter
|
4
6
|
def lint
|
5
7
|
steps do |feature, child, step|
|
6
8
|
|
7
|
-
add_issue(I18n.t('linters.avoid_full_stop'), feature, child, step) if step
|
9
|
+
add_issue(I18n.t('linters.avoid_full_stop'), feature, child, step) if step.text.strip.end_with? '.'
|
8
10
|
|
9
11
|
end
|
10
12
|
end
|
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Chutney
|
2
4
|
# service class to lint for avoiding outline for single example
|
3
5
|
class AvoidOutlineForSingleExample < Linter
|
4
6
|
def lint
|
5
|
-
scenarios do |feature, scenario|
|
6
|
-
next unless scenario
|
7
|
-
|
8
|
-
|
9
|
-
next if scenario
|
10
|
-
next if scenario
|
7
|
+
scenarios do |feature, scenario|
|
8
|
+
next unless scenario.is_a? CukeModeler::Outline
|
9
|
+
next unless scenario.examples
|
10
|
+
|
11
|
+
next if scenario.examples.length > 1
|
12
|
+
next if scenario.examples.first.rows.length > 2 # first row is the header
|
11
13
|
|
12
14
|
add_issue(I18n.t('linters.avoid_outline_for_single_example'), feature, scenario)
|
13
15
|
end
|