chutney 2.0.3.1 → 3.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
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