cuke_linter 0.13.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -1
  3. data/LICENSE.txt +1 -1
  4. data/README.md +12 -9
  5. data/cuke_linter.gemspec +41 -23
  6. data/exe/cuke_linter +4 -2
  7. data/lib/cuke_linter.rb +119 -180
  8. data/lib/cuke_linter/configuration.rb +45 -0
  9. data/lib/cuke_linter/default_linters.rb +32 -0
  10. data/lib/cuke_linter/formatters/pretty_formatter.rb +63 -35
  11. data/lib/cuke_linter/gherkin.rb +10 -0
  12. data/lib/cuke_linter/linter_registration.rb +32 -0
  13. data/lib/cuke_linter/linters/background_does_more_than_setup_linter.rb +1 -1
  14. data/lib/cuke_linter/linters/element_with_common_tags_linter.rb +23 -14
  15. data/lib/cuke_linter/linters/element_with_duplicate_tags_linter.rb +18 -13
  16. data/lib/cuke_linter/linters/element_with_too_many_tags_linter.rb +17 -11
  17. data/lib/cuke_linter/linters/feature_with_too_many_different_tags_linter.rb +1 -3
  18. data/lib/cuke_linter/linters/feature_without_description_linter.rb +1 -1
  19. data/lib/cuke_linter/linters/linter.rb +17 -13
  20. data/lib/cuke_linter/linters/step_with_too_many_characters_linter.rb +2 -2
  21. data/lib/cuke_linter/linters/test_should_use_background_linter.rb +23 -15
  22. data/lib/cuke_linter/linters/test_with_bad_name_linter.rb +4 -4
  23. data/lib/cuke_linter/linters/test_with_setup_step_as_final_step_linter.rb +1 -1
  24. data/lib/cuke_linter/version.rb +1 -1
  25. data/testing/cucumber/features/command_line.feature +5 -2
  26. data/testing/cucumber/features/configuration/configuring_linters.feature +5 -2
  27. data/testing/cucumber/features/configuration/locally_scoping_linters.feature +7 -2
  28. data/testing/cucumber/features/configuration/using_configurations.feature +2 -1
  29. data/testing/cucumber/features/custom_linters.feature +3 -1
  30. metadata +113 -108
  31. data/.gitignore +0 -19
  32. data/.simplecov +0 -8
  33. data/.travis.yml +0 -33
  34. data/CONTRIBUTING.md +0 -26
  35. data/Gemfile +0 -6
  36. data/Rakefile +0 -63
  37. data/appveyor.yml +0 -43
  38. data/bin/console +0 -14
  39. data/bin/setup +0 -8
  40. data/environments/common_env.rb +0 -12
  41. data/environments/cucumber_env.rb +0 -22
  42. data/environments/rspec_env.rb +0 -50
  43. data/testing/cucumber/step_definitions/action_steps.rb +0 -84
  44. data/testing/cucumber/step_definitions/setup_steps.rb +0 -258
  45. data/testing/cucumber/step_definitions/verification_steps.rb +0 -94
  46. data/testing/file_helper.rb +0 -41
  47. data/testing/formatter_factory.rb +0 -15
  48. data/testing/gemfiles/cuke_modeler1.gemfile +0 -8
  49. data/testing/gemfiles/cuke_modeler2.gemfile +0 -8
  50. data/testing/linter_factory.rb +0 -60
  51. data/testing/model_factory.rb +0 -109
  52. data/testing/rspec/spec/integration/cli_integration_spec.rb +0 -556
  53. data/testing/rspec/spec/integration/configuration_spec.rb +0 -811
  54. data/testing/rspec/spec/integration/cuke_linter_integration_spec.rb +0 -243
  55. data/testing/rspec/spec/integration/formatters/formatter_integration_specs.rb +0 -5
  56. data/testing/rspec/spec/integration/formatters/pretty_formatter_integration_spec.rb +0 -8
  57. data/testing/rspec/spec/integration/linters/background_does_more_than_setup_linter_integration_spec.rb +0 -8
  58. data/testing/rspec/spec/integration/linters/element_with_common_tags_linter_integration_spec.rb +0 -8
  59. data/testing/rspec/spec/integration/linters/element_with_duplicate_tags_linter_integration_spec.rb +0 -8
  60. data/testing/rspec/spec/integration/linters/element_with_too_many_tags_linter_integration_spec.rb +0 -8
  61. data/testing/rspec/spec/integration/linters/example_without_name_linter_integration_spec.rb +0 -8
  62. data/testing/rspec/spec/integration/linters/feature_file_with_invalid_name_integration_spec.rb +0 -8
  63. data/testing/rspec/spec/integration/linters/feature_file_with_mismatched_name_integration_spec.rb +0 -8
  64. data/testing/rspec/spec/integration/linters/feature_with_too_many_different_tags_linter_integration_spec.rb +0 -8
  65. data/testing/rspec/spec/integration/linters/feature_without_description_linter_integration_spec.rb +0 -8
  66. data/testing/rspec/spec/integration/linters/feature_without_name_linter_integration_spec.rb +0 -8
  67. data/testing/rspec/spec/integration/linters/feature_without_scenarios_linter_integration_spec.rb +0 -8
  68. data/testing/rspec/spec/integration/linters/linter_integration_spec.rb +0 -8
  69. data/testing/rspec/spec/integration/linters/linter_integration_specs.rb +0 -7
  70. data/testing/rspec/spec/integration/linters/outline_with_single_example_row_linter_integration_spec.rb +0 -8
  71. data/testing/rspec/spec/integration/linters/single_test_background_linter_integration_spec.rb +0 -8
  72. data/testing/rspec/spec/integration/linters/step_with_end_period_linter_integration_spec.rb +0 -8
  73. data/testing/rspec/spec/integration/linters/step_with_too_many_characters_linter_integration_spec.rb +0 -8
  74. data/testing/rspec/spec/integration/linters/test_should_use_background_linter_integration_spec.rb +0 -8
  75. data/testing/rspec/spec/integration/linters/test_with_action_step_as_final_step_linter_integration_spec.rb +0 -8
  76. data/testing/rspec/spec/integration/linters/test_with_bad_name_integration_spec.rb +0 -8
  77. data/testing/rspec/spec/integration/linters/test_with_no_action_step_integration_spec.rb +0 -8
  78. data/testing/rspec/spec/integration/linters/test_with_no_name_integration_spec.rb +0 -8
  79. data/testing/rspec/spec/integration/linters/test_with_no_verification_step_integration_spec.rb +0 -8
  80. data/testing/rspec/spec/integration/linters/test_with_setup_step_after_action_step_linter_integration_spec.rb +0 -8
  81. data/testing/rspec/spec/integration/linters/test_with_setup_step_after_verification_step_linter_integration_spec.rb +0 -8
  82. data/testing/rspec/spec/integration/linters/test_with_setup_step_as_final_step_linter_integration_spec.rb +0 -8
  83. data/testing/rspec/spec/integration/linters/test_with_too_many_steps_linter_integration_spec.rb +0 -8
  84. data/testing/rspec/spec/unit/cuke_linter_unit_spec.rb +0 -114
  85. data/testing/rspec/spec/unit/formatters/formatter_unit_specs.rb +0 -11
  86. data/testing/rspec/spec/unit/formatters/pretty_formatter_unit_spec.rb +0 -115
  87. data/testing/rspec/spec/unit/linters/background_does_more_than_setup_linter_unit_spec.rb +0 -186
  88. data/testing/rspec/spec/unit/linters/configurable_linter_unit_specs.rb +0 -11
  89. data/testing/rspec/spec/unit/linters/element_with_common_tags_linter_unit_spec.rb +0 -248
  90. data/testing/rspec/spec/unit/linters/element_with_duplicate_tags_linter_unit_spec.rb +0 -203
  91. data/testing/rspec/spec/unit/linters/element_with_too_many_tags_linter_unit_spec.rb +0 -296
  92. data/testing/rspec/spec/unit/linters/example_without_name_linter_unit_spec.rb +0 -81
  93. data/testing/rspec/spec/unit/linters/feature_file_with_invalid_name_linter_unit_spec.rb +0 -106
  94. data/testing/rspec/spec/unit/linters/feature_file_with_mismatched_name_linter_unit_spec.rb +0 -124
  95. data/testing/rspec/spec/unit/linters/feature_with_too_many_different_tags_linter_unit_spec.rb +0 -293
  96. data/testing/rspec/spec/unit/linters/feature_without_description_linter_unit_spec.rb +0 -80
  97. data/testing/rspec/spec/unit/linters/feature_without_name_linter_unit_spec.rb +0 -84
  98. data/testing/rspec/spec/unit/linters/feature_without_scenarios_linter_unit_spec.rb +0 -102
  99. data/testing/rspec/spec/unit/linters/linter_unit_spec.rb +0 -197
  100. data/testing/rspec/spec/unit/linters/linter_unit_specs.rb +0 -57
  101. data/testing/rspec/spec/unit/linters/outline_with_single_example_row_linter_unit_spec.rb +0 -184
  102. data/testing/rspec/spec/unit/linters/single_test_background_linter_unit_spec.rb +0 -89
  103. data/testing/rspec/spec/unit/linters/step_with_end_period_linter_unit_spec.rb +0 -54
  104. data/testing/rspec/spec/unit/linters/step_with_too_many_characters_linter_unit_spec.rb +0 -155
  105. data/testing/rspec/spec/unit/linters/test_should_use_background_linter_unit_spec.rb +0 -464
  106. data/testing/rspec/spec/unit/linters/test_with_action_step_as_final_step_linter_unit_spec.rb +0 -165
  107. data/testing/rspec/spec/unit/linters/test_with_bad_name_linter_unit_spec.rb +0 -81
  108. data/testing/rspec/spec/unit/linters/test_with_no_action_step_linter_unit_spec.rb +0 -244
  109. data/testing/rspec/spec/unit/linters/test_with_no_name_linter_unit_spec.rb +0 -88
  110. data/testing/rspec/spec/unit/linters/test_with_no_verification_step_linter_unit_spec.rb +0 -246
  111. data/testing/rspec/spec/unit/linters/test_with_setup_step_after_action_step_linter_unit_spec.rb +0 -233
  112. data/testing/rspec/spec/unit/linters/test_with_setup_step_after_verification_step_linter_unit_spec.rb +0 -233
  113. data/testing/rspec/spec/unit/linters/test_with_setup_step_as_final_step_linter_unit_spec.rb +0 -164
  114. 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: 7669fb1b66c4e9c103aedb209961dbeca8b308f74bcc1159401fa21e1ffd5b05
