rubycritic-simplecov 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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