rubycritic 0.0.5 → 1.4.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +593 -0
  3. data/.travis.yml +11 -0
  4. data/CHANGELOG.md +45 -0
  5. data/CONTRIBUTING.md +67 -15
  6. data/README.md +105 -14
  7. data/Rakefile +9 -6
  8. data/bin/rubycritic +3 -1
  9. data/lib/rubycritic/analysers/attributes.rb +21 -0
  10. data/lib/rubycritic/analysers/churn.rb +21 -0
  11. data/lib/rubycritic/analysers/complexity.rb +22 -0
  12. data/lib/rubycritic/analysers/helpers/ast_node.rb +69 -0
  13. data/lib/rubycritic/analysers/{config.reek → helpers/config.reek} +8 -0
  14. data/lib/rubycritic/analysers/helpers/flay.rb +13 -0
  15. data/lib/rubycritic/analysers/helpers/flog.rb +17 -0
  16. data/lib/rubycritic/analysers/helpers/methods_counter.rb +25 -0
  17. data/lib/rubycritic/analysers/helpers/modules_locator.rb +43 -0
  18. data/lib/rubycritic/analysers/helpers/parser.rb +12 -0
  19. data/lib/rubycritic/analysers/helpers/reek.rb +17 -0
  20. data/lib/rubycritic/analysers/smells/flay.rb +68 -0
  21. data/lib/rubycritic/analysers/smells/flog.rb +62 -0
  22. data/lib/rubycritic/analysers/smells/reek.rb +44 -0
  23. data/lib/rubycritic/analysers_runner.rb +21 -10
  24. data/lib/rubycritic/cli/application.rb +24 -0
  25. data/lib/rubycritic/cli/options.rb +79 -0
  26. data/lib/rubycritic/commands/ci.rb +26 -0
  27. data/lib/rubycritic/commands/default.rb +28 -0
  28. data/lib/rubycritic/commands/help.rb +13 -0
  29. data/lib/rubycritic/commands/version.rb +11 -0
  30. data/lib/rubycritic/configuration.rb +33 -0
  31. data/lib/rubycritic/core/analysed_module.rb +66 -0
  32. data/lib/rubycritic/{location.rb → core/location.rb} +17 -2
  33. data/lib/rubycritic/core/rating.rb +30 -0
  34. data/lib/rubycritic/{smell.rb → core/smell.rb} +22 -13
  35. data/lib/rubycritic/generators/html/assets/javascripts/application.js +104 -0
  36. data/lib/rubycritic/generators/html/assets/javascripts/highcharts.src-4.0.1.js +17672 -0
  37. data/lib/rubycritic/generators/html/assets/javascripts/jquery.floatThead-v1.2.7.js +754 -0
  38. data/lib/rubycritic/generators/html/assets/javascripts/jquery.scrollTo-1.4.11.js +186 -0
  39. data/lib/rubycritic/generators/html/assets/javascripts/jquery.tablesorter.js +2089 -0
  40. data/lib/rubycritic/generators/html/assets/javascripts/jquery.timeago-v1.4.1.js +214 -0
  41. data/lib/rubycritic/generators/html/assets/stylesheets/application.css +303 -0
  42. data/lib/rubycritic/{report_generators → generators/html}/assets/stylesheets/prettify.custom_theme.css +1 -4
  43. data/lib/rubycritic/generators/html/base.rb +52 -0
  44. data/lib/rubycritic/generators/html/code_file.rb +40 -0
  45. data/lib/rubycritic/generators/html/code_index.rb +26 -0
  46. data/lib/rubycritic/generators/html/line.rb +37 -0
  47. data/lib/rubycritic/generators/html/overview.rb +27 -0
  48. data/lib/rubycritic/generators/html/smells_index.rb +38 -0
  49. data/lib/rubycritic/generators/html/templates/code_file.html.erb +38 -0
  50. data/lib/rubycritic/generators/html/templates/code_index.html.erb +30 -0
  51. data/lib/rubycritic/generators/html/templates/layouts/application.html.erb +34 -0
  52. data/lib/rubycritic/generators/html/templates/overview.html.erb +5 -0
  53. data/lib/rubycritic/generators/html/templates/smells_index.html.erb +26 -0
  54. data/lib/rubycritic/{report_generators → generators/html}/templates/smelly_line.html.erb +1 -1
  55. data/lib/rubycritic/generators/html/turbulence.rb +17 -0
  56. data/lib/rubycritic/generators/html/view_helpers.rb +43 -0
  57. data/lib/rubycritic/generators/html_report.rb +66 -0
  58. data/lib/rubycritic/generators/json/simple.rb +30 -0
  59. data/lib/rubycritic/generators/json_report.rb +23 -0
  60. data/lib/rubycritic/reporter.rb +20 -0
  61. data/lib/rubycritic/revision_comparator.rb +27 -29
  62. data/lib/rubycritic/serializer.rb +32 -0
  63. data/lib/rubycritic/smells_status_setter.rb +7 -16
  64. data/lib/rubycritic/source_control_systems/base.rb +37 -0
  65. data/lib/rubycritic/source_control_systems/double.rb +18 -0
  66. data/lib/rubycritic/source_control_systems/git.rb +41 -37
  67. data/lib/rubycritic/source_control_systems/mercurial.rb +29 -0
  68. data/lib/rubycritic/source_locator.rb +25 -13
  69. data/lib/rubycritic/version.rb +1 -1
  70. data/lib/rubycritic.rb +17 -22
  71. data/rubycritic.gemspec +10 -7
  72. data/test/analysers_test_helper.rb +3 -0
  73. data/test/lib/rubycritic/analysers/churn_test.rb +33 -0
  74. data/test/lib/rubycritic/analysers/complexity_test.rb +16 -0
  75. data/test/lib/rubycritic/analysers/helpers/methods_counter_test.rb +29 -0
  76. data/test/lib/rubycritic/analysers/helpers/modules_locator_test.rb +53 -0
  77. data/test/lib/rubycritic/analysers/smells/flay_test.rb +39 -0
  78. data/test/lib/rubycritic/analysers/smells/flog_test.rb +26 -0
  79. data/test/lib/rubycritic/analysers/smells/reek_test.rb +33 -0
  80. data/test/lib/rubycritic/configuration_test.rb +29 -0
  81. data/test/lib/rubycritic/core/analysed_module_test.rb +88 -0
  82. data/test/lib/rubycritic/{location_test.rb → core/location_test.rb} +8 -4
  83. data/test/lib/rubycritic/core/smell_test.rb +72 -0
  84. data/test/lib/rubycritic/{smells_array_test.rb → core/smells_array_test.rb} +1 -1
  85. data/test/lib/rubycritic/report_generators/turbulence_test.rb +17 -0
  86. data/test/lib/rubycritic/report_generators/view_helpers_test.rb +83 -0
  87. data/test/lib/rubycritic/smells_status_setter_test.rb +5 -5
  88. data/test/lib/rubycritic/source_control_systems/base_test.rb +29 -0
  89. data/test/lib/rubycritic/source_control_systems/double_test.rb +11 -0
  90. data/test/lib/rubycritic/source_control_systems/git_test.rb +13 -0
  91. data/test/lib/rubycritic/source_control_systems/interfaces/basic.rb +7 -0
  92. data/test/lib/rubycritic/source_control_systems/interfaces/time_travel.rb +7 -0
  93. data/test/lib/rubycritic/source_control_systems/mercurial_test.rb +11 -0
  94. data/test/lib/rubycritic/source_locator_test.rb +42 -18
  95. data/test/lib/rubycritic/version_test.rb +1 -0
  96. data/test/samples/empty.rb +0 -0
  97. data/test/samples/flay/smelly.rb +8 -0
  98. data/test/samples/flay/smelly2.rb +8 -0
  99. data/test/samples/flog/complex.rb +11 -0
  100. data/test/samples/flog/smelly.rb +7 -2
  101. data/test/samples/location/file0_symlink.rb +0 -0
  102. data/test/samples/methods_count.rb +7 -0
  103. data/test/samples/module_names.rb +18 -0
  104. data/test/samples/reek/not_smelly.rb +31 -3
  105. data/test/samples/unparsable.rb +1 -0
  106. data/test/test_helper.rb +39 -0
  107. metadata +178 -53
  108. data/SPEC.md +0 -58
  109. data/lib/rubycritic/analysers/flog.rb +0 -20
  110. data/lib/rubycritic/analysers/reek.rb +0 -15
  111. data/lib/rubycritic/cli.rb +0 -25
  112. data/lib/rubycritic/report_generators/assets/javascripts/application.js +0 -25
  113. data/lib/rubycritic/report_generators/assets/stylesheets/application.css +0 -26
  114. data/lib/rubycritic/report_generators/base_generator.rb +0 -42
  115. data/lib/rubycritic/report_generators/file_generator.rb +0 -42
  116. data/lib/rubycritic/report_generators/index_generator.rb +0 -28
  117. data/lib/rubycritic/report_generators/line_generator.rb +0 -27
  118. data/lib/rubycritic/report_generators/reporter.rb +0 -45
  119. data/lib/rubycritic/report_generators/templates/file.html.erb +0 -3
  120. data/lib/rubycritic/report_generators/templates/index.html.erb +0 -14
  121. data/lib/rubycritic/report_generators/templates/layouts/application.html.erb +0 -19
  122. data/lib/rubycritic/report_generators/view_helpers.rb +0 -26
  123. data/lib/rubycritic/smell_adapters/flog.rb +0 -41
  124. data/lib/rubycritic/smell_adapters/reek.rb +0 -35
  125. data/lib/rubycritic/smells_aggregator.rb +0 -29
  126. data/lib/rubycritic/smelly_pathnames_serializer.rb +0 -34
  127. data/lib/rubycritic/source_control_systems/source_control_system.rb +0 -42
  128. data/test/lib/rubycritic/metric_adapters/flog_adapter_test.rb +0 -25
  129. data/test/lib/rubycritic/metric_adapters/reek_adapter_test.rb +0 -34
  130. data/test/lib/rubycritic/smell_test.rb +0 -71
  131. data/test/lib/rubycritic/smells_aggregator_test.rb +0 -47
  132. data/test/lib/rubycritic/source_control_systems/source_control_system_test.rb +0 -26
  133. /data/lib/rubycritic/{report_generators → generators/html}/assets/javascripts/jquery-2.1.0.js +0 -0
  134. /data/lib/rubycritic/{report_generators/assets/javascripts/prettify.js → generators/html/assets/javascripts/prettify-4-Mar-2013.js} +0 -0
  135. /data/lib/rubycritic/{report_generators → generators/html}/templates/line.html.erb +0 -0