4
- data.tar.gz: 36ef3927bc68e7d52bde3c91f26ab0428d07b81c7163c0c31aaabf2847fa78db
3
+ metadata.gz: a58ecea412dacacc176aff36844a22a2be89b6e8fb530140c4a4c9af6f68b33d
4
+ data.tar.gz: 6c59e3ef19cecf4f8348ffbb4b6e5f64b230bd38c569c776dda1afe0b214c856
5
5
  SHA512:
6
- metadata.gz: 449a18606c23f574ffba7b5a2d80f9cc7170839daf9187e52404d31ca93e3fb20396860476d827125b6a85968d98985ba83e2ad92d127066a69322edee40459b
7
- data.tar.gz: 407d387c3ce3c6eb3648179837757caa688b5b6bdab42b7cc684d3480054fadc0d7aa4c4ef555d241c77919ecb6e8ae7438736ba6b449e56c6ee76cfbdac07e4
6
+ metadata.gz: 163ed0e7cc11da407ce4af1a275f2cd51d0513caacdd007395af27782a111af83baa72928a85c7ba13b6a47d332564a71da906d068293f0d5cb4a5074c27d563
7
+ data.tar.gz: 0d8098a3e266207030fa8bfcd573344aacf849eda5f33a308a3ecea3f4931ff6fb3f8dd86b62afcf76116ba30e5b2fad468d8ad287d8cb45aa1a3f6819c41c62
data/CHANGELOG.md CHANGED
@@ -8,6 +8,35 @@ 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.1] - 2021-06-13
12
+
13
+ ### Fixed
14
+ - Updated the minimum required Ruby version to `2.1`. The gem has never had `2.0` compatible code (due to using
15
+ named parameters without default values), so it's easier to just officially drop `2.0` 'support' than to change
16
+ the API to enable a use case that was never used anyway.
17
+
18
+ ## [1.2.0] - 2021-01-08
19
+
20
+ ### Added
21
+ - Ruby 3.x is now supported
22
+
23
+ ## [1.1.0] - 2020-06-14
24
+
25
+ ### Added
26
+ - Now compatible with CukeModeler 3.x
27
+
28
+ ## [1.0.1] - 2020-05-06
29
+
30
+ ### Fixed
31
+ - Replaced some code that was not valid with earlier versions of Ruby 2.x, with which this gem is specified as being compatible.
32
+
33
+
34
+ ## [1.0.0] - 2020-03-09
35
+
36
+ ### Changed
37
+ - 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.
38
+
39
+
11
40
  ## [0.13.0] - 2020-03-01
