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.
- 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
|