rubycritic-simplecov 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +290 -0
  3. data/CONTRIBUTING.md +93 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +259 -0
  7. data/ROADMAP.md +56 -0
  8. data/Rakefile +28 -0
  9. data/bin/rubycritic +10 -0
  10. data/lib/rubycritic/analysers/attributes.rb +29 -0
  11. data/lib/rubycritic/analysers/churn.rb +30 -0
  12. data/lib/rubycritic/analysers/complexity.rb +30 -0
  13. data/lib/rubycritic/analysers/coverage.rb +118 -0
  14. data/lib/rubycritic/analysers/helpers/ast_node.rb +76 -0
  15. data/lib/rubycritic/analysers/helpers/flay.rb +13 -0
  16. data/lib/rubycritic/analysers/helpers/flog.rb +17 -0
  17. data/lib/rubycritic/analysers/helpers/methods_counter.rb +25 -0
  18. data/lib/rubycritic/analysers/helpers/modules_locator.rb +42 -0
  19. data/lib/rubycritic/analysers/helpers/parser.rb +14 -0
  20. data/lib/rubycritic/analysers/helpers/reek.rb +11 -0
  21. data/lib/rubycritic/analysers/smells/flay.rb +79 -0
  22. data/lib/rubycritic/analysers/smells/flog.rb +69 -0
  23. data/lib/rubycritic/analysers/smells/reek.rb +53 -0
  24. data/lib/rubycritic/analysers_runner.rb +41 -0
  25. data/lib/rubycritic/analysis_summary.rb +40 -0
  26. data/lib/rubycritic/browser.rb +19 -0
  27. data/lib/rubycritic/cli/application.rb +34 -0
  28. data/lib/rubycritic/cli/options/argv.rb +137 -0
  29. data/lib/rubycritic/cli/options/file.rb +100 -0
  30. data/lib/rubycritic/cli/options.rb +33 -0
  31. data/lib/rubycritic/colorize.rb +17 -0
  32. data/lib/rubycritic/command_factory.rb +25 -0
  33. data/lib/rubycritic/commands/base.rb +18 -0
  34. data/lib/rubycritic/commands/ci.rb +14 -0
  35. data/lib/rubycritic/commands/compare.rb +106 -0
  36. data/lib/rubycritic/commands/default.rb +38 -0
  37. data/lib/rubycritic/commands/help.rb +18 -0
  38. data/lib/rubycritic/commands/status_reporter.rb +45 -0
  39. data/lib/rubycritic/commands/utils/build_number_file.rb +37 -0
  40. data/lib/rubycritic/commands/version.rb +16 -0
  41. data/lib/rubycritic/configuration.rb +69 -0
  42. data/lib/rubycritic/core/analysed_module.rb +93 -0
  43. data/lib/rubycritic/core/analysed_modules_collection.rb +95 -0
  44. data/lib/rubycritic/core/location.rb +47 -0
  45. data/lib/rubycritic/core/rating.rb +30 -0
  46. data/lib/rubycritic/core/smell.rb +84 -0
  47. data/lib/rubycritic/generators/console_report.rb +20 -0
  48. data/lib/rubycritic/generators/html/assets/fonts/FontAwesome.otf +0 -0
  49. data/lib/rubycritic/generators/html/assets/fonts/Roboto-Medium.ttf +0 -0
  50. data/lib/rubycritic/generators/html/assets/fonts/Roboto-Regular.ttf +0 -0
  51. data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.eot +0 -0
  52. data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.svg +2671 -0
  53. data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.ttf +0 -0
  54. data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.woff +0 -0
  55. data/lib/rubycritic/generators/html/assets/fonts/fontawesome-webfont.woff2 +0 -0
  56. data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  57. data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.svg +288 -0
  58. data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  59. data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  60. data/lib/rubycritic/generators/html/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
  61. data/lib/rubycritic/generators/html/assets/images/logo.png +0 -0
  62. data/lib/rubycritic/generators/html/assets/javascripts/application.js +281 -0
  63. data/lib/rubycritic/generators/html/assets/javascripts/bootstrap.min.js +7 -0
  64. data/lib/rubycritic/generators/html/assets/javascripts/highcharts.src-4.0.1.js +17672 -0
  65. data/lib/rubycritic/generators/html/assets/javascripts/jquery.filtertable.min.js +13 -0
  66. data/lib/rubycritic/generators/html/assets/javascripts/jquery.min.js +4 -0
  67. data/lib/rubycritic/generators/html/assets/javascripts/jquery.scrollTo.min.js +7 -0
  68. data/lib/rubycritic/generators/html/assets/javascripts/jquery.tablesorter.js +1031 -0
  69. data/lib/rubycritic/generators/html/assets/javascripts/jquery.tablesorter.min.js +4 -0
  70. data/lib/rubycritic/generators/html/assets/javascripts/jquery.timeago.js +231 -0
  71. data/lib/rubycritic/generators/html/assets/javascripts/prettify.js +46 -0
  72. data/lib/rubycritic/generators/html/assets/stylesheets/application.css +570 -0
  73. data/lib/rubycritic/generators/html/assets/stylesheets/bootstrap.min.css +6 -0
  74. data/lib/rubycritic/generators/html/assets/stylesheets/font-awesome.min.css +4 -0
  75. data/lib/rubycritic/generators/html/assets/stylesheets/prettify.css +1 -0
  76. data/lib/rubycritic/generators/html/assets/stylesheets/prettify.custom_theme.css +69 -0
  77. data/lib/rubycritic/generators/html/base.rb +54 -0
  78. data/lib/rubycritic/generators/html/code_file.rb +51 -0
  79. data/lib/rubycritic/generators/html/code_index.rb +33 -0
  80. data/lib/rubycritic/generators/html/line.rb +37 -0
  81. data/lib/rubycritic/generators/html/overview.rb +38 -0
  82. data/lib/rubycritic/generators/html/simple_cov_index.rb +44 -0
  83. data/lib/rubycritic/generators/html/smells_index.rb +45 -0
  84. data/lib/rubycritic/generators/html/templates/code_file.html.erb +61 -0
  85. data/lib/rubycritic/generators/html/templates/code_index.html.erb +55 -0
  86. data/lib/rubycritic/generators/html/templates/layouts/application.html.erb +66 -0
  87. data/lib/rubycritic/generators/html/templates/line.html.erb +1 -0
  88. data/lib/rubycritic/generators/html/templates/overview.html.erb +68 -0
  89. data/lib/rubycritic/generators/html/templates/simple_cov_index.html.erb +44 -0
  90. data/lib/rubycritic/generators/html/templates/smells_index.html.erb +47 -0
  91. data/lib/rubycritic/generators/html/templates/smelly_line.html.erb +23 -0
  92. data/lib/rubycritic/generators/html/turbulence.rb +17 -0
  93. data/lib/rubycritic/generators/html/view_helpers.rb +58 -0
  94. data/lib/rubycritic/generators/html_report.rb +77 -0
  95. data/lib/rubycritic/generators/json/simple.rb +43 -0
  96. data/lib/rubycritic/generators/json_report.rb +26 -0
  97. data/lib/rubycritic/generators/lint_report.rb +30 -0
  98. data/lib/rubycritic/generators/text/lint.rb +41 -0
  99. data/lib/rubycritic/generators/text/list.rb +45 -0
  100. data/lib/rubycritic/generators/text/templates/lint.erb +3 -0
  101. data/lib/rubycritic/generators/text/templates/list.erb +12 -0
  102. data/lib/rubycritic/rake_task.rb +71 -0
  103. data/lib/rubycritic/reporter.rb +39 -0
  104. data/lib/rubycritic/revision_comparator.rb +45 -0
  105. data/lib/rubycritic/serializer.rb +32 -0
  106. data/lib/rubycritic/smells_status_setter.rb +18 -0
  107. data/lib/rubycritic/source_control_systems/base.rb +41 -0
  108. data/lib/rubycritic/source_control_systems/double.rb +19 -0
  109. data/lib/rubycritic/source_control_systems/git.rb +95 -0
  110. data/lib/rubycritic/source_control_systems/mercurial.rb +29 -0
  111. data/lib/rubycritic/source_control_systems/perforce.rb +116 -0
  112. data/lib/rubycritic/source_locator.rb +52 -0
  113. data/lib/rubycritic/version.rb +5 -0
  114. data/lib/rubycritic.rb +6 -0
  115. 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,3 @@
1
+ <%- @analysed_module.smells.each do |smell| -%>
2
+ <%= smell.locations.first.to_s %>: <%= smell.to_s %>
3
+ <%- 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