reconn 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 13aa9f0063ed8270baa802db3b86f308b2aa4066
4
+ data.tar.gz: 1b7df1c4764ce1a90b64aab61036819d6e6d67c3
5
+ SHA512:
6
+ metadata.gz: 97c1778b30eed241da1babbd81be892ffc32dadc4dfc451864bacc26ecc9dc600c841e8c7752e6b1ba229bd707c3cb58a52ebbf3aeb92c29e419f8ef6d8d3943
7
+ data.tar.gz: 78d10bae27845cd07162bb3470753c575811cba9bbb3a51b559bac7a9f8911de147d17c376538118fd075ceb5c8531fdaa6292a70b6738658665629c6f1c782b
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .vr_settings.yaml
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rubycode_analyzer.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Reconn
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'reconn'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install reconn
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( http://github.com/<my-github-username>/recon/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
data/bin/reconn ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'vrlib'
4
+
5
+ #make program output in real time so errors visible in VR.
6
+ STDOUT.sync = true
7
+ STDERR.sync = true
8
+
9
+ #everything in these directories will be included
10
+ my_path = File.expand_path(File.dirname(__FILE__))
11
+ require_all Dir.glob(my_path + "/../lib/reconn/**/*.rb")
12
+
13
+ View.new.show
data/lib/reconn.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'reconn/version'
2
+ require 'reconn/analyzer'
3
+
4
+ module Reconn
5
+ end
@@ -0,0 +1,201 @@
1
+ # @author Mateusz Czarnecki <mateusz.czarnecki92@gmail.com>
2
+
3
+ require 'ruby_parser'
4
+ require 'sexp_processor'
5
+ require_relative 'util/project_scanner'
6
+ require_relative 'analyzer/project_elements/class.rb'
7
+ require_relative 'analyzer/project_elements/method.rb'
8
+
9
+ module Analyzer
10
+ class Analyzer < MethodBasedSexpProcessor
11
+
12
+ MAX_METHOD_LENGTH = 10
13
+ MAX_COMPLEXITY = 6
14
+
15
+ def initialize
16
+ super()
17
+ self.auto_shift_type = false
18
+ @classes = []
19
+ @methods = []
20
+ @smells = []
21
+ @current_class = Class.new(:none)
22
+ end
23
+
24
+ # Analyzes all the ruby files in the given directory and its subdirectories
25
+ # @param dir [String] path to the directory
26
+ # @return [Array(Array<Class>, Array<Method>, Array<CodeSmell>)] found classes,
27
+ # methods and code smells
28
+ def analyze(dir)
29
+ parser = RubyParser.new
30
+ paths = ProjectScanner.scan(dir)
31
+ paths.each do |path|
32
+ @current_path = path
33
+ ast = parser.process(File.binread(path), path)
34
+ process ast
35
+ @current_class = Class.new(:none)
36
+ end
37
+
38
+ merged_classes = merge_duplicate_classes
39
+ @classes = merged_classes unless merged_classes.nil?
40
+
41
+ @classes.each do |klass|
42
+ klass.lines = count_lines_in_class(klass)
43
+ klass.complexity = count_complexity_in_class(klass)
44
+ end
45
+ prune_dependencies
46
+
47
+ # Deletes empty classes
48
+ @classes.delete(Class.new(:none))
49
+
50
+ @smells = find_code_smells
51
+
52
+ return @classes, @methods, @smells
53
+ end
54
+
55
+ #########################################
56
+ # Process methods:
57
+
58
+ def process_class(exp)
59
+ exp.shift
60
+ in_klass(exp.shift) do
61
+ @current_class = Class.new(klass_name)
62
+ @classes << @current_class
63
+ process_until_empty exp
64
+ end
65
+
66
+ @current_class = Class.new(:none)
67
+ s()
68
+ end
69
+
70
+ def process_defn(exp)
71
+ exp.shift
72
+ method_name = exp.shift.to_s
73
+ lines = count_lines_in_method(method_name)
74
+ @current_method = Method.new(method_name, @current_class.name, lines)
75
+ exp.shift
76
+ process_until_empty exp
77
+
78
+ @methods << @current_method
79
+ @current_class.add_method(@current_method)
80
+ @current_method = Method.new(:none)
81
+ s()
82
+ end
83
+
84
+ def process_defs(exp)
85
+ exp.shift
86
+ process_defn(exp)
87
+ end
88
+
89
+ def process_colon2(exp)
90
+ exp.shift
91
+ name = exp.flatten
92
+ name.delete :colon2
93
+ name.delete :const
94
+ name = name.join("::")
95
+ @current_class.add_dependency(name)
96
+ exp.shift
97
+ process_until_empty exp
98
+
99
+ s()
100
+ end
101
+
102
+ def process_const(exp)
103
+ exp.shift
104
+ name = exp.shift.to_s
105
+ @current_class.add_dependency(name)
106
+ exp.shift
107
+ process_until_empty exp
108
+
109
+ s()
110
+ end
111
+
112
+ def process_if(exp)
113
+ exp.shift
114
+ process_until_empty exp
115
+
116
+ @current_method.incr_complexity unless @current_method.nil?
117
+ s()
118
+ end
119
+ alias process_for process_if
120
+ alias process_when process_if
121
+ alias process_until process_if
122
+ alias process_while process_if
123
+ alias process_rescue process_if
124
+ alias process_and process_if
125
+ alias process_or process_if
126
+
127
+ ########################################
128
+
129
+ #Counts lines of code in a method
130
+ #
131
+ #@param method_name [String] the name of the method
132
+ #@return [Integer] lines of code count
133
+ def count_lines_in_method(method_name)
134
+ method_name = method_name.gsub(/[\.\|\(\)\[\]\{\}\+\\\^\$\*\?]/) {|match| '\\' + match}
135
+ flag = false
136
+ lines = []
137
+ File.foreach(@current_path) do |line|
138
+ break if line =~ /def/ && flag
139
+ lines << line if flag && line.strip != '' && line.strip[0] != '#'
140
+ flag = true if line =~ /def #{method_name}/ || line =~ /def self.#{method_name}/
141
+ end
142
+
143
+ lines.size
144
+ end
145
+
146
+ #Counts lines of code in a class (sums LOC of methods)
147
+ #
148
+ #@param klass [Class] the class
149
+ #@return [Integer] lines of code count
150
+ def count_lines_in_class(klass)
151
+ lines = klass.methods.map {|method| method.lines}.inject(:+)
152
+ lines.nil? ? 0 : lines
153
+ end
154
+
155
+ def count_complexity_in_class(klass)
156
+ complexity = klass.methods.map {|method| method.complexity}.inject(:+)
157
+ complexity.nil? ? 0 : complexity
158
+ end
159
+
160
+ #Deletes dependencies which are not classes within analyzed project
161
+ def prune_dependencies
162
+ class_names = @classes.map {|klass| klass.name}
163
+ @classes.each do |klass|
164
+ klass.dependencies = klass.dependencies.map do |dep|
165
+ dep_split = dep.split('::')
166
+ klass_split = klass.name.split('::')
167
+ klass_split.pop(dep_split.size)
168
+ (klass_split + dep_split).join('::')
169
+ end
170
+ klass.dependencies = klass.dependencies.uniq.keep_if {|dep| dep != klass.name && class_names.include?(dep)}
171
+ end
172
+ end
173
+
174
+ def merge_duplicate_classes
175
+ duplicates = @classes.group_by {|c| c.name}.select {|k, v| v.size > 1}.values
176
+ if !duplicates.empty?
177
+ merged_dups = []
178
+ duplicates.each do |dup|
179
+ merged_dups << dup.inject(:+)
180
+ end
181
+ @classes.uniq {|c| c.name}.each do |klass|
182
+ klass = merged_dups.find {|d| d == klass} if merged_dups.include?(klass)
183
+ end
184
+ end
185
+ end
186
+
187
+ def find_code_smells
188
+ code_smells = []
189
+ @methods.each do |method|
190
+ if method.lines > MAX_METHOD_LENGTH
191
+ code_smells << CodeSmell.new(:too_big_method, method.class_name, method.name)
192
+ end
193
+ if method.complexity > MAX_COMPLEXITY
194
+ code_smells << CodeSmell.new(:too_complex_method, method.class_name, method.name)
195
+ end
196
+ end
197
+ code_smells
198
+ end
199
+
200
+ end
201
+ end
@@ -0,0 +1,16 @@
1
+ module Analyzer
2
+ class CodeSmell
3
+ attr_reader :type, :class_name, :method_name
4
+
5
+ def initialize(type, class_name = :none, method_name)
6
+ @type = type
7
+ @class_name = class_name
8
+ @method_name = method_name
9
+ end
10
+
11
+ def to_s
12
+ "Smell: #{@type.to_s} in Class: #{@class_name.to_s} "\
13
+ "Method: #{@method_name.to_s}"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,48 @@
1
+ module Analyzer
2
+ # Represents a class in the project
3
+ class Class
4
+ attr_reader :name, :methods
5
+ attr_accessor :lines, :complexity, :dependencies
6
+
7
+ include Comparable
8
+
9
+ def ==(other)
10
+ name == other.name
11
+ end
12
+
13
+ def initialize(name)
14
+ @name = name
15
+ @dependencies = []
16
+ @methods = []
17
+ @lines = 0
18
+ @complexity = 0
19
+ end
20
+
21
+ def add_dependency(class_name)
22
+ @dependencies << class_name.to_s
23
+ end
24
+
25
+ def add_method(method_name)
26
+ @methods << method_name
27
+ end
28
+
29
+ def methods_number
30
+ methods.size
31
+ end
32
+
33
+ def +(other)
34
+ other.methods.each do |method|
35
+ if !@methods.index(method)
36
+ @methods << method
37
+ end
38
+ end
39
+ @dependencies += other.dependencies
40
+ self
41
+ end
42
+
43
+ def to_s
44
+ name.to_s
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ module Analyzer
2
+ # Represents a method in the project
3
+ class Method
4
+ attr_reader :name, :class_name, :lines, :complexity
5
+
6
+ include Comparable
7
+
8
+ def ==(other)
9
+ name == other.name && class_name == other.class_name
10
+ end
11
+
12
+ def initialize(name, class_name = :none, lines = 0)
13
+ @name = name
14
+ @class_name = class_name
15
+ @lines = lines
16
+ @complexity = 1
17
+ end
18
+
19
+ def to_s
20
+ name.to_s
21
+ end
22
+
23
+ def incr_complexity
24
+ @complexity += 1
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'find'
2
+
3
+ class ProjectScanner
4
+ # Scans the given directory and all its subdirectories for ruby files
5
+ #
6
+ # @param proj_path [String] path to the project directory
7
+ # @return [Array<String>] paths to the ruby files
8
+ # @raise [InvalidPathException] if it can't open the directory
9
+ def self.scan(proj_path)
10
+ paths = []
11
+ begin
12
+ Find.find(proj_path) do |path|
13
+ if FileTest.directory?(path)
14
+ if File.basename(path)[0] == '.'
15
+ Find.prune
16
+ end
17
+ end
18
+ if File.extname(path) == '.rb'
19
+ paths << path
20
+ end
21
+ end
22
+ rescue
23
+ raise InvalidPathException, "Can't open the directory"
24
+ end
25
+
26
+ paths
27
+ end
28
+ end