chutney 2.0.3.1 → 3.0.0.beta.2

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -1
  3. data/.rufo +1 -0
  4. data/Gemfile +2 -0
  5. data/README.md +6 -1
  6. data/Rakefile +2 -0
  7. data/chutney.gemspec +14 -6
  8. data/config/chutney.yml +4 -0
  9. data/config/cucumber.yml +1 -0
  10. data/docs/usage/rules.md +7 -1
  11. data/exe/chutney +20 -0
  12. data/lib/chutney.rb +20 -20
  13. data/lib/chutney/configuration.rb +7 -1
  14. data/lib/chutney/formatter.rb +2 -0
  15. data/lib/chutney/formatter/json_formatter.rb +2 -0
  16. data/lib/chutney/formatter/pie_formatter.rb +2 -0
  17. data/lib/chutney/formatter/rainbow_formatter.rb +6 -3
  18. data/lib/chutney/issue.rb +2 -0
  19. data/lib/chutney/linter.rb +65 -61
  20. data/lib/chutney/linter/avoid_full_stop.rb +3 -1
  21. data/lib/chutney/linter/avoid_outline_for_single_example.rb +8 -6
  22. data/lib/chutney/linter/avoid_scripting.rb +6 -4
  23. data/lib/chutney/linter/avoid_typographers_quotes.rb +39 -0
  24. data/lib/chutney/linter/background_does_more_than_setup.rb +8 -6
  25. data/lib/chutney/linter/background_requires_multiple_scenarios.rb +6 -3
  26. data/lib/chutney/linter/bad_scenario_name.rb +4 -2
  27. data/lib/chutney/linter/empty_feature_file.rb +10 -0
  28. data/lib/chutney/linter/file_name_differs_feature_name.rb +4 -2
  29. data/lib/chutney/linter/givens_after_background.rb +5 -6
  30. data/lib/chutney/linter/invalid_file_name.rb +2 -0
  31. data/lib/chutney/linter/invalid_step_flow.rb +7 -7
  32. data/lib/chutney/linter/missing_example_name.rb +7 -5
  33. data/lib/chutney/linter/missing_feature_description.rb +6 -3
  34. data/lib/chutney/linter/missing_feature_name.rb +5 -2
  35. data/lib/chutney/linter/missing_scenario_name.rb +3 -4
  36. data/lib/chutney/linter/missing_test_action.rb +3 -1
  37. data/lib/chutney/linter/missing_verification.rb +3 -1
  38. data/lib/chutney/linter/required_tags_starts_with.rb +2 -0
  39. data/lib/chutney/linter/same_tag_for_all_scenarios.rb +11 -10
  40. data/lib/chutney/linter/scenario_names_match.rb +4 -3
  41. data/lib/chutney/linter/tag_used_multiple_times.rb +2 -0
  42. data/lib/chutney/linter/too_clumsy.rb +3 -1
  43. data/lib/chutney/linter/too_long_step.rb +4 -2
  44. data/lib/chutney/linter/too_many_different_tags.rb +4 -2
  45. data/lib/chutney/linter/too_many_steps.rb +4 -2
  46. data/lib/chutney/linter/too_many_tags.rb +2 -0
  47. data/lib/chutney/linter/unique_scenario_names.rb +3 -3
  48. data/lib/chutney/linter/unknown_variable.rb +13 -13
  49. data/lib/chutney/linter/unused_variable.rb +13 -13
  50. data/lib/chutney/linter/use_background.rb +17 -16
  51. data/lib/chutney/linter/use_outline.rb +8 -7
  52. data/lib/chutney/version.rb +3 -1
  53. data/lib/config/locales/en.yml +6 -0
  54. data/spec/chutney_spec.rb +2 -0
  55. data/spec/spec_helper.rb +2 -0
  56. metadata +37 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2638bbebf9e9f48bf42c2871d2c5c3ef4a13b06bd67d3c52096fd270135e759
