Skip to content

Commit

Permalink
Add feature to perform comparisons between file diffs
Browse files Browse the repository at this point in the history
  • Loading branch information
Hemant Gowda authored and nunosilva800 committed Apr 11, 2018
1 parent e32e6fb commit f8d06a8
Show file tree
Hide file tree
Showing 22 changed files with 447 additions and 13 deletions.
23 changes: 22 additions & 1 deletion .todo.reek
Expand Up @@ -67,6 +67,16 @@ Attribute:
- RubyCritic::Configuration#open_with
- RubyCritic::Configuration#source_control_system
- RubyCritic::Configuration#suppress_ratings
- RubyCritic::Configuration#base_branch
- RubyCritic::Configuration#base_branch_score
- RubyCritic::Configuration#base_root_directory
- RubyCritic::Configuration#build_root_directory
- RubyCritic::Configuration#feature_branch
- RubyCritic::Configuration#feature_branch_score
- RubyCritic::Configuration#feature_root_directory
- RubyCritic::Configuration#threshold_score
- RubyCritic::Configuration#base_branch_collection
- RubyCritic::Configuration#feature_branch_collection
- RubyCritic::RakeTask#name
- RubyCritic::RakeTask#options
- RubyCritic::RakeTask#paths
Expand All @@ -88,6 +98,7 @@ TooManyStatements:
- RubyCritic::Generator::Html::CodeFile#render
- RubyCritic::Reporter#self.report_generator_class
- RubyCritic::SourceLocator#deduplicate_symlinks
- RubyCritic::Command::Compare#compare_branches
FeatureEnvy:
exclude:
- Parser::AST::Node#module_name
Expand All @@ -104,6 +115,7 @@ NestedIterators:
- RubyCritic::Analyser::FlaySmells#run
- RubyCritic::Cli::Options#parse
- RubyCritic::Generator::HtmlReport#create_directories_and_files
- RubyCritic::AnalysedModulesCollection#initialize
UtilityFunction:
exclude:
- RubyCritic::Analyser::FlaySmells#cost
Expand All @@ -121,9 +133,18 @@ UtilityFunction:
- RubyCritic::SourceControlSystem::Git#stashes_count
- RubyCritic::SourceControlSystem::Mercurial#date_of_last_commit
- RubyCritic::SourceControlSystem::Mercurial#revisions_count
- RubyCritic::ViewHelpers#code_index_path
- RubyCritic::AnalysedModulesCollection#build_analysed_module
- RubyCritic::Command::Compare#branch_directory
- RubyCritic::Command::Compare#build_details
- RubyCritic::Command::Compare#threshold_reached?
- RubyCritic::Command::Compare#threshold_values_set?
TooManyInstanceVariables:
exclude:
- RubyCritic::Cli::Options
- RubyCritic::Generator::Html::CodeFile
- RubyCritic::Generator::Html::Overview
- RubyCritic::Generator::Html::SmellsIndex
ControlParameter:
exclude:
- RubyCritic::CommandFactory#self.command_class
Expand All @@ -139,4 +160,4 @@ ClassVariable:
- RubyCritic::SourceControlSystem::Base
BooleanParameter:
exclude:
- RubyCritic::Config#self.respond_to_missing?
- RubyCritic::Config#self.respond_to_missing?
5 changes: 4 additions & 1 deletion features/command_line_interface/options.feature
Expand Up @@ -21,13 +21,16 @@ Feature: RubyCritic can be controlled using command-line options
"""
Usage: rubycritic [options] [paths]
-p, --path [PATH] Set path where report will be saved (tmp/rubycritic by default)
-b, --branch BRANCH Set branch to compare
-t [MAX_DECREASE], Set a threshold for score difference between two branches (works only with -b)
--maximum-decrease
-f, --format [FORMAT] Report smells in the given format:
html (default; will open in a browser)
json
console
lint
-s, --minimum-score [MIN_SCORE] Set a minimum score
-m, --mode-ci Use CI mode (faster, but only analyses last commit)
-m, --mode-ci [BASE_BRANCH] Use CI mode (faster, analyses diffs w.r.t base_branch (default: master))
--deduplicate-symlinks De-duplicate symlinks based on their final target
--suppress-ratings Suppress letter ratings
--no-browser Do not open html report with browser
Expand Down
28 changes: 25 additions & 3 deletions lib/rubycritic/cli/options.rb
Expand Up @@ -20,6 +20,17 @@ def parse
@root = path
end

opts.on('-b', '--branch BRANCH', 'Set branch to compare') do |branch|
self.base_branch = String(branch)
set_current_branch
self.mode = :compare_branches
end

opts.on('-t', '--maximum-decrease [MAX_DECREASE]',
'Set a threshold for score difference between two branches (works only with -b)') do |threshold_score|
self.threshold_score = Integer(threshold_score)
end

opts.on(
'-f', '--format [FORMAT]',
%i[html json console lint],
Expand All @@ -36,7 +47,10 @@ def parse
self.minimum_score = Float(min_score)
end

opts.on('-m', '--mode-ci', 'Use CI mode (faster, but only analyses last commit)') do
opts.on('-m', '--mode-ci [BASE_BRANCH]',
'Use CI mode (faster, analyses diffs w.r.t base_branch (default: master))') do |branch|
self.base_branch = branch || 'master'
set_current_branch
self.mode = :ci
end

Expand Down Expand Up @@ -73,22 +87,30 @@ def to_h
suppress_ratings: suppress_ratings,
help_text: parser.help,
minimum_score: minimum_score || 0,
no_browser: no_browser
no_browser: no_browser,
base_branch: base_branch,
feature_branch: feature_branch,
threshold_score: threshold_score || 0
}
end
# rubocop:enable Metrics/MethodLength

private

attr_accessor :mode, :root, :format, :deduplicate_symlinks,
:suppress_ratings, :minimum_score, :no_browser, :parser
:suppress_ratings, :minimum_score, :no_browser,
:parser, :base_branch, :feature_branch, :threshold_score
def paths
if @argv.empty?
['.']
else
@argv
end
end

def set_current_branch
self.feature_branch = SourceControlSystem::Git.current_branch
end
end
end
end
3 changes: 3 additions & 0 deletions lib/rubycritic/command_factory.rb
Expand Up @@ -20,6 +20,9 @@ def self.command_class(mode)
when :ci
require 'rubycritic/commands/ci'
Command::Ci
when :compare_branches
require 'rubycritic/commands/compare'
Command::Compare
else
require 'rubycritic/commands/default'
Command::Default
Expand Down
9 changes: 5 additions & 4 deletions lib/rubycritic/commands/ci.rb
Expand Up @@ -4,13 +4,14 @@
require 'rubycritic/analysers_runner'
require 'rubycritic/reporter'
require 'rubycritic/commands/default'
require 'rubycritic/commands/compare'

module RubyCritic
module Command
class Ci < Default
def critique
AnalysersRunner.new(paths).run
end
class Ci < Compare
# def critique
# AnalysersRunner.new(paths).run
# end

private

Expand Down
115 changes: 115 additions & 0 deletions lib/rubycritic/commands/compare.rb
@@ -0,0 +1,115 @@
# frozen_string_literal: true
require 'rubycritic/source_control_systems/base'
require 'rubycritic/analysers_runner'
require 'rubycritic/revision_comparator'
require 'rubycritic/reporter'
require 'rubycritic/commands/base'
require 'rubycritic/commands/default'

module RubyCritic
module Command
class Compare < Default
def initialize(options)
super
@build_number = 0
end

def execute
compare_branches
status_reporter.score = Config.feature_branch_score
status_reporter
end

private

attr_reader :paths, :status_reporter

def compare_branches
update_build_number
set_root_paths
Config.no_browser = true
analyse_branch(:base_branch)
analyse_branch(:feature_branch)
Config.no_browser = false
analyse_modified_files
compare_code_quality
end

# keep track of the number of builds and
# use this build number to create seperate directory for each build
def update_build_number
build_file_location = '/tmp/build_count.txt'
File.new(build_file_location, 'a') unless File.exist?(build_file_location)
@build_number = File.open(build_file_location).readlines.first.to_i + 1
File.write(build_file_location, @build_number)
end

def set_root_paths
Config.base_root_directory = Pathname.new(branch_directory(:base_branch))
Config.feature_root_directory = Pathname.new(branch_directory(:feature_branch))
Config.build_root_directory = Pathname.new(build_directory)
end

# switch branch and analyse files
def analyse_branch(branch)
SourceControlSystem::Git.switch_branch(Config.send(branch))
critic = critique(branch)
Config.send(:"#{branch}_score=", critic.score)
Config.root = branch_directory(branch)
report(critic)
end

# generate report only for modified files
def analyse_modified_files
modified_files = Config.feature_branch_collection.where(SourceControlSystem::Git.modified_files)
analysed_modules = AnalysedModulesCollection.new(modified_files.map(&:path), modified_files)
Config.root = build_directory
report(analysed_modules)
end

def compare_code_quality
build_details
compare_threshold
end

# mark build as failed if the diff b/w the score of
# two branches is greater than threshold value
def compare_threshold
return unless mark_build_fail?
print("Threshold: #{Config.threshold_score}\n")
print("Difference: #{(Config.base_branch_score - Config.feature_branch_score).abs}\n")
abort('The score difference between the two branches is over the threshold.')
end

def mark_build_fail?
Config.threshold_score > 0 && threshold_reached?
end

def threshold_reached?
(Config.base_branch_score - Config.feature_branch_score) > Config.threshold_score
end

def branch_directory(branch)
"tmp/rubycritic/compare/#{Config.send(branch)}"
end

def build_directory
"tmp/rubycritic/compare/builds/build_#{@build_number}"
end

# create a txt file with the branch score details
def build_details
details = "Base branch (#{Config.base_branch}) score: #{Config.base_branch_score} \n"\
"Feature branch (#{Config.feature_branch}) score: #{Config.feature_branch_score} \n"
File.open("#{Config.build_root_directory}/build_details.txt", 'w') { |file| file.write(details) }
end

# store the analysed moduled collection based on the branch
def critique(branch)
module_collection = AnalysersRunner.new(paths).run
Config.send(:"#{branch}_collection=", module_collection)
RevisionComparator.new(paths).set_statuses(module_collection)
end
end
end
end
19 changes: 18 additions & 1 deletion lib/rubycritic/configuration.rb
Expand Up @@ -6,7 +6,11 @@ module RubyCritic
class Configuration
attr_reader :root
attr_accessor :source_control_system, :mode, :format, :deduplicate_symlinks,
:suppress_ratings, :open_with, :no_browser
:suppress_ratings, :open_with, :no_browser, :base_branch,
:feature_branch, :base_branch_score, :feature_branch_score,
:base_root_directory, :feature_root_directory,
:build_root_directory, :threshold_score, :base_branch_collection,
:feature_branch_collection

def set(options)
self.mode = options[:mode] || :default
Expand All @@ -16,6 +20,9 @@ def set(options)
self.suppress_ratings = options[:suppress_ratings] || false
self.open_with = options[:open_with]
self.no_browser = options[:no_browser]
self.base_branch = options[:base_branch]
self.feature_branch = options[:feature_branch]
self.threshold_score = options[:threshold_score]
end

def root=(path)
Expand All @@ -37,6 +44,16 @@ def self.set(options = {})
configuration.set(options)
end

def self.compare_branches_mode?
mode = Config.mode
mode == :compare_branches || mode == :ci
end

def self.build_mode?
mode = Config.mode
(mode == :compare_branches || mode == :ci) && !Config.no_browser
end

def self.method_missing(method, *args, &block)
configuration.public_send(method, *args, &block)
end
Expand Down
24 changes: 22 additions & 2 deletions lib/rubycritic/core/analysed_modules_collection.rb
Expand Up @@ -17,16 +17,29 @@ class AnalysedModulesCollection
ZERO_SCORE_COST = 16.0
COST_MULTIPLIER = MAX_SCORE / ZERO_SCORE_COST

def initialize(paths)
def initialize(paths, modules = nil)
@modules = SourceLocator.new(paths).pathnames.map do |pathname|
AnalysedModule.new(pathname: pathname)
if modules
analysed_module = modules.find { |mod| mod.pathname == pathname }
build_analysed_module(analysed_module)
else
AnalysedModule.new(pathname: pathname)
end
end
end

def each(&block)
@modules.each(&block)
end

def where(module_paths)
@modules.find_all { |mod| module_paths.include? mod.path }
end

def find(module_path)
@modules.find { |mod| mod.pathname == module_path }
end

def to_json(*options)
@modules.to_json(*options)
end
Expand Down Expand Up @@ -65,5 +78,12 @@ def average_cost
def limited_cost_for(mod)
[mod.cost, COST_LIMIT].min
end

def build_analysed_module(analysed_module)
AnalysedModule.new(pathname: analysed_module.pathname, name: analysed_module.name,
smells: analysed_module.smells, churn: analysed_module.churn,
committed_at: analysed_module.committed_at, complexity: analysed_module.complexity,
duplication: analysed_module.duplication, methods_count: analysed_module.methods_count)
end
end
end

0 comments on commit f8d06a8

Please sign in to comment.