chutney 3.0.0.beta.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -2
  3. data/README.md +1 -1
  4. data/chutney.gemspec +1 -1
  5. data/config/{chutney.yml → chutney_defaults.yml} +0 -0
  6. data/exe/chutney +5 -5
  7. data/lib/chutney.rb +8 -3
  8. data/lib/chutney/configuration.rb +3 -2
  9. data/lib/chutney/formatter.rb +4 -5
  10. data/lib/chutney/formatter/pie_formatter.rb +9 -10
  11. data/lib/chutney/formatter/rainbow_formatter.rb +8 -11
  12. data/lib/chutney/linter.rb +34 -34
  13. data/lib/chutney/linter/avoid_full_stop.rb +1 -3
  14. data/lib/chutney/linter/avoid_outline_for_single_example.rb +3 -3
  15. data/lib/chutney/linter/avoid_scripting.rb +2 -2
  16. data/lib/chutney/linter/background_does_more_than_setup.rb +3 -4
  17. data/lib/chutney/linter/background_requires_multiple_scenarios.rb +1 -1
  18. data/lib/chutney/linter/bad_scenario_name.rb +2 -2
  19. data/lib/chutney/linter/file_name_differs_feature_name.rb +3 -3
  20. data/lib/chutney/linter/givens_after_background.rb +2 -2
  21. data/lib/chutney/linter/invalid_file_name.rb +1 -1
  22. data/lib/chutney/linter/invalid_step_flow.rb +2 -2
  23. data/lib/chutney/linter/missing_example_name.rb +2 -4
  24. data/lib/chutney/linter/missing_feature_description.rb +1 -1
  25. data/lib/chutney/linter/missing_feature_name.rb +2 -2
  26. data/lib/chutney/linter/missing_scenario_name.rb +1 -2
  27. data/lib/chutney/linter/missing_test_action.rb +1 -1
  28. data/lib/chutney/linter/missing_verification.rb +1 -1
  29. data/lib/chutney/linter/required_tags_starts_with.rb +5 -6
  30. data/lib/chutney/linter/same_tag_for_all_scenarios.rb +9 -9
  31. data/lib/chutney/linter/scenario_names_match.rb +2 -3
  32. data/lib/chutney/linter/tag_used_multiple_times.rb +1 -1
  33. data/lib/chutney/linter/too_clumsy.rb +1 -1
  34. data/lib/chutney/linter/too_long_step.rb +3 -3
  35. data/lib/chutney/linter/too_many_different_tags.rb +6 -6
  36. data/lib/chutney/linter/too_many_steps.rb +3 -3
  37. data/lib/chutney/linter/too_many_tags.rb +3 -3
  38. data/lib/chutney/linter/unique_scenario_names.rb +2 -2
  39. data/lib/chutney/linter/unknown_variable.rb +3 -3
  40. data/lib/chutney/linter/unused_variable.rb +3 -4
  41. data/lib/chutney/linter/use_background.rb +4 -4
  42. data/lib/chutney/linter/use_outline.rb +7 -7
  43. data/lib/chutney/version.rb +1 -1
  44. data/spec/chutney_spec.rb +9 -9
  45. metadata +7 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c99f32f7cba016b8d419d1867c5005b448e05939565afff74db238c68222f2d9
4
- data.tar.gz: 9f21a905656a69d4bbfc7288b7abcec37e36e3fb5950c852aa8c6e4183e52cd9
3
+ metadata.gz: a489eb526fae6a79ff386986463cf473aa4f5bc353448e99338781556f7fd194
4
+ data.tar.gz: 5cd7a3264aba055c8fe738147edfa661f57e2c0e258d5991b42adbfb2af45f05
5
5
  SHA512:
6
- metadata.gz: 3a4339e31214900e809bdbdaf968a2d21af9bc7526365cd2f00a17745b10272947114c018e6b182c6679769636ffc127d6880d69dbbbb8dc66f0d5f5762a410d
7
- data.tar.gz: b755781258f1781e4586a77f639682b7ebb0a584d50aa0c001a7eca5c5e8b0c16487b2d1dd1978e97744c5f7ec163ea169ae8d9c6114e1ed22cda716e3173a34
6
+ metadata.gz: 9fb8325c116764fadd900b0b98961eca7cf95f49ac171ef64e77e29f1806d6ff78bcca90ea9e2119320d33d81b13e67749fb3bf8e916df13e154871b54602b35
7
+ data.tar.gz: 8290dd430b9b55c900179717c3b3fcd83ea805e9142751dec8c69ae4c1e2233a3d5392d8951705a3fed0678b3a9fd768941889b5e9086465fa54ced8c43b1811
@@ -52,11 +52,11 @@ Layout/HeredocIndentation:
52
52
  - "**/*_spec.rb"
53
53
 
54
54
  Layout/TrailingWhitespace:
55
- Enabled: false
55
+ Enabled: true
56
56
 
57
57
  Style/FrozenStringLiteralComment:
58
58
  Enabled: true
59
-
59
+
60
60
  Style/StringConcatenation:
61
61
  Enabled: false
62
62
 
data/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  </div>
21
21
 
