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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -1
- data/LICENSE.txt +1 -1
- data/README.md +12 -9
- data/cuke_linter.gemspec +41 -23
- data/exe/cuke_linter +4 -2
- data/lib/cuke_linter.rb +119 -180
- data/lib/cuke_linter/configuration.rb +45 -0
- data/lib/cuke_linter/default_linters.rb +32 -0
- data/lib/cuke_linter/formatters/pretty_formatter.rb +63 -35
- data/lib/cuke_linter/gherkin.rb +10 -0
- data/lib/cuke_linter/linter_registration.rb +32 -0
- data/lib/cuke_linter/linters/background_does_more_than_setup_linter.rb +1 -1
- data/lib/cuke_linter/linters/element_with_common_tags_linter.rb +23 -14
- data/lib/cuke_linter/linters/element_with_duplicate_tags_linter.rb +18 -13
- data/lib/cuke_linter/linters/element_with_too_many_tags_linter.rb +17 -11
- data/lib/cuke_linter/linters/feature_with_too_many_different_tags_linter.rb +1 -3
- data/lib/cuke_linter/linters/feature_without_description_linter.rb +1 -1
- data/lib/cuke_linter/linters/linter.rb +17 -13
- data/lib/cuke_linter/linters/step_with_too_many_characters_linter.rb +2 -2
- data/lib/cuke_linter/linters/test_should_use_background_linter.rb +23 -15
- data/lib/cuke_linter/linters/test_with_bad_name_linter.rb +4 -4
- data/lib/cuke_linter/linters/test_with_setup_step_as_final_step_linter.rb +1 -1
- data/lib/cuke_linter/version.rb +1 -1
- data/testing/cucumber/features/command_line.feature +5 -2
- data/testing/cucumber/features/configuration/configuring_linters.feature +5 -2
- data/testing/cucumber/features/configuration/locally_scoping_linters.feature +7 -2
- data/testing/cucumber/features/configuration/using_configurations.feature +2 -1
- data/testing/cucumber/features/custom_linters.feature +3 -1
- metadata +113 -108
- data/.gitignore +0 -19
- data/.simplecov +0 -8
- data/.travis.yml +0 -33
- data/CONTRIBUTING.md +0 -26
- data/Gemfile +0 -6
- data/Rakefile +0 -63
- data/appveyor.yml +0 -43
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/environments/common_env.rb +0 -12
- data/environments/cucumber_env.rb +0 -22
- data/environments/rspec_env.rb +0 -50
- data/testing/cucumber/step_definitions/action_steps.rb +0 -84
- data/testing/cucumber/step_definitions/setup_steps.rb +0 -258
- data/testing/cucumber/step_definitions/verification_steps.rb +0 -94
- data/testing/file_helper.rb +0 -41
- data/testing/formatter_factory.rb +0 -15
- data/testing/gemfiles/cuke_modeler1.gemfile +0 -8
- data/testing/gemfiles/cuke_modeler2.gemfile +0 -8
- data/testing/linter_factory.rb +0 -60
- data/testing/model_factory.rb +0 -109
- data/testing/rspec/spec/integration/cli_integration_spec.rb +0 -556
- data/testing/rspec/spec/integration/configuration_spec.rb +0 -811
- data/testing/rspec/spec/integration/cuke_linter_integration_spec.rb +0 -243
- data/testing/rspec/spec/integration/formatters/formatter_integration_specs.rb +0 -5
- data/testing/rspec/spec/integration/formatters/pretty_formatter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/background_does_more_than_setup_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/element_with_common_tags_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/element_with_duplicate_tags_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/element_with_too_many_tags_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/example_without_name_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/feature_file_with_invalid_name_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/feature_file_with_mismatched_name_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/feature_with_too_many_different_tags_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/feature_without_description_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/feature_without_name_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/feature_without_scenarios_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/linter_integration_specs.rb +0 -7
- data/testing/rspec/spec/integration/linters/outline_with_single_example_row_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/single_test_background_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/step_with_end_period_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/step_with_too_many_characters_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_should_use_background_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_with_action_step_as_final_step_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_with_bad_name_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_with_no_action_step_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_with_no_name_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_with_no_verification_step_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_with_setup_step_after_action_step_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_with_setup_step_after_verification_step_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_with_setup_step_as_final_step_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/integration/linters/test_with_too_many_steps_linter_integration_spec.rb +0 -8
- data/testing/rspec/spec/unit/cuke_linter_unit_spec.rb +0 -114
- data/testing/rspec/spec/unit/formatters/formatter_unit_specs.rb +0 -11
- data/testing/rspec/spec/unit/formatters/pretty_formatter_unit_spec.rb +0 -115
- data/testing/rspec/spec/unit/linters/background_does_more_than_setup_linter_unit_spec.rb +0 -186
- data/testing/rspec/spec/unit/linters/configurable_linter_unit_specs.rb +0 -11
- data/testing/rspec/spec/unit/linters/element_with_common_tags_linter_unit_spec.rb +0 -248
- data/testing/rspec/spec/unit/linters/element_with_duplicate_tags_linter_unit_spec.rb +0 -203
- data/testing/rspec/spec/unit/linters/element_with_too_many_tags_linter_unit_spec.rb +0 -296
- data/testing/rspec/spec/unit/linters/example_without_name_linter_unit_spec.rb +0 -81
- data/testing/rspec/spec/unit/linters/feature_file_with_invalid_name_linter_unit_spec.rb +0 -106
- data/testing/rspec/spec/unit/linters/feature_file_with_mismatched_name_linter_unit_spec.rb +0 -124
- data/testing/rspec/spec/unit/linters/feature_with_too_many_different_tags_linter_unit_spec.rb +0 -293
- data/testing/rspec/spec/unit/linters/feature_without_description_linter_unit_spec.rb +0 -80
- data/testing/rspec/spec/unit/linters/feature_without_name_linter_unit_spec.rb +0 -84
- data/testing/rspec/spec/unit/linters/feature_without_scenarios_linter_unit_spec.rb +0 -102
- data/testing/rspec/spec/unit/linters/linter_unit_spec.rb +0 -197
- data/testing/rspec/spec/unit/linters/linter_unit_specs.rb +0 -57
- data/testing/rspec/spec/unit/linters/outline_with_single_example_row_linter_unit_spec.rb +0 -184
- data/testing/rspec/spec/unit/linters/single_test_background_linter_unit_spec.rb +0 -89
- data/testing/rspec/spec/unit/linters/step_with_end_period_linter_unit_spec.rb +0 -54
- data/testing/rspec/spec/unit/linters/step_with_too_many_characters_linter_unit_spec.rb +0 -155
- data/testing/rspec/spec/unit/linters/test_should_use_background_linter_unit_spec.rb +0 -464
- data/testing/rspec/spec/unit/linters/test_with_action_step_as_final_step_linter_unit_spec.rb +0 -165
- data/testing/rspec/spec/unit/linters/test_with_bad_name_linter_unit_spec.rb +0 -81
- data/testing/rspec/spec/unit/linters/test_with_no_action_step_linter_unit_spec.rb +0 -244
- data/testing/rspec/spec/unit/linters/test_with_no_name_linter_unit_spec.rb +0 -88
- data/testing/rspec/spec/unit/linters/test_with_no_verification_step_linter_unit_spec.rb +0 -246
- data/testing/rspec/spec/unit/linters/test_with_setup_step_after_action_step_linter_unit_spec.rb +0 -233
- data/testing/rspec/spec/unit/linters/test_with_setup_step_after_verification_step_linter_unit_spec.rb +0 -233
- data/testing/rspec/spec/unit/linters/test_with_setup_step_as_final_step_linter_unit_spec.rb +0 -164
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a58ecea412dacacc176aff36844a22a2be89b6e8fb530140c4a4c9af6f68b33d
|
4
|
+
data.tar.gz: 6c59e3ef19cecf4f8348ffbb4b6e5f64b230bd38c569c776dda1afe0b214c856
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
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
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://
|
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://
|
12
|
-
[![
|
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=
|
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',
|
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
|
138
|
-
CukeLinter.lint(linters: [linter],
|
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
|
3
|
+
require 'cuke_linter/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
6
|
+
spec.name = 'cuke_linter'
|
8
7
|
spec.version = CukeLinter::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
8
|
+
spec.authors = ['Eric Kessler']
|
9
|
+
spec.email = ['morrow748@gmail.com']
|
11
10
|
|
12
|
-
spec.summary =
|
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 =
|
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
|
21
|
-
`git ls-files -z`.split("\x0")
|
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 =
|
34
|
+
spec.bindir = 'exe'
|
24
35
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
|
-
spec.require_paths = [
|
36
|
+
spec.require_paths = ['lib']
|
26
37
|
|
27
|
-
spec.required_ruby_version = '
|
38
|
+
spec.required_ruby_version = '>= 2.1', '< 4.0'
|
28
39
|
|
29
|
-
spec.add_runtime_dependency 'cuke_modeler', '>= 1.5', '<
|
40
|
+
spec.add_runtime_dependency 'cuke_modeler', '>= 1.5', '< 4.0'
|
30
41
|
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency
|
33
|
-
spec.add_development_dependency
|
34
|
-
spec.add_development_dependency
|
35
|
-
spec.add_development_dependency
|
36
|
-
spec.add_development_dependency
|
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
|
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
|
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
|
-
|
39
|
-
|
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
|
-
|
81
|
-
to_delete = []
|
44
|
+
class << self
|
82
45
|
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
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
|
-
|
90
|
-
|
60
|
+
linting_data = lint_models(model_sets, linters)
|
61
|
+
format_data(formatters, linting_data)
|
91
62
|
|
92
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
180
|
-
|
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
|
-
|
185
|
-
feature_file_model = model.get_ancestor(:feature_file)
|
109
|
+
linter_modifications_for_model = {}
|
186
110
|
|
187
|
-
|
188
|
-
|
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
|
-
|
115
|
+
linter_modifications_for_model[directive[:linter_class]] = directive[:enabled_status]
|
116
|
+
end
|
191
117
|
|
192
|
-
|
193
|
-
|
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
|
-
|
121
|
+
determine_final_linters(base_linters, disabled_linter_classes, enabled_linter_classes)
|
197
122
|
end
|
198
123
|
|
199
|
-
|
200
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
221
|
-
|
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
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
233
|
-
|
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
|
-
|
237
|
-
end
|
177
|
+
formatted_data = formatter.format(linting_data)
|
238
178
|
|
239
|
-
|
179
|
+
if location
|
180
|
+
File.write(location, formatted_data)
|
181
|
+
else
|
182
|
+
puts formatted_data
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
240
186
|
|
241
|
-
|
242
|
-
#
|
243
|
-
|
244
|
-
#
|
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
|
-
#
|
247
|
-
|
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
|