chutney 2.2.1 → 3.0.0.beta.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.
- 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
|