data/CONTRIBUTING.md CHANGED
@@ -1,19 +1,71 @@
1
1
  Contributing
2
2
  ============
3
3
 
4
+ RubyCritic is open source and contributions from the community are encouraged! No contribution is too small. Please consider:
5
+
6
+ * [Writing some Code](#writing-some-code)
7
+ * [Improving the Documentation](#improving-the-documentation)
8
+ * [Reporting a Bug](#reporting-a-bug)
9
+
10
+ Writing some Code
11
+ -----------------
12
+
13
+ If you want to squash a bug or add a new feature, please:
14
+
4
15
  1. Fork the project.
5
- 2. Create your feature branch (`git checkout -b my-new-feature`).
6
- 3. Stage partial-file changesets (`git -p`).
7
- 4. Commit your changes (`git commit`).
8
- Make exactly as many commits as you need.
9
- Too many is confusing. Too few will result in epic diffs. Strive for just the right number.
10
- Each commit should do one thing and one thing only.
11
- Follow [these guidelines][1] when writing your commit messages.
12
- 5. [Hide the sausage making][2]. Squash, split and reorder commits if necessary (`git rebase -i`).
13
- For a more in-depth look at interactive rebasing, be sure to check [how to rewrite history][3].
14
- 6. Push to the branch (`git push origin my-new-feature`).
15
- 7. Create new Pull Request.
16
-
17
- [1]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
18
- [2]: http://sethrobertson.github.io/GitBestPractices/#sausage
19
- [3]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages
16
+
17
+ 2. Create a feature branch (`git checkout -b my-new-feature`).
18
+
19
+ 3. Make your changes. Include tests for your changes, otherwise I may accidentally break them in the future.
20
+
21
+ 4. Run the tests with the `rake` command. Make sure that they are still passing.
22
+
23
+ 5. Stage partial-file changesets (`git -p`).
24
+
25
+ 6. Commit your changes (`git commit`).
26
+ Make exactly as many commits as you need.
27
+ Each commit should do one thing and one thing only. For example, all whitespace fixes should be relegated to a single commit.
28
+
29
+ 7. Write [descriptive commit messages].
30
+
31
+ 8. Push the branch to GitHub (`git push origin my-new-feature`).
32
+
33
+ 9. [Create a Pull Request] and send it to be merged with the master branch.
34
+
35
+ 10. After your code is reviewed, [hide the sausage making]. Squash, split and reorder commits if necessary (`git rebase -i`).
36
+ For a more in-depth look at interactive rebasing, be sure to check [how to rewrite history].
37
+
38
+ Improving the Documentation
39
+ ---------------------------
40
+
41
+ You are welcome to clarify how something works or simply fix a typo. Please include `[ci skip]` on a new line in each of your commit messages. This will signal [Travis] that running the test suite is not necessary for these changes.
42
+
43
+ Reporting a Bug
44
+ ---------------
45
+
46
+ If you are experiencing unexpected behavior and, after having read the documentation, you are convinced this behavior is a bug, please:
47
+
48
+ 1. Search the [issues tracker] to see if it was already reported / fixed.
49
+
50
+ 2. [Create a new issue].
51
+
52
+ 3. Include the Ruby and RubyCritic versions in your report. Here's a little table to help you out:
53
+
54
+ ```
55
+ | | Version |
56
+ |------------|---------|
57
+ | Ruby | 2.1.2 |
58
+ | RubyCritic | 1.0.0 |
59
+ ```
60
+
61
+ The more information you provide, the easier it will be to track down the issue and fix it.
62
+ If you have never written a bug report before, or if you want to brush up on your bug reporting skills, read Simon Tatham's essay [How to Report Bugs Effectively].
63
+
64
+ [descriptive commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
65
+ [Create a pull request]: https://help.github.com/articles/creating-a-pull-request
66
+ [hide the sausage making]: http://sethrobertson.github.io/GitBestPractices/#sausage
67
+ [how to rewrite history]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages
68
+ [Travis]: https://travis-ci.org
69
+ [issues tracker]: https://github.com/whitesmith/rubycritic/issues
70
+ [Create a new issue]: https://github.com/whitesmith/rubycritic/issues/new
71
+ [How to Report Bugs Effectively]: http://www.chiark.greenend.org.uk/~sgtatham/bugs.html
data/README.md CHANGED
@@ -1,33 +1,73 @@
1
- Ruby Critic
2
- ===========
1
+ RubyCritic
2
+ ==========
3
3
 
4
- Ruby Critic is a tool that detects and reports smells in Ruby code.
4
+ [![Gem Version](https://badge.fury.io/rb/rubycritic.svg)](http://badge.fury.io/rb/rubycritic)
5
+ [![Build Status](https://travis-ci.org/whitesmith/rubycritic.svg?branch=master)](https://travis-ci.org/whitesmith/rubycritic)
6
+ [![Code Climate](https://codeclimate.com/github/whitesmith/rubycritic/badges/gpa.svg)](https://codeclimate.com/github/whitesmith/rubycritic)
5
7
 
6
- Installation
7
- ------------
8
+ <img src="http://i.imgur.com/66HACCD.png" alt="RubyCritic Icon" align="right" />
9
+ RubyCritic is a gem that wraps around static analysis gems such as [Reek][1], [Flay][2] and [Flog][3] to provide a quality report of your Ruby code.
8
10
 
9
- Add this line to your application's Gemfile:
11
+ This gem provides features such as:
10
12
 
11
- ```ruby
12
- gem "rubycritic"
13
- ```
13
+ 1. An overview of your project:
14
14
 
15
- And then execute:
15
+ ![RubyCritic overview screenshot](http://i.imgur.com/OrOflfj.png)
16
+
17
+ 2. An index of the project files with their respective number of smells:
18
+
19
+ ![RubyCritic code index screenshot](http://i.imgur.com/0ETNrX7.png)
20
+
21
+ 3. An index of the smells detected:
22
+
23
+ ![RubyCritic smells index screenshot](http://i.imgur.com/5CpPt9v.png)
24
+
25
+ 4. Finally, when analysing code like the following:
26
+
27
+ ```ruby
28
+ class Dirty
29
+ def awful(x, y)
30
+ if y
31
+ @screen = widgets.map {|w| w.each {|key| key += 3}}
32
+ end
33
+ end
34
+ end
35
+ ```
36
+
37
+ It basically turns something like this:
38
+
39
+ ![Reek output screenshot](http://i.imgur.com/tCgZX9I.png)
40
+
41
+ Into something like this:
42
+
43
+ ![RubyCritic file code screenshot](http://i.imgur.com/KLVrhMm.png)
44
+
45
+ Getting Started
46
+ ---------------
47
+
48
+ RubyCritic can be installed with the following command:
16
49
 
17
50
  ```bash
18
- $ bundle
51
+ $ gem install rubycritic
19
52
  ```
20
53
 
21
- Or just install it yourself:
54
+ If you'd rather install RubyCritic using Bundler, add this line to your
55
+ application's Gemfile:
56
+
57
+ ```ruby
58
+ gem "rubycritic", :require => false
59
+ ```
60
+
61
+ And then execute:
22
62
 
23
63
  ```bash
24
- $ gem install rubycritic
64
+ $ bundle
25
65
  ```
26
66
 
27
67
  Usage
28
68
  -----
29
69
 
30
- Running `rubycritic` with no arguments will check all Ruby source files in the
70
+ Running `rubycritic` with no arguments will analyse all the Ruby files in the
31
71
  current directory:
32
72
 
33
73
  ```bash
@@ -39,3 +79,54 @@ Alternatively you can pass `rubycritic` a list of files and directories to check
39
79
  ```bash
40
80
  $ rubycritic app lib/foo.rb
41
81
  ```
82
+
83
+ For a full list of the command-line options run:
84
+
85
+ ```bash
86
+ $ rubycritic --help
87
+ ```
88
+
89
+ | Command flag | Description |
90
+ |--------------------------|-------------------------------------------------------|
91
+ | `-v/--version` | Displays the current version and exits |
92
+ | `-p/--path` | Sets the output directory (tmp/rubycritic by default) |
93
+ | `-m/--mode-ci` | Uses CI mode (faster, but only analyses last commit) |
94
+ | `--deduplicate-symlinks` | De-duplicate symlinks based on their final target |
95
+ | `--suppress-ratings` | Suppress letter ratings |
96
+
97
+ Alternative Usage Methods
98
+ -------------------------
99
+
100
+ If you're fond of Guard you might like [guard-rubycritic][4]. It automatically analyses your Ruby files as they are modified.
101
+
102
+ For continuous integration, you can give [Jenkins CI][5] a spin. With it, you can [easily build your own (poor-man's) Code Climate][6]!
103
+
104
+ Improving RubyCritic
105
+ --------------------
106
+
107
+ RubyCritic doesn't have to remain a second choice to other code quality analysis services. Together, we can improve it and continue to build on the great code metric tools that are available in the Ruby ecosystem.
108
+
109
+ Arguably, the [better_errors gem][7] only got popular after receiving a [(pretty awesome) Pull Request][8] that changed its page design.
110
+
111
+ Similarly, Pull Requests that improve the look and feel of the gem, that tweak the calculation of ratings or that fix existing issues will be most welcome. This is my first gem, so just commenting on an issue and giving some insight into how something should work will be appreciated. No contribution is too small.
112
+
113
+ See RubyCritic's [contributing guidelines](CONTRIBUTING.md) about how to proceed.
114
+
115
+ Credits
116
+ -------
117
+
118
+ ![Whitesmith](http://i.imgur.com/Si2l3kd.png)
119
+
120
+ RubyCritic is maintained and funded by [Whitesmith][9]. Tweet your questions or suggestions to [@glitchdout][10] or [@Whitesmithco][11].
121
+
122
+ [1]: https://github.com/troessner/reek
123
+ [2]: https://github.com/seattlerb/flay
124
+ [3]: https://github.com/seattlerb/flog
125
+ [4]: https://github.com/whitesmith/guard-rubycritic
126
+ [5]: http://jenkins-ci.org/
127
+ [6]: https://github.com/whitesmith/rubycritic/wiki/Building-your-own-Code-Climate
128
+ [7]: https://github.com/charliesome/better_errors
129
+ [8]: https://github.com/charliesome/better_errors/pull/22
130
+ [9]: http://www.whitesmith.co/
131
+ [10]: https://twitter.com/glitchdout
132
+ [11]: https://twitter.com/Whitesmithco
data/Rakefile CHANGED
@@ -1,10 +1,13 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
+ require "rubocop/rake_task"
3
4
 
4
- task :default => :test
5
-
6
- Rake::TestTask.new do |t|
7
- t.libs.push "lib"
8
- t.libs.push "test"
9
- t.pattern = "test/**/*_test.rb"
5
+ Rake::TestTask.new do |task|
6
+ task.libs.push "lib"
7
+ task.libs.push "test"
8
+ task.pattern = "test/**/*_test.rb"
10
9
  end
10
+
11
+ RuboCop::RakeTask.new
12
+
13
+ task :default => [:test, :rubocop]
data/bin/rubycritic CHANGED
@@ -1,3 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "rubycritic/cli"
3
+ require "rubycritic/cli/application"
4
+
5
+ exit Rubycritic::Cli::Application.new(ARGV).execute
@@ -0,0 +1,21 @@
1
+ require "rubycritic/analysers/helpers/methods_counter"
2
+ require "rubycritic/analysers/helpers/modules_locator"
3
+
4
+ module Rubycritic
5
+ module Analyser
6
+
7
+ class Attributes
8
+ def initialize(analysed_modules)
9
+ @analysed_modules = analysed_modules
10
+ end
11
+
12
+ def run
13
+ @analysed_modules.each do |analysed_module|
14
+ analysed_module.methods_count = MethodsCounter.new(analysed_module).count
15
+ analysed_module.name = ModulesLocator.new(analysed_module).first_name
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Rubycritic
2
+ module Analyser
3
+
4
+ class Churn
5
+ attr_writer :source_control_system
6
+
7
+ def initialize(analysed_modules)
8
+ @analysed_modules = analysed_modules
9
+ @source_control_system = Config.source_control_system
10
+ end
11
+
12
+ def run
13
+ @analysed_modules.each do |analysed_module|
14
+ analysed_module.churn = @source_control_system.revisions_count(analysed_module.path)
15
+ analysed_module.committed_at = @source_control_system.date_of_last_commit(analysed_module.path)
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ require "rubycritic/analysers/helpers/flog"
2
+
3
+ module Rubycritic
4
+ module Analyser
5
+
6
+ class Complexity
7
+ def initialize(analysed_modules)
8
+ @flog = Flog.new
9
+ @analysed_modules = analysed_modules
10
+ end
11
+
12
+ def run
13
+ @analysed_modules.each do |analysed_module|
14
+ @flog.reset
15
+ @flog.flog(analysed_module.path)
16
+ analysed_module.complexity = @flog.total_score.round
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,69 @@
1
+ module Parser
2
+ module AST
3
+
4
+ class Node
5
+ MODULE_TYPES = [:module, :class]
6
+
7
+ def count_nodes_of_type(*types)
8
+ count = 0
9
+ recursive_children do |child|
10
+ count += 1 if types.include?(child.type)
11
+ end
12
+ count
13
+ end
14
+
15
+ def recursive_children
16
+ children.each do |child|
17
+ next unless child.is_a?(Parser::AST::Node)
18
+ yield child
19
+ child.recursive_children { |grand_child| yield grand_child }
20
+ end
21
+ end
22
+
23
+ def get_module_names
24
+ children_modules = children
25
+ .select { |child| child.is_a?(Parser::AST::Node) }
26
+ .flat_map(&:get_module_names)
27
+
28
+ if MODULE_TYPES.include?(type)
29
+ if children_modules.empty?
30
+ [module_name]
31
+ else
32
+ children_modules.map do |children_module|
33
+ "#{module_name}::#{children_module}"
34
+ end
35
+ end
36
+ else
37
+ children_modules
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def module_name
44
+ name_segments = []
45
+ current_node = children[0]
46
+ while current_node
47
+ name_segments.unshift(current_node.children[1])
48
+ current_node = current_node.children[0]
49
+ end
50
+ name_segments.join("::")
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ module Rubycritic
57
+ module AST
58
+ class EmptyNode
59
+ def count_nodes_of_type(*)
60
+ 0
61
+ end
62
+
63
+ def get_module_names
64
+ []
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -1,8 +1,14 @@
1
1
  ---
2
2
  Attribute:
3
3
  enabled: false
4
+ DuplicateMethodCall:
5
+ max_calls: 2
4
6
  IrresponsibleModule:
5
7
  enabled: false
8
+ NestedIterators:
9
+ max_allowed_nesting: 2
10
+ TooManyStatements:
11
+ enabled: false
6
12
  UncommunicativeMethodName:
7
13
  reject:
8
14
  - !ruby/regexp /^[a-z]$/
@@ -16,3 +22,5 @@ UncommunicativeVariableName:
16
22
  reject:
17
23
  - !ruby/regexp /^.$/
18
24
  - !ruby/regexp /[0-9]$/
25
+ UtilityFunction:
26
+ enabled: false
@@ -0,0 +1,13 @@
1
+ require "flay"
2
+
3
+ module Rubycritic
4
+
5
+ class Flay < ::Flay
6
+ def initialize(paths)
7
+ super()
8
+ process(*paths)
9
+ analyze
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,17 @@
1
+ require "flog"
2
+
3
+ module Rubycritic
4
+
5
+ class Flog < ::Flog
6
+ DEFAULT_OPTIONS = {
7
+ :all => true,
8
+ :continue => true,
9
+ :methods => true
10
+ }
11
+
12
+ def initialize
13
+ super(DEFAULT_OPTIONS)
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,25 @@
1
+ require "rubycritic/analysers/helpers/parser"
2
+
3
+ module Rubycritic
4
+
5
+ class MethodsCounter
6
+ def initialize(analysed_module)
7
+ @analysed_module = analysed_module
8
+ end
9
+
10
+ def count
11
+ node.count_nodes_of_type(:def, :defs)
12
+ end
13
+
14
+ private
15
+
16
+ def node
17
+ Parser.parse(content)
18
+ end
19
+
20
+ def content
21
+ File.read(@analysed_module.path)
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,43 @@
1
+ require "rubycritic/analysers/helpers/parser"
2
+
3
+ module Rubycritic
4
+
5
+ class ModulesLocator
6
+ def initialize(analysed_module)
7
+ @analysed_module = analysed_module
8
+ end
9
+
10
+ def first_name
11
+ names.first
12
+ end
13
+
14
+ def names
15
+ return name_from_path if @analysed_module.methods_count == 0
16
+ names = node.get_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
+
43
+ end
@@ -0,0 +1,12 @@
1
+ require "parser/current"
2
+ require "rubycritic/analysers/helpers/ast_node"
3
+
4
+ module Rubycritic
5
+ module Parser
6
+ def self.parse(content)
7
+ ::Parser::CurrentRuby.parse(content) || AST::EmptyNode.new
8
+ rescue ::Parser::SyntaxError
9
+ AST::EmptyNode.new
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ require "reek"
2
+ require "reek/configuration/app_configuration"
3
+ require "ostruct"
4
+
5
+ module Rubycritic
6
+
7
+ class Reek < ::Reek::Examiner
8
+ DEFAULT_CONFIG_FILE = File.expand_path("../config.reek", __FILE__)
9
+
10
+ def initialize(paths)
11
+ config = OpenStruct.new(:options => OpenStruct.new(:config_file => DEFAULT_CONFIG_FILE))
12
+ ::Reek::Configuration::AppConfiguration.initialize_with(config)
13
+ super(Array(paths))
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,68 @@
1
+ require "rubycritic/analysers/helpers/flay"
2
+ require "rubycritic/core/smell"
3
+
4
+ module Rubycritic
5
+ module Analyser
6
+
7
+ class FlaySmells
8
+ def initialize(analysed_modules)
9
+ @analysed_modules = paths_to_analysed_modules(analysed_modules)
10
+ @flay = Flay.new(@analysed_modules.keys)
11
+ end
12
+
13
+ def run
14
+ @flay.hashes.each do |structural_hash, nodes|
15
+ smell = create_smell(structural_hash, nodes)
16
+ nodes.map(&:file).uniq.each do |file|
17
+ @analysed_modules[file].smells << smell
18
+ end
19
+
20
+ nodes.each do |node|
21
+ @analysed_modules[node.file].duplication += node.mass
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def paths_to_analysed_modules(analysed_modules)
29
+ paths = {}
30
+ analysed_modules.each do |analysed_module|
31
+ paths[analysed_module.path] = analysed_module
32
+ end
33
+ paths
34
+ end
35
+
36
+ def create_smell(structural_hash, nodes)
37
+ mass = @flay.masses[structural_hash]
38
+ Smell.new(
39
+ :locations => smell_locations(nodes),
40
+ :context => similarity(structural_hash),
41
+ :message => "found in #{nodes.size} nodes",
42
+ :score => mass,
43
+ :type => "DuplicateCode",
44
+ :cost => cost(mass)
45
+ )
46
+ end
47
+
48
+ def smell_locations(nodes)
49
+ nodes.map do |node|
50
+ Location.new(node.file, node.line)
51
+ end.sort
52
+ end
53
+
54
+ def similarity(structural_hash)
55
+ if @flay.identical[structural_hash]
56
+ "Identical code"
57
+ else
58
+ "Similar code"
59
+ end
60
+ end
61
+
62
+ def cost(mass)
63
+ mass / 25
64
+ end
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,62 @@
1
+ require "rubycritic/analysers/helpers/flog"
2
+ require "rubycritic/core/smell"
3
+
4
+ module Rubycritic
5
+ module Analyser
6
+
7
+ class FlogSmells
8
+ HIGH_COMPLEXITY_SCORE_THRESHOLD = 25
9
+ VERY_HIGH_COMPLEXITY_SCORE_THRESHOLD = 60
10
+
11
+ def initialize(analysed_modules)
12
+ @flog = Flog.new
13
+ @analysed_modules = analysed_modules
14
+ end
15
+
16
+ def run
17
+ @analysed_modules.each do |analysed_module|
18
+ add_smells_to(analysed_module)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def add_smells_to(analysed_module)
25
+ @flog.reset
26
+ @flog.flog(analysed_module.path)
27
+ @flog.each_by_score do |class_method, original_score|
28
+ score = original_score.round
29
+ if score >= HIGH_COMPLEXITY_SCORE_THRESHOLD
30
+ analysed_module.smells << create_smell(class_method, score)
31
+ end
32
+ end
33
+ end
34
+
35
+ def create_smell(context, score)
36
+ Smell.new(
37
+ :locations => smell_locations(context),
38
+ :context => context,
39
+ :message => "has a flog score of #{score}",
40
+ :score => score,
41
+ :type => type(score),
42
+ :cost => 0
43
+ )
44
+ end
45
+
46
+ def smell_locations(context)
47
+ line = @flog.method_locations[context]
48
+ file_path, file_line = line.split(":")
49
+ [Location.new(file_path, file_line)]
50
+ end
51
+
52
+ def type(score)
53
+ if score >= VERY_HIGH_COMPLEXITY_SCORE_THRESHOLD
54
+ "VeryHighComplexity"
55
+ else
56
+ "HighComplexity"
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+ end