4
- data.tar.gz: 18c502227f4918d434dc876fd3548bd532218b2e3250f94cdd33b4860119eea2
3
+ metadata.gz: c99f32f7cba016b8d419d1867c5005b448e05939565afff74db238c68222f2d9
4
+ data.tar.gz: 9f21a905656a69d4bbfc7288b7abcec37e36e3fb5950c852aa8c6e4183e52cd9
5
5
  SHA512:
6
- metadata.gz: e5ca617c4feec58478cef5f00784e52425b79489cac43244824019e1ab7e7f7136dc4a7f355175282e55e634fb924ebd52a69400dfadace0bf140c824aa8a922
7
- data.tar.gz: 4effe41d2b7d6213aff06a4083d78b57461366f540508c1ad946028a6c19d3e96f77411bc134e82de337a3fcdf376faf7fde96d7475fe82ab05a6c5d36b40b66
6
+ metadata.gz: 3a4339e31214900e809bdbdaf968a2d21af9bc7526365cd2f00a17745b10272947114c018e6b182c6679769636ffc127d6880d69dbbbb8dc66f0d5f5762a410d
7
+ data.tar.gz: b755781258f1781e4586a77f639682b7ebb0a584d50aa0c001a7eca5c5e8b0c16487b2d1dd1978e97744c5f7ec163ea169ae8d9c6114e1ed22cda716e3173a34
@@ -8,7 +8,7 @@
8
8
 
9
9
  # Offense count: 1
10
10
  Metrics/AbcSize:
11
- Max: 16
11
+ Enabled: false
12
12
 
13
13
  # Offense count: 1
14
14
  # Configuration parameters: CountComments.
@@ -55,4 +55,10 @@ Layout/TrailingWhitespace:
55
55
  Enabled: false
56
56
 
57
57
  Style/FrozenStringLiteralComment:
58
+ Enabled: true
59
+
60
+ Style/StringConcatenation:
58
61
  Enabled: false
62
+
63
+ AllCops:
64
+ NewCops: enable
data/.rufo ADDED
@@ -0,0 +1 @@
1
+ quote_style :single
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/README.md CHANGED
@@ -12,10 +12,15 @@
12
12
  <div align="center">
13
13
 
