cuke_linter 0.12.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -1
  3. data/README.md +12 -8
  4. data/cuke_linter.gemspec +26 -22
  5. data/exe/cuke_linter +4 -2
  6. data/lib/cuke_linter.rb +119 -176
  7. data/lib/cuke_linter/configuration.rb +45 -0
  8. data/lib/cuke_linter/default_linters.rb +32 -0
  9. data/lib/cuke_linter/formatters/pretty_formatter.rb +63 -35
  10. data/lib/cuke_linter/gherkin.rb +10 -0
  11. data/lib/cuke_linter/linter_registration.rb +32 -0
  12. data/lib/cuke_linter/linters/background_does_more_than_setup_linter.rb +17 -1
  13. data/lib/cuke_linter/linters/element_with_common_tags_linter.rb +23 -14
  14. data/lib/cuke_linter/linters/element_with_duplicate_tags_linter.rb +18 -13
  15. data/lib/cuke_linter/linters/element_with_too_many_tags_linter.rb +17 -11
  16. data/lib/cuke_linter/linters/feature_with_too_many_different_tags_linter.rb +1 -3
  17. data/lib/cuke_linter/linters/feature_without_description_linter.rb +1 -1
  18. data/lib/cuke_linter/linters/linter.rb +17 -13
  19. data/lib/cuke_linter/linters/step_with_too_many_characters_linter.rb +2 -2
  20. data/lib/cuke_linter/linters/test_should_use_background_linter.rb +23 -15
  21. data/lib/cuke_linter/linters/test_with_action_step_as_final_step_linter.rb +12 -1
  22. data/lib/cuke_linter/linters/test_with_bad_name_linter.rb +4 -4
  23. data/lib/cuke_linter/linters/test_with_no_action_step_linter.rb +12 -1
  24. data/lib/cuke_linter/linters/test_with_no_verification_step_linter.rb +12 -1
  25. data/lib/cuke_linter/linters/test_with_setup_step_after_action_step_linter.rb +18 -2
  26. data/lib/cuke_linter/linters/test_with_setup_step_after_verification_step_linter.rb +18 -2
  27. data/lib/cuke_linter/linters/test_with_setup_step_as_final_step_linter.rb +12 -1
  28. data/lib/cuke_linter/version.rb +1 -1
  29. data/testing/cucumber/features/linters/background_does_more_than_setup.feature +34 -0
  30. data/testing/cucumber/features/linters/test_with_action_as_final_step.feature +25 -3
  31. data/testing/cucumber/features/linters/test_with_no_action_step.feature +27 -1
  32. data/testing/cucumber/features/linters/test_with_no_verification_step.feature +28 -1
  33. data/testing/cucumber/features/linters/test_with_setup_step_after_action_step.feature +28 -1
  34. data/testing/cucumber/features/linters/test_with_setup_step_after_verification_step.feature +28 -1
  35. data/testing/cucumber/features/linters/test_with_setup_step_as_final_step.feature +25 -3
  36. metadata +101 -117
  37. data/.gitignore +0 -19
  38. data/.simplecov +0 -8
  39. data/.travis.yml +0 -33
  40. data/CONTRIBUTING.md +0 -26
  41. data/Gemfile +0 -6
  42. data/Rakefile +0 -63
  43. data/appveyor.yml +0 -43
  44. data/bin/console +0 -14
  45. data/bin/setup +0 -8
  46. data/environments/common_env.rb +0 -12
  47. data/environments/cucumber_env.rb +0 -22
  48. data/environments/rspec_env.rb +0 -50
  49. data/testing/cucumber/step_definitions/action_steps.rb +0 -84
  50. data/testing/cucumber/step_definitions/setup_steps.rb +0 -230
  51. data/testing/cucumber/step_definitions/verification_steps.rb +0 -94
  52. data/testing/file_helper.rb +0 -41
  53. data/testing/formatter_factory.rb +0 -15
  54. data/testing/gemfiles/cuke_modeler1.gemfile +0 -8
  55. data/testing/gemfiles/cuke_modeler2.gemfile +0 -8
  56. data/testing/linter_factory.rb +0 -60
  57. data/testing/model_factory.rb +0 -109
  58. data/testing/rspec/spec/integration/cli_integration_spec.rb +0 -556
  59. data/testing/rspec/spec/integration/configuration_spec.rb +0 -811
  60. data/testing/rspec/spec/integration/cuke_linter_integration_spec.rb +0 -243
  61. data/testing/rspec/spec/integration/formatters/formatter_integration_specs.rb +0 -5
  62. data/testing/rspec/spec/integration/formatters/pretty_formatter_integration_spec.rb +0 -8
  63. data/testing/rspec/spec/integration/linters/background_does_more_than_setup_linter_integration_spec.rb +0 -8
  64. data/testing/rspec/spec/integration/linters/element_with_common_tags_linter_integration_spec.rb +0 -8
  65. data/testing/rspec/spec/integration/linters/element_with_duplicate_tags_linter_integration_spec.rb +0 -8
  66. data/testing/rspec/spec/integration/linters/element_with_too_many_tags_linter_integration_spec.rb +0 -8
  67. data/testing/rspec/spec/integration/linters/example_without_name_linter_integration_spec.rb +0 -8
  68. data/testing/rspec/spec/integration/linters/feature_file_with_invalid_name_integration_spec.rb +0 -8
  69. data/testing/rspec/spec/integration/linters/feature_file_with_mismatched_name_integration_spec.rb +0 -8
  70. data/testing/rspec/spec/integration/linters/feature_with_too_many_different_tags_linter_integration_spec.rb +0 -8
  71. data/testing/rspec/spec/integration/linters/feature_without_description_linter_integration_spec.rb +0 -8
  72. data/testing/rspec/spec/integration/linters/feature_without_name_linter_integration_spec.rb +0 -8
  73. data/testing/rspec/spec/integration/linters/feature_without_scenarios_linter_integration_spec.rb +0 -8
  74. data/testing/rspec/spec/integration/linters/linter_integration_spec.rb +0 -8
  75. data/testing/rspec/spec/integration/linters/linter_integration_specs.rb +0 -7
  76. data/testing/rspec/spec/integration/linters/outline_with_single_example_row_linter_integration_spec.rb +0 -8
  77. data/testing/rspec/spec/integration/linters/single_test_background_linter_integration_spec.rb +0 -8
  78. data/testing/rspec/spec/integration/linters/step_with_end_period_linter_integration_spec.rb +0 -8
  79. data/testing/rspec/spec/integration/linters/step_with_too_many_characters_linter_integration_spec.rb +0 -8
  80. data/testing/rspec/spec/integration/linters/test_should_use_background_linter_integration_spec.rb +0 -8
  81. data/testing/rspec/spec/integration/linters/test_with_action_step_as_final_step_linter_integration_spec.rb +0 -8
  82. data/testing/rspec/spec/integration/linters/test_with_bad_name_integration_spec.rb +0 -8
  83. data/testing/rspec/spec/integration/linters/test_with_no_action_step_integration_spec.rb +0 -8
  84. data/testing/rspec/spec/integration/linters/test_with_no_name_integration_spec.rb +0 -8
  85. data/testing/rspec/spec/integration/linters/test_with_no_verification_step_integration_spec.rb +0 -8
  86. data/testing/rspec/spec/integration/linters/test_with_setup_step_after_action_step_linter_integration_spec.rb +0 -8
  87. data/testing/rspec/spec/integration/linters/test_with_setup_step_after_verification_step_linter_integration_spec.rb +0 -8
  88. data/testing/rspec/spec/integration/linters/test_with_setup_step_as_final_step_linter_integration_spec.rb +0 -8
  89. data/testing/rspec/spec/integration/linters/test_with_too_many_steps_linter_integration_spec.rb +0 -8
  90. data/testing/rspec/spec/unit/cuke_linter_unit_spec.rb +0 -102
  91. data/testing/rspec/spec/unit/formatters/formatter_unit_specs.rb +0 -11
  92. data/testing/rspec/spec/unit/formatters/pretty_formatter_unit_spec.rb +0 -115
  93. data/testing/rspec/spec/unit/linters/background_does_more_than_setup_linter_unit_spec.rb +0 -77
  94. data/testing/rspec/spec/unit/linters/configurable_linter_unit_specs.rb +0 -11
  95. data/testing/rspec/spec/unit/linters/element_with_common_tags_linter_unit_spec.rb +0 -248
  96. data/testing/rspec/spec/unit/linters/element_with_duplicate_tags_linter_unit_spec.rb +0 -203
  97. data/testing/rspec/spec/unit/linters/element_with_too_many_tags_linter_unit_spec.rb +0 -296
  98. data/testing/rspec/spec/unit/linters/example_without_name_linter_unit_spec.rb +0 -81
  99. data/testing/rspec/spec/unit/linters/feature_file_with_invalid_name_linter_unit_spec.rb +0 -106
  100. data/testing/rspec/spec/unit/linters/feature_file_with_mismatched_name_linter_unit_spec.rb +0 -124
  101. data/testing/rspec/spec/unit/linters/feature_with_too_many_different_tags_linter_unit_spec.rb +0 -293
  102. data/testing/rspec/spec/unit/linters/feature_without_description_linter_unit_spec.rb +0 -80
  103. data/testing/rspec/spec/unit/linters/feature_without_name_linter_unit_spec.rb +0 -84
  104. data/testing/rspec/spec/unit/linters/feature_without_scenarios_linter_unit_spec.rb +0 -102
  105. data/testing/rspec/spec/unit/linters/linter_unit_spec.rb +0 -197
  106. data/testing/rspec/spec/unit/linters/linter_unit_specs.rb +0 -57
  107. data/testing/rspec/spec/unit/linters/outline_with_single_example_row_linter_unit_spec.rb +0 -184
  108. data/testing/rspec/spec/unit/linters/single_test_background_linter_unit_spec.rb +0 -89
  109. data/testing/rspec/spec/unit/linters/step_with_end_period_linter_unit_spec.rb +0 -54
  110. data/testing/rspec/spec/unit/linters/step_with_too_many_characters_linter_unit_spec.rb +0 -155
  111. data/testing/rspec/spec/unit/linters/test_should_use_background_linter_unit_spec.rb +0 -464
  112. data/testing/rspec/spec/unit/linters/test_with_action_step_as_final_step_linter_unit_spec.rb +0 -98
  113. data/testing/rspec/spec/unit/linters/test_with_bad_name_linter_unit_spec.rb +0 -81
  114. data/testing/rspec/spec/unit/linters/test_with_no_action_step_linter_unit_spec.rb +0 -176
  115. data/testing/rspec/spec/unit/linters/test_with_no_name_linter_unit_spec.rb +0 -88
  116. data/testing/rspec/spec/unit/linters/test_with_no_verification_step_linter_unit_spec.rb +0 -179
  117. data/testing/rspec/spec/unit/linters/test_with_setup_step_after_action_step_linter_unit_spec.rb +0 -124
  118. data/testing/rspec/spec/unit/linters/test_with_setup_step_after_verification_step_linter_unit_spec.rb +0 -125
  119. data/testing/rspec/spec/unit/linters/test_with_setup_step_as_final_step_linter_unit_spec.rb +0 -98
  120. data/testing/rspec/spec/unit/linters/test_with_too_many_steps_linter_unit_spec.rb +0 -192
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 670cec37df02d865f45baf856baab441942d1dd54f8b39089b88fd4657afc098
4
- data.tar.gz: 840623e9e37f4f38ac3d5f1f28332b77f759b77a8dd3aa12a9554ede015ccddb
3
+ metadata.gz: '02379fe769af15463f334b6762e9ced230bdc612b4243e44d180afa40dd9fce4'
4
+ data.tar.gz: 042acec109e1ac3fd283ac115019946038a9ec6bc6716a3636cb215f6fec4513
5
5
  SHA512:
