# frozen_string_literal: true

require_relative 'spec/should_reek'
require_relative 'spec/should_reek_of'
require_relative 'spec/should_reek_only_of'

module Reek
  #
  # Provides matchers for RSpec, making it easy to check code quality.
  #
  # If you require this module somewhere within your spec (or in your spec_helper),
  # Reek will arrange to update RSpec::Runner's config so that it knows about the
  # matchers defined here.
  #
  # === Examples
  #
  # Here's a spec that ensures there are no active code smells in the current project:
  #
  #  describe 'source code quality' do
  #    Pathname.glob('lib/**/*.rb').each do |path|
  #      it "reports no smells in #{path}" do
  #        expect(path).not_to reek
  #      end
  #    end
  #  end
  #
  # And here's an even simpler way to do the same:
  #
  #  it 'has no code smells' do
  #    Pathname.glob('lib/**/*.rb').each do |path|
  #      expect(path).not_to reek
  #    end
  #  end
  #
  # Here's a simple check of a code fragment:
  #
  #  'def equals(other) other.thing == self.thing end'.should_not reek
  #
  # To check for specific smells, use something like this:
  #
  #   ruby = 'def double_thing() @other.thing.foo + @other.thing.foo end'
  #   ruby.should reek_of(:DuplicateMethodCall, name: '@other.thing')
  #   ruby.should reek_of(:DuplicateMethodCall, name: '@other.thing.foo', count: 2)
  #   ruby.should_not reek_of(:FeatureEnvy)
  #
  # @public
  module Spec
    #
    # Checks the target source code for instances of "smell type"
    # and returns true only if it can find one of them that matches.
    #
    # You can pass the smell type you want to check for as String or as Symbol:
    #
    #   - :UtilityFunction
    #   - "UtilityFunction"
    #
    # It is recommended to pass this as a symbol like :UtilityFunction. However we don't
    # enforce this.
    #
    # Additionally you can be more specific and pass in "smell_details" you
    # want to check for as well e.g. "name" or "count" (see the examples below).
    # The parameters you can check for are depending on the smell you are checking for.
    # For instance "count" doesn't make sense everywhere whereas "name" does in most cases.
    # If you pass in a parameter that doesn't exist (e.g. you make a typo like "namme") Reek will
    # raise an ArgumentError to give you a hint that you passed something that doesn't make
    # much sense.
    #
    # @param smell_type [Symbol, String] The "smell type" to check for.
    # @param smell_details [Hash] A hash containing "smell warning" parameters
    #
    # @example Without smell_details
    #
    #   reek_of(:FeatureEnvy)
    #   reek_of(:UtilityFunction)
    #
    # @example With smell_details
    #
    #   reek_of(:UncommunicativeParameterName, name: 'x2')
    #   reek_of(:DataClump, count: 3)
    #
    # @example From a real spec
    #
    #   expect(src).to reek_of(:DuplicateMethodCall, name: '@other.thing')
    #
    # @public
    #
    # @quality :reek:UtilityFunction
    def reek_of(smell_type,
                smell_details = {},
                configuration = Configuration::AppConfiguration.default)
      ShouldReekOf.new(smell_type, smell_details, configuration)
    end

    #
    # See the documentation for "reek_of".
    #
    # Notable differences to reek_of:
    #   1.) "reek_of" doesn't mind if there are other smells of a different type.
    #       "reek_only_of" will fail in that case.
    #   2.) "reek_only_of" doesn't support the additional smell_details hash.
    #
    # @param smell_type [Symbol, String] The "smell type" to check for.
    #
    # @public
    #
    # @quality :reek:UtilityFunction
    def reek_only_of(smell_type, configuration = Configuration::AppConfiguration.default)
      ShouldReekOnlyOf.new(smell_type, configuration)
    end

    #
    # Returns +true+ if and only if the target source code contains smells.
    #
    # @public
    #
    # @quality :reek:UtilityFunction
    def reek(configuration = Configuration::AppConfiguration.from_default_path)
      ShouldReek.new(configuration: configuration)
    end
  end
end

if Object.const_defined?(:RSpec)
  RSpec.configure do |config|
    config.include(Reek::Spec)
  end
end
