chutney 3.0.0.beta.1 → 3.1.1
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/.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