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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +16 -0
- data/.rubocop.yml +4 -3
- data/LICENSE.txt +1 -1
- data/README.md +7 -1
- data/Rakefile +6 -8
- data/chutney.gemspec +9 -6
- data/config/{chutney.yml → chutney_defaults.yml} +2 -0
- data/docs/index.md +1 -1
- data/docs/usage/rules.md +34 -64
- data/exe/chutney +21 -3
- data/lib/chutney.rb +9 -3
- data/lib/chutney/configuration.rb +7 -2
- data/lib/chutney/formatter.rb +4 -5
- data/lib/chutney/formatter/pie_formatter.rb +8 -13
- data/lib/chutney/formatter/rainbow_formatter.rb +11 -13
- data/lib/chutney/linter.rb +50 -43
- data/lib/chutney/linter/avoid_full_stop.rb +1 -3
- data/lib/chutney/linter/avoid_outline_for_single_example.rb +3 -3
- data/lib/chutney/linter/avoid_scripting.rb +2 -2
- data/lib/chutney/linter/background_does_more_than_setup.rb +3 -4
- data/lib/chutney/linter/background_requires_multiple_scenarios.rb +1 -1
- data/lib/chutney/linter/bad_scenario_name.rb +2 -2
- data/lib/chutney/linter/file_name_differs_feature_name.rb +3 -3
- data/lib/chutney/linter/givens_after_background.rb +2 -2
- data/lib/chutney/linter/invalid_file_name.rb +1 -1
- data/lib/chutney/linter/invalid_step_flow.rb +2 -2
- data/lib/chutney/linter/missing_example_name.rb +2 -4
- data/lib/chutney/linter/missing_feature_description.rb +1 -1
- data/lib/chutney/linter/missing_feature_name.rb +2 -2
- data/lib/chutney/linter/missing_scenario_name.rb +1 -2
- data/lib/chutney/linter/missing_test_action.rb +1 -1
- data/lib/chutney/linter/missing_verification.rb +1 -1
- data/lib/chutney/linter/required_tags_starts_with.rb +5 -6
- data/lib/chutney/linter/same_tag_different_case.rb +37 -0
- data/lib/chutney/linter/same_tag_for_all_scenarios.rb +9 -9
- data/lib/chutney/linter/scenario_names_match.rb +2 -3
- data/lib/chutney/linter/tag_used_multiple_times.rb +1 -1
- data/lib/chutney/linter/too_clumsy.rb +1 -1
- data/lib/chutney/linter/too_long_step.rb +3 -3
- data/lib/chutney/linter/too_many_different_tags.rb +6 -6
- data/lib/chutney/linter/too_many_steps.rb +3 -3
- data/lib/chutney/linter/too_many_tags.rb +3 -3
- data/lib/chutney/linter/unique_scenario_names.rb +2 -2
- data/lib/chutney/linter/unknown_variable.rb +3 -3
- data/lib/chutney/linter/unused_variable.rb +3 -4
- data/lib/chutney/linter/use_background.rb +4 -4
- data/lib/chutney/linter/use_outline.rb +7 -7
- data/lib/chutney/version.rb +1 -1
- data/lib/config/locales/en.yml +3 -0
- data/spec/chutney_spec.rb +9 -9
- metadata +17 -33
@@ -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?
|
@@ -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
|
)
|
@@ -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
|
data/lib/chutney/version.rb
CHANGED