6
- metadata.gz: 96beaf4a96234000319d61976ee7b19de8fe0c4d5a83583d0cd3fa33579078cbfce40eac96e52302423c69266db8cc0d3582263a1b913674a741142ee044f0bc
7
- data.tar.gz: b21f8016215c971dcff8565232c3e078c9e2f0f6a4421c554e2479cb0a5d42d1abeb6b08ef5f6851d28d50b13b3a8362772d8ecbecfbcc6a66efa34681deaeb5
6
+ metadata.gz: 42c283c86a2617818efdb8585281075397b9e6c4f14688f43baab1733259811c5c9b0e5c76987446fd32705003d41ab5efc9a789aeb0c5332b89880bc15aea83
7
+ data.tar.gz: eb53e88817540ff8fc3bdea75e9f36d0930407ed9ce301695382cf74f583f6a60bac2b215ca6a03ed21d46146734dac6c650f95451ce77f95bce3d627420a385
@@ -8,6 +8,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  Nothing yet...
10
10
 
11
+ ## [1.2.0] - 2021-01-08
12
+
13
+ ### Added
14
+ - Ruby 3.x is now supported
15
+
16
+ ## [1.1.0] - 2020-06-14
17
+
18
+ ### Added
19
+ - Now compatible with CukeModeler 3.x
20
+
21
+ ## [1.0.1] - 2020-05-06
22
+
23
+ ### Fixed
24
+ - Replaced some code that was not valid with earlier versions of Ruby 2.x, with which this gem is specified as being compatible.
25
+
26
+
27
+ ## [1.0.0] - 2020-03-09
28
+
29
+ ### Changed
30
+ - No longer including every file in the Git repository as part of the gem. Only the files needed for using the linter (and the informative ones like the README) will be packaged into the released gem.
31
+
32
+
33
+ ## [0.13.0] - 2020-03-01
34
+
35
+ ### Added
36
+ - Added the ability to configure relevant keywords for linters whose behavior is impacted by the dialect in which feature files are written.
37
+ - BackgroundDoesMoreThanSetupLinter
38
+ - TestWithActionStepAsFinalStepLinter
39
+ - TestWithNoActionStepLinter
40
+ - TestWithNoVerificationStepLinter
41
+ - TestWithSetupStepAfterActionStepLinter
42
+ - TestWithSetupStepAfterVerificationStepLinter
43
+ - TestWithSetupStepAsFinalStepLinter
44
+
11
45
 
