Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
enkessler committed Oct 4, 2019
2 parents a9bbc03 + a2e870a commit ae7852b
Show file tree
Hide file tree
Showing 53 changed files with 1,849 additions and 734 deletions.
18 changes: 17 additions & 1 deletion CHANGELOG.md
Expand Up @@ -8,6 +8,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

Nothing yet...


## [0.10.0] - 2019-10-04

### Added
- New linters
- FeatureWithTooManyDifferentTagsLinter
- TestShouldUseBackgroundLinter
- TestWithActionStepAsFinalStepLinter
- TestWithSetupStepAfterActionStepLinter
- TestWithSetupStepAfterVerificationStepLinter
- TestWithSetupStepAsFinalStepLinter

### Changed
- Improved some of the documentation and linting messages so that it is more clear what the problem is.

## [0.9.0] - 2019-09-11

### Added
Expand Down Expand Up @@ -90,7 +105,8 @@ Nothing yet...
- Custom linters, formatters, and command line usability


[Unreleased]: https://github.com/enkessler/cuke_linter/compare/v0.9.0...HEAD
[Unreleased]: https://github.com/enkessler/cuke_linter/compare/v0.10.0...HEAD
[0.10.0]: https://github.com/enkessler/cuke_linter/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/enkessler/cuke_linter/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/enkessler/cuke_linter/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/enkessler/cuke_linter/compare/v0.6.0...v0.7.0
Expand Down
26 changes: 26 additions & 0 deletions CONTRIBUTING.md
@@ -0,0 +1,26 @@
# Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake cuke_linter:test_everything` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.


## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/enkessler/cuke_linter.

1. Fork it
2. Create your feature branch **(off of the development branch)**
`git checkout -b my-new-feature dev`
3. Commit your changes
`git commit -am 'Add some feature'`
4. Push to the branch
`git push origin my-new-feature`
5. Create new Pull Request


### Adding a new linter

Some guidelines when adding a new linter
* Inherit from the base linter class. It will handle almost all of the functional requirements of a linter.
* Existing linters should provide decent examples of how to create new linters and how to test them. A copy/paste/tweak approach is perfectly valid.
* Keep linters simple. Rather than have one linter that has different behaviors depending on context, create a different linter class for each context.
* Keep things alphabetical. There are going to be lots of linters and things will be easier to find if lists of them in the code base (e.g. `require` statments, documentation, etc.) are in an intuitive order.
17 changes: 2 additions & 15 deletions README.md
Expand Up @@ -147,23 +147,10 @@ Rather than using the default linters or providing a custom set of of modified l

