rubycritic-simplecov 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +290 -0
- data/CONTRIBUTING.md +93 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +259 -0
- data/ROADMAP.md +56 -0
- data/Rakefile +28 -0
- data/bin/rubycritic +10 -0
- data/lib/rubycritic/analysers/attributes.rb +29 -0
- data/lib/rubycritic/analysers/churn.rb +30 -0
- data/lib/rubycritic/analysers/complexity.rb +30 -0
- data/lib/rubycritic/analysers/coverage.rb +118 -0
- data/lib/rubycritic/analysers/helpers/ast_node.rb +76 -0
- data/lib/rubycritic/analysers/helpers/flay.rb +13 -0
- data/lib/rubycritic/analysers/helpers/flog.rb +17 -0
- data/lib/rubycritic/analysers/helpers/methods_counter.rb +25 -0
- data/lib/rubycritic/analysers/helpers/modules_locator.rb +42 -0
- data/lib/rubycritic/analysers/helpers/parser.rb +14 -0
- data/lib/rubycritic/analysers/helpers/reek.rb +11 -0
- data/lib/rubycritic/analysers/smells/flay.rb +79 -0
- data/lib/rubycritic/analysers/smells/flog.rb +69 -0
- data/lib/rubycritic/analysers/smells/reek.rb +53 -0
- data/lib/rubycritic/analysers_runner.rb +41 -0
- data/lib/rubycritic/analysis_summary.rb +40 -0
- data/lib/rubycritic/browser.rb +19 -0
- data/lib/rubycritic/cli/application.rb +34 -0
- data/lib/rubycritic/cli/options/argv.rb +137 -0
- data/lib/rubycritic/cli/options/file.rb +100 -0
- data/lib/rubycritic/cli/options.rb +33 -0
- data/lib/rubycritic/colorize.rb +17 -0
- data/lib/rubycritic/command_factory.rb +25 -0
- data/lib/rubycritic/commands/base.rb +18 -0
- data/lib/rubycritic/commands/ci.rb +14 -0
- data/lib/rubycritic/commands/compare.rb +106 -0
- data/lib/rubycritic/commands/default.rb +38 -0
- data/lib/rubycritic/commands/help.rb +18 -0
- data/lib/rubycritic/commands/status_reporter.rb +45 -0
- data/lib/rubycritic/commands/utils/build_number_file.rb +37 -0
- data/lib/rubycritic/commands/version.rb +16 -0
- data/lib/rubycritic/configuration.rb +69 -0
- data/lib/rubycritic/core/analysed_module.rb +93 -0
- data/lib/rubycritic/core/analysed_modules_collection.rb +95 -0
- data/lib/rubycritic/core/location.rb +47 -0
- data/lib/rubycritic/core/rating.rb +30 -0
- data/lib/rubycritic/core/smell.rb +84 -0
- data/lib/rubycritic/generators/console_report.rb +20 -0
- data/lib/rubycritic/generators/html/assets/fonts/FontAwesome.otf +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/Roboto-Medium.ttf +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/Roboto-Regular.ttf +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.eot +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.svg +2671 -0
- data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.ttf +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.woff +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.woff2 +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.svg +288 -0
- data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/lib/rubycritic/generators/html/assets/images/logo.png +0 -0
- data/lib/rubycritic/generators/html/assets/javascripts/application.js +281 -0
- data/lib/rubycritic/generators/html/assets/javascripts/bootstrap.min.js +7 -0
- data/lib/rubycritic/generators/html/assets/javascripts/highcharts.src-4.0.1.js +17672 -0
- data/lib/rubycritic/generators/html/assets/javascripts/jquery.filtertable.min.js +13 -0
- data/lib/rubycritic/generators/html/assets/javascripts/jquery.min.js +4 -0
- data/lib/rubycritic/generators/html/assets/javascripts/jquery.scrollTo.min.js +7 -0
- data/lib/rubycritic/generators/html/assets/javascripts/jquery.tablesorter.js +1031 -0
- data/lib/rubycritic/generators/html/assets/javascripts/jquery.tablesorter.min.js +4 -0
- data/lib/rubycritic/generators/html/assets/javascripts/jquery.timeago.js +231 -0
- data/lib/rubycritic/generators/html/assets/javascripts/prettify.js +46 -0
- data/lib/rubycritic/generators/html/assets/stylesheets/application.css +570 -0
- data/lib/rubycritic/generators/html/assets/stylesheets/bootstrap.min.css +6 -0
- data/lib/rubycritic/generators/html/assets/stylesheets/font-awesome.min.css +4 -0
- data/lib/rubycritic/generators/html/assets/stylesheets/prettify.css +1 -0
- data/lib/rubycritic/generators/html/assets/stylesheets/prettify.custom_theme.css +69 -0
- data/lib/rubycritic/generators/html/base.rb +54 -0
- data/lib/rubycritic/generators/html/code_file.rb +51 -0
- data/lib/rubycritic/generators/html/code_index.rb +33 -0
- data/lib/rubycritic/generators/html/line.rb +37 -0
- data/lib/rubycritic/generators/html/overview.rb +38 -0
- data/lib/rubycritic/generators/html/simple_cov_index.rb +44 -0
- data/lib/rubycritic/generators/html/smells_index.rb +45 -0
- data/lib/rubycritic/generators/html/templates/code_file.html.erb +61 -0
- data/lib/rubycritic/generators/html/templates/code_index.html.erb +55 -0
- data/lib/rubycritic/generators/html/templates/layouts/application.html.erb +66 -0
- data/lib/rubycritic/generators/html/templates/line.html.erb +1 -0
- data/lib/rubycritic/generators/html/templates/overview.html.erb +68 -0
- data/lib/rubycritic/generators/html/templates/simple_cov_index.html.erb +44 -0
- data/lib/rubycritic/generators/html/templates/smells_index.html.erb +47 -0
- data/lib/rubycritic/generators/html/templates/smelly_line.html.erb +23 -0
- data/lib/rubycritic/generators/html/turbulence.rb +17 -0
- data/lib/rubycritic/generators/html/view_helpers.rb +58 -0
- data/lib/rubycritic/generators/html_report.rb +77 -0
- data/lib/rubycritic/generators/json/simple.rb +43 -0
- data/lib/rubycritic/generators/json_report.rb +26 -0
- data/lib/rubycritic/generators/lint_report.rb +30 -0
- data/lib/rubycritic/generators/text/lint.rb +41 -0
- data/lib/rubycritic/generators/text/list.rb +45 -0
- data/lib/rubycritic/generators/text/templates/lint.erb +3 -0
- data/lib/rubycritic/generators/text/templates/list.erb +12 -0
- data/lib/rubycritic/rake_task.rb +71 -0
- data/lib/rubycritic/reporter.rb +39 -0
- data/lib/rubycritic/revision_comparator.rb +45 -0
- data/lib/rubycritic/serializer.rb +32 -0
- data/lib/rubycritic/smells_status_setter.rb +18 -0
- data/lib/rubycritic/source_control_systems/base.rb +41 -0
- data/lib/rubycritic/source_control_systems/double.rb +19 -0
- data/lib/rubycritic/source_control_systems/git.rb +95 -0
- data/lib/rubycritic/source_control_systems/mercurial.rb +29 -0
- data/lib/rubycritic/source_control_systems/perforce.rb +116 -0
- data/lib/rubycritic/source_locator.rb +52 -0
- data/lib/rubycritic/version.rb +5 -0
- data/lib/rubycritic.rb +6 -0
- metadata +528 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'rubycritic/configuration'
|
5
|
+
require 'rubycritic/generators/html/overview'
|
6
|
+
require 'rubycritic/generators/html/smells_index'
|
7
|
+
require 'rubycritic/generators/html/code_index'
|
8
|
+
require 'rubycritic/generators/html/simple_cov_index'
|
9
|
+
require 'rubycritic/generators/html/code_file'
|
10
|
+
|
11
|
+
module RubyCritic
|
12
|
+
module Generator
|
13
|
+
class HtmlReport
|
14
|
+
ASSETS_DIR = File.expand_path('html/assets', __dir__)
|
15
|
+
|
16
|
+
def initialize(analysed_modules)
|
17
|
+
@analysed_modules = analysed_modules
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_report
|
21
|
+
create_directories_and_files
|
22
|
+
copy_assets_to_report_directory
|
23
|
+
puts "New critique at #{report_location}"
|
24
|
+
browser.open unless Config.no_browser
|
25
|
+
end
|
26
|
+
|
27
|
+
def browser
|
28
|
+
@browser ||= RubyCritic::Browser.new(report_location)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def create_directories_and_files
|
34
|
+
Array(generators).each do |generator|
|
35
|
+
FileUtils.mkdir_p(generator.file_directory)
|
36
|
+
File.open(generator.file_pathname, 'w+') do |file|
|
37
|
+
file.write(generator.render)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def generators
|
43
|
+
[overview_generator, code_index_generator, smells_index_generator, simple_cov_index_generator] + file_generators
|
44
|
+
end
|
45
|
+
|
46
|
+
def overview_generator
|
47
|
+
@overview_generator ||= Html::Overview.new(@analysed_modules)
|
48
|
+
end
|
49
|
+
|
50
|
+
def code_index_generator
|
51
|
+
Html::CodeIndex.new(@analysed_modules)
|
52
|
+
end
|
53
|
+
|
54
|
+
def smells_index_generator
|
55
|
+
Html::SmellsIndex.new(@analysed_modules)
|
56
|
+
end
|
57
|
+
|
58
|
+
def simple_cov_index_generator
|
59
|
+
Html::SimpleCovIndex.new(@analysed_modules)
|
60
|
+
end
|
61
|
+
|
62
|
+
def file_generators
|
63
|
+
@analysed_modules.map do |analysed_module|
|
64
|
+
Html::CodeFile.new(analysed_module)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def copy_assets_to_report_directory
|
69
|
+
FileUtils.cp_r(ASSETS_DIR, Config.root)
|
70
|
+
end
|
71
|
+
|
72
|
+
def report_location
|
73
|
+
overview_generator.file_href
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'rubycritic/version'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
module RubyCritic
|
8
|
+
module Generator
|
9
|
+
module Json
|
10
|
+
class Simple
|
11
|
+
def initialize(analysed_modules)
|
12
|
+
@analysed_modules = analysed_modules
|
13
|
+
end
|
14
|
+
|
15
|
+
FILE_NAME = 'report.json'.freeze
|
16
|
+
|
17
|
+
def render
|
18
|
+
JSON.dump(data)
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
{
|
23
|
+
metadata: {
|
24
|
+
rubycritic: {
|
25
|
+
version: RubyCritic::VERSION
|
26
|
+
}
|
27
|
+
},
|
28
|
+
analysed_modules: @analysed_modules,
|
29
|
+
score: @analysed_modules.score
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def file_directory
|
34
|
+
@file_directory ||= Pathname.new(Config.root)
|
35
|
+
end
|
36
|
+
|
37
|
+
def file_pathname
|
38
|
+
Pathname.new(file_directory).join FILE_NAME
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubycritic/generators/json/simple'
|
4
|
+
|
5
|
+
module RubyCritic
|
6
|
+
module Generator
|
7
|
+
class JsonReport
|
8
|
+
def initialize(analysed_modules)
|
9
|
+
@analysed_modules = analysed_modules
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate_report
|
13
|
+
FileUtils.mkdir_p(generator.file_directory)
|
14
|
+
File.open(generator.file_pathname, 'w+') do |file|
|
15
|
+
file.write(generator.render)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def generator
|
22
|
+
Json::Simple.new(@analysed_modules)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubycritic/generators/text/lint'
|
4
|
+
|
5
|
+
module RubyCritic
|
6
|
+
module Generator
|
7
|
+
class LintReport
|
8
|
+
def initialize(analysed_modules)
|
9
|
+
@analysed_modules = analysed_modules
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate_report
|
13
|
+
FileUtils.mkdir_p(generator.file_directory)
|
14
|
+
File.open(generator.file_pathname, 'w+') do |file|
|
15
|
+
file.write(reports.join("\n"))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def generator
|
20
|
+
Text::Lint
|
21
|
+
end
|
22
|
+
|
23
|
+
def reports
|
24
|
+
@analysed_modules.sort.map do |mod|
|
25
|
+
generator.new(mod).render
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
module RubyCritic
|
5
|
+
module Generator
|
6
|
+
module Text
|
7
|
+
class Lint
|
8
|
+
class << self
|
9
|
+
TEMPLATE_PATH = File.expand_path('templates/lint.erb', __dir__)
|
10
|
+
FILE_NAME = 'lint.txt'.freeze
|
11
|
+
|
12
|
+
def file_directory
|
13
|
+
@file_directory ||= Pathname.new(Config.root)
|
14
|
+
end
|
15
|
+
|
16
|
+
def file_pathname
|
17
|
+
Pathname.new(file_directory).join FILE_NAME
|
18
|
+
end
|
19
|
+
|
20
|
+
def erb_template
|
21
|
+
@erb_template ||= ERB.new(File.read(TEMPLATE_PATH), nil, '-')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(analysed_module)
|
26
|
+
@analysed_module = analysed_module
|
27
|
+
end
|
28
|
+
|
29
|
+
def render
|
30
|
+
erb_template.result(binding)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def erb_template
|
36
|
+
self.class.erb_template
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rainbow'
|
4
|
+
|
5
|
+
module RubyCritic
|
6
|
+
module Generator
|
7
|
+
module Text
|
8
|
+
class List
|
9
|
+
class << self
|
10
|
+
TEMPLATE_PATH = File.expand_path('templates/list.erb', __dir__)
|
11
|
+
|
12
|
+
def erb_template
|
13
|
+
@erb_template ||= ERB.new(File.read(TEMPLATE_PATH), nil, '-')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
RATING_TO_COLOR = {
|
18
|
+
'A' => :green,
|
19
|
+
'B' => :green,
|
20
|
+
'C' => :yellow,
|
21
|
+
'D' => :orange,
|
22
|
+
'F' => :red
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
def initialize(analysed_module)
|
26
|
+
@analysed_module = analysed_module
|
27
|
+
end
|
28
|
+
|
29
|
+
def render
|
30
|
+
erb_template.result(binding)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def erb_template
|
36
|
+
self.class.erb_template
|
37
|
+
end
|
38
|
+
|
39
|
+
def color
|
40
|
+
@color ||= RATING_TO_COLOR[@analysed_module.rating.to_s]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%= Rainbow(@analysed_module.name.to_s).foreground(color) %>:
|
2
|
+
Rating: <%= Rainbow(@analysed_module.rating.to_s).foreground(color) %>
|
3
|
+
Churn: <%= @analysed_module.churn %>
|
4
|
+
Complexity: <%= @analysed_module.complexity %>
|
5
|
+
Duplication: <%= @analysed_module.duplication %>
|
6
|
+
Smells: <%= @analysed_module.smells.count %>
|
7
|
+
<%- @analysed_module.smells.each do |smell| -%>
|
8
|
+
* <%= smell %>
|
9
|
+
<%- smell.locations.each do |location| -%>
|
10
|
+
- <%= location %>
|
11
|
+
<%- end -%>
|
12
|
+
<%- end -%>
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/tasklib'
|
5
|
+
require 'English'
|
6
|
+
require 'rubycritic/cli/application'
|
7
|
+
|
8
|
+
module RubyCritic
|
9
|
+
#
|
10
|
+
# A rake task that runs RubyCritic on a set of source files.
|
11
|
+
#
|
12
|
+
# This will create a task that can be run with:
|
13
|
+
#
|
14
|
+
# rake rubycritic
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
#
|
18
|
+
# require 'rubycritic/rake_task'
|
19
|
+
#
|
20
|
+
# RubyCritic::RakeTask.new do |task|
|
21
|
+
# task.paths = FileList['lib/**/*.rb', 'spec/**/*.rb']
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
class RakeTask < ::Rake::TaskLib
|
25
|
+
# Name of RubyCritic task. Defaults to :rubycritic.
|
26
|
+
attr_writer :name
|
27
|
+
|
28
|
+
# Glob pattern to match source files. Defaults to FileList['.'].
|
29
|
+
attr_writer :paths
|
30
|
+
|
31
|
+
# Use verbose output. If this is set to true, the task will print
|
32
|
+
# the rubycritic command to stdout. Defaults to false.
|
33
|
+
attr_writer :verbose
|
34
|
+
|
35
|
+
# You can pass all the options here in that are shown by "rubycritic -h" except for
|
36
|
+
# "-p / --path" since that is set separately. Defaults to ''.
|
37
|
+
attr_writer :options
|
38
|
+
|
39
|
+
def initialize(name = :rubycritic)
|
40
|
+
@name = name
|
41
|
+
@paths = FileList['.']
|
42
|
+
@options = ''
|
43
|
+
@verbose = false
|
44
|
+
|
45
|
+
yield self if block_given?
|
46
|
+
define_task
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :name, :paths, :verbose, :options
|
52
|
+
|
53
|
+
def define_task
|
54
|
+
desc 'Run RubyCritic'
|
55
|
+
task(name) { run_task }
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_task
|
59
|
+
if verbose
|
60
|
+
puts "\n\n!!! Running `#{name}` rake command\n"
|
61
|
+
puts "!!! Inspecting #{paths} #{options.empty? ? '' : "with options #{options}"}\n\n"
|
62
|
+
end
|
63
|
+
application = RubyCritic::Cli::Application.new(options_as_arguments + paths)
|
64
|
+
application.execute
|
65
|
+
end
|
66
|
+
|
67
|
+
def options_as_arguments
|
68
|
+
options.split(/\s+/)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyCritic
|
4
|
+
module Reporter
|
5
|
+
REPORT_GENERATOR_CLASS_FORMATS = %i[json console lint].freeze
|
6
|
+
|
7
|
+
def self.generate_report(analysed_modules)
|
8
|
+
RubyCritic::Config.formats.uniq.each do |format|
|
9
|
+
report_generator_class(format).new(analysed_modules).generate_report
|
10
|
+
end
|
11
|
+
RubyCritic::Config.formatters.each do |formatter|
|
12
|
+
report_generator_class_from_formatter(formatter).new(analysed_modules).generate_report
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.report_generator_class(config_format)
|
17
|
+
if REPORT_GENERATOR_CLASS_FORMATS.include? config_format
|
18
|
+
require "rubycritic/generators/#{config_format}_report"
|
19
|
+
Generator.const_get("#{config_format.capitalize}Report")
|
20
|
+
else
|
21
|
+
require 'rubycritic/generators/html_report'
|
22
|
+
Generator::HtmlReport
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.report_generator_class_from_formatter(formatter)
|
27
|
+
require_path, class_name = formatter.sub(/([^:]):([^:])/, '\1\;\2').split('\;', 2)
|
28
|
+
class_name ||= require_path
|
29
|
+
require require_path unless require_path == class_name
|
30
|
+
class_from_path(class_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.class_from_path(path)
|
34
|
+
path.split('::').inject(Object) { |obj, klass| obj.const_get klass }
|
35
|
+
rescue NameError => error
|
36
|
+
raise "Could not create reporter for class #{path}. Error: #{error}!"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubycritic/serializer'
|
4
|
+
require 'rubycritic/analysers_runner'
|
5
|
+
require 'rubycritic/smells_status_setter'
|
6
|
+
require 'rubycritic/version'
|
7
|
+
require 'digest/md5'
|
8
|
+
|
9
|
+
module RubyCritic
|
10
|
+
class RevisionComparator
|
11
|
+
SNAPSHOTS_DIR_NAME = 'snapshots'.freeze
|
12
|
+
|
13
|
+
def initialize(paths)
|
14
|
+
@paths = paths
|
15
|
+
end
|
16
|
+
|
17
|
+
def statuses=(analysed_modules_now)
|
18
|
+
return unless Config.source_control_system.revision?
|
19
|
+
|
20
|
+
analysed_modules_before = load_cached_analysed_modules
|
21
|
+
return unless analysed_modules_before
|
22
|
+
|
23
|
+
SmellsStatusSetter.set(
|
24
|
+
analysed_modules_before.flat_map(&:smells),
|
25
|
+
analysed_modules_now.flat_map(&:smells)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def load_cached_analysed_modules
|
32
|
+
Serializer.new(revision_file).load if File.file?(revision_file)
|
33
|
+
end
|
34
|
+
|
35
|
+
def revision_file
|
36
|
+
@revision_file ||= File.join(
|
37
|
+
Config.root,
|
38
|
+
SNAPSHOTS_DIR_NAME,
|
39
|
+
VERSION,
|
40
|
+
Digest::MD5.hexdigest(Marshal.dump(@paths)),
|
41
|
+
Config.source_control_system.head_reference
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module RubyCritic
|
6
|
+
class Serializer
|
7
|
+
def initialize(file)
|
8
|
+
@file = file
|
9
|
+
end
|
10
|
+
|
11
|
+
def load
|
12
|
+
Marshal.load(File.binread(@file))
|
13
|
+
end
|
14
|
+
|
15
|
+
def dump(content)
|
16
|
+
create_file_directory
|
17
|
+
File.open(@file, 'w+') do |file|
|
18
|
+
Marshal.dump(content, file)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def create_file_directory
|
25
|
+
FileUtils.mkdir_p(file_directory)
|
26
|
+
end
|
27
|
+
|
28
|
+
def file_directory
|
29
|
+
File.dirname(@file)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyCritic
|
4
|
+
module SmellsStatusSetter
|
5
|
+
def self.set(smells_before, smells_now)
|
6
|
+
old_smells = smells_now & smells_before
|
7
|
+
set_status(old_smells, :old)
|
8
|
+
new_smells = smells_now - smells_before
|
9
|
+
set_status(new_smells, :new)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.set_status(smells, status)
|
13
|
+
smells.each { |smell| smell.status = status }
|
14
|
+
end
|
15
|
+
|
16
|
+
private_class_method :set_status
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
require 'shellwords'
|
5
|
+
|
6
|
+
module RubyCritic
|
7
|
+
module SourceControlSystem
|
8
|
+
class Base
|
9
|
+
@@systems = []
|
10
|
+
|
11
|
+
def self.register_system
|
12
|
+
@@systems << self
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.systems
|
16
|
+
@@systems
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create
|
20
|
+
supported_system = systems.find(&:supported?)
|
21
|
+
if supported_system
|
22
|
+
supported_system.new
|
23
|
+
else
|
24
|
+
puts 'RubyCritic can provide more feedback if you use '\
|
25
|
+
"a #{connected_system_names} repository. "\
|
26
|
+
'Churn will not be calculated.'
|
27
|
+
Double.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.connected_system_names
|
32
|
+
"#{systems[0...-1].join(', ')} or #{systems[-1]}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
require 'rubycritic/source_control_systems/double'
|
39
|
+
require 'rubycritic/source_control_systems/git'
|
40
|
+
require 'rubycritic/source_control_systems/mercurial'
|
41
|
+
require 'rubycritic/source_control_systems/perforce'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyCritic
|
4
|
+
module SourceControlSystem
|
5
|
+
class Double < Base
|
6
|
+
def revisions_count(_path)
|
7
|
+
0
|
8
|
+
end
|
9
|
+
|
10
|
+
def date_of_last_commit(_path)
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def revision?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tty/which'
|
4
|
+
|
5
|
+
module RubyCritic
|
6
|
+
module SourceControlSystem
|
7
|
+
class Git < Base
|
8
|
+
register_system
|
9
|
+
GIT_EXECUTABLE = TTY::Which.which('git')
|
10
|
+
|
11
|
+
def self.git(arg)
|
12
|
+
if Gem.win_platform?
|
13
|
+
`\"#{GIT_EXECUTABLE}\" #{arg}`
|
14
|
+
else
|
15
|
+
`#{GIT_EXECUTABLE} #{arg}`
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def git(arg)
|
20
|
+
self.class.git(arg)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.supported?
|
24
|
+
git('branch 2>&1') && $CHILD_STATUS.success?
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.to_s
|
28
|
+
'Git'
|
29
|
+
end
|
30
|
+
|
31
|
+
def revisions_count(path)
|
32
|
+
git("log --follow --format=%h #{path.shellescape}").count("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
def date_of_last_commit(path)
|
36
|
+
git("log -1 --date=iso --format=%ad #{path.shellescape}").chomp!
|
37
|
+
end
|
38
|
+
|
39
|
+
def revision?
|
40
|
+
head_reference && $CHILD_STATUS.success?
|
41
|
+
end
|
42
|
+
|
43
|
+
def head_reference
|
44
|
+
git('rev-parse --verify HEAD').chomp!
|
45
|
+
end
|
46
|
+
|
47
|
+
def travel_to_head
|
48
|
+
stash_successful = stash_changes
|
49
|
+
yield
|
50
|
+
ensure
|
51
|
+
travel_to_original_state if stash_successful
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.switch_branch(branch)
|
55
|
+
uncommitted_changes? ? abort('Uncommitted changes are present.') : `git checkout #{branch}`
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.uncommitted_changes?
|
59
|
+
!`git diff-index HEAD --`.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.modified_files
|
63
|
+
modified_files = `git diff --name-status #{Config.base_branch} #{Config.feature_branch}`
|
64
|
+
modified_files.split("\n").map do |line|
|
65
|
+
next if line.start_with?('D')
|
66
|
+
|
67
|
+
file_name = line.split("\t")[1]
|
68
|
+
file_name
|
69
|
+
end.compact
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.current_branch
|
73
|
+
branch_list = `git branch`
|
74
|
+
branch_list.match(/\*.*$/)[0].gsub('* ', '')
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def stash_changes
|
80
|
+
stashes_count_before = stashes_count
|
81
|
+
git('stash')
|
82
|
+
stashes_count_after = stashes_count
|
83
|
+
stashes_count_after > stashes_count_before
|
84
|
+
end
|
85
|
+
|
86
|
+
def stashes_count
|
87
|
+
git('stash list --format=%h').count("\n")
|
88
|
+
end
|
89
|
+
|
90
|
+
def travel_to_original_state
|
91
|
+
git('stash pop')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyCritic
|
4
|
+
module SourceControlSystem
|
5
|
+
class Mercurial < Base
|
6
|
+
register_system
|
7
|
+
|
8
|
+
def self.supported?
|
9
|
+
`hg verify 2>&1` && $CHILD_STATUS.success?
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.to_s
|
13
|
+
'Mercurial'
|
14
|
+
end
|
15
|
+
|
16
|
+
def revisions_count(path)
|
17
|
+
`hg log #{path.shellescape} --template '1'`.size
|
18
|
+
end
|
19
|
+
|
20
|
+
def date_of_last_commit(path)
|
21
|
+
`hg log #{path.shellescape} --template '{date|isodate}' --limit 1`.chomp
|
22
|
+
end
|
23
|
+
|
24
|
+
def revision?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|