12
46
  ## [0.12.1] - 2020-02-15
13
47
 
@@ -135,7 +169,12 @@ Nothing yet...
135
169
  - Custom linters, formatters, and command line usability
136
170
 
137
171
 
138
- [Unreleased]: https://github.com/enkessler/cuke_linter/compare/v0.12.1...HEAD
172
+ [Unreleased]: https://github.com/enkessler/cuke_linter/compare/v1.2.0...HEAD
173
+ [1.2.0]: https://github.com/enkessler/cuke_linter/compare/v1.1.0...v1.2.0
174
+ [1.1.0]: https://github.com/enkessler/cuke_linter/compare/v1.0.1...v1.1.0
175
+ [1.0.1]: https://github.com/enkessler/cuke_linter/compare/v1.0.0...v1.0.1
176
+ [1.0.0]: https://github.com/enkessler/cuke_linter/compare/v0.13.0...v1.0.0
177
+ [0.13.0]: https://github.com/enkessler/cuke_linter/compare/v0.12.1...v0.13.0
139
178
  [0.12.1]: https://github.com/enkessler/cuke_linter/compare/v0.12.0...v0.12.1
140
179
  [0.12.0]: https://github.com/enkessler/cuke_linter/compare/v0.11.1...v0.12.0
141
180
  [0.11.1]: https://github.com/enkessler/cuke_linter/compare/v0.11.0...v0.11.1