For more detailed examples of usage, see the documentation [here](https://app.cucumber.pro/projects/cuke_linter).

## Development and Contributing

## Development
See [CONTRIBUTING.md](https://github.com/enkessler/cuke_linter/blob/master/CONTRIBUTING.md)

After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake cuke_linter:test_everything` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/enkessler/cuke_linter.

1. Fork it
2. Create your feature branch **(off of the development branch)**
`git checkout -b my-new-feature`
3. Commit your changes
`git commit -am 'Add some feature'`
4. Push to the branch
`git push origin my-new-feature`
5. Create new Pull Request

## License

Expand Down
54 changes: 36 additions & 18 deletions lib/cuke_linter.rb
Expand Up @@ -7,37 +7,49 @@
require 'cuke_linter/linters/background_does_more_than_setup_linter'
require 'cuke_linter/linters/element_with_too_many_tags_linter'
require 'cuke_linter/linters/example_without_name_linter'
require 'cuke_linter/linters/feature_with_too_many_different_tags_linter'
require 'cuke_linter/linters/feature_without_name_linter'
require 'cuke_linter/linters/feature_without_description_linter'
require 'cuke_linter/linters/feature_without_scenarios_linter'
require 'cuke_linter/linters/outline_with_single_example_row_linter'
require 'cuke_linter/linters/single_test_background_linter'
require 'cuke_linter/linters/step_with_end_period_linter'
require 'cuke_linter/linters/step_with_too_many_characters_linter'
require 'cuke_linter/linters/test_should_use_background_linter'
require 'cuke_linter/linters/test_with_action_step_as_final_step_linter'
require 'cuke_linter/linters/test_with_no_action_step_linter'
require 'cuke_linter/linters/test_with_no_name_linter'
require 'cuke_linter/linters/test_with_no_verification_step_linter'
require 'cuke_linter/linters/test_with_setup_step_after_action_step_linter'
require 'cuke_linter/linters/test_with_setup_step_after_verification_step_linter'
require 'cuke_linter/linters/test_with_setup_step_as_final_step_linter'
require 'cuke_linter/linters/test_with_too_many_steps_linter'


# The top level namespace used by this gem

module CukeLinter

@original_linters = { 'BackgroundDoesMoreThanSetupLinter' => BackgroundDoesMoreThanSetupLinter.new,
'ElementWithTooManyTagsLinter' => ElementWithTooManyTagsLinter.new,
'ExampleWithoutNameLinter' => ExampleWithoutNameLinter.new,
'FeatureWithoutDescriptionLinter' => FeatureWithoutDescriptionLinter.new,
'FeatureWithoutNameLinter' => FeatureWithoutNameLinter.new,
'FeatureWithoutScenariosLinter' => FeatureWithoutScenariosLinter.new,
'OutlineWithSingleExampleRowLinter' => OutlineWithSingleExampleRowLinter.new,
'SingleTestBackgroundLinter' => SingleTestBackgroundLinter.new,
'StepWithEndPeriodLinter' => StepWithEndPeriodLinter.new,
'TestWithNoActionStepLinter' => TestWithNoActionStepLinter.new,
'TestWithNoNameLinter' => TestWithNoNameLinter.new,
'TestWithNoVerificationStepLinter' => TestWithNoVerificationStepLinter.new,
'StepWithTooManyCharactersLinter' => StepWithTooManyCharactersLinter.new,
'TestWithTooManyStepsLinter' => TestWithTooManyStepsLinter.new }
@original_linters = { 'BackgroundDoesMoreThanSetupLinter' => BackgroundDoesMoreThanSetupLinter.new,
'ElementWithTooManyTagsLinter' => ElementWithTooManyTagsLinter.new,
'ExampleWithoutNameLinter' => ExampleWithoutNameLinter.new,
'FeatureWithTooManyDifferentTagsLinter' => FeatureWithTooManyDifferentTagsLinter.new,
'FeatureWithoutDescriptionLinter' => FeatureWithoutDescriptionLinter.new,
'FeatureWithoutNameLinter' => FeatureWithoutNameLinter.new,
'FeatureWithoutScenariosLinter' => FeatureWithoutScenariosLinter.new,
'OutlineWithSingleExampleRowLinter' => OutlineWithSingleExampleRowLinter.new,
'SingleTestBackgroundLinter' => SingleTestBackgroundLinter.new,
'StepWithEndPeriodLinter' => StepWithEndPeriodLinter.new,
'StepWithTooManyCharactersLinter' => StepWithTooManyCharactersLinter.new,
'TestShouldUseBackgroundLinter' => TestShouldUseBackgroundLinter.new,
'TestWithActionStepAsFinalStepLinter' => TestWithActionStepAsFinalStepLinter.new,
'TestWithNoActionStepLinter' => TestWithNoActionStepLinter.new,
'TestWithNoNameLinter' => TestWithNoNameLinter.new,
'TestWithNoVerificationStepLinter' => TestWithNoVerificationStepLinter.new,
'TestWithSetupStepAfterActionStepLinter' => TestWithSetupStepAfterActionStepLinter.new,
'TestWithSetupStepAfterVerificationStepLinter' => TestWithSetupStepAfterVerificationStepLinter.new,
'TestWithSetupStepAsFinalStepLinter' => TestWithSetupStepAsFinalStepLinter.new,
'TestWithTooManyStepsLinter' => TestWithTooManyStepsLinter.new }


# Configures linters based on the given options
Expand Down Expand Up @@ -97,8 +109,15 @@ def self.clear_registered_linters
# 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
def self.lint(file_paths: [], model_trees: [], linters: self.registered_linters.values, formatters: [[CukeLinter::PrettyFormatter.new]])

model_trees = [CukeModeler::Directory.new(Dir.pwd)] if model_trees.empty? && file_paths.empty?
file_path_models = file_paths.collect do |file_path|
# TODO: Test this?
# Because directive memoization is based on a model's `#object_id` and Ruby reuses object IDs over the life
# life of a program as objects are garbage collected, it is not safe to remember the IDs forever. However,
# models shouldn't get GC'd in the middle of the linting process and so the start of the linting process is
# a good time to reset things
@directives_for_feature_file = {}

model_trees = [CukeModeler::Directory.new(Dir.pwd)] if model_trees.empty? && file_paths.empty?
file_path_models = file_paths.collect do |file_path|
# TODO: raise exception unless path exists
case
when File.directory?(file_path)
Expand Down Expand Up @@ -178,8 +197,7 @@ def self.relevant_linters_for_model(base_linters, model)


def self.linter_directives_for_feature_file(feature_file_model)
# IMPORTANT ASSUMPTION: Models never change during the life of the program, so data only has to be gathered once
@directives_for_feature_file ||= {}
# IMPORTANT ASSUMPTION: Models never change during the life of a linting, so data only has to be gathered once
return @directives_for_feature_file[feature_file_model.object_id] if @directives_for_feature_file[feature_file_model.object_id]


Expand Down
2 changes: 1 addition & 1 deletion lib/cuke_linter/linters/example_without_name_linter.rb
Expand Up @@ -13,7 +13,7 @@ def rule(model)

# The message used to describe the problem that has been found
def message
'Example has no name'
'Example grouping has no name'
end

end
Expand Down
@@ -0,0 +1,38 @@
module CukeLinter

# A linter that detects features that contain too many different tags

class FeatureWithTooManyDifferentTagsLinter < Linter

# Changes the linting settings on the linter using the provided configuration
def configure(options)
@tag_threshold = options['TagCountThreshold']
end

# The rule used to determine if a model has a problem
def rule(model)
return false unless model.is_a?(CukeModeler::Feature)

tags = model.tags

model.each_descendant do |descendant_model|
if descendant_model.respond_to?(:tags)
tags.concat(descendant_model.tags)
end
end

tags = tags.collect(&:name).uniq

@linted_tag_threshold = @tag_threshold || 10
@linted_tag_count = tags.count

@linted_tag_count > @linted_tag_threshold
end

# The message used to describe the problem that has been found
def message
"Feature contains too many different tags. #{@linted_tag_count} tags found (max #{@linted_tag_threshold})."
end

end
end
28 changes: 28 additions & 0 deletions lib/cuke_linter/linters/test_should_use_background_linter.rb
@@ -0,0 +1,28 @@
module CukeLinter

# A linter that detects scenarios and outlines within a feature that all share common beginning steps

class TestShouldUseBackgroundLinter < Linter

# The rule used to determine if a model has a problem
def rule(model)
return false unless model.is_a?(CukeModeler::Scenario) || model.is_a?(CukeModeler::Outline)

model_steps = model.steps || []
parent_feature_model = model.get_ancestor(:feature)

return false unless parent_feature_model.tests.count > 1

parent_feature_model.tests.all? do |test|
test_steps = test.steps || []
test_steps.first == model_steps.first
end
end

# The message used to describe the problem that has been found
def message
'Test shares steps with all other tests in feature. Use a background.'
end

end
end
@@ -0,0 +1,23 @@
module CukeLinter

# A linter that detects scenarios and outlines that have an action step as their final step

class TestWithActionStepAsFinalStepLinter < Linter

# The rule used to determine if a model has a problem
def rule(model)
return false unless model.is_a?(CukeModeler::Scenario) || model.is_a?(CukeModeler::Outline)

model_steps = model.steps || []
return false unless model_steps.last

model_steps.last.keyword == 'When'
end

# The message used to describe the problem that has been found
def message
"Test has 'When' as the final step."
end

end
end
@@ -0,0 +1,31 @@
module CukeLinter

# A linter that detects scenarios and outlines that have a setup step that comes after an action step

class TestWithSetupStepAfterActionStepLinter < Linter

# The rule used to determine if a model has a problem
def rule(model)
return false unless model.is_a?(CukeModeler::Scenario) || model.is_a?(CukeModeler::Outline)

model_steps = model.steps || []
action_step_found = false

model_steps.each do |step|
if action_step_found
return true if step.keyword == 'Given'
else
action_step_found = step.keyword == 'When'
end
end

false
end

# The message used to describe the problem that has been found
def message
"Test has 'Given' step after 'When' step."
end

end
end
@@ -0,0 +1,31 @@
module CukeLinter

# A linter that detects scenarios and outlines that have a setup step that comes after a verification step

class TestWithSetupStepAfterVerificationStepLinter < Linter

# The rule used to determine if a model has a problem
def rule(model)
return false unless model.is_a?(CukeModeler::Scenario) || model.is_a?(CukeModeler::Outline)

model_steps = model.steps || []
verification_step_found = false

model_steps.each do |step|
if verification_step_found
return true if step.keyword == 'Given'
else
verification_step_found = step.keyword == 'Then'
end
end

false
end

# The message used to describe the problem that has been found
def message
"Test has 'Given' step after 'Then' step."
end

end
end
@@ -0,0 +1,23 @@
module CukeLinter

# A linter that detects scenarios and outlines that have a setup step as their final step

class TestWithSetupStepAsFinalStepLinter < Linter

# The rule used to determine if a model has a problem
def rule(model)
return false unless model.is_a?(CukeModeler::Scenario) || model.is_a?(CukeModeler::Outline)

model_steps = model.steps || []
return false unless model_steps.last

model_steps.last.keyword == 'Given'
end

# The message used to describe the problem that has been found
def message
"Test has 'Given' as the final step."
end

end
end
2 changes: 1 addition & 1 deletion lib/cuke_linter/version.rb
@@ -1,4 +1,4 @@
module CukeLinter
# The release version of this gem
VERSION = "0.9.0"
VERSION = '0.10.0'
end

0 comments on commit ae7852b

Please sign in to comment.