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
data/ROADMAP.md ADDED
@@ -0,0 +1,56 @@
1
+ These are more nice-to-haves than promises. We can always dream. But this is what we hope to improve in RubyCritic:
2
+
3
+ - [ ] Explain every single code smell. This includes the "Churn vs Complexity" scatter plot and other graphs that may be implemented.
4
+
5
+ - [ ] Provide suggestions to fix every code smell. Figure out how to present them in the UI.
6
+
7
+ - [ ] Improve how modules are graded. Each module is awarded a score, depending on:
8
+
9
+ * Its complexity, based off of this [post by Jake Scruggs](http://jakescruggs.blogspot.pt/2008/08/whats-good-flog-score.html), creator of the MetricFu gem. For every 25 points of complexity (as calculated by Flog), the score increases by 1.
10
+
11
+ * Its duplication mass, based off of observations of a few Code Climate repos. For every 25 points of mass (as calculated by Flay), the score increases by 1.
12
+
13
+ Finally, this score is translated to a grade like [this](https://github.com/whitesmith/rubycritic/blob/43005e7b76dd0c648c7715133e42afdd6ea9a065/lib/rubycritic/core/rating.rb), based off of a [Code Climate blog post](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/#value-objects).
14
+
15
+ - [ ] Implement a project rating, ala Code Climate.
16
+
17
+ - [ ] Explain ratings. What's the difference between an A and a B? Bryan Helmkamp, the creator of Code Climate, wrote [a great essay on the subject](https://gist.github.com/brynary/21369b5892525e1bd102). #63
18
+
19
+ - [ ] Explore alternative ratings. GPA and A-F grades are quite US centric. #50
20
+
21
+ - [ ] Make the gem configurable using a dotfile like .rubycritic.yml. #30
22
+ Here are some possible settings:
23
+
24
+ - [ ] Quiet mode. As of right now, any Ruby code that is unparsable will be reported three times (one time by Flog, another by Flay and another by Reek). Only Flog implements a quiet option, which means we have to implement that quiet option on Flay and on Reek before we can add it to RubyCritic. Or we could just do `$stderr = StringIO.new`... I wonder if that's really really smart or really really stupid.
25
+
26
+ - [ ] Verbose mode. #61
27
+
28
+ - [ ] Ignoring/excluding files. #11
29
+
30
+ - [ ] Allow configuring date range of Churn calculation. #37 Right now, they are limited to the last year. #39
31
+
32
+ - [ ] Highlight blocks of duplicated code instead of just the start of the block. This will probably require rewriting Flay with [parser](https://github.com/whitequark/parser) instead of ruby_parser.
33
+
34
+ - [ ] Integrate other gems, like:
35
+
36
+ - [ ] [Simplecov](https://github.com/colszowka/simplecov) to provide code coverage
37
+
38
+ - [ ] [Rubocop](https://github.com/bbatsov/rubocop/) to provide style issues
39
+
40
+ - [ ] [Brakeman](https://github.com/presidentbeef/brakeman) to provide security issues (Rails-only feature)
41
+
42
+ - [ ] [Rails Best Practices](https://github.com/railsbp/rails_best_practices) to provide Rails smells (Rails-only feature) #14
43
+
44
+ - [ ] [SandiMeter](https://github.com/makaroni4/sandi_meter) #15
45
+
46
+ - [ ] Improve UI.
47
+
48
+ - [ ] Make it beautiful.
49
+
50
+ - [ ] Figure out where the "suggestions to fix code smells" should be presented.
51
+
52
+ - [ ] Create some kind of toggle option between various types of issues. Just like we can toggle between "Smells" and "Coverage" in Code Climate:
53
+
54
+ ![Code Climate Toggle Option](https://camo.githubusercontent.com/d97fc62dae6ebef1f35bda91942d4a6bacc445b2/687474703a2f2f626c6f672e636f6465636c696d6174652e636f6d2f696d616765732f706f7374732f74657374696e672e676966)
55
+
56
+ Having an option to toggle between "Smells", "Security" (Brakeman) and "Style" (Rubocop) would be great. But that's already assuming we can integrate those gems into RubyCritic.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+ require 'rubocop/rake_task'
6
+ require 'cucumber/rake/task'
7
+ require 'reek/rake/task'
8
+ require 'rubycritic/rake_task'
9
+
10
+ Rake::TestTask.new do |task|
11
+ task.libs.push 'lib'
12
+ task.libs.push 'test'
13
+ task.pattern = 'test/**/*_test.rb'
14
+ end
15
+
16
+ Cucumber::Rake::Task.new(:features) do |t|
17
+ t.cucumber_opts = 'features --format progress --color'
18
+ end
19
+
20
+ RuboCop::RakeTask.new
21
+
22
+ Reek::Rake::Task.new
23
+
24
+ RubyCritic::RakeTask.new do |task|
25
+ task.paths = FileList['lib/**/*.rb']
26
+ end
27
+
28
+ task default: %i[test features reek rubocop]
data/bin/rubycritic ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Always look in the lib directory of this gem
5
+ # first when searching the load path
6
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
7
+
8
+ require 'rubycritic/cli/application'
9
+
10
+ exit RubyCritic::Cli::Application.new(ARGV).execute
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycritic/analysers/helpers/methods_counter'
4
+ require 'rubycritic/analysers/helpers/modules_locator'
5
+ require 'rubycritic/colorize'
6
+
7
+ module RubyCritic
8
+ module Analyser
9
+ class Attributes
10
+ include Colorize
11
+ def initialize(analysed_modules)
12
+ @analysed_modules = analysed_modules
13
+ end
14
+
15
+ def run
16
+ @analysed_modules.each do |analysed_module|
17
+ analysed_module.methods_count = MethodsCounter.new(analysed_module).count
18
+ analysed_module.name = ModulesLocator.new(analysed_module).first_name
19
+ print green '.'
20
+ end
21
+ puts ''
22
+ end
23
+
24
+ def to_s
25
+ 'attributes'
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycritic/colorize'
4
+
5
+ module RubyCritic
6
+ module Analyser
7
+ class Churn
8
+ include Colorize
9
+ attr_writer :source_control_system
10
+
11
+ def initialize(analysed_modules)
12
+ @analysed_modules = analysed_modules
13
+ @source_control_system = Config.source_control_system
14
+ end
15
+
16
+ def run
17
+ @analysed_modules.each do |analysed_module|
18
+ analysed_module.churn = @source_control_system.revisions_count(analysed_module.path)
19
+ analysed_module.committed_at = @source_control_system.date_of_last_commit(analysed_module.path)
20
+ print green '.'
21
+ end
22
+ puts ''
23
+ end
24
+
25
+ def to_s
26
+ 'churn'
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycritic/analysers/helpers/flog'
4
+ require 'rubycritic/colorize'
5
+
6
+ module RubyCritic
7
+ module Analyser
8
+ class Complexity
9
+ include Colorize
10
+ def initialize(analysed_modules)
11
+ @flog = Flog.new
12
+ @analysed_modules = analysed_modules
13
+ end
14
+
15
+ def run
16
+ @analysed_modules.each do |analysed_module|
17
+ @flog.reset
18
+ @flog.flog(analysed_module.path)
19
+ analysed_module.complexity = @flog.total_score.round(2)
20
+ print green '.'
21
+ end
22
+ puts ''
23
+ end
24
+
25
+ def to_s
26
+ 'complexity'
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycritic/colorize'
4
+ require 'json'
5
+ require 'simplecov'
6
+
7
+ module RubyCritic
8
+ module Analyser
9
+ class Coverage
10
+ include Colorize
11
+
12
+ def initialize(analysed_modules)
13
+ @analysed_modules = analysed_modules
14
+ @result = results.first
15
+ end
16
+
17
+ def run
18
+ @analysed_modules.each do |analysed_module|
19
+ analysed_module.coverage = find_coverage_percentage(analysed_module)
20
+ print green '.'
21
+ end
22
+ puts ''
23
+ end
24
+
25
+ def to_s
26
+ 'simple_cov'
27
+ end
28
+
29
+ private
30
+
31
+ def find_coverage_percentage(analysed_module)
32
+ source_file = find_source_file(analysed_module)
33
+
34
+ return 0 unless source_file
35
+
36
+ source_file.covered_percent
37
+ end
38
+
39
+ def find_source_file(analysed_module)
40
+ return unless @result
41
+
42
+ needle = File.join(SimpleCov.root, analysed_module.path)
43
+
44
+ @result.source_files.detect { |file| file.filename == needle }
45
+ end
46
+
47
+ # The path to the .resultset.json cache file
48
+ def resultset_path
49
+ File.join(SimpleCov.coverage_path, '.resultset.json')
50
+ end
51
+
52
+ def resultset_writelock
53
+ File.join(SimpleCov.coverage_path, '.resultset.json.lock')
54
+ end
55
+
56
+ # Loads the cached resultset from JSON and returns it as a Hash,
57
+ # caching it for subsequent accesses.
58
+ def resultset
59
+ @resultset ||= begin
60
+ if (data = stored_data)
61
+ begin
62
+ JSON.parse(data) || {}
63
+ rescue JSON::ParserError => err
64
+ puts "Error: Loading .resultset.json: #{err.message}"
65
+ {}
66
+ end
67
+ else
68
+ {}
69
+ end
70
+ end
71
+ end
72
+
73
+ # Returns the contents of the resultset cache as a string or if the file is missing or empty nil
74
+ def stored_data
75
+ synchronize_resultset do
76
+ return unless File.exist?(resultset_path)
77
+
78
+ return unless (data = File.read(resultset_path))
79
+
80
+ return if data.length < 2
81
+
82
+ data
83
+ end
84
+ end
85
+
86
+ # Ensure only one process is reading or writing the resultset at any
87
+ # given time
88
+ def synchronize_resultset
89
+ # make it reentrant
90
+ return yield if @resultset_locked == true
91
+
92
+ return yield unless File.exist?(resultset_writelock)
93
+
94
+ begin
95
+ @resultset_locked = true
96
+ File.open(resultset_writelock, 'w+') do |file|
97
+ file.flock(File::LOCK_EX)
98
+ yield
99
+ end
100
+ ensure
101
+ @resultset_locked = false
102
+ end
103
+ end
104
+
105
+ # Gets the resultset hash and re-creates all included instances
106
+ # of SimpleCov::Result from that.
107
+ # All results that are above the SimpleCov.merge_timeout will be
108
+ # dropped. Returns an array of SimpleCov::Result items.
109
+ def results
110
+ array = []
111
+ resultset.each do |command_name, data|
112
+ array << ::SimpleCov::Result.from_hash(command_name => data)
113
+ end
114
+ array
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parser
4
+ module AST
5
+ class Node
6
+ MODULE_TYPES = %i[module class].freeze
7
+
8
+ def count_nodes_of_type(*types)
9
+ count = 0
10
+ recursive_children do |child|
11
+ count += 1 if types.include?(child.type)
12
+ end
13
+ count
14
+ end
15
+
16
+ def recursive_children
17
+ children.each do |child|
18
+ next unless child.is_a?(Parser::AST::Node)
19
+
20
+ yield child
21
+ child.recursive_children { |grand_child| yield grand_child }
22
+ end
23
+ end
24
+
25
+ def module_names
26
+ ast_node_children = children.select do |child|
27
+ child.is_a?(Parser::AST::Node)
28
+ end
29
+
30
+ children_modules = ast_node_children.flat_map(&:module_names)
31
+
32
+ if MODULE_TYPES.include?(type)
33
+ module_names_with_children children_modules
34
+ else
35
+ children_modules
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def module_names_with_children(children_modules)
42
+ if children_modules.empty?
43
+ [module_name]
44
+ else
45
+ children_modules.map do |children_module|
46
+ "#{module_name}::#{children_module}"
47
+ end
48
+ end
49
+ end
50
+
51
+ def module_name
52
+ name_segments = []
53
+ current_node = children[0]
54
+ while current_node
55
+ name_segments.unshift(current_node.children[1])
56
+ current_node = current_node.children[0]
57
+ end
58
+ name_segments.join('::')
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ module RubyCritic
65
+ module AST
66
+ class EmptyNode
67
+ def count_nodes_of_type(*)
68
+ 0
69
+ end
70
+
71
+ def module_names
72
+ []
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'flay'
4
+
5
+ module RubyCritic
6
+ class Flay < ::Flay
7
+ def initialize(paths)
8
+ super()
9
+ process(*paths)
10
+ analyze
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'flog'
4
+
5
+ module RubyCritic
6
+ class Flog < ::Flog
7
+ DEFAULT_OPTIONS = {
8
+ all: true,
9
+ continue: true,
10
+ methods: true
11
+ }.freeze
12
+
13
+ def initialize
14
+ super(DEFAULT_OPTIONS)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycritic/analysers/helpers/parser'
4
+
5
+ module RubyCritic
6
+ class MethodsCounter
7
+ def initialize(analysed_module)
8
+ @analysed_module = analysed_module
9
+ end
10
+
11
+ def count
12
+ node.count_nodes_of_type(:def, :defs)
13
+ end
14
+
15
+ private
16
+
17
+ def node
18
+ Parser.parse(content)
19
+ end
20
+
21
+ def content
22
+ File.read(@analysed_module.path)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycritic/analysers/helpers/parser'
4
+
5
+ module RubyCritic
6
+ class ModulesLocator
7
+ def initialize(analysed_module)
8
+ @analysed_module = analysed_module
9
+ end
10
+
11
+ def first_name
12
+ names.first
13
+ end
14
+
15
+ def names
16
+ names = node.module_names
17
+ if names.empty?
18
+ name_from_path
19
+ else
20
+ names
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def node
27
+ Parser.parse(content)
28
+ end
29
+
30
+ def content
31
+ File.read(@analysed_module.path)
32
+ end
33
+
34
+ def name_from_path
35
+ [file_name.split('_').map(&:capitalize).join]
36
+ end
37
+
38
+ def file_name
39
+ @analysed_module.pathname.basename.sub_ext('').to_s
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parser/current'
4
+ require 'rubycritic/analysers/helpers/ast_node'
5
+
6
+ module RubyCritic
7
+ module Parser
8
+ def self.parse(content)
9
+ ::Parser::CurrentRuby.parse(content) || AST::EmptyNode.new
10
+ rescue ::Parser::SyntaxError
11
+ AST::EmptyNode.new
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'reek'
4
+
5
+ module RubyCritic
6
+ class Reek < ::Reek::Examiner
7
+ def initialize(analysed_module)
8
+ super(analysed_module, configuration: ::Reek::Configuration::AppConfiguration.from_default_path)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycritic/analysers/helpers/flay'
4
+ require 'rubycritic/core/smell'
5
+ require 'rubycritic/colorize'
6
+
7
+ module RubyCritic
8
+ module Analyser
9
+ class FlaySmells
10
+ include Colorize
11
+ def initialize(analysed_modules)
12
+ @analysed_modules = paths_to_analysed_modules(analysed_modules)
13
+ @flay = Flay.new(@analysed_modules.keys)
14
+ end
15
+
16
+ def run
17
+ @flay.hashes.each do |structural_hash, nodes|
18
+ analyze_modules(structural_hash, nodes)
19
+ print green '.'
20
+ end
21
+ puts ''
22
+ end
23
+
24
+ def to_s
25
+ 'flay smells'
26
+ end
27
+
28
+ private
29
+
30
+ def paths_to_analysed_modules(analysed_modules)
31
+ paths = {}
32
+ analysed_modules.each do |analysed_module|
33
+ paths[analysed_module.path] = analysed_module
34
+ end
35
+ paths
36
+ end
37
+
38
+ def create_smell(structural_hash, nodes)
39
+ mass = @flay.masses[structural_hash]
40
+ Smell.new(
41
+ locations: smell_locations(nodes),
42
+ context: similarity(structural_hash),
43
+ message: "found in #{nodes.size} nodes",
44
+ score: mass,
45
+ type: 'DuplicateCode',
46
+ analyser: 'flay',
47
+ cost: cost(mass)
48
+ )
49
+ end
50
+
51
+ def smell_locations(nodes)
52
+ nodes.map do |node|
53
+ Location.new(node.file, node.line)
54
+ end.sort
55
+ end
56
+
57
+ def similarity(structural_hash)
58
+ if @flay.identical[structural_hash]
59
+ 'Identical code'
60
+ else
61
+ 'Similar code'
62
+ end
63
+ end
64
+
65
+ def cost(mass)
66
+ mass / 25
67
+ end
68
+
69
+ def analyze_modules(structural_hash, nodes)
70
+ nodes.map(&:file).uniq.each do |file|
71
+ @analysed_modules[file].smells << create_smell(structural_hash, nodes)
72
+ end
73
+ nodes.each do |node|
74
+ @analysed_modules[node.file].duplication += node.mass
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycritic/analysers/helpers/flog'
4
+ require 'rubycritic/core/smell'
5
+ require 'rubycritic/colorize'
6
+
7
+ module RubyCritic
8
+ module Analyser
9
+ class FlogSmells
10
+ include Colorize
11
+ HIGH_COMPLEXITY_SCORE_THRESHOLD = 25
12
+ VERY_HIGH_COMPLEXITY_SCORE_THRESHOLD = 60
13
+
14
+ def initialize(analysed_modules)
15
+ @flog = Flog.new
16
+ @analysed_modules = analysed_modules
17
+ end
18
+
19
+ def run
20
+ @analysed_modules.each do |analysed_module|
21
+ add_smells_to(analysed_module)
22
+ print green '.'
23
+ end
24
+ puts ''
25
+ end
26
+
27
+ def to_s
28
+ 'flog smells'
29
+ end
30
+
31
+ private
32
+
33
+ def add_smells_to(analysed_module)
34
+ @flog.reset
35
+ @flog.flog(analysed_module.path)
36
+ @flog.each_by_score do |class_method, original_score|
37
+ score = original_score.round
38
+ analysed_module.smells << create_smell(class_method, score) if score >= HIGH_COMPLEXITY_SCORE_THRESHOLD
39
+ end
40
+ end
41
+
42
+ def create_smell(context, score)
43
+ Smell.new(
44
+ locations: smell_locations(context),
45
+ context: context,
46
+ message: "has a flog score of #{score}",
47
+ score: score,
48
+ type: type(score),
49
+ analyser: 'flog',
50
+ cost: 0
51
+ )
52
+ end
53
+
54
+ def smell_locations(context)
55
+ line = @flog.method_locations[context]
56
+ file_path, file_line = line.split(':')
57
+ [Location.new(file_path, file_line)]
58
+ end
59
+
60
+ def type(score)
61
+ if score >= VERY_HIGH_COMPLEXITY_SCORE_THRESHOLD
62
+ 'VeryHighComplexity'
63
+ else
64
+ 'HighComplexity'
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end