bin/code_climate_reek
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# Wrapper for the CodeClimate integration.
require_relative '../lib/reek'
require_relative '../lib/reek/cli/application'
require_relative '../lib/reek/code_climate'
Reek::CLI::Silencer.silently { require 'parser/current' }
# Map input coming from CodeClimate to Reek.
class CodeClimateToReek
# Following the spec (https://github.com/codeclimate/spec/blob/master/SPEC.md)
# we have to exit with a zero for both failure and success.
ENGINE_CONFIGURATION = [
'--failure-exit-code', '0',
'--success-exit-code', '0'
].freeze
CUSTOM_CONFIG_KEY = 'config'
CUSTOM_CONFIG_TARGET_RUBY_VERSION_KEY = 'target_ruby_version'
attr_reader :configuration_file_path, :include_paths_key, :include_paths_default
def initialize(configuration_file_path: '/config.json',
include_paths_key: 'include_paths',
include_paths_default: ['.'])
@configuration_file_path = configuration_file_path
@include_paths_key = include_paths_key
@include_paths_default = include_paths_default
end
def cli_arguments
include_paths + ENGINE_CONFIGURATION
end
def target_ruby_version
config.dig(CUSTOM_CONFIG_KEY, CUSTOM_CONFIG_TARGET_RUBY_VERSION_KEY)
end
private
def configuration_file_exists?
Pathname.new(configuration_file_path).exist?
end
# The config.json file we try to read below might look like this:
# {
# "include_paths":[
# "lib",
# "spec"
# ]
# }
def include_paths
config.fetch include_paths_key, include_paths_default
end
def config
if configuration_file_exists?
JSON.parse File.read(configuration_file_path)
else
{}
end
end
end
# Override for ReportCommand to force the use of CodeClimateReport.
module ReportClassOverride
def report_class
Reek::CodeClimate::CodeClimateReport
end
end
# Override Reek::Source::SourceCode to use a parser version specified by the user
module SourceCodeOverride
# override self.default_parser method
def default_parser
parser_class.new(Reek::AST::Builder.new).tap do |parser|
diagnostics = parser.diagnostics
diagnostics.all_errors_are_fatal = true
diagnostics.ignore_warnings = true
end
end
# config.json file will look like this:
# {
# "include_paths":[
# "lib",
# "spec"
# ],
# "config": {
# "target_ruby_version": "3.1.0"
# }
# }
def parser_class
# convert an X.Y.Z version number to an XY two digit number
requested_version = CodeClimateToReek.new.target_ruby_version
return Parser::CurrentRuby if requested_version.nil?
version_number = Gem::Version.new(requested_version).segments[0..1].join
begin
Reek::CLI::Silencer.silently { require "parser/ruby#{version_number}" }
Module.const_get("Parser::Ruby#{version_number}")
rescue LoadError, NameError
# use Parser::CurrentRuby when an invalid version number is provided
Parser::CurrentRuby
end
end
end
Reek::CLI::Command::ReportCommand.prepend ReportClassOverride
Reek::Source::SourceCode.singleton_class.prepend SourceCodeOverride
application = Reek::CLI::Application.new(CodeClimateToReek.new.cli_arguments)
exit application.execute