troessner/reek

View on GitHub
lib/reek/rake/task.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

require 'rake'
require 'rake/tasklib'
require 'pathname'
require 'English'

module Reek
  #
  # Defines a task library for running Reek.
  #
  # @public
  module Rake
    # A Rake task that runs Reek on a set of source files.
    #
    # Example:
    #
    #   require 'reek/rake/task'
    #
    #   Reek::Rake::Task.new do |t|
    #     t.fail_on_error = false
    #   end
    #
    # This will create a task that can be run with:
    #
    #   rake reek
    #
    # Examples:
    #
    #   rake reek                                # checks lib/**/*.rb
    #   rake reek REEK_SRC=just_one_file.rb      # checks a single source file
    #   rake reek REEK_OPTS=-s                   # sorts the report by smell
    #
    # @public
    #
    # @quality :reek:TooManyInstanceVariables { max_instance_variables: 6 }
    # @quality :reek:Attribute
    class Task < ::Rake::TaskLib
      # Name of Reek task. Defaults to :reek.
      # @public
      attr_writer :name

      # Path to Reek's config file.
      # Setting the REEK_CFG environment variable overrides this.
      # @public
      attr_accessor :config_file

      # Glob pattern to match source files.
      # Setting the REEK_SRC environment variable overrides this.
      # Defaults to 'lib/**/*.rb'.
      # @public
      attr_reader :source_files

      # String containing commandline options to be passed to Reek.
      # Setting the REEK_OPTS environment variable overrides this value.
      # Defaults to ''.
      # @public
      attr_accessor :reek_opts

      # Whether or not to fail Rake when an error occurs (typically when smells are found).
      # Defaults to true.
      # @public
      attr_writer :fail_on_error

      # Use verbose output. If this is set to true, the task will print
      # the reek command to stdout. Defaults to false.
      # @public
      attr_writer :verbose

      # @public
      def initialize(name = :reek)
        @config_file   = ENV.fetch('REEK_CFG', nil)
        @name          = name
        @reek_opts     = ENV.fetch('REEK_OPTS', '')
        @fail_on_error = true
        @verbose       = false

        yield self if block_given?

        if (reek_src = ENV.fetch('REEK_SRC', nil))
          @source_files = FileList[reek_src]
        end
        @source_files ||= FileList['lib/**/*.rb']
        define_task
      end

      # @public
      def source_files=(files)
        unless files.is_a?(String) || files.is_a?(FileList)
          raise ArgumentError, 'File list should be a FileList or a String that can contain ' \
                               "a glob pattern, e.g. '{app,lib,spec}/**/*.rb'"
        end
        @source_files = FileList[files]
      end

      private

      attr_reader :fail_on_error, :name, :verbose

      def define_task
        desc 'Check for code smells'
        task(name) { run_task }
      end

      def run_task
        puts "\n\n!!! Running 'reek' rake command: #{command}\n\n" if verbose
        system(*command)
        abort("\n\n!!! Reek has found smells - exiting!") if sys_call_failed? && fail_on_error
      end

      def command
        ['reek', *config_file_as_argument, *reek_opts_as_arguments, *source_files].
          compact.
          reject(&:empty?)
      end

      # @quality :reek:UtilityFunction
      def sys_call_failed?
        !$CHILD_STATUS.success?
      end

      def config_file_as_argument
        config_file ? ['-c', config_file] : []
      end

      def reek_opts_as_arguments
        reek_opts.split(/\s+/)
      end
    end
  end
end