troessner/reek

View on GitHub
docs/How-To-Write-New-Detectors.md

Summary

Maintainability
Test Coverage
## How to write new detectors

### Outline what you have in mind

Before starting to code you should discuss the overall idea for your new smell detector with
us in a corresponding github issue.
We all should have a solid understanding of what this detector actually reports, the edge cases
it covers and the overall rationale behind it.

### Structure

All smell detectors reside in `lib/reek/smell_detectors` and have the following base structure:

```ruby
require_relative 'base_detector'
require_relative 'smell_warning'

module Reek
  module SmellDetectors
    #
    # Here goes your introduction for this detector.
    #
    # See {file:docs/Your-Detector.md} for details.
    class YourDetector < BaseDetector
      def self.contexts
        [:class] # In case you're operating on class contexts only - just an example.
      end

      #
      # Here you should document what you expect the detector's context to look
      # like.
      #
      # @return [Array<SmellWarning>]
      #
      def sniff
        # "found_smells" below is just an abstraction for
        # "find the smells in question" and iterate over them.
        # This can just be a method but it can also be a more sophisticated set up.
        # Check out other smell detectors to get a feeling for what to do here.
        found_smells.map do |smell|
          # "smell_warning" is defined in BaseDetector and should be used by you
          # to construct smell warnings
          smell_warning(
            lines: [], # lines on which the smell was detected
            message: "...", # the message that is printed on STDOUT
            # whatever you interpolate into the "message" should go into
            # parameters below - if you do not interpolate anything you
            # can omit this
            parameters: { })
          end
        end
      end

      private

      # Here goes everything you need for finding smells.
    end
  end
end
```

For your detector to be properly loaded you need to require it in `lib/reek/smell_detectors.rb` as well.

### defaults.reek.yml

After you ran

```
bundle exec rake
```

for the first time with your shiny new detector in place the `docs/defaults.reek.yml`
file should have been updated automatically. Make sure you don't forget to check
in those changes as well.

### Documentation

* Above every `SmellDetector::sniff` method it should be documented what the expected AST is
* Every detector should have a separate documentation page in /docs. You can
  take any arbitrary existing smell detector documentation page as template (since
  they all have the same structure already)
* The detector should be listed under [Code Smells](docs/Code-Smells.md)
* Depending on what your detector does it might make sense to add it to other doc pages as
  well e.g. [Simulated Polymorphism](docs/Simulated-Polymorphism.md)

### Rspec examples

All smell detector specs start out with 2 generic examples like below - the second one
only if it makes sense.
Here's what it looks like for `UncommunicativeVariableName`:

```ruby
it 'reports the right values' do
  src = <<-RUBY
    def alfa
      bravo = 5
    end
  RUBY

  expect(src).to reek_of(:UncommunicativeVariableName,
                         lines:   [2],
                         context: 'alfa',
                         message: "has the variable name 'bravo'",
                         source:  'string',
                         name:    'bravo')
end

it 'does count all occurrences' do
  src = <<-RUBY
    def alfa
      bravo = 3
      charlie = 7
    end
  RUBY

  expect(src).to reek_of(:UncommunicativeVariableName,
                         lines: [2],
                         name:  'bravo')
  expect(src).to reek_of(:UncommunicativeVariableName,
                         lines: [3],
                         name:  'charlie')
end
```

The following examples should then cover the detector specific features.

### Schema validation

We make use of [dry-schema](https://github.com/dry-rb/dry-schema) to validate the
the reek configuration file (usually named `.reek.yml` in the root of a project).
The validation will warn reek users of missing or incorrectly defined configuration.

If you add or modify a detector you will need to make sure that you update the 
[schema](lib/reek/configuration/schema.rb) file. For help with the schema syntax
you can refer to the [dry-schema documentation](https://dry-rb.org/gems/dry-schema).
You can also take a look at the existing schema rules for they all look fairly
similar.

### Cucumber features

We are trying to write as few Cucumber features as possible.
Normally, there should be no need to write a new feature for a new smell detector.
If you feel like this is necessary in this case, please discuss this with us via
github issue or in your work-in-progress pull request before doing anything.