chutney 1.6.3 → 2.0.0.rc1
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/.rspec +1 -0
- data/.rubocop.yml +3 -3
- data/README.md +44 -30
- data/Rakefile +10 -24
- data/chutney.gemspec +13 -10
- data/config/{default.yml → chutney.yml} +6 -3
- data/docs/.keep +0 -0
- data/exe/chutney +28 -22
- data/img/chutney.svg +852 -0
- data/img/formatters.png +0 -0
- data/lib/chutney.rb +61 -85
- data/lib/chutney/configuration.rb +6 -7
- data/lib/chutney/formatter.rb +21 -0
- data/lib/chutney/formatter/json_formatter.rb +8 -0
- data/lib/chutney/formatter/pie_formatter.rb +78 -0
- data/lib/chutney/formatter/rainbow_formatter.rb +47 -0
- data/lib/chutney/linter.rb +145 -113
- data/lib/chutney/linter/avoid_full_stop.rb +12 -0
- data/lib/chutney/linter/avoid_outline_for_single_example.rb +3 -6
- data/lib/chutney/linter/avoid_scripting.rb +10 -15
- data/lib/chutney/linter/background_does_more_than_setup.rb +7 -10
- data/lib/chutney/linter/background_requires_multiple_scenarios.rb +2 -3
- data/lib/chutney/linter/bad_scenario_name.rb +4 -11
- data/lib/chutney/linter/file_name_differs_feature_name.rb +5 -10
- data/lib/chutney/linter/givens_after_background.rb +17 -0
- data/lib/chutney/linter/invalid_file_name.rb +14 -6
- data/lib/chutney/linter/invalid_step_flow.rb +18 -18
- data/lib/chutney/linter/missing_example_name.rb +13 -11
- data/lib/chutney/linter/missing_feature_description.rb +2 -9
- data/lib/chutney/linter/missing_feature_name.rb +3 -12
- data/lib/chutney/linter/missing_scenario_name.rb +3 -7
- data/lib/chutney/linter/missing_test_action.rb +3 -6
- data/lib/chutney/linter/missing_verification.rb +3 -4
- data/lib/chutney/linter/required_tags_starts_with.rb +20 -5
- data/lib/chutney/linter/same_tag_for_all_scenarios.rb +24 -28
- data/lib/chutney/linter/scenario_names_match.rb +6 -8
- data/lib/chutney/linter/tag_used_multiple_times.rb +6 -13
- data/lib/chutney/linter/too_clumsy.rb +4 -4
- data/lib/chutney/linter/too_long_step.rb +8 -18
- data/lib/chutney/linter/too_many_different_tags.rb +13 -34
- data/lib/chutney/linter/too_many_steps.rb +10 -6
- data/lib/chutney/linter/too_many_tags.rb +11 -10
- data/lib/chutney/linter/unique_scenario_names.rb +19 -13
- data/lib/chutney/linter/unknown_variable.rb +5 -6
- data/lib/chutney/linter/unused_variable.rb +6 -7
- data/lib/chutney/linter/use_background.rb +11 -29
- data/lib/chutney/linter/use_outline.rb +21 -15
- data/lib/chutney/version.rb +1 -1
- data/lib/config/locales/en.yml +93 -0
- data/spec/chutney_spec.rb +54 -62
- data/spec/spec_helper.rb +103 -0
- metadata +75 -44
- data/Guardfile +0 -3
- data/lib/chutney/linter/avoid_period.rb +0 -19
- data/lib/chutney/linter/be_declarative.rb +0 -49
- data/lib/chutney/linter/tag_collector.rb +0 -10
- data/lib/chutney/linter/tag_constraint.rb +0 -35
- data/spec/configuration_spec.rb +0 -58
- data/spec/required_tags_starts_with_spec.rb +0 -74
- data/spec/shared_contexts/file_exists.rb +0 -12
- 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
|
-
|
8
|
-
|
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
|
16
|
-
tags =
|
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 == '
|
17
|
+
next if tag == 'skip'
|
24
18
|
|
25
|
-
|
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
|
27
|
+
def lint_examples
|
30
28
|
feature[:children].each do |scenario|
|
31
|
-
tags =
|
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 == '
|
34
|
+
next if tag == 'skip'
|
38
35
|
|
39
|
-
|
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
|
42
|
+
def scenario_tags
|
45
43
|
result = nil
|
46
|
-
|
44
|
+
scenarios do |_feature, scenario|
|
47
45
|
next if scenario[:type] == :Background
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
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
|
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 |
|
10
|
+
scenarios do |feature, scenario|
|
11
11
|
name = scenario.key?(:name) ? scenario[:name].strip : ''
|
12
|
-
|
13
|
-
next unless (name =~ /#{@pattern}/).nil?
|
12
|
+
next unless (name =~ /#{configuration['Matcher']}/).nil?
|
14
13
|
|
15
|
-
|
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 |
|
8
|
-
|
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
|
-
|
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 |
|
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
|
-
|
13
|
-
|
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 |
|
17
|
-
next if step[:text].length <=
|
5
|
+
steps do |feature, scenario, step|
|
6
|
+
next if step[:text].length <= maxlength
|
18
7
|
|
19
|
-
|
20
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
11
|
-
|
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
|
-
|
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
|
32
|
-
|
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
|
40
|
-
return [] unless feature.include?
|
17
|
+
|
18
|
+
def all_tags
|
19
|
+
return [] unless feature.include?(:children)
|
41
20
|
|
42
|
-
|
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 |
|
8
|
-
next if scenario[:steps].length
|
5
|
+
filled_scenarios do |feature, scenario|
|
6
|
+
next if scenario[:steps].length <= maxcount
|
9
7
|
|
10
|
-
|
11
|
-
|
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 |
|
11
|
-
tags =
|
12
|
-
next unless tags.length
|
5
|
+
scenarios do |feature, scenario|
|
6
|
+
tags = tags_for(feature) + tags_for(scenario)
|
7
|
+
next unless tags.length > maxcount
|
13
8
|
|
14
|
-
|
15
|
-
|
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 =
|
8
|
-
scenarios do |
|
5
|
+
references_by_name = {}
|
6
|
+
scenarios do |feature, scenario|
|
9
7
|
next unless scenario.key? :name
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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 |
|
8
|
-
known_vars = Set.new
|
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
|
-
|
14
|
-
|
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 |
|
8
|
-
next unless scenario.key?
|
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?
|
9
|
+
next unless example.key?(:tableHeader)
|
12
10
|
|
13
11
|
example[:tableHeader][:cells].map { |cell| cell[:value] }.each do |variable|
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
21
|
-
|
22
|
-
return
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
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]
|
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
|
28
|
+
expanded_steps { |given| result.push given }
|
47
29
|
result
|
48
30
|
end
|
49
31
|
|
50
|
-
def expanded_steps
|
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
|