12
41
 
13
42
  ### Added
@@ -147,7 +176,12 @@ Nothing yet...
147
176
  - Custom linters, formatters, and command line usability
148
177
 
149
178
 
150
- [Unreleased]: https://github.com/enkessler/cuke_linter/compare/v0.13.0...HEAD
179
+ [Unreleased]: https://github.com/enkessler/cuke_linter/compare/v1.2.1...HEAD
180
+ [1.2.1]: https://github.com/enkessler/cuke_linter/compare/v1.2.0...v1.2.1
181
+ [1.2.0]: https://github.com/enkessler/cuke_linter/compare/v1.1.0...v1.2.0
182
+ [1.1.0]: https://github.com/enkessler/cuke_linter/compare/v1.0.1...v1.1.0
183
+ [1.0.1]: https://github.com/enkessler/cuke_linter/compare/v1.0.0...v1.0.1
184
+ [1.0.0]: https://github.com/enkessler/cuke_linter/compare/v0.13.0...v1.0.0
151
185
  [0.13.0]: https://github.com/enkessler/cuke_linter/compare/v0.12.1...v0.13.0
152
186
  [0.12.1]: https://github.com/enkessler/cuke_linter/compare/v0.12.0...v0.12.1
153
187
  [0.12.0]: https://github.com/enkessler/cuke_linter/compare/v0.11.1...v0.12.0
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018-2020 Eric Kessler
3
+ Copyright (c) 2018-2021 Eric Kessler
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -4,15 +4,14 @@ Basic stuff:
4
4
  [![Downloads](https://img.shields.io/gem/dt/cuke_linter.svg)](https://rubygems.org/gems/cuke_linter)
5
5
 
6
6
  User stuff:
7
- [![Cucumber Docs](http://img.shields.io/badge/Documentation-Features-green.svg)](https://app.cucumber.pro/projects/cuke_linter)
7
+ [![Cucumber Docs](http://img.shields.io/badge/Documentation-Features-green.svg)](https://github.com/enkessler/cuke_linter/tree/master/testing/cucumber/features)
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://github.com/enkessler/cuke_linter/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/enkessler/cuke_linter/actions/workflows/ci.yml)
12
+ [![Coverage Status](https://coveralls.io/repos/github/enkessler/cuke_linter/badge.svg?branch=master)](https://coveralls.io/github/enkessler/cuke_linter?branch=master)
14
13
  [![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)
14
+ [![Inline docs](http://inch-ci.org/github/enkessler/cuke_linter.svg?branch=master)](https://inch-ci.org/github/enkessler/cuke_linter?branch=master)
16
15
 
17
16
  ---
18
17
 
@@ -104,7 +103,8 @@ class MyCustomLinter
104
103
  return nil unless model.is_a?(CukeModeler::Scenario)
105
104
 
106
105
  if model.name.empty?
107
- { problem: 'Scenario has no name', location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }
106
+ { problem: 'Scenario has no name',
107
+ location: "#{model.get_ancestor(:feature_file).path}:#{model.source_line}" }
108
108
  else
109
109
  nil
110
110
  end
@@ -134,13 +134,16 @@ output_path = "#{__dir__}/my_report.txt"
134
134
  model_tree_root = CukeModeler::Directory.new(Dir.pwd)
135
135
  additional_file_path = 'path/to/some.feature'
136
136
 
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])
137
+ # Providing the formatter twice so that output also is printed to the console
138
+ CukeLinter.lint(linters: [linter],
139
+ formatters: [[formatter], [formatter, output_path]],
140
+ model_trees: [model_tree_root],
141
+ file_paths: [additional_file_path])
139
142
  ```
140
143
 
141
144
  ### Configuration
142
145
 
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.
146
+ 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
147
 
145
148
 
146
149
  ### <a id="documentation"></a>Everything Else
data/cuke_linter.gemspec CHANGED
@@ -1,40 +1,58 @@
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.'
12
+ spec.description = ["This gem provides linters for detecting common 'smells' in `.feature` files. ",
13
+ 'In addition to the provided linters, custom linters can be made in order to ',
14
+ 'create custom linting rules.'].join
13
15
  spec.homepage = 'https://github.com/enkessler/cuke_linter'
14
- spec.license = "MIT"
16
+ spec.license = 'MIT'
17
+
18
+ spec.metadata = {
19
+ 'bug_tracker_uri' => 'https://github.com/enkessler/cuke_linter/issues',
20
+ 'changelog_uri' => 'https://github.com/enkessler/cuke_linter/blob/master/CHANGELOG.md',
21
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/cuke_linter',
22
+ 'homepage_uri' => 'https://github.com/enkessler/cuke_linter',
23
+ 'source_code_uri' => 'https://github.com/enkessler/cuke_linter'
24
+ }
15
25
 
16
26
 
17
- # TODO: don't just include everything in the gem
18
27
  # Specify which files should be added to the gem when it is released.
19
28
  # 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)/}) }
29
+ spec.files = Dir.chdir(File.expand_path('', __dir__)) do
30
+ source_controlled_files = `git ls-files -z`.split("\x0")
31
+ source_controlled_files.keep_if { |file| file =~ %r{^(lib|exe|testing/cucumber/features)} }
32
+ source_controlled_files + ['README.md', 'LICENSE.txt', 'CHANGELOG.md', 'cuke_linter.gemspec']
22
33
  end
23
- spec.bindir = "exe"
34
+ spec.bindir = 'exe'
24
35
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
- spec.require_paths = ["lib"]
36
+ spec.require_paths = ['lib']
26
37
 
27
- spec.required_ruby_version = '~> 2.0'
38
+ spec.required_ruby_version = '>= 2.1', '< 4.0'
28
39
 
29
- spec.add_runtime_dependency 'cuke_modeler', '>= 1.5', '< 3.0'
40
+ spec.add_runtime_dependency 'cuke_modeler', '>= 1.5', '< 4.0'
30
41
 
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'
38
- spec.add_development_dependency 'coveralls', '< 1.0.0'
42
+ spec.add_development_dependency 'bundler', '< 3.0'
43
+ spec.add_development_dependency 'childprocess', '< 4.0'
44
+ spec.add_development_dependency 'cucumber', '< 5.0'
45
+ spec.add_development_dependency 'cuke_slicer', '>= 2.0.2', '< 3.0'
46
+ spec.add_development_dependency 'ffi', '~> 1.0'
47
+ spec.add_development_dependency 'parallel', '~> 1.0'
39
48
  spec.add_development_dependency 'rainbow', '< 4.0.0'
49
+ spec.add_development_dependency 'rake', '~> 12.0'
50
+ spec.add_development_dependency 'require_all', '~> 2.0'
51
+ spec.add_development_dependency 'rspec', '~> 3.0'
52
+ # RuboCop drops Ruby 2.1 support after this version and we need
53
+ # to maintain Ruby 2.1 compatibility when writing code for this gem
54
+ spec.add_development_dependency 'rubocop', '<= 0.57.2'
55
+ spec.add_development_dependency 'simplecov', '< 1.0'
56
+ spec.add_development_dependency 'simplecov-lcov', '< 1.0'
57
+ spec.add_development_dependency 'yard', '< 1.0'
40
58
  end
data/exe/cuke_linter CHANGED
@@ -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?
data/lib/cuke_linter.rb CHANGED
@@ -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,231 +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
- DEFAULT_GIVEN_KEYWORD = 'Given'.freeze
39
- DEFAULT_WHEN_KEYWORD = 'When'.freeze
40
- DEFAULT_THEN_KEYWORD = 'Then'.freeze
41
-
42
- @original_linters = { 'BackgroundDoesMoreThanSetupLinter' => BackgroundDoesMoreThanSetupLinter.new,
43
- 'ElementWithCommonTagsLinter' => ElementWithCommonTagsLinter.new,
44
- 'ElementWithDuplicateTagsLinter' => ElementWithDuplicateTagsLinter.new,
45
- 'ElementWithTooManyTagsLinter' => ElementWithTooManyTagsLinter.new,
46
- 'ExampleWithoutNameLinter' => ExampleWithoutNameLinter.new,
47
- 'FeatureFileWithInvalidNameLinter' => FeatureFileWithInvalidNameLinter.new,
48
- 'FeatureFileWithMismatchedNameLinter' => FeatureFileWithMismatchedNameLinter.new,
49
- 'FeatureWithTooManyDifferentTagsLinter' => FeatureWithTooManyDifferentTagsLinter.new,
50
- 'FeatureWithoutDescriptionLinter' => FeatureWithoutDescriptionLinter.new,
51
- 'FeatureWithoutNameLinter' => FeatureWithoutNameLinter.new,
52
- 'FeatureWithoutScenariosLinter' => FeatureWithoutScenariosLinter.new,
53
- 'OutlineWithSingleExampleRowLinter' => OutlineWithSingleExampleRowLinter.new,
54
- 'SingleTestBackgroundLinter' => SingleTestBackgroundLinter.new,
55
- 'StepWithEndPeriodLinter' => StepWithEndPeriodLinter.new,
56
- 'StepWithTooManyCharactersLinter' => StepWithTooManyCharactersLinter.new,
57
- 'TestShouldUseBackgroundLinter' => TestShouldUseBackgroundLinter.new,
58
- 'TestWithActionStepAsFinalStepLinter' => TestWithActionStepAsFinalStepLinter.new,
59
- 'TestWithBadNameLinter' => TestWithBadNameLinter.new,
60
- 'TestWithNoActionStepLinter' => TestWithNoActionStepLinter.new,
61
- 'TestWithNoNameLinter' => TestWithNoNameLinter.new,
62
- 'TestWithNoVerificationStepLinter' => TestWithNoVerificationStepLinter.new,
63
- 'TestWithSetupStepAfterActionStepLinter' => TestWithSetupStepAfterActionStepLinter.new,
64
- 'TestWithSetupStepAfterVerificationStepLinter' => TestWithSetupStepAfterVerificationStepLinter.new,
65
- 'TestWithSetupStepAsFinalStepLinter' => TestWithSetupStepAsFinalStepLinter.new,
66
- 'TestWithTooManyStepsLinter' => TestWithTooManyStepsLinter.new }
67
-
68
-
69
- # Configures linters based on the given options
70
- def self.load_configuration(config_file_path: nil, config: nil)
71
- # TODO: define what happens if both a configuration file and a configuration are provided. Merge them or have direct config take precedence? Both?
72
-
73
- unless config || config_file_path
74
- config_file_path = "#{Dir.pwd}/.cuke_linter"
75
- raise 'No configuration or configuration file given and no .cuke_linter file found' unless File.exist?(config_file_path)
76
- end
77
-
78
- config = config || YAML.load_file(config_file_path)
41
+ extend CukeLinter::Configuration
42
+ extend CukeLinter::LinterRegistration
79
43
 
80
- common_config = config['AllLinters'] || {}
81
- to_delete = []
44
+ class << self
82
45
 
83
- registered_linters.each_pair do |name, linter|
84
- linter_config = config[name] || {}
85
- 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 = {}
86
55
 
87
- 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
88
59
 
89
- # Just save it for afterwards because modifying a collection while iterating through it is not a good idea
90
- to_delete << name if disabled
60
+ linting_data = lint_models(model_sets, linters)
61
+ format_data(formatters, linting_data)
91
62
 
92
- linter.configure(final_config) if linter.respond_to?(:configure)
63
+ linting_data
93
64
  end
94
65
 
95
- to_delete.each { |linter_name| unregister_linter(linter_name) }
96
- end
97
-
98
- # Returns the registered linters to their default state
99
- def self.reset_linters
100
- @registered_linters = nil
101
- end
102
-
103
- # Registers for linting use the given linter object, tracked by the given name
104
- def self.register_linter(linter:, name:)
105
- self.registered_linters[name] = linter
106
- end
107
-
108
- # Unregisters the linter object tracked by the given name so that it is not used for linting
109
- def self.unregister_linter(name)
110
- self.registered_linters.delete(name)
111
- end
112
66
 
113
- # Lists the names of the currently registered linting objects
114
- def self.registered_linters
115
- @registered_linters ||= Marshal.load(Marshal.dump(@original_linters))
116
- end
67
+ private
117
68
 
118
- # Unregisters all currently registered linting objects
119
- def self.clear_registered_linters
120
- self.registered_linters.clear
121
- end
122
69
 
123
- # 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
124
- def self.lint(file_paths: [], model_trees: [], linters: self.registered_linters.values, formatters: [[CukeLinter::PrettyFormatter.new]])
125
-
126
- # TODO: Test this?
127
- # Because directive memoization is based on a model's `#object_id` and Ruby reuses object IDs over the
128
- # life of a program as objects are garbage collected, it is not safe to remember the IDs forever. However,
129
- # models shouldn't get GC'd in the middle of the linting process and so the start of the linting process is
130
- # a good time to reset things
131
- @directives_for_feature_file = {}
132
-
133
- model_trees = [CukeModeler::Directory.new(Dir.pwd)] if model_trees.empty? && file_paths.empty?
134
- file_path_models = file_paths.collect do |file_path|
135
- # TODO: raise exception unless path exists
136
- case
137
- 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)
138
74
  CukeModeler::Directory.new(file_path)
139
- when File.file?(file_path) && File.extname(file_path) == '.feature'
75
+ elsif File.file?(file_path) && File.extname(file_path) == '.feature'
140
76
  CukeModeler::FeatureFile.new(file_path)
141
- else
142
- # Non-feature files are not modeled
143
- end
144
- end.compact # Compacting in order to get rid of any `nil` values left over from non-feature files
145
-
146
- linting_data = []
147
- model_sets = model_trees + file_path_models
148
-
149
- model_sets.each do |model_tree|
150
- model_tree.each_model do |model|
151
- applicable_linters = relevant_linters_for_model(linters, model)
152
- applicable_linters.each do |linter|
153
- # TODO: have linters lint only certain types of models
154
- # linting_data.concat(linter.lint(model)) if relevant_model?(linter, model)
155
-
156
- result = linter.lint(model)
157
-
158
- if result
159
- result[:linter] = linter.name
160
- linting_data << result
161
- end
162
77
  end
163
- end
78
+ end.compact # Compacting in order to get rid of any `nil` values left over from non-feature files
164
79
  end
165
80
 
166
- formatters.each do |formatter_output_pair|
167
- formatter = formatter_output_pair[0]
168
- location = formatter_output_pair[1]
169
-
170
- formatted_data = formatter.format(linting_data)
171
-
172
- if location
173
- File.write(location, formatted_data)
174
- else
175
- 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
176
99
  end
177
100
  end
178
101
 
179
- # TODO: keep this or always format data?
180
- linting_data
181
- end
102
+ def relevant_linters_for_model(base_linters, model)
103
+ feature_file_model = model.get_ancestor(:feature_file)
182
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?
183
108
 
184
- def self.relevant_linters_for_model(base_linters, model)
185
- feature_file_model = model.get_ancestor(:feature_file)
109
+ linter_modifications_for_model = {}
186
110
 
187
- # 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.
188
- 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
189
114
 
190
- linter_modifications_for_model = {}
115
+ linter_modifications_for_model[directive[:linter_class]] = directive[:enabled_status]
116
+ end
191
117
 
192
- linter_directives_for_feature_file(feature_file_model).each do |directive|
193
- # Assuming that the directives are in the same order that they appear in the file
194
- 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
195
120
 
196
- linter_modifications_for_model[directive[:linter_class]] = directive[:enabled_status]
121
+ determine_final_linters(base_linters, disabled_linter_classes, enabled_linter_classes)
197
122
  end
198
123
 
199
- disabled_linter_classes = linter_modifications_for_model.reject { |_name, status| status }.keys
200
- 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) }
201
126
 
202
- final_linters = base_linters.reject { |linter| disabled_linter_classes.include?(linter.class) }
203
- enabled_linter_classes.each do |clazz|
204
- final_linters << dynamic_linters[clazz] unless final_linters.map(&:class).include?(clazz)
205
- end
206
-
207
- final_linters
208
- end
127
+ enabled_linter_classes.each do |clazz|
128
+ final_linters << dynamic_linters[clazz] unless final_linters.map(&:class).include?(clazz)
129
+ end
209
130
 
210
- private_class_method(:relevant_linters_for_model)
131
+ final_linters
132
+ end
211
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]
212
137
 
213
- def self.linter_directives_for_feature_file(feature_file_model)
214
- # IMPORTANT ASSUMPTION: Models never change during the life of a linting, so data only has to be gathered once
215
- 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
216
139
 
140
+ directives = gather_directives_in_feature(feature_file_model)
217
141
 
218
- @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] }
219
144
 
220
- feature_file_model.comments.each do |comment|
221
- pieces = comment.text.match(/#\s*cuke_linter:(disable|enable)\s+(.*)/)
222
- next unless pieces # Skipping non-directive file comments
145
+ @directives_for_feature_file[feature_file_model.object_id] = directives
146
+ end
223
147
 
224
- linter_classes = pieces[2].gsub(',', ' ').split(' ')
225
- linter_classes.each do |clazz|
226
- @directives_for_feature_file[feature_file_model.object_id] << { linter_class: Kernel.const_get(clazz),
227
- enabled_status: pieces[1] != 'disable',
228
- 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
229
161
  end
230
162
  end
231
163
 
232
- # Make sure that the directives are in the same order as they appear in the source file
233
- @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
234
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]
235
176
 
236
- @directives_for_feature_file[feature_file_model.object_id]
237
- end
177
+ formatted_data = formatter.format(linting_data)
238
178
 
239
- 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
240
186
 
241
- def self.dynamic_linters
242
- # No need to keep making new ones over and over...
243
- @dynamic_linters ||= Hash.new { |hash, key| hash[key] = key.new }
244
- # 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
245
193
  #
246
- # @dynamic_linters = {}
247
- end
248
-
249
- private_class_method(:dynamic_linters)
250
-
251
-
252
- # # def self.relevant_model?(linter, model)
253
- # # model_classes = linter.class.target_model_types.map { |type| CukeModeler.const_get(type.to_s.capitalize.chop) }
254
- # # model_classes.any? { |clazz| model.is_a?(clazz) }
255
- # # end
256
- # #
257
- # # private_class_method(:relevant_model?)
194
+ # private_class_method(:relevant_model?)
195
+ # rubocop:enable Metrics/LineLength
258
196
 
197
+ end
259
198
  end