22
- Read the documentation [here](https://billyruffian.github.io/chutney/).
22
+ Read the documentation [here](https://billyruffian.github.io/chutney/) or try [Chutney Online](https://chutney.billy-ruffian.co.uk).
23
23
 
24
24
  ## Notes
25
25
 
@@ -61,7 +61,7 @@ Gem::Specification.new do |spec|
61
61
  spec.add_development_dependency 'rake', '~> 13.0'
62
62
  spec.add_development_dependency 'rerun', '~> 0.13'
63
63
  spec.add_development_dependency 'rspec-expectations', '~> 3.0'
64
- spec.add_development_dependency 'rubocop', '~> 0.89.0'
64
+ spec.add_development_dependency 'rubocop', '~> 0.90.0'
65
65
  spec.add_development_dependency 'rspec', '~> 3.8'
66
66
 
67
67
  spec.required_ruby_version = '~> 2.6'
@@ -12,14 +12,14 @@ formatters = Set.new
12
12
 
13
13
  OptionParser.new do |opts|
14
14
  opts.banner = 'Usage: chutney [files]'
15
- opts.on('-f',
16
- '--format [formatter]',
15
+ opts.on('-f',
16
+ '--format [formatter]',
17
17
  'One of JSONFormatter, PieFormatter or RainbowFormatter (default).') do |formatter|
18
18
  raise 'No Such Formatter' unless %w[JSONFormatter PieFormatter RainbowFormatter].include? formatter
19
19
 
20
20
  formatters << formatter
21
21
  end
22
-
22
+
23
23
  opts.on('-l',
24
24
  '--linters',
25
25
  'List the linter status by this configuration and exit') do
@@ -28,7 +28,7 @@ OptionParser.new do |opts|
28
28
  max_name_length = chutney_config.keys.map(&:length).max + 1
29
29
  chutney_config.each do |linter, value|
30
30
  print pastel.cyan(linter.ljust(max_name_length))
31
-
31
+
32
32
  if value['Enabled']
33
33
  puts pastel.green('enabled')
34
34
  else
@@ -42,7 +42,7 @@ end.parse!
42
42
  formatters << 'RainbowFormatter' if formatters.empty?
43
43
 
44
44
  files = ARGV.map { |pattern| Dir.glob(pattern) }.flatten
45
- files = Dir.glob('features/**/*.feature') if ARGV.empty?
45
+ files = Dir.glob('features/**/*.feature') if ARGV.empty?
46
46
 
47
47
  linter = Chutney::ChutneyLint.new(*files)
48
48
  report = linter.analyse
@@ -36,6 +36,7 @@ require 'chutney/linter/unknown_variable'
36
36
  require 'chutney/linter/unused_variable'
37
37
  require 'chutney/linter/use_background'
38
38
  require 'chutney/linter/use_outline'
39
+ require 'chutney/version'
39
40
 
40
41
  require 'cuke_modeler'
41
42
  require 'forwardable'
@@ -58,14 +59,18 @@ module Chutney
58
59
  @files = files
59
60
  @results = Hash.new { |h, k| h[k] = [] }
60
61
  i18n_paths = Dir[File.expand_path(File.join(__dir__, 'config/locales')) + '/*.yml']
61
- return if I18n.load_path.include?(i18n_paths)
62
62
 
63
- I18n.load_path += i18n_paths
63
+ i18n_paths.each do |path|
64
+ next if I18n.load_path.include?(path)
65
+
66
+ I18n.load_path << path
67
+ I18n.backend.reload!
68
+ end
64
69
  end
65
70
 
66
71
  def configuration
67
72
  unless @config
68
- default_file = [File.expand_path('..', __dir__), '**/config', 'chutney.yml']
73
+ default_file = [File.expand_path('..', __dir__), '**/config', 'chutney_defaults.yml']
69
74
  config_file = Dir.glob(File.join(default_file)).first.freeze
70
75
  @config = Configuration.new(config_file)
71
76
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'delegate'
3
4
 
4
- module Chutney
5
+ module Chutney
5
6
  # gherkin_lint configuration object
6
7
  class Configuration < SimpleDelegator
7
8
  def initialize(path)
@@ -23,7 +24,7 @@ module Chutney
23
24
  config_files = ['chutney.yml', '.chutney.yml'].map do |fname|
24
25
  Dir.glob(File.join(Dir.pwd, '**', fname))
25
26
  end.flatten
26
-
27
+
27
28
  config_file = config_files.first
28
29
  merge_config(config_file) if !config_file.nil? && File.exist?(config_file)
29
30
  end
@@ -4,18 +4,17 @@ module Chutney
4
4
  # base class for all formatters
5
5
  class Formatter
6
6
  attr_accessor :results
7
-
7
+
8
8
  def initialize
9
9
  @results = {}
10
- end
11
-
10
+ end
11
+
12
12
  def files
13
13
  results.map { |k, _v| k }
14
14
  end
15
-
15
+
16
16
  def files_with_issues
17
17
  results.filter { |_k, v| v.any? { |r| r[:issues].count.positive? } }
18
18
  end
19
-
20
19
  end
21
20
  end
@@ -9,7 +9,7 @@ module Chutney
9
9
  def initialize
10
10
  super
11
11
  end
12
-
12
+
13
13
  def format
14
14
  data = top_offences.map do |offence|
15
15
  {
@@ -21,14 +21,14 @@ module Chutney
21
21
  end
22
22
  print_report(data)
23
23
  end
24
-
24
+
25
25
  def print_report(data)
26
26
  return if data.empty?
27
27
 
28
28
  print TTY::Pie.new(data: data, radius: 8, legend: { format: '%<label>s %<name>s %<value>i' })
29
29
  puts
30
30
  end
31
-
31
+
32
32
  def top_offences
33
33
  offence = Hash.new(0)
34
34
  files_with_issues.each do |_file, linter|
@@ -38,9 +38,9 @@ module Chutney
38
38
  end
39
39
  offence.reject { |_k, v| v.zero? }.sort_by { |_linter, count| -count }
40
40
  end
41
-
41
+
42
42
  def char_loop
43
- @char_looper ||= Fiber.new do
43
+ @char_looper ||= Fiber.new do
44
44
  chars = %w[• x + @ * / -]
45
45
  current = 0
46
46
  loop do
@@ -51,10 +51,10 @@ module Chutney
51
51
  end
52
52
  @char_looper.resume
53
53
  end
54
-
54
+
55
55
  def colour_loop
56
- @colour_looper ||= Fiber.new do
57
- colours = %i[bright_cyan bright_magenta bright_yellow bright_green]
56
+ @colour_looper ||= Fiber.new do
57
+ colours = %i[bright_cyan bright_magenta bright_yellow bright_green]
58
58
  current = 0
59
59
  loop do
60
60
  current = 0 if current >= colours.count
@@ -64,7 +64,7 @@ module Chutney
64
64
  end
65
65
  @colour_looper.resume
66
66
  end
67
-
67
+
68
68
  def put_summary
69
69
  pastel = Pastel.new
70
70
  print "#{files.count} features inspected, "
@@ -74,6 +74,5 @@ module Chutney
74
74
  puts pastel.red("#{files_with_issues.count} taste nasty")
75
75
  end
76
76
  end
77
-
78
77
  end
79
78
  end
@@ -5,38 +5,36 @@ require 'pastel'
5
5
  module Chutney
6
6
  # pretty formatter
7
7
  class RainbowFormatter < Formatter
8
-
9
8
  def initialize
10
9
  super
11
-
10
+
12
11
  @pastel = Pastel.new
13
12
  end
14
-
15
- def format
13
+
14
+ def format
16
15
  files_with_issues.each do |file, linter|
17
16
  put_file(file)
18
17
  linter.filter { |l| !l[:issues].empty? }.each do |linter_with_issues|
19
-
20
18
  put_linter(linter_with_issues)
21
19
  linter_with_issues[:issues].each { |i| put_issue(file, i) }
22
20
  end
23
21
  end
24
22
  put_summary
25
23
  end
26
-
24
+
27
25
  def put_file(file)
28
26
  puts @pastel.cyan(file.to_s)
29
27
  end
30
-
28
+
31
29
  def put_linter(linter)
32
30
  puts @pastel.red(" #{linter[:linter]}")
33
31
  end
34
-
35
- def put_issue(file, issue)
32
+
33
+ def put_issue(file, issue)
36
34
  puts " #{issue[:message]}"
37
35
  puts " #{@pastel.dim file.to_s}:#{@pastel.dim(issue.dig(:location, :line))}"
38
36
  end
39
-
37
+
40
38
  def put_summary
41
39
  print "#{files.count} features inspected, "
42
40
  if files_with_issues.count.zero?
@@ -45,6 +43,5 @@ module Chutney
45
43
  puts @pastel.red("#{files_with_issues.count} taste nasty")
46
44
  end
47
45
  end
48
-
49
46
  end
50
47
  end
@@ -7,7 +7,7 @@ module Chutney
7
7
  class Linter
8
8
  attr_accessor :issues
9
9
  attr_reader :filename, :configuration
10
-
10
+
11
11
  Lint = Struct.new(:message, :gherkin_type, :location, :feature, :scenario, :step, keyword_init: true)
12
12
 
13
13
  def self.descendants
@@ -26,56 +26,56 @@ module Chutney
26
26
  def lint
27
27
  raise 'not implemented'
28
28
  end
29
-
29
+
30
30
  def and_word?(word)
31
31
  dialect_word(:and).include?(word)
32
32
  end
33
-
33
+
34
34
  def background_word?(word)
35
35
  dialect_word(:background).include?(word)
36
36
  end
37
-
37
+
38
38
  def but_word?(word)
39
39
  dialect_word(:but).include?(word)
40
40
  end
41
-
41
+
42
42
  def examples_word?(word)
43
43
  dialect_word(:examples).include?(word)
44
44
  end
45
-
45
+
46
46
  def feature_word?(word)
47
47
  dialect_word(:feature).include?(word)
48
48
  end
49
-
49
+
50
50
  def given_word?(word)
51
51
  dialect_word(:given).include?(word)
52
52
  end
53
-
53
+
54
54
  def scenario_outline_word?(word)
55
55
  dialect_word(:scenarioOutline).include?(word)
56
56
  end
57
-
57
+
58
58
  def then_word?(word)
59
59
  dialect_word(:then).include?(word)
60
60
  end
61
-
61
+
62
62
  def when_word?(word)
63
63
  dialect_word(:when).include?(word)
64
64
  end
65
-
65
+
66
66
  def dialect_word(word)
67
67
  CukeModeler::Parsing.dialects[dialect][word.to_s].map(&:strip)
68
68
  end
69
-
69
+
70
70
  def dialect
71
71
  @content.feature&.parsing_data&.dig(:language) || 'en'
72
72
  end
73
-
73
+
74
74
  def tags_for(element)
75
75
  element.tags.map { |tag| tag.name[1..-1] }
76
76
  end
77
-
78
- def add_issue(message, feature = nil, scenario = nil, item = nil)
77
+
78
+ def add_issue(message, feature = nil, scenario = nil, item = nil)
79
79
  issues << Lint.new(
80
80
  message: message,
81
81
  gherkin_type: type(feature, scenario, item),
@@ -85,17 +85,17 @@ module Chutney
85
85
  step: item&.parsing_data&.dig(:name)
86
86
  ).to_h
87
87
  end
88
-
88
+
89
89
  def location(feature, scenario, step)
90
90
  if step
91
91
  step.parsing_data[:location]
92
92
  elsif scenario
93
93
  scenario.parsing_data.dig(:scenario, :location) || scenario.parsing_data.dig(:background, :location)
94
- else
95
- feature ? feature.parsing_data[:location] : 0
94
+ else
95
+ feature ? feature.parsing_data[:location] : { line: 0, column: 0 }
96
96
  end
97
97
  end
98
-
98
+
99
99
  def type(_feature, scenario, step)
100
100
  if step
101
101
  :step
@@ -111,14 +111,14 @@ module Chutney
111
111
  @content.feature
112
112
  end
113
113
  end
114
-
115
- def elements
114
+
115
+ def elements
116
116
  return [] unless feature
117
-
118
- if block_given?
117
+
118
+ if block_given?
119
119
  feature.children.each do |child|
120
120
  next if off_switch?(child)
121
-
121
+
122
122
  yield(feature, child)
123
123
  end
124
124
  else
@@ -136,38 +136,38 @@ module Chutney
136
136
  off_switch ||= off_switch?(feature) unless element == feature
137
137
  off_switch
138
138
  end
139
-
139
+
140
140
  def background
141
- if block_given?
141
+ if block_given?
142
142
  yield(feature, feature&.background)
143
143
  else
144
144
  feature&.background
145
145
  end
146
146
  end
147
-
147
+
148
148
  def scenarios
149
149
  if block_given?
150
150
  feature&.tests&.each do |test|
151
151
  yield(feature, test)
152
152
  end
153
-
153
+
154
154
  else
155
155
  feature&.tests
156
156
  end
157
157
  end
158
-
158
+
159
159
  def filled_scenarios
160
160
  if block_given?
161
161
  scenarios do |feature, scenario|
162
162
  next if scenario.steps.empty?
163
-
163
+
164
164
  yield(feature, scenario)
165
165
  end
166
166
  else
167
167
  scenarios ? scenarios.filter { |s| !s.steps.empty? } : []
168
168
  end
169
169
  end
170
-
170
+
171
171
  def steps
172
172
  feature&.tests&.each do |t|
173
173
  t.steps.each { |s| yield(feature, t, s) }
@@ -177,7 +177,7 @@ module Chutney
177
177
  def self.linter_name
178
178
  name.split('::').last
179
179
  end
180
-
180
+
181
181
  def linter_name
182
182
  self.class.linter_name
183
183
  end
@@ -187,10 +187,10 @@ module Chutney
187
187
  value += render_step_argument(step.block) if step.block
188
188
  value
189
189
  end
190
-
190
+
191
191
  def render_step_argument(argument)
192
192
  return "\n#{argument.content}" if argument.is_a?(CukeModeler::DocString)
193
-
193
+
194
194
  result = argument.rows.map do |row|
195
195
  "|#{row.cells.map(&:value).join '|'}|"
196
196
  end.join "\n"
@@ -2,12 +2,10 @@
2
2
 
3
3
  module Chutney
4
4
  # service class to lint for avoiding periods
5
- class AvoidFullStop < Linter
5
+ class AvoidFullStop < Linter
6
6
  def lint
7
7
  steps do |feature, child, step|
8
-
9
8
  add_issue(I18n.t('linters.avoid_full_stop'), feature, child, step) if step.text.strip.end_with? '.'
10
-
11
9
  end
12
10
  end
13
11
  end
@@ -4,13 +4,13 @@ module Chutney
4
4
  # service class to lint for avoiding outline for single example
5
5
  class AvoidOutlineForSingleExample < Linter
6
6
  def lint
7
- scenarios do |feature, scenario|
7
+ scenarios do |feature, scenario|
8
8
  next unless scenario.is_a? CukeModeler::Outline
9
9
  next unless scenario.examples
10
-
10
+
11
11
  next if scenario.examples.length > 1
12
12
  next if scenario.examples.first.rows.length > 2 # first row is the header
13
-
13
+
14
14
  add_issue(I18n.t('linters.avoid_outline_for_single_example'), feature, scenario)
15
15
  end
16
16
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Chutney
4
4
  # service class to lint for avoid scripting
5
- class AvoidScripting < Linter
5
+ class AvoidScripting < Linter
6
6
  def lint
7
7
  scenarios do |feature, scenario|
8
8
  when_steps = filter_when_steps(scenario.steps)
@@ -10,7 +10,7 @@ module Chutney
10
10
  add_issue(I18n.t('linters.avoid_scripting', count: whens), feature, scenario, when_steps.last) if whens > 1
11
11
  end
12
12
  end
13
-
13
+
14
14
  def filter_when_steps(steps)
15
15
  steps
16
16
  .drop_while { |step| !when_word?(step.keyword) }
@@ -2,14 +2,13 @@
2
2
 
3
3
  module Chutney
4
4
  # service class to lint for background that does more than setup
5
- class BackgroundDoesMoreThanSetup < Linter
5
+ class BackgroundDoesMoreThanSetup < Linter
6
6
  def lint
7
7
  background do |feature, background|
8
-
9
- invalid_steps = background&.steps&.select do |step|
8
+ invalid_steps = background&.steps&.select do |step|
10
9
  when_word?(step.keyword) || then_word?(step.keyword)
11
10
  end
12
-
11
+
13
12
  next if invalid_steps.nil? || invalid_steps.empty?
14
13
 
15
14
  add_issue(I18n.t('linters.background_does_more_than_setup'), feature, background, invalid_steps.first)
@@ -6,7 +6,7 @@ module Chutney
6
6
  # service class for check that there are multiple scenarios once a background is used
7
7
  class BackgroundRequiresMultipleScenarios < Linter
8
8
  MESSAGE = 'Avoid using Background steps for just one scenario'
9
-
9
+
10
10
  def lint
11
11
  background do |feature, background|
12
12
  next unless background
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chutney
4
- # service class to lint for bad scenario names
4
+ # service class to lint for bad scenario names
5
5
  class BadScenarioName < Linter
6
6
  def lint
7
7
  scenarios do |feature, scenario|
8
8
  next if scenario.name.empty?
9
-
9
+
10
10
  bad = /\w*(test|verif|check)\w*/i
11
11
  match = scenario.name.match(bad).to_a.first
12
12
  add_issue(I18n.t('linters.bad_scenario_name', word: match), feature, scenario) if match
@@ -5,11 +5,11 @@ module Chutney
5
5
  class FileNameDiffersFeatureName < Linter
6
6
  def lint
7
7
  return unless feature
8
-
8
+
9
9
  expected_feature_name = title_case(filename)
10
10
  return if ignore_whitespaces(feature.name).casecmp(ignore_whitespaces(expected_feature_name)) == 0
11
-
12
- add_issue(I18n.t('linters.file_name_differs_feature_name', expected: expected_feature_name), feature)
11
+
12
+ add_issue(I18n.t('linters.file_name_differs_feature_name', expected: expected_feature_name), feature)
13
13
  end
14
14
 
15
15
  def title_case(value)
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chutney
4
- # service class to lint for bad scenario names
4
+ # service class to lint for bad scenario names
5
5
  class GivensAfterBackground < Linter
6
6
  def lint
7
7
  return unless background
8
-
8
+
9
9
  filled_scenarios do |feature, scenario|
10
10
  scenario.steps.each do |step|
11
11
  add_issue(I18n.t('linters.givens_after_background'), feature, scenario, step) if given_word?(step.keyword)
@@ -13,7 +13,7 @@ module Chutney
13
13
  end
14
14
  end
15
15
  end
16
-
16
+
17
17
  def recommend(filename)
18
18
  File.basename(filename, '.*').gsub(/::/, '/')
19
19
  .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
@@ -7,7 +7,7 @@ module Chutney
7
7
  filled_scenarios do |feature, scenario|
8
8
  steps = scenario.steps.select { |step| !and_word?(step.keyword) && !but_word?(step.keyword) }
9
9
  next if steps.empty?
10
-
10
+
11
11
  last_step_is_an_action(feature, scenario, steps)
12
12
  given_after_non_given(feature, scenario, steps)
13
13
  verification_before_action(feature, scenario, steps)
@@ -33,7 +33,7 @@ module Chutney
33
33
  def verification_before_action(feature, scenario, steps)
34
34
  steps.each do |step|
35
35
  break if when_word?(step.keyword)
36
-
36
+
37
37
  add_issue(I18n.t('linters.invalid_step_flow.missing_action'), feature, scenario) if then_word?(step.keyword)
38
38
  end
39
39
  end
@@ -3,7 +3,6 @@
3
3
  module Chutney
4
4
  # service class to lint for missing example names
5
5
  class MissingExampleName < Linter
6
-
7
6
  def lint
8
7
  scenarios do |_feature, scenario|
9
8
  next unless scenario.is_a? CukeModeler::Outline
@@ -11,17 +10,16 @@ module Chutney
11
10
  scenario.examples.each do |example|
12
11
  example_count = scenario.examples&.length || 0
13
12
  next unless example_count > 1
14
-
13
+
15
14
  check_example(scenario, example)
16
15
  end
17
16
  end
18
- end
17
+ end
19
18
 
20
19
  def check_example(scenario, example)
21
20
  name = example.name.strip
22
21
  duplicate_name_count = scenario.examples.filter { |e| e.name == name }.count
23
22
  add_issue(I18n.t('linters.missing_example_name'), feature, scenario, example) if duplicate_name_count >= 2
24
23
  end
25
-
26
24
  end
27
25
  end
@@ -6,7 +6,7 @@ module Chutney
6
6
  MESSAGE = 'Features should have a description so that its purpose is clear'
7
7
  def lint
8
8
  return unless feature
9
-
9
+
10
10
  add_issue(I18n.t('linters.missing_feature_description'), feature) if feature.description.empty?
11
11
  end
12
12
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Chutney
4
4
  # service class to lint for missing feature names
5
- class MissingFeatureName < Linter
5
+ class MissingFeatureName < Linter
6
6
  def lint
7
7
  return unless feature
8
-
8
+
9
9
  add_issue(I18n.t('linters.missing_feature_name'), feature) if feature.name.empty?
10
10
  end
11
11
  end
@@ -2,8 +2,7 @@
2
2
 
3
3
  module Chutney
4
4
  # service class to lint for missing scenario names
5
- class MissingScenarioName < Linter
6
-
5
+ class MissingScenarioName < Linter
7
6
  def lint
8
7
  scenarios do |feature, scenario|
9
8
  add_issue(I18n.t('linters.missing_scenario_name'), feature, scenario) if scenario.name.empty?
@@ -7,7 +7,7 @@ module Chutney
7
7
  filled_scenarios do |feature, scenario|
8
8
  when_steps = scenario.steps.select { |step| when_word?(step.keyword) }
9
9
  next unless when_steps.empty?
10
-
10
+
11
11
  add_issue(I18n.t('linters.missing_test_action'), feature, scenario)
12
12
  end
13
13
  end
@@ -9,7 +9,7 @@ module Chutney
9
9
  filled_scenarios do |feature, scenario|
10
10
  then_steps = scenario.steps.select { |step| then_word?(step.keyword) }
11
11
  next unless then_steps.empty?
12
-
12
+
13
13
  add_issue(I18n.t('linters.missing_test_verification'), feature, scenario)
14
14
  end
15
15
  end
@@ -3,26 +3,25 @@
3
3
  module Chutney
4
4
  # service class to lint for tags used multiple times
5
5
  class RequiredTagsStartsWith < Linter
6
-
7
6
  def lint
8
7
  return unless pattern
9
8
 
10
9
  scenarios do |feature, scenario|
11
10
  next if match_pattern? tags_for(feature)
12
11
  next if match_pattern? tags_for(scenario)
13
-
12
+
14
13
  add_issue(
15
- I18n.t('linters.required_tags_starts_with',
16
- allowed: pattern.join(', ')),
14
+ I18n.t('linters.required_tags_starts_with',
15
+ allowed: pattern.join(', ')),
17
16
  feature, scenario
18
17
  )
19
18
  end
20
19
  end
21
-
20
+
22
21
  def pattern
23
22
  configuration['Matcher'] || nil
24
23
  end
25
-
24
+
26
25
  def match_pattern?(target)
27
26
  target.each do |t|
28
27
  return true if t.start_with?(*pattern)
@@ -14,13 +14,13 @@ module Chutney
14
14
  tags = scenario_tags
15
15
  return if tags.nil? || tags.empty?
16
16
  return unless feature.tests.length > 1
17
-
17
+
18
18
  tags.each do |tag|
19
19
  next if tag == 'skip'
20
20
 
21
21
  add_issue(
22
- I18n.t('linters.same_tag_for_all_scenarios.feature_level',
23
- tag: tag),
22
+ I18n.t('linters.same_tag_for_all_scenarios.feature_level',
23
+ tag: tag),
24
24
  feature
25
25
  )
26
26
  end
@@ -32,12 +32,12 @@ module Chutney
32
32
  next if tags.nil? || tags.empty?
33
33
  next unless scenario.is_a? CukeModeler::Outline
34
34
  next unless scenario.examples.length > 1
35
-
35
+
36
36
  tags.each do |tag|
37
37
  next if tag == 'skip'
38
38
 
39
- add_issue(I18n.t('linters.same_tag_for_all_scenarios.example_level',
40
- tag: tag), feature, scenario)
39
+ add_issue(I18n.t('linters.same_tag_for_all_scenarios.example_level',
40
+ tag: tag), feature, scenario)
41
41
  end
42
42
  end
43
43
  end
@@ -55,13 +55,13 @@ module Chutney
55
55
  def example_tags(scenario)
56
56
  result = nil
57
57
  return result unless scenario.is_a?(CukeModeler::Outline) && scenario.examples
58
-
58
+
59
59
  scenario.examples.each do |example|
60
60
  return nil unless example.tags
61
-
61
+
62
62
  tags = tags_for(example)
63
63
  result = tags if result.nil?
64
-
64
+
65
65
  result &= tags
66
66
  end
67
67
  result
@@ -6,14 +6,13 @@ module Chutney
6
6
  # service class to lint for tags used multiple times
7
7
  class ScenarioNamesMatch < Linter
8
8
  MESSAGE = 'Scenario Name does not match pattern'
9
-
10
9
 
11
10
  def lint
12
11
  scenarios do |feature, scenario|
13
12
  next unless (scenario.name =~ /#{configuration['Matcher']}/).nil?
14
-
13
+
15
14
  add_issue(
16
- I18n.t('linters.scenario_names_match',
15
+ I18n.t('linters.scenario_names_match',
17
16
  pattern: configuration['Matcher']), feature, scenario
18
17
  )
19
18
  end
@@ -8,7 +8,7 @@ module Chutney
8
8
  total_tags = tags_for(feature) + tags_for(scenario)
9
9
  double_used_tags = total_tags.find_all { |a| total_tags.count(a) > 1 }.uniq!
10
10
  next if double_used_tags.nil?
11
-
11
+
12
12
  add_issue(
13
13
  I18n.t('linters.tag_used_multiple_times', tags: double_used_tags.join(',')), feature
14
14
  )
@@ -9,7 +9,7 @@ module Chutney
9
9
  filled_scenarios do |feature, scenario|
10
10
  characters = scenario.steps.map { |step| step.text.length }.inject(0, :+)
11
11
  next if characters < 400
12
-
12
+
13
13
  add_issue(
14
14
  I18n.t('linters.too_clumsy', length: characters), feature, scenario
15
15
  )
@@ -6,14 +6,14 @@ module Chutney
6
6
  def lint
7
7
  steps do |feature, scenario, step|
8
8
  next if step.text.length <= maxlength
9
-
9
+
10
10
  add_issue(
11
- I18n.t('linters.too_long_step', length: step.text.length, max: maxlength),
11
+ I18n.t('linters.too_long_step', length: step.text.length, max: maxlength),
12
12
  feature, scenario
13
13
  )
14
14
  end
15
15
  end
16
-
16
+
17
17
  def maxlength
18
18
  configuration['MaxLength'] || '120'
19
19
  end
@@ -6,20 +6,20 @@ module Chutney
6
6
  def lint
7
7
  tags = all_tags
8
8
  return if tags.length <= maxcount
9
-
9
+
10
10
  add_issue(
11
- I18n.t('linters.too_many_different_tags', count: tags.length, max: maxcount),
11
+ I18n.t('linters.too_many_different_tags', count: tags.length, max: maxcount),
12
12
  feature
13
13
  )
14
14
  end
15
-
16
- def maxcount
15
+
16
+ def maxcount
17
17
  configuration['MaxCount']&.to_i || 3
18
18
  end
19
-
19
+
20
20
  def all_tags
21
21
  return [] unless feature&.scenarios
22
-
22
+
23
23
  tags_for(feature) + feature.scenarios.map { |scenario| tags_for(scenario) }.flatten
24
24
  end
25
25
  end
@@ -6,14 +6,14 @@ module Chutney
6
6
  def lint
7
7
  filled_scenarios do |feature, scenario|
8
8
  next if scenario.steps.length <= maxcount
9
-
9
+
10
10
  add_issue(
11
- I18n.t('linters.too_many_steps', count: scenario.steps.length, max: maxcount),
11
+ I18n.t('linters.too_many_steps', count: scenario.steps.length, max: maxcount),
12
12
  feature
13
13
  )
14
14
  end
15
15
  end
16
-
16
+
17
17
  def maxcount
18
18
  configuration['MaxCount']&.to_i || 10
19
19
  end
@@ -7,14 +7,14 @@ module Chutney
7
7
  scenarios do |feature, scenario|
8
8
  tags = tags_for(feature) + tags_for(scenario)
9
9
  next unless tags.length > maxcount
10
-
10
+
11
11
  add_issue(
12
- I18n.t('linters.too_many_tags', count: tags.length, max: maxcount),
12
+ I18n.t('linters.too_many_tags', count: tags.length, max: maxcount),
13
13
  feature
14
14
  )
15
15
  end
16
16
  end
17
-
17
+
18
18
  def maxcount
19
19
  configuration['MaxCount']&.to_i || 3
20
20
  end
@@ -14,13 +14,13 @@ module Chutney
14
14
  end
15
15
  end
16
16
  end
17
-
17
+
18
18
  def issue(name, first_location, scenario)
19
19
  add_issue(
20
20
  I18n.t('linters.unique_scenario_names',
21
21
  name: name,
22
22
  line: first_location[:line],
23
- column: first_location[:column]),
23
+ column: first_location[:column]),
24
24
  feature, scenario
25
25
  )
26
26
  end
@@ -9,7 +9,7 @@ module Chutney
9
9
  scenario.steps.each do |step|
10
10
  step_vars(step).each do |used_var|
11
11
  next if known_vars.include? used_var
12
-
12
+
13
13
  add_issue(
14
14
  I18n.t('linters.unknown_variable', variable: used_var), feature, scenario
15
15
  )
@@ -21,7 +21,7 @@ module Chutney
21
21
  def step_vars(step)
22
22
  vars = gather_vars step.text
23
23
  return vars unless step.block
24
-
24
+
25
25
  vars + gather_vars_from_argument(step.block)
26
26
  end
27
27
 
@@ -39,7 +39,7 @@ module Chutney
39
39
 
40
40
  def known_variables(scenario)
41
41
  return [] unless scenario.is_a? CukeModeler::Outline
42
-
42
+
43
43
  scenario.examples.map { |ex| ex.rows.first.cells.map(&:value) }.flatten
44
44
  end
45
45
  end
@@ -6,9 +6,8 @@ module Chutney
6
6
  def lint
7
7
  scenarios do |feature, scenario|
8
8
  next unless scenario.is_a? CukeModeler::Outline
9
-
9
+
10
10
  scenario.examples.each do |example|
11
-
12
11
  example.rows.first.cells.map(&:value).each do |variable|
13
12
  next if used?(variable, scenario)
14
13
 
@@ -20,7 +19,7 @@ module Chutney
20
19
 
21
20
  def used?(variable, scenario)
22
21
  variable = "<#{variable}>"
23
-
22
+
24
23
  scenario.steps.each do |step|
25
24
  return true if step.text.include? variable
26
25
  next unless step.block
@@ -36,7 +35,7 @@ module Chutney
36
35
 
37
36
  def used_in_table?(variable, step)
38
37
  return false unless step.block.is_a?(CukeModeler::Table)
39
-
38
+
40
39
  step.block.rows.each do |row|
41
40
  row.cells.each { |cell| return true if cell.value.include?(variable) }
42
41
  end
@@ -16,11 +16,11 @@ module Chutney
16
16
 
17
17
  def gather_givens
18
18
  return unless feature.children
19
-
19
+
20
20
  has_non_given_step = false
21
21
  scenarios do |_feature, scenario|
22
22
  next unless scenario.steps
23
-
23
+
24
24
  has_non_given_step = true unless given_word?(scenario.steps.first.keyword)
25
25
  end
26
26
  return if has_non_given_step
@@ -33,7 +33,7 @@ module Chutney
33
33
  def expanded_steps(&block)
34
34
  scenarios do |_feature, scenario|
35
35
  next unless scenario.steps
36
-
36
+
37
37
  prototypes = [render_step(scenario.steps.first)]
38
38
  prototypes = expand_examples(scenario.examples, prototypes) if scenario.is_a? CukeModeler::Outline
39
39
  prototypes.each(&block)
@@ -52,7 +52,7 @@ module Chutney
52
52
  headers = example.rows.first.cells.map(&:value)
53
53
  example.rows.each_with_index do |row, idx|
54
54
  next if idx.zero? # skip the header
55
-
55
+
56
56
  modified_sentence = sentence.dup
57
57
  headers.zip(row.cells.map(&:value)).map do |key, value|
58
58
  modified_sentence.gsub!("<#{key}>", value)
@@ -11,18 +11,18 @@ module Chutney
11
11
  scenarios.product(scenarios) do |lhs, rhs|
12
12
  next if lhs == rhs
13
13
  next if lhs[:reference][:line] > rhs[:reference][:line]
14
-
14
+
15
15
  similarity = determine_similarity(lhs[:text], rhs[:text])
16
16
  next unless similarity >= 0.95
17
-
17
+
18
18
  similarity_pct = similarity.round(3) * 100
19
-
19
+
20
20
  add_issue(lhs, rhs, similarity_pct)
21
21
  end
22
22
  end
23
-
23
+
24
24
  def add_issue(lhs, rhs, pct)
25
- super(I18n.t('linters.use_outline',
25
+ super(I18n.t('linters.use_outline',
26
26
  pct: pct,
27
27
  lhs_name: lhs[:name],
28
28
  lhs_line: lhs[:reference][:line],
@@ -39,11 +39,11 @@ module Chutney
39
39
  def gather_scenarios(feature)
40
40
  scenarios = []
41
41
  return scenarios if feature.nil? || !feature.tests
42
-
42
+
43
43
  scenarios do |_feature, scenario|
44
44
  next unless scenario.steps
45
45
  next if scenario.steps.empty?
46
-
46
+
47
47
  scenarios.push generate_reference(feature, scenario)
48
48
  end
49
49
  scenarios
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chutney
4
- VERSION = '3.0.0.beta.2'
4
+ VERSION = '3.0.0'
5
5
  end
@@ -8,25 +8,25 @@ describe Chutney::ChutneyLint do
8
8
  it 'has a version number' do
9
9
  expect(Chutney::VERSION).not_to be nil
10
10
  end
11
-
11
+
12
12
  describe '#initialize' do
13
13
  it 'creates an instance' do
14
14
  expect(subject).not_to be_nil
15
15
  end
16
-
16
+
17
17
  it 'has an empty list of files if none are given' do
18
18
  expect(subject.files).to eq []
19
19
  end
20
-
20
+
21
21
  it 'has a list of files given on initialization' do
22
22
  alt_subject = Chutney::ChutneyLint.new('a', 'b')
23
23
  expect(alt_subject.files).to eq %w[a b]
24
24
  end
25
-
25
+
26
26
  it 'initializes a results hash' do
27
27
  expect(subject.results).to eq({})
28
28
  end
29
-
29
+
30
30
  it 'sets the load path for I18n' do
31
31
  expect(I18n.load_path).not_to eq []
32
32
  end
@@ -36,25 +36,25 @@ describe Chutney::ChutneyLint do
36
36
  expect(subject.configuration).not_to be_nil
37
37
  expect(subject.configuration).to respond_to :[]
38
38
  end
39
-
39
+
40
40
  it 'allows the configuration to be set explicitly' do
41
41
  config = { 'BackgroundDoesMoreThanSetup' => { 'Enabled' => true } }
42
42
  subject.configuration = config
43
43
  expect(subject.configuration).to be config
44
44
  end
45
-
45
+
46
46
  it 'controls the available linters' do
47
47
  subject.configuration = {}
48
48
  expect(subject.linters).to be_empty
49
49
  end
50
-
50
+
51
51
  it 'enables linters to be activated' do
52
52
  config = { 'BackgroundDoesMoreThanSetup' => { 'Enabled' => true } }
53
53
  subject.configuration = config
54
54
  expect(subject.linters).to eq [Chutney::BackgroundDoesMoreThanSetup]
55
55
  end
56
56
  end
57
-
57
+
58
58
  context 'linting' do
59
59
  it 'aliases analyse and analyze' do
60
60
  expect(subject).to respond_to :analyse
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chutney
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.beta.2
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nigel Brookes-Thomas
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2020-09-17 00:00:00.000000000 Z
14
+ date: 2020-09-23 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: amatch
@@ -173,14 +173,14 @@ dependencies:
173
173
  requirements:
174
174
  - - "~>"
175
175
  - !ruby/object:Gem::Version
176
- version: 0.89.0
176
+ version: 0.90.0
177
177
  type: :development
178
178
  prerelease: false
179
179
  version_requirements: !ruby/object:Gem::Requirement
180
180
  requirements:
181
181
  - - "~>"
182
182
  - !ruby/object:Gem::Version
183
- version: 0.89.0
183
+ version: 0.90.0
184
184
  - !ruby/object:Gem::Dependency
185
185
  name: rspec
186
186
  requirement: !ruby/object:Gem::Requirement
@@ -217,7 +217,7 @@ files:
217
217
  - README.md
218
218
  - Rakefile
219
219
  - chutney.gemspec
220
- - config/chutney.yml
220
+ - config/chutney_defaults.yml
221
221
  - config/cucumber.yml
222
222
  - docs/.keep
223
223
  - docs/_config.yml
@@ -297,9 +297,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
297
297
  version: '2.6'
298
298
  required_rubygems_version: !ruby/object:Gem::Requirement
299
299
  requirements:
300
- - - ">"
300
+ - - ">="
301
301
  - !ruby/object:Gem::Version
302
- version: 1.3.1
302
+ version: '0'
303
303
  requirements: []
304
304
  rubygems_version: 3.1.2
305
305
  signing_key: