chutney 1.6.3 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +3 -3
  4. data/README.md +44 -30
  5. data/Rakefile +10 -24
  6. data/chutney.gemspec +13 -10
  7. data/config/{default.yml → chutney.yml} +6 -3
  8. data/docs/.keep +0 -0
  9. data/exe/chutney +28 -22
  10. data/img/chutney.svg +852 -0
  11. data/img/formatters.png +0 -0
  12. data/lib/chutney.rb +61 -85
  13. data/lib/chutney/configuration.rb +6 -7
  14. data/lib/chutney/formatter.rb +21 -0
  15. data/lib/chutney/formatter/json_formatter.rb +8 -0
  16. data/lib/chutney/formatter/pie_formatter.rb +78 -0
  17. data/lib/chutney/formatter/rainbow_formatter.rb +47 -0
  18. data/lib/chutney/linter.rb +145 -113
  19. data/lib/chutney/linter/avoid_full_stop.rb +12 -0
  20. data/lib/chutney/linter/avoid_outline_for_single_example.rb +3 -6
  21. data/lib/chutney/linter/avoid_scripting.rb +10 -15
  22. data/lib/chutney/linter/background_does_more_than_setup.rb +7 -10
  23. data/lib/chutney/linter/background_requires_multiple_scenarios.rb +2 -3
  24. data/lib/chutney/linter/bad_scenario_name.rb +4 -11
  25. data/lib/chutney/linter/file_name_differs_feature_name.rb +5 -10
  26. data/lib/chutney/linter/givens_after_background.rb +17 -0
  27. data/lib/chutney/linter/invalid_file_name.rb +14 -6
  28. data/lib/chutney/linter/invalid_step_flow.rb +18 -18
  29. data/lib/chutney/linter/missing_example_name.rb +13 -11
  30. data/lib/chutney/linter/missing_feature_description.rb +2 -9
  31. data/lib/chutney/linter/missing_feature_name.rb +3 -12
  32. data/lib/chutney/linter/missing_scenario_name.rb +3 -7
  33. data/lib/chutney/linter/missing_test_action.rb +3 -6
  34. data/lib/chutney/linter/missing_verification.rb +3 -4
  35. data/lib/chutney/linter/required_tags_starts_with.rb +20 -5
  36. data/lib/chutney/linter/same_tag_for_all_scenarios.rb +24 -28
  37. data/lib/chutney/linter/scenario_names_match.rb +6 -8
  38. data/lib/chutney/linter/tag_used_multiple_times.rb +6 -13
  39. data/lib/chutney/linter/too_clumsy.rb +4 -4
  40. data/lib/chutney/linter/too_long_step.rb +8 -18
  41. data/lib/chutney/linter/too_many_different_tags.rb +13 -34
  42. data/lib/chutney/linter/too_many_steps.rb +10 -6
  43. data/lib/chutney/linter/too_many_tags.rb +11 -10
  44. data/lib/chutney/linter/unique_scenario_names.rb +19 -13
  45. data/lib/chutney/linter/unknown_variable.rb +5 -6
  46. data/lib/chutney/linter/unused_variable.rb +6 -7
  47. data/lib/chutney/linter/use_background.rb +11 -29
  48. data/lib/chutney/linter/use_outline.rb +21 -15
  49. data/lib/chutney/version.rb +1 -1
  50. data/lib/config/locales/en.yml +93 -0
  51. data/spec/chutney_spec.rb +54 -62
  52. data/spec/spec_helper.rb +103 -0
  53. metadata +75 -44
  54. data/Guardfile +0 -3
  55. data/lib/chutney/linter/avoid_period.rb +0 -19
  56. data/lib/chutney/linter/be_declarative.rb +0 -49
  57. data/lib/chutney/linter/tag_collector.rb +0 -10
  58. data/lib/chutney/linter/tag_constraint.rb +0 -35
  59. data/spec/configuration_spec.rb +0 -58
  60. data/spec/required_tags_starts_with_spec.rb +0 -74
  61. data/spec/shared_contexts/file_exists.rb +0 -12
  62. data/spec/shared_contexts/gherkin_linter.rb +0 -14
@@ -4,65 +4,61 @@ module Chutney
4
4
  # service class to lint for using same tag on all scenarios
5
5
  class SameTagForAllScenarios < Linter
6
6
  def lint
7
- features do |file, feature|
8
- next unless feature.include? :children
9
-
10
- lint_scenarios file, feature
11
- lint_examples file, feature
12
- end
7
+ lint_scenarios if feature.include? :children
8
+ lint_examples if feature.include? :children
13
9
  end
14
10
 
15
- def lint_scenarios(file, feature)
16
- tags = gather_same_tags feature
17
- return if tags.nil?
18
- return if tags.empty?
11
+ def lint_scenarios
12
+ tags = scenario_tags
13
+ return if tags.nil? || tags.empty?
19
14
  return unless feature[:children].length > 1
20
-
21
- references = [reference(file, feature)]
15
+
22
16
  tags.each do |tag|
23
- next if tag == '@skip'
17
+ next if tag == 'skip'
24
18
 
25
- add_error(references, "Tag '#{tag}' should be used at Feature level")
19
+ add_issue(
20
+ I18n.t('linters.same_tag_for_all_scenarios.feature_level',
21
+ tag: tag),
22
+ feature
23
+ )
26
24
  end
27
25
  end
28
26
 
29
- def lint_examples(file, feature)
27
+ def lint_examples
30
28
  feature[:children].each do |scenario|
31
- tags = gather_same_tags_for_outline scenario
29
+ tags = example_tags(scenario)
32
30
  next if tags.nil? || tags.empty?
33
31
  next unless scenario[:examples].length > 1
34
32
 
35
- references = [reference(file, feature, scenario)]
36
33
  tags.each do |tag|
37
- next if tag == '@skip'
34
+ next if tag == 'skip'
38
35
 
39
- add_error(references, "Tag '#{tag}' should be used at Scenario Outline level")
36
+ add_issue(I18n.t('linters.same_tag_for_all_scenarios.example_level',
37
+ tag: tag), feature, scenario)
40
38
  end
41
39
  end
42
40
  end
43
41
 
44
- def gather_same_tags(feature)
42
+ def scenario_tags
45
43
  result = nil
46
- feature[:children].each do |scenario|
44
+ scenarios do |_feature, scenario|
47
45
  next if scenario[:type] == :Background
48
- return nil unless scenario.include? :tags
49
-
50
- tags = scenario[:tags].map { |tag| tag[:name] }
51
- result = tags if result.nil?
52
-
46
+
47
+ tags = tags_for(scenario)
48
+ result ||= tags
53
49
  result &= tags
54
50
  end
55
51
  result
56
52
  end
57
53
 
58
- def gather_same_tags_for_outline(scenario)
54
+ def example_tags(scenario)
59
55
  result = nil
60
56
  return result unless scenario.include? :examples
61
57
 
62
58
  scenario[:examples].each do |example|
63
59
  return nil unless example.include? :tags
64
60
 
65
- tags = example[:tags].map { |tag| tag[:name] }
61
+ tags = tags_for(example)
66
62
  result = tags if result.nil?
67
63
 
68
64
  result &= tags
@@ -7,17 +7,15 @@ module Chutney
7
7
 
8
8
 
9
9
  def lint
10
- scenarios do |file, feature, scenario|
10
+ scenarios do |feature, scenario|
11
11
  name = scenario.key?(:name) ? scenario[:name].strip : ''
12
- references = [reference(file, feature, scenario)]
13
- next unless (name =~ /#{@pattern}/).nil?
12
+ next unless (name =~ /#{configuration['Matcher']}/).nil?
14
13
 
15
- add_warning(references, MESSAGE)
14
+ add_issue(
15
+ I18n.t('linters.scenario_names_match',
16
+ pattern: configuration['Matcher']), feature, scenario
17
+ )
16
18
  end
17
19
  end
18
-
19
- def matcher(pattern)
20
- @pattern = pattern
21
- end
22
20
  end
23
21
  end
@@ -1,23 +1,16 @@
1
- require 'chutney/linter'
2
-
3
1
  module Chutney
4
2
  # service class to lint for tags used multiple times
5
3
  class TagUsedMultipleTimes < Linter
6
4
  def lint
7
- scenarios do |file, feature, scenario|
8
- references = [reference(file, feature, scenario)]
9
- total_tags = tags(feature) + tags(scenario)
5
+ scenarios do |feature, scenario|
6
+ total_tags = tags_for(feature) + tags_for(scenario)
10
7
  double_used_tags = total_tags.find_all { |a| total_tags.count(a) > 1 }.uniq!
11
8
  next if double_used_tags.nil?
12
-
13
- add_error(references, "Tag #{double_used_tags.join(' and ')} used multiple times")
9
+
10
+ add_issue(
11
+ I18n.t('linters.tag_used_multiple_times', tags: double_used_tags.join(',')), feature
12
+ )
14
13
  end
15
14
  end
16
-
17
- def tags(element)
18
- return [] unless element.include? :tags
19
-
20
- element[:tags].map { |a| a[:name] }
21
- end
22
15
  end
23
16
  end
@@ -3,14 +3,14 @@ require 'chutney/linter'
3
3
  module Chutney
4
4
  # service class to lint for too clumsy scenarios
5
5
  class TooClumsy < Linter
6
- MESSAGE = 'This scenario is too long at %d characters'.freeze
7
6
  def lint
8
- filled_scenarios do |file, feature, scenario|
7
+ filled_scenarios do |feature, scenario|
9
8
  characters = scenario[:steps].map { |step| step[:text].length }.inject(0, :+)
10
9
  next if characters < 400
11
10
 
12
- references = [reference(file, feature, scenario)]
13
- add_error(references, MESSAGE % characters)
11
+ add_issue(
12
+ I18n.t('linters.too_clumsy', length: characters), feature, scenario
13
+ )
14
14
  end
15
15
  end
16
16
  end
@@ -1,29 +1,19 @@
1
- require 'chutney/linter'
2
-
3
- # rubocop:disable Lint/MissingCopEnableDirective
4
1
  module Chutney
5
2
  # service class to lint for too long steps
6
3
  class TooLongStep < Linter
7
-
8
- def initialize
9
- @maxlength = 80
10
- super
11
- end
12
-
13
- MESSAGE = 'This step is too long at %d characters'.freeze
14
-
15
4
  def lint
16
- steps do |file, feature, scenario, step|
17
- next if step[:text].length <= @maxlength
5
+ steps do |feature, scenario, step|
6
+ next if step[:text].length <= maxlength
18
7
 
19
- references = [reference(file, feature, scenario, step)]
20
- add_error(references, MESSAGE % step[:text].length)
8
+ add_issue(
9
+ I18n.t('linters.too_long_step', length: step[:text].length, max: maxlength),
10
+ feature, scenario
11
+ )
21
12
  end
22
13
  end
23
14
 
24
- # rubocop:disable Style/TrivialAccessors
25
- def maxlength(length)
26
- @maxlength = length
15
+ def maxlength
16
+ configuration['MaxLength'] || '120'
27
17
  end
28
18
  end
29
19
  end
@@ -1,45 +1,24 @@
1
- require 'chutney/linter'
2
- require 'chutney/linter/tag_collector'
3
-
4
1
  module Chutney
5
2
  # service class to lint for too many different tags
6
3
  class TooManyDifferentTags < Linter
7
- include TagCollector
8
-
9
4
  def lint
10
- overall_tags = []
11
- overall_references = []
12
- features do |file, feature|
13
- tags = tags_for_feature(feature)
14
- overall_tags += tags
15
- references = [reference(file, feature)]
16
- overall_references += references unless tags.empty?
17
-
18
- warn_single_feature(references, tags)
19
- end
20
- warn_across_all_features(overall_references, overall_tags)
21
- end
22
-
23
- def warn_single_feature(references, tags)
24
- tags.uniq!
25
- references.uniq!
26
- return false unless tags.length >= 3
5
+ tags = all_tags
6
+ return if tags.length <= maxcount
27
7
 
28
- add_error(references, "Used #{tags.length} Tags within single Feature")
8
+ add_issue(
9
+ I18n.t('linters.too_many_different_tags', count: tags.length, max: maxcount),
10
+ feature
11
+ )
29
12
  end
30
-
31
- def warn_across_all_features(references, tags)
32
- tags.uniq!
33
- references.uniq!
34
- return false unless tags.length >= 10
35
-
36
- add_error(references, "Used #{tags.length} Tags across all Features")
13
+
14
+ def maxcount
15
+ configuration['MaxCount']&.to_i || 3
37
16
  end
38
-
39
- def tags_for_feature(feature)
40
- return [] unless feature.include? :children
17
+
18
+ def all_tags
19
+ return [] unless feature.include?(:children)
41
20
 
42
- gather_tags(feature) + feature[:children].map { |scenario| gather_tags(scenario) }.flatten
21
+ tags_for(feature) + feature[:children].map { |scenario| tags_for(scenario) }.flatten
43
22
  end
44
23
  end
45
24
  end
@@ -1,15 +1,19 @@
1
- require 'chutney/linter'
2
-
3
1
  module Chutney
4
2
  # service class to lint for too many steps
5
3
  class TooManySteps < Linter
6
4
  def lint
7
- filled_scenarios do |file, feature, scenario|
8
- next if scenario[:steps].length < 10
5
+ filled_scenarios do |feature, scenario|
6
+ next if scenario[:steps].length <= maxcount
9
7
 
10
- references = [reference(file, feature, scenario)]
11
- add_error(references, "Scenario is too long at #{scenario[:steps].length} steps")
8
+ add_issue(
9
+ I18n.t('linters.too_many_steps', count: scenario[:steps].length, max: maxcount),
10
+ feature
11
+ )
12
12
  end
13
13
  end
14
+
15
+ def maxcount
16
+ configuration['MaxCount']&.to_i || 10
17
+ end
14
18
  end
15
19
  end
@@ -1,19 +1,20 @@
1
- require 'chutney/linter'
2
- require 'chutney/linter/tag_collector'
3
-
4
1
  module Chutney
5
2
  # service class to lint for too many tags
6
3
  class TooManyTags < Linter
7
- include TagCollector
8
-
9
4
  def lint
10
- scenarios do |file, feature, scenario|
11
- tags = gather_tags(feature) + gather_tags(scenario)
12
- next unless tags.length >= 3
5
+ scenarios do |feature, scenario|
6
+ tags = tags_for(feature) + tags_for(scenario)
7
+ next unless tags.length > maxcount
13
8
 
14
- references = [reference(file, feature, scenario)]
15
- add_error(references, "Used #{tags.length} Tags")
9
+ add_issue(
10
+ I18n.t('linters.too_many_tags', count: tags.length, max: maxcount),
11
+ feature
12
+ )
16
13
  end
17
14
  end
15
+
16
+ def maxcount
17
+ configuration['MaxCount']&.to_i || 3
18
+ end
18
19
  end
19
20
  end
@@ -1,22 +1,28 @@
1
- require 'chutney/linter'
2
-
3
1
  module Chutney
4
2
  # service class to lint for unique scenario names
5
3
  class UniqueScenarioNames < Linter
6
4
  def lint
7
- references_by_name = Hash.new []
8
- scenarios do |file, feature, scenario|
5
+ references_by_name = {}
6
+ scenarios do |feature, scenario|
9
7
  next unless scenario.key? :name
10
-
11
- scenario_name = "#{feature[:name]}.#{scenario[:name]}"
12
- references_by_name[scenario_name] = references_by_name[scenario_name] + [reference(file, feature, scenario)]
13
- end
14
-
15
- references_by_name.each do |name, references|
16
- next if references.length <= 1
17
-
18
- add_error(references, "Scenario name should be unique, '#{name}' used #{references.length} times")
8
+
9
+ name = scenario[:name]
10
+ if references_by_name[name]
11
+ issue(name, references_by_name[name], scenario)
12
+ else
13
+ references_by_name[name] = location(feature, scenario, nil)
14
+ end
19
15
  end
20
16
  end
17
+
18
+ def issue(name, first_location, scenario)
19
+ add_issue(
20
+ I18n.t('linters.unique_scenario_names',
21
+ name: name,
22
+ line: first_location[:line],
23
+ column: first_location[:column]),
24
+ feature, scenario
25
+ )
26
+ end
21
27
  end
22
28
  end
@@ -1,17 +1,16 @@
1
- require 'chutney/linter'
2
-
3
1
  module Chutney
4
2
  # service class to lint for unknown variables
5
3
  class UnknownVariable < Linter
6
4
  def lint
7
- filled_scenarios do |file, feature, scenario|
8
- known_vars = Set.new known_variables scenario
5
+ filled_scenarios do |feature, scenario|
6
+ known_vars = Set.new(known_variables(scenario))
9
7
  scenario[:steps].each do |step|
10
8
  step_vars(step).each do |used_var|
11
9
  next if known_vars.include? used_var
12
10
 
13
- references = [reference(file, feature, scenario)]
14
- add_error(references, "Variable '<#{used_var}>' is unknown")
11
+ add_issue(
12
+ I18n.t('linters.unknown_variable', variable: used_var), feature, scenario
13
+ )
15
14
  end
16
15
  end
17
16
  end
@@ -1,18 +1,17 @@
1
- require 'chutney/linter'
2
-
3
1
  module Chutney
4
2
  # service class to lint for unused variables
5
3
  class UnusedVariable < Linter
6
4
  def lint
7
- scenarios do |file, feature, scenario|
8
- next unless scenario.key? :examples
5
+ scenarios do |feature, scenario|
6
+ next unless scenario.key?(:examples)
9
7
 
10
8
  scenario[:examples].each do |example|
11
- next unless example.key? :tableHeader
9
+ next unless example.key?(:tableHeader)
12
10
 
13
11
  example[:tableHeader][:cells].map { |cell| cell[:value] }.each do |variable|
14
- references = [reference(file, feature, scenario)]
15
- add_error(references, "Variable '<#{variable}>' is unused") unless used?(variable, scenario)
12
+ next if used?(variable, scenario)
13
+
14
+ add_issue(I18n.t('linters.unused_variable', variable: variable), feature, scenario, example)
16
15
  end
17
16
  end
18
17
  end
@@ -1,36 +1,18 @@
1
- require 'chutney/linter'
2
-
3
1
  module Chutney
4
2
  # service class to lint for using background
5
3
  class UseBackground < Linter
6
4
  def lint
7
- features do |file, feature|
8
- next if scenarios_with_steps(feature) <= 1
9
-
10
- givens = gather_givens feature
11
- next if givens.nil?
12
- next if givens.length <= 1
13
- next if givens.uniq.length > 1
14
-
15
- references = [reference(file, feature)]
16
- add_error(references, "Step '#{givens.uniq.first}' should be part of background")
17
- end
18
- end
5
+ return unless filled_scenarios.count > 1
19
6
 
20
- def scenarios_with_steps(feature)
21
- scenarios = 0
22
- return 0 unless feature.key? :children
23
-
24
- feature[:children].each do |scenario|
25
- next unless scenario.include? :steps
26
- next if scenario[:steps].empty?
27
-
28
- scenarios += 1
29
- end
30
- scenarios
7
+ givens = gather_givens
8
+ return if givens.nil?
9
+ return if givens.length <= 1
10
+ return if givens.uniq.length > 1
11
+
12
+ add_issue(I18n.t('linters.use_background', step: givens.uniq.first), feature)
31
13
  end
32
14
 
33
- def gather_givens(feature)
15
+ def gather_givens
34
16
  return unless feature.include? :children
35
17
 
36
18
  has_non_given_step = false
@@ -38,16 +20,16 @@ module Chutney
38
20
  next unless scenario.include? :steps
39
21
  next if scenario[:steps].empty?
40
22
 
41
- has_non_given_step = true unless scenario[:steps].first[:keyword] == 'Given '
23
+ has_non_given_step = true unless given_word?(scenario[:steps].first[:keyword])
42
24
  end
43
25
  return if has_non_given_step
44
26
 
45
27
  result = []
46
- expanded_steps(feature) { |given| result.push given }
28
+ expanded_steps { |given| result.push given }
47
29
  result
48
30
  end
49
31
 
50
- def expanded_steps(feature)
32
+ def expanded_steps
51
33
  feature[:children].each do |scenario|
52
34
  next unless scenario[:type] != :Background
53
35
  next unless scenario.include? :steps