data/README.md CHANGED
@@ -8,11 +8,11 @@ User stuff:
8
8
  [![Yard Docs](http://img.shields.io/badge/Documentation-API-blue.svg)](https://www.rubydoc.info/gems/cuke_linter)
9
9
 
10
10
  Developer stuff:
11
- [![Build Status](https://travis-ci.org/enkessler/cuke_linter.svg?branch=dev)](https://travis-ci.org/enkessler/cuke_linter)
12
- [![Build Status](https://ci.appveyor.com/api/projects/status/g5o70u747x073evy/branch/dev?svg=true)](https://ci.appveyor.com/project/enkessler/cuke-linter/branch/dev)
13
- [![Coverage Status](https://coveralls.io/repos/github/enkessler/cuke_linter/badge.svg?branch=dev)](https://coveralls.io/github/enkessler/cuke_linter?branch=dev)
11
+ [![Build Status](https://travis-ci.org/enkessler/cuke_linter.svg?branch=master)](https://travis-ci.org/enkessler/cuke_linter)
12
+ [![Build Status](https://ci.appveyor.com/api/projects/status/g5o70u747x073evy/branch/master?svg=true)](https://ci.appveyor.com/project/enkessler/cuke-linter/branch/master)
13
+ [![Coverage Status](https://coveralls.io/repos/github/enkessler/cuke_linter/badge.svg?branch=master)](https://coveralls.io/github/enkessler/cuke_linter?branch=master)
14
14
  [![Maintainability](https://api.codeclimate.com/v1/badges/d1b86760e59a457c8e73/maintainability)](https://codeclimate.com/github/enkessler/cuke_linter/maintainability)
15
- [![Inline docs](http://inch-ci.org/github/enkessler/cuke_linter.svg?branch=dev)](https://inch-ci.org/github/enkessler/cuke_linter?branch=dev)
15
+ [![Inline docs](http://inch-ci.org/github/enkessler/cuke_linter.svg?branch=master)](https://inch-ci.org/github/enkessler/cuke_linter?branch=master)
16
16
 
17
17
  ---
18
18
 
@@ -104,7 +104,8 @@ class MyCustomLinter
104
104
  return nil unless model.is_a?(CukeModeler::Scenario)
105
105
 
106
106
  if model.name.empty?
107
- { problem: 'Scenario has no name', location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }
107
+ { problem: 'Scenario has no name',
108
+ location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }
108
109
  else
109
110
  nil
110
111
  end
@@ -134,13 +135,16 @@ output_path = "#{__dir__}/my_report.txt"
134
135
  model_tree_root = CukeModeler::Directory.new(Dir.pwd)
135
136
  additional_file_path = 'path/to/some.feature'
136
137
 
137
- # Providing the formatter twice so that it also is printed to the console
138
- CukeLinter.lint(linters: [linter], formatters: [[formatter], [formatter, output_path]], model_trees: [model_tree_root], file_paths: [additional_file_path])
138
+ # Providing the formatter twice so that output also is printed to the console
139
+ CukeLinter.lint(linters: [linter],
140
+ formatters: [[formatter], [formatter, output_path]],
141
+ model_trees: [model_tree_root],
142
+ file_paths: [additional_file_path])
139
143
  ```
140
144
 
141
145
  ### Configuration
142
146
 
143
- Rather than using the default linters or providing a custom set of of modified linters every time linting occurs, which linters to use and any linter specific modifications can be configured in a more static manner via a configuration file or setting the configuration directly in code. See [documentation](#documentation) for specifics.
147
+ Rather than using the default linters or providing a custom set of of modified linters every time linting occurs, which linters to use and any linter specific modifications (such as choosing a non-default dialect) can be configured in a more static manner via a configuration file or setting the configuration directly in code. See [documentation](#documentation) for specifics.
144
148
 
145
149
 
146
150
  ### <a id="documentation"></a>Everything Else
@@ -1,40 +1,44 @@
1
-
2
- lib = File.expand_path("../lib", __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "cuke_linter/version"
3
+ require 'cuke_linter/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "cuke_linter"
6
+ spec.name = 'cuke_linter'
8
7
  spec.version = CukeLinter::VERSION
9
- spec.authors = ["Eric Kessler"]
10
- spec.email = ["morrow748@gmail.com"]
8
+ spec.authors = ['Eric Kessler']
9
+ spec.email = ['morrow748@gmail.com']
11
10
 
12
- spec.summary = %q{Lints feature files used by Cucumber and other similar frameworks.}
11
+ spec.summary = 'Lints feature files used by Cucumber and other similar frameworks.'
13
12
  spec.homepage = 'https://github.com/enkessler/cuke_linter'
14
- spec.license = "MIT"
13
+ spec.license = 'MIT'
15
14
 
16
15
 
17
- # TODO: don't just include everything in the gem
18
16
  # Specify which files should be added to the gem when it is released.
19
17
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.files = Dir.chdir(File.expand_path('', __dir__)) do
19
+ source_controlled_files = `git ls-files -z`.split("\x0")
20
+ source_controlled_files.keep_if { |file| file =~ %r{^(lib|exe|testing/cucumber/features)} }
21
+ source_controlled_files + ['README.md', 'LICENSE.txt', 'CHANGELOG.md', 'cuke_linter.gemspec']
22
22
  end
23
- spec.bindir = "exe"
23
+ spec.bindir = 'exe'
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
- spec.require_paths = ["lib"]
25
+ spec.require_paths = ['lib']
26
26
 
27
- spec.required_ruby_version = '~> 2.0'
27
+ spec.required_ruby_version = '>= 2.0', '< 4.0'
28
28
 
29
- spec.add_runtime_dependency 'cuke_modeler', '>= 1.5', '< 3.0'
29
+ spec.add_runtime_dependency 'cuke_modeler', '>= 1.5', '< 4.0'
30
30
 
31
- spec.add_development_dependency "bundler", "< 3.0"
32
- spec.add_development_dependency "cucumber", "~> 3.0"
33
- spec.add_development_dependency "racatt", "~> 1.0"
34
- spec.add_development_dependency "rake", "~> 12.0"
35
- spec.add_development_dependency "require_all", "~> 2.0"
36
- spec.add_development_dependency "rspec", "~> 3.0"
37
- spec.add_development_dependency 'simplecov', '< 1.0.0'
31
+ spec.add_development_dependency 'bundler', '< 3.0'
32
+ spec.add_development_dependency 'childprocess', '~> 3.0'
38
33
  spec.add_development_dependency 'coveralls', '< 1.0.0'
34
+ spec.add_development_dependency 'cucumber', '< 5.0'
35
+ spec.add_development_dependency 'cuke_slicer', '>= 2.0.2', '< 3.0'
36
+ spec.add_development_dependency 'ffi', '~> 1.0'
37
+ spec.add_development_dependency 'parallel', '~> 1.0'
39
38
  spec.add_development_dependency 'rainbow', '< 4.0.0'
39
+ spec.add_development_dependency 'rake', '~> 12.0'
40
+ spec.add_development_dependency 'require_all', '~> 2.0'
41
+ spec.add_development_dependency 'rspec', '~> 3.0'
42
+ spec.add_development_dependency 'rubocop', '< 1.0.0'
43
+ spec.add_development_dependency 'simplecov', '<= 0.16.1' # Coveralls gem does not support any newer version than this
40
44
  end
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "cuke_linter"
3
+ require 'cuke_linter'
4
4
  require 'optparse'
5
5
 
6
6
  params = {}
@@ -9,6 +9,7 @@ params[:formatters] = []
9
9
  params[:outs] = []
10
10
  params[:requires] = []
11
11
 
12
+ # rubocop:disable Metrics/BlockLength
12
13
  parser = OptionParser.new do |options|
13
14
 
14
15
  options.set_summary_width(30)
@@ -68,6 +69,7 @@ parser = OptionParser.new do |options|
68
69
  end
69
70
 
70
71
  end
72
+ # rubocop:enable Metrics/BlockLength
71
73
 
72
74
  begin
73
75
  parser.parse!
@@ -105,6 +107,6 @@ elsif File.exist?("#{Dir.pwd}/.cuke_linter")
105
107
  CukeLinter.load_configuration
106
108
  end
107
109
 
108
- results = CukeLinter.lint(options)
110
+ results = CukeLinter.lint(**options)
109
111
 
110
112
  exit(1) unless results.empty?
@@ -1,7 +1,7 @@
1
1
  require 'yaml'
2
2
  require 'cuke_modeler'
3
3
 
4
- require "cuke_linter/version"
4
+ require 'cuke_linter/version'
5
5
  require 'cuke_linter/formatters/pretty_formatter'
6
6
  require 'cuke_linter/linters/linter'
7
7
  require 'cuke_linter/linters/background_does_more_than_setup_linter'
@@ -29,227 +29,170 @@ require 'cuke_linter/linters/test_with_setup_step_after_action_step_linter'
29
29
  require 'cuke_linter/linters/test_with_setup_step_after_verification_step_linter'
30
30
  require 'cuke_linter/linters/test_with_setup_step_as_final_step_linter'
31
31
  require 'cuke_linter/linters/test_with_too_many_steps_linter'
32
+ require 'cuke_linter/configuration'
33
+ require 'cuke_linter/default_linters'
34
+ require 'cuke_linter/gherkin'
35
+ require 'cuke_linter/linter_registration'
32
36
 
33
37
 
34
38
  # The top level namespace used by this gem
35
-
36
39
  module CukeLinter
37
40
 
38
- @original_linters = { 'BackgroundDoesMoreThanSetupLinter' => BackgroundDoesMoreThanSetupLinter.new,
39
- 'ElementWithCommonTagsLinter' => ElementWithCommonTagsLinter.new,
40
- 'ElementWithDuplicateTagsLinter' => ElementWithDuplicateTagsLinter.new,
41
- 'ElementWithTooManyTagsLinter' => ElementWithTooManyTagsLinter.new,
42
- 'ExampleWithoutNameLinter' => ExampleWithoutNameLinter.new,
43
- 'FeatureFileWithInvalidNameLinter' => FeatureFileWithInvalidNameLinter.new,
44
- 'FeatureFileWithMismatchedNameLinter' => FeatureFileWithMismatchedNameLinter.new,
45
- 'FeatureWithTooManyDifferentTagsLinter' => FeatureWithTooManyDifferentTagsLinter.new,
46
- 'FeatureWithoutDescriptionLinter' => FeatureWithoutDescriptionLinter.new,
47
- 'FeatureWithoutNameLinter' => FeatureWithoutNameLinter.new,
48
- 'FeatureWithoutScenariosLinter' => FeatureWithoutScenariosLinter.new,
49
- 'OutlineWithSingleExampleRowLinter' => OutlineWithSingleExampleRowLinter.new,
50
- 'SingleTestBackgroundLinter' => SingleTestBackgroundLinter.new,
51
- 'StepWithEndPeriodLinter' => StepWithEndPeriodLinter.new,
52
- 'StepWithTooManyCharactersLinter' => StepWithTooManyCharactersLinter.new,
53
- 'TestShouldUseBackgroundLinter' => TestShouldUseBackgroundLinter.new,
54
- 'TestWithActionStepAsFinalStepLinter' => TestWithActionStepAsFinalStepLinter.new,
55
- 'TestWithBadNameLinter' => TestWithBadNameLinter.new,
56
- 'TestWithNoActionStepLinter' => TestWithNoActionStepLinter.new,
57
- 'TestWithNoNameLinter' => TestWithNoNameLinter.new,
58
- 'TestWithNoVerificationStepLinter' => TestWithNoVerificationStepLinter.new,
59
- 'TestWithSetupStepAfterActionStepLinter' => TestWithSetupStepAfterActionStepLinter.new,
60
- 'TestWithSetupStepAfterVerificationStepLinter' => TestWithSetupStepAfterVerificationStepLinter.new,
61
- 'TestWithSetupStepAsFinalStepLinter' => TestWithSetupStepAsFinalStepLinter.new,
62
- 'TestWithTooManyStepsLinter' => TestWithTooManyStepsLinter.new }
63
-
64
-
65
- # Configures linters based on the given options
66
- def self.load_configuration(config_file_path: nil, config: nil)
67
- # TODO: define what happens if both a configuration file and a configuration are provided. Merge them or have direct config take precedence? Both?
68
-
69
- unless config || config_file_path
70
- config_file_path = "#{Dir.pwd}/.cuke_linter"
71
- raise 'No configuration or configuration file given and no .cuke_linter file found' unless File.exist?(config_file_path)
72
- end
73
-
74
- config = config || YAML.load_file(config_file_path)
41
+ extend CukeLinter::Configuration
42
+ extend CukeLinter::LinterRegistration
75
43
 
76
- common_config = config['AllLinters'] || {}
77
- to_delete = []
44
+ class << self
78
45
 
79
- registered_linters.each_pair do |name, linter|
80
- linter_config = config[name] || {}
81
- final_config = common_config.merge(linter_config)
46
+ # Lints the given model trees and file paths using the given linting objects and formatting
47
+ # the results with the given formatters and their respective output locations
48
+ def lint(file_paths: [], model_trees: [], linters: registered_linters.values, formatters: [[CukeLinter::PrettyFormatter.new]]) # rubocop:disable Metrics/LineLength
49
+ # TODO: Test this?
50
+ # Because directive memoization is based on a model's `#object_id` and Ruby reuses object IDs over the
51
+ # life of a program as objects are garbage collected, it is not safe to remember the IDs forever. However,
52
+ # models shouldn't get GC'd in the middle of the linting process and so the start of the linting process is
53
+ # a good time to reset things
54
+ @directives_for_feature_file = {}
82
55
 
83
- disabled = (final_config.key?('Enabled') && !final_config['Enabled'])
56
+ model_trees = [CukeModeler::Directory.new(Dir.pwd)] if model_trees.empty? && file_paths.empty?
57
+ file_path_models = collect_file_path_models(file_paths)
58
+ model_sets = model_trees + file_path_models
84
59
 
85
- # Just save it for afterwards because modifying a collection while iterating through it is not a good idea
86
- to_delete << name if disabled
60
+ linting_data = lint_models(model_sets, linters)
61
+ format_data(formatters, linting_data)
87
62
 
88
- linter.configure(final_config) if linter.respond_to?(:configure)
63
+ linting_data
89
64
  end
90
65
 
91
- to_delete.each { |linter_name| unregister_linter(linter_name) }
92
- end
93
-
94
- # Returns the registered linters to their default state
95
- def self.reset_linters
96
- @registered_linters = nil
97
- end
98
-
99
- # Registers for linting use the given linter object, tracked by the given name
100
- def self.register_linter(linter:, name:)
101
- self.registered_linters[name] = linter
102
- end
103
-
104
- # Unregisters the linter object tracked by the given name so that it is not used for linting
105
- def self.unregister_linter(name)
106
- self.registered_linters.delete(name)
107
- end
108
66
 
109
- # Lists the names of the currently registered linting objects
110
- def self.registered_linters
111
- @registered_linters ||= Marshal.load(Marshal.dump(@original_linters))
112
- end
67
+ private
113
68
 
114
- # Unregisters all currently registered linting objects
115
- def self.clear_registered_linters
116
- self.registered_linters.clear
117
- end
118
69
 
119
- # Lints the given model trees and file paths using the given linting objects and formatting the results with the given formatters and their respective output locations
120
- def self.lint(file_paths: [], model_trees: [], linters: self.registered_linters.values, formatters: [[CukeLinter::PrettyFormatter.new]])
121
-
122
- # TODO: Test this?
123
- # Because directive memoization is based on a model's `#object_id` and Ruby reuses object IDs over the
124
- # life of a program as objects are garbage collected, it is not safe to remember the IDs forever. However,
125
- # models shouldn't get GC'd in the middle of the linting process and so the start of the linting process is
126
- # a good time to reset things
127
- @directives_for_feature_file = {}
128
-
129
- model_trees = [CukeModeler::Directory.new(Dir.pwd)] if model_trees.empty? && file_paths.empty?
130
- file_path_models = file_paths.collect do |file_path|
131
- # TODO: raise exception unless path exists
132
- case
133
- when File.directory?(file_path)
70
+ def collect_file_path_models(file_paths)
71
+ file_paths.collect do |file_path|
72
+ # TODO: raise exception unless path exists?
73
+ if File.directory?(file_path)
134
74
  CukeModeler::Directory.new(file_path)
135
- when File.file?(file_path) && File.extname(file_path) == '.feature'
75
+ elsif File.file?(file_path) && File.extname(file_path) == '.feature'
136
76
  CukeModeler::FeatureFile.new(file_path)
137
- else
138
- # Non-feature files are not modeled
139
- end
140
- end.compact # Compacting in order to get rid of any `nil` values left over from non-feature files
141
-
142
- linting_data = []
143
- model_sets = model_trees + file_path_models
144
-
145
- model_sets.each do |model_tree|
146
- model_tree.each_model do |model|
147
- applicable_linters = relevant_linters_for_model(linters, model)
148
- applicable_linters.each do |linter|
149
- # TODO: have linters lint only certain types of models
150
- # linting_data.concat(linter.lint(model)) if relevant_model?(linter, model)
151
-
152
- result = linter.lint(model)
153
-
154
- if result
155
- result[:linter] = linter.name
156
- linting_data << result
157
- end
158
77
  end
159
- end
78
+ end.compact # Compacting in order to get rid of any `nil` values left over from non-feature files
160
79
  end
161
80
 
162
- formatters.each do |formatter_output_pair|
163
- formatter = formatter_output_pair[0]
164
- location = formatter_output_pair[1]
165
-
166
- formatted_data = formatter.format(linting_data)
167
-
168
- if location
169
- File.write(location, formatted_data)
170
- else
171
- puts formatted_data
81
+ def lint_models(model_sets, linters)
82
+ [].tap do |linting_data|
83
+ model_sets.each do |model_tree|
84
+ model_tree.each_model do |model|
85
+ applicable_linters = relevant_linters_for_model(linters, model)
86
+ applicable_linters.each do |linter|
87
+ # TODO: have linters lint only certain types of models?
88
+ # linting_data.concat(linter.lint(model)) if relevant_model?(linter, model)
89
+
90
+ result = linter.lint(model)
91
+
92
+ if result
93
+ result[:linter] = linter.name
94
+ linting_data << result
95
+ end
96
+ end
97
+ end
98
+ end
172
99
  end
173
100
  end
174
101
 
175
- # TODO: keep this or always format data?
176
- linting_data
177
- end
102
+ def relevant_linters_for_model(base_linters, model)
103
+ feature_file_model = model.get_ancestor(:feature_file)
178
104
 
105
+ # Linter directives are not applicable for directory and feature file models. Every other
106
+ # model type should have a feature file ancestor from which to grab linter directive comments.
107
+ return base_linters if feature_file_model.nil?
179
108
 
180
- def self.relevant_linters_for_model(base_linters, model)
181
- feature_file_model = model.get_ancestor(:feature_file)
109
+ linter_modifications_for_model = {}
182
110
 
183
- # Linter directives are not applicable for directory and feature file models. Every other model type should have a feature file ancestor from which to grab linter directive comments.
184
- return base_linters if feature_file_model.nil?
111
+ linter_directives_for_feature_file(feature_file_model).each do |directive|
112
+ # Assuming that the directives are in the same order that they appear in the file
113
+ break if directive[:source_line] > model.source_line
185
114
 
186
- linter_modifications_for_model = {}
115
+ linter_modifications_for_model[directive[:linter_class]] = directive[:enabled_status]
116
+ end
187
117
 
188
- linter_directives_for_feature_file(feature_file_model).each do |directive|
189
- # Assuming that the directives are in the same order that they appear in the file
190
- break if directive[:source_line] > model.source_line
118
+ disabled_linter_classes = linter_modifications_for_model.reject { |_name, status| status }.keys
119
+ enabled_linter_classes = linter_modifications_for_model.select { |_name, status| status }.keys
191
120
 
192
- linter_modifications_for_model[directive[:linter_class]] = directive[:enabled_status]
121
+ determine_final_linters(base_linters, disabled_linter_classes, enabled_linter_classes)
193
122
  end
194
123
 
195
- disabled_linter_classes = linter_modifications_for_model.reject { |_name, status| status }.keys
196
- enabled_linter_classes = linter_modifications_for_model.select { |_name, status| status }.keys
124
+ def determine_final_linters(base_linters, disabled_linter_classes, enabled_linter_classes)
125
+ final_linters = base_linters.reject { |linter| disabled_linter_classes.include?(linter.class) }
197
126
 
198
- final_linters = base_linters.reject { |linter| disabled_linter_classes.include?(linter.class) }
199
- enabled_linter_classes.each do |clazz|
200
- final_linters << dynamic_linters[clazz] unless final_linters.map(&:class).include?(clazz)
201
- end
202
-
203
- final_linters
204
- end
127
+ enabled_linter_classes.each do |clazz|
128
+ final_linters << dynamic_linters[clazz] unless final_linters.map(&:class).include?(clazz)
129
+ end
205
130
 
206
- private_class_method(:relevant_linters_for_model)
131
+ final_linters
132
+ end
207
133
 
134
+ def linter_directives_for_feature_file(feature_file_model)
135
+ # IMPORTANT ASSUMPTION: Models never change during the life of a linting, so data only has to be gathered once
136
+ existing_directives = @directives_for_feature_file[feature_file_model.object_id]
208
137
 
209
- def self.linter_directives_for_feature_file(feature_file_model)
210
- # IMPORTANT ASSUMPTION: Models never change during the life of a linting, so data only has to be gathered once
211
- return @directives_for_feature_file[feature_file_model.object_id] if @directives_for_feature_file[feature_file_model.object_id]
138
+ return existing_directives if existing_directives
212
139
 
140
+ directives = gather_directives_in_feature(feature_file_model)
213
141
 
214
- @directives_for_feature_file[feature_file_model.object_id] = []
142
+ # Make sure that the directives are in the same order as they appear in the source file
143
+ directives = directives.sort_by { |a| a[:source_line] }
215
144
 
216
- feature_file_model.comments.each do |comment|
217
- pieces = comment.text.match(/#\s*cuke_linter:(disable|enable)\s+(.*)/)
218
- next unless pieces # Skipping non-directive file comments
145
+ @directives_for_feature_file[feature_file_model.object_id] = directives
146
+ end
219
147
 
220
- linter_classes = pieces[2].gsub(',', ' ').split(' ')
221
- linter_classes.each do |clazz|
222
- @directives_for_feature_file[feature_file_model.object_id] << { linter_class: Kernel.const_get(clazz),
223
- enabled_status: pieces[1] != 'disable',
224
- source_line: comment.source_line }
148
+ def gather_directives_in_feature(feature_file_model)
149
+ [].tap do |directives|
150
+ feature_file_model.comments.each do |comment|
151
+ pieces = comment.text.match(/#\s*cuke_linter:(disable|enable)\s+(.*)/)
152
+ next unless pieces # Skipping non-directive file comments
153
+
154
+ linter_classes = pieces[2].tr(',', ' ').split(' ')
155
+ linter_classes.each do |clazz|
156
+ directives << { linter_class: Kernel.const_get(clazz),
157
+ enabled_status: pieces[1] != 'disable',
158
+ source_line: comment.source_line }
159
+ end
160
+ end
225
161
  end
226
162
  end
227
163
 
228
- # Make sure that the directives are in the same order as they appear in the source file
229
- @directives_for_feature_file[feature_file_model.object_id] = @directives_for_feature_file[feature_file_model.object_id].sort { |a, b| a[:source_line] <=> b[:source_line] }
164
+ def dynamic_linters
165
+ # No need to keep making new ones over and over...
166
+ @dynamic_linters ||= Hash.new { |hash, key| hash[key] = key.new }
167
+ # return @dynamic_linters if @dynamic_linters
168
+ #
169
+ # @dynamic_linters = {}
170
+ end
230
171
 
172
+ def format_data(formatters, linting_data)
173
+ formatters.each do |formatter_output_pair|
174
+ formatter = formatter_output_pair[0]
175
+ location = formatter_output_pair[1]
231
176
 
232
- @directives_for_feature_file[feature_file_model.object_id]
233
- end
177
+ formatted_data = formatter.format(linting_data)
234
178
 
235
- private_class_method(:linter_directives_for_feature_file)
179
+ if location
180
+ File.write(location, formatted_data)
181
+ else
182
+ puts formatted_data
183
+ end
184
+ end
185
+ end
236
186
 
237
- def self.dynamic_linters
238
- # No need to keep making new ones over and over...
239
- @dynamic_linters ||= Hash.new { |hash, key| hash[key] = key.new }
240
- # return @dynamic_linters if @dynamic_linters
187
+ # Not linting unused code
188
+ # rubocop:disable Metrics/LineLength
189
+ # def self.relevant_model?(linter, model)
190
+ # model_classes = linter.class.target_model_types.map { |type| CukeModeler.const_get(type.to_s.capitalize.chop) }
191
+ # model_classes.any? { |clazz| model.is_a?(clazz) }
192
+ # end
241
193
  #
242
- # @dynamic_linters = {}
243
- end
244
-
245
- private_class_method(:dynamic_linters)
246
-
247
-
248
- # # def self.relevant_model?(linter, model)
249
- # # model_classes = linter.class.target_model_types.map { |type| CukeModeler.const_get(type.to_s.capitalize.chop) }
250
- # # model_classes.any? { |clazz| model.is_a?(clazz) }
251
- # # end
252
- # #
253
- # # private_class_method(:relevant_model?)
194
+ # private_class_method(:relevant_model?)
195
+ # rubocop:enable Metrics/LineLength
254
196
 
197
+ end
255
198
  end