14
14
  [![Gem Version](https://badge.fury.io/rb/chutney.svg)](https://badge.fury.io/rb/chutney)
15
+ [![Downloads](https://img.shields.io/gem/dt/chutney)](https://rubygems.org/gems/chutney)
15
16
  ![CircleCI branch](https://img.shields.io/circleci/project/github/BillyRuffian/chutney/master.svg?style=flat-square)
16
17
  [![CodeFactor](https://www.codefactor.io/repository/github/billyruffian/chutney/badge?style=flat-square)](https://www.codefactor.io/repository/github/billyruffian/chutney)
17
18
  ![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/BillyRuffian/chutney.svg?style=flat-square)
18
19
 
19
20
  </div>
20
21
 
21
- Read the documentation [here](https://billyruffian.github.io/chutney/).
22
+ Read the documentation [here](https://billyruffian.github.io/chutney/).
23
+
24
+ ## Notes
25
+
26
+ Chutney 3+ (in beta) has replaced its direct dependency on Cucumber and instead uses the excellent [cuke_modeller](https://github.com/enkessler/cuke_modeler) to parse your feature files.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rake/testtask'
2
4
 
3
5
  task default: :build
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Disable rubocop checks for the .gemspec
2
4
  # I'll take the output from 'bundle gem new' to be authoritative
3
5
  # rubocop:disable all
@@ -12,9 +14,13 @@ Gem::Specification.new do |spec|
12
14
  spec.authors = ['Nigel Brookes-Thomas', 'Stefan Rohe', 'Nishtha Argawal', 'John Gluck']
13
15
  spec.email = ['nigel@brookes-thomas.co.uk']
14
16
 
15
- spec.summary = 'A linter for English language Gherkin'
16
- spec.description = 'A linter for your Cucumber features. ' \
17
- 'It supports any spoken language Cucumber v3 supports.'
17
+ spec.summary = 'A linter for multi-lingual Gherkin'
18
+ spec.description = 'A linter for your Cucumber features. ' \
19
+ 'Making sure you have nice, expressible Gherkin is ' \
20
+ 'essential is making sure you have a readable test-base. ' \
21
+ 'Chutney is designed to sniff out smells in your feature ' \
22
+ 'files. ' \
23
+ 'It supports any spoken language Cucumber supports.'
18
24
 
19
25
  spec.homepage = 'https://billyruffian.github.io/chutney/'
20
26
  spec.license = 'MIT'
@@ -43,18 +49,20 @@ Gem::Specification.new do |spec|
43
49
  spec.require_paths = ['lib']
44
50
 
45
51
  spec.add_runtime_dependency 'amatch', '~> 0.4.0'
46
- spec.add_runtime_dependency 'gherkin', '~> 5.1.0'
52
+ spec.add_runtime_dependency 'cuke_modeler', '~> 3.3'
47
53
  spec.add_runtime_dependency 'i18n', '~> 1.8.2'
48
54
  spec.add_runtime_dependency 'pastel', '~> 0.7'
49
55
  spec.add_runtime_dependency 'tty-pie', '~> 0.3'
50
56
 
51
57
 
52
58
  spec.add_development_dependency 'coveralls', '~> 0.8'
53
- spec.add_development_dependency 'cucumber', '~> 3.0'
59
+ spec.add_development_dependency 'cucumber', '~> 5.1'
60
+ spec.add_development_dependency 'pry-byebug', '~> 3.0'
54
61
  spec.add_development_dependency 'rake', '~> 13.0'
55
62
  spec.add_development_dependency 'rerun', '~> 0.13'
56
63
  spec.add_development_dependency 'rspec-expectations', '~> 3.0'
57
- spec.add_development_dependency 'rubocop', '~> 0.82.0'
64
+ spec.add_development_dependency 'rubocop', '~> 0.89.0'
58
65
  spec.add_development_dependency 'rspec', '~> 3.8'
59
66
 
67
+ spec.required_ruby_version = '~> 2.6'
60
68
  end
@@ -4,12 +4,16 @@ AvoidFullStop:
4
4
  Enabled: true
5
5
  AvoidScripting:
6
6
  Enabled: true
7
+ AvoidTypographersQuotes:
8
+ Enabled: true
7
9
  BackgroundDoesMoreThanSetup:
8
10
  Enabled: true
9
11
  BackgroundRequiresMultipleScenarios:
10
12
  Enabled: true
11
13
  BadScenarioName:
12
14
  Enabled: true
15
+ EmptyFeatureFile:
16
+ Enabled: true
13
17
  FileNameDiffersFeatureName:
14
18
  Enabled: true
15
19
  GivensAfterBackground:
@@ -0,0 +1 @@
1
+ default: --publish-quiet
@@ -18,6 +18,9 @@ Chutney enforces its rules with the linters. These are:
18
18
  [AvoidScripting](https://github.com/BillyRuffian/chutney/blob/master/features/avoid_scripting.feature)
19
19
  : You have a lot of steps, are you sure you're not scripting the scenario when you should be specifying the behaviour of the system?
20
20
 
21
+ [AvoidTypographersQuotes](https://github.com/BillyRuffian/chutney/blob/master/features/avoid_typographers_quotes.feature)
22
+ : Cutting and pasting from Word documents? Is that pasting in curly-quotes instead of neutral ones you would type on a keyboard? Are you sure that's what you want?
23
+
21
24
  [BackgroundDoesMoreThanSetup](https://github.com/BillyRuffian/chutney/blob/master/features/background_does_more_than_setup.feature)
22
25
  : Background in feature files should only do setup activity and so they should only contain `Given` steps.
23
26
 
@@ -27,6 +30,9 @@ Chutney enforces its rules with the linters. These are:
27
30
  [BadScenarioName](https://github.com/BillyRuffian/chutney/blob/master/features/bad_scenario_name.feature)
28
31
  : You should avoid using words like 'test' or 'check' in your scenario names, instead you should define the behaviour of your system.
29
32
 
33
+ [EmptyFeatureFile](https://github.com/BillyRuffian/chutney/blob/master/features/empty_feature_file.feature)
34
+ : The feature should have content and should avoid committing empty features to repositories.
35
+
30
36
  [FileNameDiffersFeatureName](https://github.com/BillyRuffian/chutney/blob/master/features/file_name_differs_feature_name.feature)
31
37
  : The feature should have a name that follows the file name.
32
38
 
@@ -76,7 +82,7 @@ Chutney enforces its rules with the linters. These are:
76
82
  : This is a very long step. Consider writing it more concisely.
77
83
 
78
84
  [TooManyDifferentTags](https://github.com/BillyRuffian/chutney/blob/master/features/too_many_different_tags.feature)
79
- : This feature has a lot of differnt tags.
85
+ : This feature has a lot of different tags.
80
86
 
81
87
  [TooManySteps](https://github.com/BillyRuffian/chutney/blob/master/features/too_many_steps.feature)
82
88
  : This feature has a lot of steps. Consider writing it more concisely.
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'chutney'
3
5
  require 'chutney/formatter'
4
6
  require 'chutney/formatter/json_formatter'
@@ -17,6 +19,24 @@ OptionParser.new do |opts|
17
19
 
18
20
  formatters << formatter
19
21
  end
22
+
23
+ opts.on('-l',
24
+ '--linters',
25
+ 'List the linter status by this configuration and exit') do
26
+ pastel = Pastel.new
27
+ chutney_config = Chutney::ChutneyLint.new.configuration
28
+ max_name_length = chutney_config.keys.map(&:length).max + 1
29
+ chutney_config.each do |linter, value|
30
+ print pastel.cyan(linter.ljust(max_name_length))
31
+
32
+ if value['Enabled']
33
+ puts pastel.green('enabled')
34
+ else
35
+ puts pastel.red('disabled')
36
+ end
37
+ end
38
+ exit
39
+ end
20
40
  end.parse!
21
41
 
22
42
  formatters << 'RainbowFormatter' if formatters.empty?
@@ -1,12 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amatch'
4
+
2
5
  require 'chutney/configuration'
3
6
  require 'chutney/linter'
4
7
  require 'chutney/linter/avoid_full_stop'
5
8
  require 'chutney/linter/avoid_outline_for_single_example'
6
9
  require 'chutney/linter/avoid_scripting'
10
+ require 'chutney/linter/avoid_typographers_quotes'
7
11
  require 'chutney/linter/background_does_more_than_setup'
8
12
  require 'chutney/linter/background_requires_multiple_scenarios'
9
13
  require 'chutney/linter/bad_scenario_name'
14
+ require 'chutney/linter/empty_feature_file'
10
15
  require 'chutney/linter/file_name_differs_feature_name'
11
16
  require 'chutney/linter/givens_after_background'
12
17
  require 'chutney/linter/invalid_file_name'
@@ -31,9 +36,11 @@ require 'chutney/linter/unknown_variable'
31
36
  require 'chutney/linter/unused_variable'
32
37
  require 'chutney/linter/use_background'
33
38
  require 'chutney/linter/use_outline'
39
+
40
+ require 'cuke_modeler'
34
41
  require 'forwardable'
35
- require 'gherkin/dialect'
36
- require 'gherkin/parser'
42
+ # require 'gherkin/dialect'
43
+ # require 'gherkin/parser'
37
44
  require 'i18n'
38
45
  require 'set'
39
46
  require 'yaml'
@@ -43,9 +50,8 @@ module Chutney
43
50
  class ChutneyLint
44
51
  extend Forwardable
45
52
  attr_accessor :verbose
46
- attr_reader :files
47
- attr_reader :results
48
-
53
+ attr_reader :files, :results
54
+
49
55
  def_delegators :@files, :<<, :clear, :delete, :include?
50
56
 
51
57
  def initialize(*files)
@@ -54,9 +60,9 @@ module Chutney
54
60
  i18n_paths = Dir[File.expand_path(File.join(__dir__, 'config/locales')) + '/*.yml']
55
61
  return if I18n.load_path.include?(i18n_paths)
56
62
 
57
- I18n.load_path << i18n_paths
63
+ I18n.load_path += i18n_paths
58
64
  end
59
-
65
+
60
66
  def configuration
61
67
  unless @config
62
68
  default_file = [File.expand_path('..', __dir__), '**/config', 'chutney.yml']
@@ -65,11 +71,11 @@ module Chutney
65
71
  end
66
72
  @config
67
73
  end
68
-
74
+
69
75
  def configuration=(config)
70
76
  @config = config
71
77
  end
72
-
78
+
73
79
  def analyse
74
80
  files.each do |f|
75
81
  lint(f)
@@ -79,25 +85,19 @@ module Chutney
79
85
  # alias for non-british English
80
86
  # https://dictionary.cambridge.org/dictionary/english/analyse
81
87
  alias analyze analyse
82
-
88
+
83
89
  def linters
84
90
  @linters ||= Linter.descendants.filter { |l| configuration.dig(l.linter_name, 'Enabled') }
85
91
  end
86
-
92
+
87
93
  def linters=(*linters)
88
94
  @linters = linters
89
95
  end
90
-
96
+
91
97
  private
92
-
93
- def parse(text)
94
- @parser ||= Gherkin::Parser.new
95
- scanner = Gherkin::TokenScanner.new(text)
96
- @parser.parse(scanner)
97
- end
98
-
98
+
99
99
  def lint(file)
100
- parsed = parse(File.read(file))
100
+ parsed = CukeModeler::FeatureFile.new(file)
101
101
  linters.each do |linter_class|
102
102
  linter = linter_class.new(file, parsed, configuration[linter_class.linter_name])
103
103
  linter.lint
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  require 'delegate'
3
+
2
4
  module Chutney
3
5
  # gherkin_lint configuration object
4
6
  class Configuration < SimpleDelegator
@@ -18,7 +20,11 @@ module Chutney
18
20
  end
19
21
 
20
22
  def load_user_configuration
21
- config_file = Dir.glob(File.join(Dir.pwd, '**', '.chutney.yml')).first
23
+ config_files = ['chutney.yml', '.chutney.yml'].map do |fname|
24
+ Dir.glob(File.join(Dir.pwd, '**', fname))
25
+ end.flatten
26
+
27
+ config_file = config_files.first
22
28
  merge_config(config_file) if !config_file.nil? && File.exist?(config_file)
23
29
  end
24
30
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # base class for all formatters
3
5
  class Formatter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # Plain old JSON formatter
3
5
  class JSONFormatter < Formatter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pastel'
2
4
  require 'tty/pie'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pastel'
2
4
 
3
5
  module Chutney
@@ -16,7 +18,7 @@ module Chutney
16
18
  linter.filter { |l| !l[:issues].empty? }.each do |linter_with_issues|
17
19
 
18
20
  put_linter(linter_with_issues)
19
- linter_with_issues[:issues].each { |i| put_issue(i) }
21
+ linter_with_issues[:issues].each { |i| put_issue(file, i) }
20
22
  end
21
23
  end
22
24
  put_summary
@@ -30,8 +32,9 @@ module Chutney
30
32
  puts @pastel.red(" #{linter[:linter]}")
31
33
  end
32
34
 
33
- def put_issue(issue)
34
- puts " #{@pastel.dim(issue.dig(:location, :line))} #{issue[:message]}"
35
+ def put_issue(file, issue)
36
+ puts " #{issue[:message]}"
37
+ puts " #{@pastel.dim file.to_s}:#{@pastel.dim(issue.dig(:location, :line))}"
35
38
  end
36
39
 
37
40
  def put_summary
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'term/ansicolor'
2
4
 
3
5
  module Chutney
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # gherkin utilities
4
+
2
5
  module Chutney
3
6
  # base class for all linters
4
7
  class Linter
5
8
  attr_accessor :issues
6
- attr_reader :filename
7
- attr_reader :configuration
9
+ attr_reader :filename, :configuration
8
10
 
9
11
  Lint = Struct.new(:message, :gherkin_type, :location, :feature, :scenario, :step, keyword_init: true)
10
12
 
@@ -17,8 +19,8 @@ module Chutney
17
19
  @filename = filename
18
20
  @issues = []
19
21
  @configuration = configuration
20
- language = @content.dig(:feature, :language) || 'en'
21
- @dialect = Gherkin::Dialect.for(language)
22
+ # language = @content.dig(:feature, :language) || 'en'
23
+ # @dialect = Gherkin::Dialect.for(language)
22
24
  end
23
25
 
24
26
  def lint
@@ -26,63 +28,71 @@ module Chutney
26
28
  end
27
29
 
28
30
  def and_word?(word)
29
- @dialect.and_keywords.include?(word)
31
+ dialect_word(:and).include?(word)
30
32
  end
31
33
 
32
34
  def background_word?(word)
33
- @dialect.background_keywords.include?(word)
35
+ dialect_word(:background).include?(word)
34
36
  end
35
37
 
36
38
  def but_word?(word)
37
- @dialect.but_keywords.include?(word)
39
+ dialect_word(:but).include?(word)
38
40
  end
39
41
 
40
42
  def examples_word?(word)
41
- @dialect.example_keywords.include?(word)
43
+ dialect_word(:examples).include?(word)
42
44
  end
43
45
 
44
46
  def feature_word?(word)
45
- @dialect.feature_keywords.include?(word)
47
+ dialect_word(:feature).include?(word)
46
48
  end
47
49
 
48
50
  def given_word?(word)
49
- @dialect.given_keywords.include?(word)
51
+ dialect_word(:given).include?(word)
50
52
  end
51
53
 
52
54
  def scenario_outline_word?(word)
53
- @dialect.scenario_outline_keywords.include?(word)
55
+ dialect_word(:scenarioOutline).include?(word)
54
56
  end
55
57
 
56
58
  def then_word?(word)
57
- @dialect.then_keywords.include?(word)
59
+ dialect_word(:then).include?(word)
58
60
  end
59
61
 
60
62
  def when_word?(word)
61
- @dialect.when_keywords.include?(word)
63
+ dialect_word(:when).include?(word)
64
+ end
65
+
66
+ def dialect_word(word)
67
+ CukeModeler::Parsing.dialects[dialect][word.to_s].map(&:strip)
68
+ end
69
+
70
+ def dialect
71
+ @content.feature&.parsing_data&.dig(:language) || 'en'
62
72
  end
63
73
 
64
74
  def tags_for(element)
65
- return [] unless element.include? :tags
66
-
67
- element[:tags].map { |tag| tag[:name][1..-1] }
75
+ element.tags.map { |tag| tag.name[1..-1] }
68
76
  end
69
77
 
70
- def add_issue(message, feature, scenario = nil, step = nil)
78
+ def add_issue(message, feature = nil, scenario = nil, item = nil)
71
79
  issues << Lint.new(
72
80
  message: message,
73
- gherkin_type: type(feature, scenario, step),
74
- location: location(feature, scenario, step),
75
- feature: feature[:name],
76
- scenario: scenario ? scenario[:name] : nil,
77
- step: step ? step[:text] : nil
81
+ gherkin_type: type(feature, scenario, item),
82
+ location: location(feature, scenario, item),
83
+ feature: feature&.name,
84
+ scenario: scenario&.name,
85
+ step: item&.parsing_data&.dig(:name)
78
86
  ).to_h
79
87
  end
80
88
 
81
89
  def location(feature, scenario, step)
82
90
  if step
83
- step[:location]
84
- else
85
- scenario ? scenario[:location] : feature[:location]
91
+ step.parsing_data[:location]
92
+ elsif scenario
93
+ scenario.parsing_data.dig(:scenario, :location) || scenario.parsing_data.dig(:background, :location)
94
+ else
95
+ feature ? feature.parsing_data[:location] : 0
86
96
  end
87
97
  end
88
98
 
@@ -96,77 +106,71 @@ module Chutney
96
106
 
97
107
  def feature
98
108
  if block_given?
99
- yield(@content[:feature]) if @content[:feature]
109
+ yield(@content.feature) if @content.feature
100
110
  else
101
- @content[:feature]
111
+ @content.feature
102
112
  end
103
113
  end
104
114
 
105
- def elements
115
+ def elements
116
+ return [] unless feature
117
+
106
118
  if block_given?
107
- feature[:children].each do |child|
119
+ feature.children.each do |child|
108
120
  next if off_switch?(child)
109
121
 
110
122
  yield(feature, child)
111
123
  end
112
124
  else
113
- feature[:children]
125
+ feature.children
114
126
  end
115
127
  end
116
-
128
+
117
129
  def off_switch?(element = feature)
118
- off_switch = element[:tags]
119
- .then { |tags| tags || [] }
120
- .filter { |tag| tag[:type] == :Tag }
121
- .filter { |tag| tag[:name] == "@disable#{linter_name}" }
122
- .count
123
- .positive?
130
+ off_switch = element.tags
131
+ .then { |tags| tags || [] }
132
+ .filter { |tag| tag[:type] == :Tag }
133
+ .filter { |tag| tag[:name] == "@disable#{linter_name}" }
134
+ .count
135
+ .positive?
124
136
  off_switch ||= off_switch?(feature) unless element == feature
125
137
  off_switch
126
138
  end
127
139
 
128
140
  def background
129
- if block_given?
130
- elements do |feature, child|
131
- next unless child[:type] == :Background
132
-
133
- yield(feature, child)
134
- end
141
+ if block_given?
142
+ yield(feature, feature&.background)
135
143
  else
136
- elements.filter { |child| child[:type] == :Background }
144
+ feature&.background
137
145
  end
138
146
  end
139
147
 
140
148
  def scenarios
141
149
  if block_given?
142
- elements do |feature, child|
143
- next unless %i[ScenarioOutline Scenario].include? child[:type]
144
-
145
- yield(feature, child)
150
+ feature&.tests&.each do |test|
151
+ yield(feature, test)
146
152
  end
153
+
147
154
  else
148
- elements.filter { |child| %i[ScenarioOutline Scenario].include? child[:type] }
155
+ feature&.tests
149
156
  end
150
157
  end
151
158
 
152
159
  def filled_scenarios
153
160
  if block_given?
154
161
  scenarios do |feature, scenario|
155
- next unless scenario.include? :steps
156
- next if scenario[:steps].empty?
162
+ next if scenario.steps.empty?
157
163
 
158
164
  yield(feature, scenario)
159
165
  end
160
166
  else
161
- scenarios.filter { |s| !s[:steps].empty? }
167
+ scenarios ? scenarios.filter { |s| !s.steps.empty? } : []
162
168
  end
163
169
  end
164
170
 
165
171
  def steps
166
- elements do |feature, child|
167
- next unless child.include? :steps
168
-
169
- child[:steps].each { |step| yield(feature, child, step) }
172
+ feature&.tests&.each do |t|
173
+ t.steps.each { |s| yield(feature, t, s) }
170
174
  end
171
175
  end
172
176
 
@@ -179,16 +183,16 @@ module Chutney
179
183
  end
180
184
 
181
185
  def render_step(step)
182
- value = "#{step[:keyword]}#{step[:text]}"
183
- value += render_step_argument step[:argument] if step.include? :argument
186
+ value = "#{step.keyword} #{step.text}"
187
+ value += render_step_argument(step.block) if step.block
184
188
  value
185
189
  end
186
190
 
187
191
  def render_step_argument(argument)
188
- return "\n#{argument[:content]}" if argument[:type] == :DocString
192
+ return "\n#{argument.content}" if argument.is_a?(CukeModeler::DocString)
189
193
 
190
- result = argument[:rows].map do |row|
191
- "|#{row[:cells].map { |cell| cell[:value] }.join '|'}|"
194
+ result = argument.rows.map do |row|
195
+ "|#{row.cells.map(&:value).join '|'}|"
192
196
  end.join "\n"
193
197
  "\n#{result}"
194
198
  end