chutney 3.0.0.beta.1 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
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