chutney 3.0.0.beta.1 → 3.1.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +16 -0
  3. data/.rubocop.yml +4 -3
  4. data/LICENSE.txt +1 -1
  5. data/README.md +7 -1
  6. data/Rakefile +6 -8
  7. data/chutney.gemspec +9 -6
  8. data/config/{chutney.yml → chutney_defaults.yml} +2 -0
  9. data/docs/index.md +1 -1
  10. data/docs/usage/rules.md +34 -64
  11. data/exe/chutney +21 -3
  12. data/lib/chutney.rb +9 -3
  13. data/lib/chutney/configuration.rb +7 -2
  14. data/lib/chutney/formatter.rb +4 -5
  15. data/lib/chutney/formatter/pie_formatter.rb +8 -13
  16. data/lib/chutney/formatter/rainbow_formatter.rb +11 -13
  17. data/lib/chutney/linter.rb +50 -43
  18. data/lib/chutney/linter/avoid_full_stop.rb +1 -3
  19. data/lib/chutney/linter/avoid_outline_for_single_example.rb +3 -3
  20. data/lib/chutney/linter/avoid_scripting.rb +2 -2
  21. data/lib/chutney/linter/background_does_more_than_setup.rb +3 -4
  22. data/lib/chutney/linter/background_requires_multiple_scenarios.rb +1 -1
  23. data/lib/chutney/linter/bad_scenario_name.rb +2 -2
  24. data/lib/chutney/linter/file_name_differs_feature_name.rb +3 -3
  25. data/lib/chutney/linter/givens_after_background.rb +2 -2
  26. data/lib/chutney/linter/invalid_file_name.rb +1 -1
  27. data/lib/chutney/linter/invalid_step_flow.rb +2 -2
  28. data/lib/chutney/linter/missing_example_name.rb +2 -4
  29. data/lib/chutney/linter/missing_feature_description.rb +1 -1
  30. data/lib/chutney/linter/missing_feature_name.rb +2 -2
  31. data/lib/chutney/linter/missing_scenario_name.rb +1 -2
  32. data/lib/chutney/linter/missing_test_action.rb +1 -1
  33. data/lib/chutney/linter/missing_verification.rb +1 -1
  34. data/lib/chutney/linter/required_tags_starts_with.rb +5 -6
  35. data/lib/chutney/linter/same_tag_different_case.rb +37 -0
  36. data/lib/chutney/linter/same_tag_for_all_scenarios.rb +9 -9
  37. data/lib/chutney/linter/scenario_names_match.rb +2 -3
  38. data/lib/chutney/linter/tag_used_multiple_times.rb +1 -1
  39. data/lib/chutney/linter/too_clumsy.rb +1 -1
  40. data/lib/chutney/linter/too_long_step.rb +3 -3
  41. data/lib/chutney/linter/too_many_different_tags.rb +6 -6
  42. data/lib/chutney/linter/too_many_steps.rb +3 -3
  43. data/lib/chutney/linter/too_many_tags.rb +3 -3
  44. data/lib/chutney/linter/unique_scenario_names.rb +2 -2
  45. data/lib/chutney/linter/unknown_variable.rb +3 -3
  46. data/lib/chutney/linter/unused_variable.rb +3 -4
  47. data/lib/chutney/linter/use_background.rb +4 -4
  48. data/lib/chutney/linter/use_outline.rb +7 -7
  49. data/lib/chutney/version.rb +1 -1
  50. data/lib/config/locales/en.yml +3 -0
  51. data/spec/chutney_spec.rb +9 -9
  52. metadata +17 -33
@@ -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)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'chutney/linter'
4
+
5
+ module Chutney
6
+ # service class to lint for missing verifications
7
+ class SameTagDifferentCase < Linter
8
+ def all_known_tags
9
+ # rubocop:disable Style/ClassVars
10
+ @@all_known_tags ||= []
11
+ # rubocop:enable Style/ClassVars
12
+ end
13
+
14
+ def lint
15
+ scenarios do |feature, scenario|
16
+ total_tags = tags_for(feature) + tags_for(scenario)
17
+
18
+ total_tags.each do |tag|
19
+ collision_with = case_collision(tag)
20
+ if collision_with
21
+ add_issue(I18n.t('linters.same_tag_different_case',
22
+ existing_tag: collision_with, tag: tag),
23
+ feature, scenario)
24
+ else
25
+ @@all_known_tags << tag
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def case_collision(tag)
32
+ return nil if all_known_tags.include?(tag)
33
+
34
+ all_known_tags.select { |t| t.casecmp(tag).zero? }.first
35
+ end
36
+ end
37
+ end
@@ -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.1'
4
+ VERSION = '3.1.1'
5
5
  end