metric_fu 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +12 -0
- data/MIT-LICENSE +1 -1
- data/lib/base/base_template.rb +8 -3
- data/lib/base/churn_analyzer.rb +0 -14
- data/lib/base/configuration.rb +6 -2
- data/lib/base/generator.rb +4 -0
- data/lib/base/line_numbers.rb +10 -2
- data/lib/base/reek_analyzer.rb +63 -16
- data/lib/base/report.rb +9 -1
- data/lib/generators/churn.rb +1 -1
- data/lib/generators/flog.rb +13 -0
- data/lib/generators/rails_best_practices.rb +29 -1
- data/lib/generators/rcov.rb +2 -6
- data/lib/generators/reek.rb +21 -0
- data/lib/generators/roodi.rb +8 -0
- data/lib/generators/saikuro.rb +39 -7
- data/lib/generators/stats.rb +1 -2
- data/lib/metric_fu.rb +1 -0
- data/lib/templates/awesome/awesome_template.rb +35 -0
- data/lib/templates/awesome/css/syntax.css +19 -0
- data/lib/templates/awesome/hotspots.html.erb +8 -0
- data/lib/templates/awesome/rails_best_practices.html.erb +1 -1
- data/spec/base/base_template_spec.rb +18 -2
- data/spec/base/report_spec.rb +8 -1
- data/spec/generators/churn_spec.rb +1 -1
- data/spec/generators/rails_best_practices_spec.rb +3 -3
- data/spec/generators/rcov_spec.rb +2 -2
- data/spec/generators/saikuro_spec.rb +17 -1
- metadata +35 -20
data/HISTORY
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
=== MetricFu 2.1.0 / 2011-03-1
|
2
|
+
|
3
|
+
* Flog gemspec version was >= 2.2.0, which was too early and didn't work. Changed to >= 2.3.0 - Chris Griego
|
4
|
+
* RCov generator now uses a regex with begin and end line anchor to avoid splitting on comments with equal signs in source files - Andrew Selder
|
5
|
+
* RCov generator now always strips the 3 leading characters from the lines when reconstruction source files so that heredocs and block comments parse successfully - Andrew Selder
|
6
|
+
* Dan Mayer ported some specs for the Hotspots code into MetricFu from Caliper's code.
|
7
|
+
* Stefan Huber fixed some problems with churn pretending not to support Svn.
|
8
|
+
* Kakutani Shintaro added the ability to opt out of opening files with TextMate (config.darwin_txmt_protocol_no_thanks = true).
|
9
|
+
* Joel Nimety and Andrew Selder fixed a problem where Saikuro was parsing a dir twice.
|
10
|
+
* Dan Sinclair added some awesome 'annotate' functionality to the Hotspots page. Click on it so see the file with problems in-line.
|
11
|
+
* Dan Sinclair added a verbose mode (config.verbose = true).
|
12
|
+
|
1
13
|
=== MetricFu 2.0.1 / 2010-11-13
|
2
14
|
|
3
15
|
* Delete trailing whitespaces - Delwyn de Villiers
|
data/MIT-LICENSE
CHANGED
data/lib/base/base_template.rb
CHANGED
@@ -8,7 +8,7 @@ module MetricFu
|
|
8
8
|
# to actually write out the template. See StandardTemplate for an
|
9
9
|
# example.
|
10
10
|
class Template
|
11
|
-
attr_accessor :report
|
11
|
+
attr_accessor :report, :per_file_data
|
12
12
|
|
13
13
|
private
|
14
14
|
# Creates a new erb evaluated result from the passed in section.
|
@@ -137,13 +137,19 @@ module MetricFu
|
|
137
137
|
def file_url(name, line) # :nodoc:
|
138
138
|
return '' unless name
|
139
139
|
filename = File.expand_path(name.gsub(/^\//, ''))
|
140
|
-
if
|
140
|
+
if render_as_txmt_protocol?
|
141
141
|
"txmt://open/?url=file://#{filename}" << (line ? "&line=#{line}" : "")
|
142
142
|
else
|
143
143
|
"file://#{filename}"
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
+
def render_as_txmt_protocol? # :nodoc:
|
148
|
+
config = MetricFu.configuration
|
149
|
+
return false unless config.platform.include?('darwin')
|
150
|
+
return !config.darwin_txmt_protocol_no_thanks
|
151
|
+
end
|
152
|
+
|
147
153
|
# Provides a brain dead way to cycle between two values during
|
148
154
|
# an iteration of some sort. Pass in the first_value, the second_value,
|
149
155
|
# and the cardinality of the iteration.
|
@@ -162,6 +168,5 @@ module MetricFu
|
|
162
168
|
return first_value if iteration % 2 == 0
|
163
169
|
return second_value
|
164
170
|
end
|
165
|
-
|
166
171
|
end
|
167
172
|
end
|
data/lib/base/churn_analyzer.rb
CHANGED
@@ -35,18 +35,4 @@ class ChurnAnalyzer
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
private
|
39
|
-
|
40
|
-
def self.update_changes(total, changed)
|
41
|
-
changed.each do |change|
|
42
|
-
#should work as has_key(change), but hash == doesn't work on 1.8.6 here for some reason it never matches
|
43
|
-
if total.has_key?(change.to_a.sort)
|
44
|
-
total[change.to_a.sort] += 1
|
45
|
-
else
|
46
|
-
total[change.to_a.sort] = 1
|
47
|
-
end
|
48
|
-
end
|
49
|
-
total
|
50
|
-
end
|
51
|
-
|
52
38
|
end
|
data/lib/base/configuration.rb
CHANGED
@@ -8,9 +8,9 @@ module MetricFu
|
|
8
8
|
AVAILABLE_METRICS = [:churn, :flog, :flay, :reek,
|
9
9
|
:roodi, :rcov,
|
10
10
|
:hotspots]
|
11
|
-
|
11
|
+
|
12
12
|
AVAILABLE_METRICS << :saikuro unless RUBY_VERSION == '1.9.2'
|
13
|
-
|
13
|
+
|
14
14
|
AVAILABLE_GRAPHS = [:flog, :flay, :reek, :roodi, :rcov, :rails_best_practices]
|
15
15
|
AVAILABLE_GRAPH_ENGINES = [:gchart, :bluff]
|
16
16
|
|
@@ -145,7 +145,11 @@ module MetricFu
|
|
145
145
|
@hotspots = {}
|
146
146
|
@file_globs_to_ignore = []
|
147
147
|
|
148
|
+
@verbose = false
|
149
|
+
|
148
150
|
@graph_engine = :bluff # can be :bluff or :gchart
|
151
|
+
|
152
|
+
@darwin_txmt_protocol_no_thanks = false
|
149
153
|
end
|
150
154
|
|
151
155
|
# Perform a simple check to try and guess if we're running
|
data/lib/base/generator.rb
CHANGED
@@ -121,6 +121,10 @@ module MetricFu
|
|
121
121
|
# methods to allow extra hooks into the processing methods, and help
|
122
122
|
# to keep the logic of your Generators clean.
|
123
123
|
def generate_report
|
124
|
+
if MetricFu.configuration.verbose
|
125
|
+
puts "Executing #{self.class.to_s.gsub(/.*::/, '')}"
|
126
|
+
end
|
127
|
+
|
124
128
|
%w[emit analyze].each do |meth|
|
125
129
|
send("before_#{meth}".to_sym)
|
126
130
|
send("#{meth}".to_sym)
|
data/lib/base/line_numbers.rb
CHANGED
@@ -4,8 +4,8 @@ module MetricFu
|
|
4
4
|
|
5
5
|
def initialize(contents)
|
6
6
|
rp = RubyParser.new
|
7
|
-
file_sexp = rp.parse(contents)
|
8
7
|
@locations = {}
|
8
|
+
file_sexp = rp.parse(contents)
|
9
9
|
case file_sexp[0]
|
10
10
|
when :class
|
11
11
|
process_class(file_sexp)
|
@@ -15,6 +15,9 @@ module MetricFu
|
|
15
15
|
file_sexp.each_of_type(:class) { |sexp| process_class(sexp) }
|
16
16
|
else
|
17
17
|
end
|
18
|
+
rescue Exception
|
19
|
+
#catch errors for files ruby_parser fails on
|
20
|
+
@locations
|
18
21
|
end
|
19
22
|
|
20
23
|
def in_method? line_number
|
@@ -31,6 +34,11 @@ module MetricFu
|
|
31
34
|
found_method_and_range.first
|
32
35
|
end
|
33
36
|
|
37
|
+
def start_line_for_method(method)
|
38
|
+
return nil unless @locations.has_key?(method)
|
39
|
+
@locations[method].first
|
40
|
+
end
|
41
|
+
|
34
42
|
private
|
35
43
|
|
36
44
|
def process_module(sexp)
|
@@ -63,4 +71,4 @@ module MetricFu
|
|
63
71
|
end
|
64
72
|
|
65
73
|
end
|
66
|
-
end
|
74
|
+
end
|
data/lib/base/reek_analyzer.rb
CHANGED
@@ -3,23 +3,70 @@
|
|
3
3
|
class ReekAnalyzer
|
4
4
|
include ScoringStrategies
|
5
5
|
|
6
|
-
REEK_ISSUE_INFO = {
|
7
|
-
|
6
|
+
REEK_ISSUE_INFO = {
|
7
|
+
'Uncommunicative Name' =>
|
8
|
+
{'link' => 'http://wiki.github.com/kevinrutherford/reek/uncommunicative-name',
|
9
|
+
'info' => 'An Uncommunicative Name is a name that doesn’t communicate its intent well enough.'},
|
8
10
|
'Class Variable' =>
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
'
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
'
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
'
|
22
|
-
|
11
|
+
{'link' => 'http://wiki.github.com/kevinrutherford/reek/class-variable',
|
12
|
+
'info' => 'Class variables form part of the global runtime state, and as such make it ' +
|
13
|
+
'easy for one part of the system to accidentally or inadvertently depend on ' +
|
14
|
+
'another part of the system.'},
|
15
|
+
'Duplication' =>
|
16
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/duplication',
|
17
|
+
'info' => 'Duplication occurs when two fragments of code look nearly identical, or when ' +
|
18
|
+
'two fragments of code have nearly identical effects at some conceptual level.'},
|
19
|
+
'Low Cohesion' =>
|
20
|
+
{'link' => 'http://en.wikipedia.org/wiki/Cohesion_(computer_science)',
|
21
|
+
'info' => 'Low cohesion is associated with undesirable traits such as being difficult to ' +
|
22
|
+
'maintain, difficult to test, difficult to reuse, and even difficult to understand.'},
|
23
|
+
'Nested Iterators' =>
|
24
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/nested-iterators',
|
25
|
+
'info' => 'Nested Iterator occurs when a block contains another block.'},
|
26
|
+
'Control Couple' =>
|
27
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/control-couple',
|
28
|
+
'info' => 'Control coupling occurs when a method or block checks the value of a parameter in ' +
|
29
|
+
'order to decide which execution path to take. The offending parameter is often called a “Control Couple”.'},
|
30
|
+
'Irresponsible Module' =>
|
31
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/irresponsible-module',
|
32
|
+
'info' => 'Classes and modules are the units of reuse and release. It is therefore considered ' +
|
33
|
+
'good practice to annotate every class and module with a brief comment outlining its responsibilities.'},
|
34
|
+
'Long Parameter List' =>
|
35
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/long-parameter-list',
|
36
|
+
'info' => 'A Long Parameter List occurs when a method has more than one or two parameters, ' +
|
37
|
+
'or when a method yields more than one or two objects to an associated block.'},
|
38
|
+
'Data Clump' =>
|
39
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/data-clump',
|
40
|
+
'info' => 'In general, a Data Clump occurs when the same two or three items frequently appear ' +
|
41
|
+
'together in classes and parameter lists, or when a group of instance variable names ' +
|
42
|
+
'start or end with similar substrings.'},
|
43
|
+
'Simulated Polymorphism' =>
|
44
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/simulated-polymorphism',
|
45
|
+
'info' => 'Simulated Polymorphism occurs when, code uses a case statement (especially on a ' +
|
46
|
+
'type field) or code uses instance_of?, kind_of?, is_a?, or === to decide what code to execute'},
|
47
|
+
'Large Class' =>
|
48
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/large-class',
|
49
|
+
'info' => 'A Large Class is a class or module that has a large number of instance variables, ' +
|
50
|
+
'methods or lines of code in any one piece of its specification.'},
|
51
|
+
'Long Method' =>
|
52
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/long-method',
|
53
|
+
'info' => 'Long methods can be hard to read and understand. They often are harder to test and ' +
|
54
|
+
'maintain as well, which can lead to buggier code.'},
|
55
|
+
'Feature Envy' =>
|
56
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/feature-envy',
|
57
|
+
'info' => 'Feature Envy occurs when a code fragment references another object more often than ' +
|
58
|
+
'it references itself, or when several clients do the same series of manipulations ' +
|
59
|
+
'on a particular type of object.'},
|
60
|
+
'Utility Function' =>
|
61
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/utility-function',
|
62
|
+
'info' => 'A Utility Function is any instance method that has no dependency on the state of the ' +
|
63
|
+
'instance. It reduces the code’s ability to communicate intent. Code that “belongs” on ' +
|
64
|
+
'one class but which is located in another can be hard to find.'},
|
65
|
+
'Attribute' =>
|
66
|
+
{'link' => 'http://wiki.github.com/kevinrutherford/reek/attribute',
|
67
|
+
'info' => 'A class that publishes a getter or setter for an instance variable invites client ' +
|
68
|
+
'classes to become too intimate with its inner workings, and in particular with its ' +
|
69
|
+
'representation of state.'}
|
23
70
|
}
|
24
71
|
|
25
72
|
# Note that in practice, the prefix reek__ is appended to each one
|
data/lib/base/report.rb
CHANGED
@@ -28,6 +28,9 @@ module MetricFu
|
|
28
28
|
report_hash.to_yaml
|
29
29
|
end
|
30
30
|
|
31
|
+
def per_file_data
|
32
|
+
@per_file_data ||= {}
|
33
|
+
end
|
31
34
|
|
32
35
|
def report_hash #:nodoc:
|
33
36
|
@report_hash ||= {}
|
@@ -41,6 +44,7 @@ module MetricFu
|
|
41
44
|
def save_templatized_report
|
42
45
|
@template = MetricFu.template_class.new
|
43
46
|
@template.report = report_hash
|
47
|
+
@template.per_file_data = per_file_data
|
44
48
|
@template.write
|
45
49
|
end
|
46
50
|
|
@@ -51,7 +55,11 @@ module MetricFu
|
|
51
55
|
# The hash to add to the aggregate report_hash
|
52
56
|
def add(report_type)
|
53
57
|
clazz = MetricFu.const_get(report_type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
|
54
|
-
|
58
|
+
inst = clazz.new
|
59
|
+
|
60
|
+
report_hash.merge!(inst.generate_report)
|
61
|
+
|
62
|
+
inst.per_file_info(per_file_data) if inst.respond_to?(:per_file_info)
|
55
63
|
end
|
56
64
|
|
57
65
|
# Saves the passed in content to the passed in directory. If
|
data/lib/generators/churn.rb
CHANGED
data/lib/generators/flog.rb
CHANGED
@@ -48,6 +48,19 @@ module MetricFu
|
|
48
48
|
:method_containers => sorted_containers.map {|method_container| method_container.to_h}}}
|
49
49
|
end
|
50
50
|
|
51
|
+
def per_file_info(out)
|
52
|
+
@method_containers.each_pair do |klass, container|
|
53
|
+
container.methods.each_pair do |method_name, data|
|
54
|
+
next if data[:path].nil?
|
55
|
+
|
56
|
+
file, line = data[:path].split(':')
|
57
|
+
|
58
|
+
out[file] ||= {}
|
59
|
+
out[file][line] ||= []
|
60
|
+
out[file][line] << {:type => :flog, :description => "Score of %.2f" % data[:score]}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
51
64
|
end
|
52
65
|
|
53
66
|
class MethodContainer
|
@@ -2,24 +2,52 @@ module MetricFu
|
|
2
2
|
class RailsBestPractices < Generator
|
3
3
|
|
4
4
|
def emit
|
5
|
-
@output = `rails_best_practices .`
|
5
|
+
@output = `rails_best_practices --without-color .`
|
6
6
|
end
|
7
7
|
|
8
8
|
def analyze
|
9
9
|
@matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
|
10
10
|
total = @matches.pop
|
11
|
+
cleanup_color_switches(total.first)
|
12
|
+
|
11
13
|
2.times { @matches.pop } # ignore wiki link
|
12
14
|
@matches.reject! {|array| array.empty? }
|
13
15
|
@matches.map! do |match|
|
14
16
|
file, line = match[0].split(':')
|
15
17
|
problem = match[1]
|
18
|
+
|
19
|
+
cleanup_color_switches(file)
|
20
|
+
cleanup_color_switches(problem)
|
21
|
+
|
22
|
+
file.gsub!(/^\.\//, '')
|
23
|
+
|
16
24
|
{:file => file, :line => line, :problem => problem}
|
17
25
|
end
|
18
26
|
@rails_best_practices_results = {:total => total, :problems => @matches}
|
19
27
|
end
|
20
28
|
|
29
|
+
def cleanup_color_switches(str)
|
30
|
+
return if str.nil?
|
31
|
+
str.gsub!(%r{^\e\[3[1|2]m}, '')
|
32
|
+
str.gsub!(%r{\e\[0m$}, '')
|
33
|
+
end
|
34
|
+
|
21
35
|
def to_h
|
22
36
|
{:rails_best_practices => @rails_best_practices_results}
|
23
37
|
end
|
38
|
+
|
39
|
+
def per_file_info(out)
|
40
|
+
@rails_best_practices_results[:problems].each do |problem|
|
41
|
+
next if problem[:file] == '' || problem[:problem].nil?
|
42
|
+
|
43
|
+
out[problem[:file]] ||= {}
|
44
|
+
|
45
|
+
lines = problem[:line].split(/\s*,\s*/)
|
46
|
+
lines.each do |line|
|
47
|
+
out[problem[:file]][line] ||= []
|
48
|
+
out[problem[:file]][line] << {:type => :rails_best_practices, :description => problem[:problem]}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
24
52
|
end
|
25
53
|
end
|
data/lib/generators/rcov.rb
CHANGED
@@ -3,7 +3,7 @@ require 'enumerator'
|
|
3
3
|
module MetricFu
|
4
4
|
|
5
5
|
class Rcov < Generator
|
6
|
-
NEW_FILE_MARKER =
|
6
|
+
NEW_FILE_MARKER = /^={80}$/.freeze
|
7
7
|
|
8
8
|
class Line
|
9
9
|
attr_accessor :content, :was_run
|
@@ -100,11 +100,7 @@ module MetricFu
|
|
100
100
|
files.each_pair {|fname, content| files[fname] = content.split("\n") }
|
101
101
|
files.each_pair do |fname, content|
|
102
102
|
content.map! do |raw_line|
|
103
|
-
|
104
|
-
line = Line.new(raw_line.gsub('!!', ' '), false).to_h
|
105
|
-
else
|
106
|
-
line = Line.new(raw_line, true).to_h
|
107
|
-
end
|
103
|
+
line = Line.new(raw_line[3..-1], !raw_line.match(/^!!/)).to_h
|
108
104
|
end
|
109
105
|
content.reject! {|line| line[:content].blank? }
|
110
106
|
files[fname] = {:lines => content}
|
data/lib/generators/reek.rb
CHANGED
@@ -56,5 +56,26 @@ module MetricFu
|
|
56
56
|
{:reek => {:matches => @matches}}
|
57
57
|
end
|
58
58
|
|
59
|
+
def per_file_info(out)
|
60
|
+
@matches.each do |file_data|
|
61
|
+
next if File.extname(file_data[:file_path]) == '.erb'
|
62
|
+
begin
|
63
|
+
line_numbers = MetricFu::LineNumbers.new(File.open(file_data[:file_path], 'r').read)
|
64
|
+
rescue StandardError => e
|
65
|
+
raise e unless e.message =~ /you shouldn't be able to get here/
|
66
|
+
puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level reek information for this file."
|
67
|
+
next
|
68
|
+
end
|
69
|
+
|
70
|
+
out[file_data[:file_path]] ||= {}
|
71
|
+
file_data[:code_smells].each do |smell_data|
|
72
|
+
line = line_numbers.start_line_for_method(smell_data[:method])
|
73
|
+
out[file_data[:file_path]][line.to_s] ||= []
|
74
|
+
out[file_data[:file_path]][line.to_s] << {:type => :reek,
|
75
|
+
:description => "#{smell_data[:type]} - #{smell_data[:message]}"}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
59
80
|
end
|
60
81
|
end
|
data/lib/generators/roodi.rb
CHANGED
@@ -23,5 +23,13 @@ module MetricFu
|
|
23
23
|
def to_h
|
24
24
|
{:roodi => @roodi_results}
|
25
25
|
end
|
26
|
+
|
27
|
+
def per_file_info(out)
|
28
|
+
@matches.each do |match|
|
29
|
+
out[match[:file]] ||= {}
|
30
|
+
out[match[:file]][match[:line]] ||= []
|
31
|
+
out[match[:file]][match[:line]] << {:type => :roodi, :description => match[:problem]}
|
32
|
+
end
|
33
|
+
end
|
26
34
|
end
|
27
35
|
end
|
data/lib/generators/saikuro.rb
CHANGED
@@ -4,7 +4,7 @@ module MetricFu
|
|
4
4
|
|
5
5
|
def emit
|
6
6
|
options_string = MetricFu.saikuro.inject("") do |options, option|
|
7
|
-
options + "--#{option.join(' ')} "
|
7
|
+
option[0] == :input_directory ? options : options + "--#{option.join(' ')} "
|
8
8
|
end
|
9
9
|
|
10
10
|
MetricFu.saikuro[:input_directory].each do |input_dir|
|
@@ -32,14 +32,42 @@ module MetricFu
|
|
32
32
|
def to_h
|
33
33
|
files = @files.map do |file|
|
34
34
|
my_file = file.to_h
|
35
|
-
|
35
|
+
|
36
|
+
f = file.filepath
|
37
|
+
f.gsub!(%r{^#{metric_directory}/}, '')
|
38
|
+
f << "/#{file.filename}"
|
39
|
+
|
40
|
+
my_file[:filename] = f
|
36
41
|
my_file
|
37
42
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
}
|
43
|
+
@saikuro_data = {:files => files,
|
44
|
+
:classes => @classes.map {|c| c.to_h},
|
45
|
+
:methods => @meths.map {|m| m.to_h}
|
46
|
+
}
|
47
|
+
{:saikuro => @saikuro_data}
|
48
|
+
end
|
49
|
+
|
50
|
+
def per_file_info(out)
|
51
|
+
@saikuro_data[:files].each do |file_data|
|
52
|
+
next if File.extname(file_data[:filename]) == '.erb' || !File.exists?(file_data[:filename])
|
53
|
+
begin
|
54
|
+
line_numbers = MetricFu::LineNumbers.new(File.open(file_data[:filename], 'r').read)
|
55
|
+
rescue StandardError => e
|
56
|
+
raise e unless e.message =~ /you shouldn't be able to get here/
|
57
|
+
puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level Saikuro information for this file."
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
out[file_data[:filename]] ||= {}
|
62
|
+
file_data[:classes].each do |class_data|
|
63
|
+
class_data[:methods].each do |method_data|
|
64
|
+
line = line_numbers.start_line_for_method(method_data[:name])
|
65
|
+
out[file_data[:filename]][line.to_s] ||= []
|
66
|
+
out[file_data[:filename]][line.to_s] << {:type => :saikuro,
|
67
|
+
:description => "Complexity #{method_data[:complexity]}"}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
43
71
|
end
|
44
72
|
|
45
73
|
private
|
@@ -118,6 +146,10 @@ module MetricFu
|
|
118
146
|
File.basename(@path, '_cyclo.html')
|
119
147
|
end
|
120
148
|
|
149
|
+
def filepath
|
150
|
+
File.dirname(@path)
|
151
|
+
end
|
152
|
+
|
121
153
|
def to_h
|
122
154
|
merge_classes
|
123
155
|
{:classes => @elements}
|
data/lib/generators/stats.rb
CHANGED
@@ -26,8 +26,7 @@ module MetricFu
|
|
26
26
|
|
27
27
|
def remove_noise(output)
|
28
28
|
lines = output.split("\n")
|
29
|
-
lines = lines.find_all {|line| line[
|
30
|
-
lines = lines.find_all {|line| line[0].chr != "(" }
|
29
|
+
lines = lines.find_all {|line| line =~ /^\s*[C|]/ }
|
31
30
|
lines.shift
|
32
31
|
lines
|
33
32
|
end
|
data/lib/metric_fu.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'syntax/convertors/html'
|
2
3
|
|
3
4
|
class AwesomeTemplate < MetricFu::Template
|
4
5
|
|
@@ -14,6 +15,7 @@ class AwesomeTemplate < MetricFu::Template
|
|
14
15
|
report.each_pair do |section, contents|
|
15
16
|
if template_exists?(section)
|
16
17
|
create_instance_var(section, contents)
|
18
|
+
create_instance_var(:per_file_data, per_file_data)
|
17
19
|
@html = erbify(section)
|
18
20
|
html = erbify('layout')
|
19
21
|
fn = output_filename(section)
|
@@ -28,6 +30,39 @@ class AwesomeTemplate < MetricFu::Template
|
|
28
30
|
fn = output_filename('index')
|
29
31
|
MetricFu.report.save_output(html, MetricFu.output_directory, fn)
|
30
32
|
end
|
33
|
+
|
34
|
+
write_file_data
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_file_data
|
38
|
+
convertor = Syntax::Convertors::HTML.for_syntax('ruby')
|
39
|
+
|
40
|
+
per_file_data.each_pair do |file, lines|
|
41
|
+
data = File.open(file, 'r').readlines
|
42
|
+
fn = "#{file.gsub(%r{/}, '_')}.html"
|
43
|
+
|
44
|
+
out = "<html><head><style>#{inline_css('css/syntax.css')}</style></head><body>"
|
45
|
+
out << "<table cellpadding='0' cellspacing='0' class='ruby'>"
|
46
|
+
data.each_with_index do |line, idx|
|
47
|
+
out << "<tr><td valign='top'><small>#{idx + 1}</small></td>"
|
48
|
+
out << "<td valign='top'>"
|
49
|
+
if lines.has_key?((idx + 1).to_s)
|
50
|
+
out << "<ul>"
|
51
|
+
lines[(idx + 1).to_s].each do |problem|
|
52
|
+
out << "<li>#{problem[:description]} » #{problem[:type]}</li>"
|
53
|
+
end
|
54
|
+
out << "</ul>"
|
55
|
+
else
|
56
|
+
out << " "
|
57
|
+
end
|
58
|
+
out << "</td>"
|
59
|
+
out << "<td valign='top'><a name='line#{idx + 1}'>#{convertor.convert(line)}</a></td>"
|
60
|
+
out << "</tr>"
|
61
|
+
end
|
62
|
+
out << "<table></body></html>"
|
63
|
+
|
64
|
+
MetricFu.report.save_output(out, MetricFu.output_directory, fn)
|
65
|
+
end
|
31
66
|
end
|
32
67
|
|
33
68
|
def this_directory
|
@@ -0,0 +1,19 @@
|
|
1
|
+
table { background: #fff; color: #000; }
|
2
|
+
.ruby .normal { color: #000; }
|
3
|
+
.ruby .comment { color: #005; font-style: italic; }
|
4
|
+
.ruby .keyword { color: #A44; font-weight: bold; }
|
5
|
+
.ruby .method { color: #44f; }
|
6
|
+
.ruby .class { color: #b1713d; }
|
7
|
+
.ruby .module { color: #050; }
|
8
|
+
.ruby .punct { color: #668; font-weight: bold; }
|
9
|
+
.ruby .symbol { color: #00f; }
|
10
|
+
.ruby .string { color: #4a4; }
|
11
|
+
.ruby .char { color: #F07; }
|
12
|
+
.ruby .ident { color: #000; }
|
13
|
+
.ruby .constant { color: #b1713d; }
|
14
|
+
.ruby .regex { color: #B66; background: #FEF; }
|
15
|
+
.ruby .number { color: #F99; }
|
16
|
+
.ruby .attribute { color: #f84; }
|
17
|
+
.ruby .global { color: #7FB; }
|
18
|
+
.ruby .expr { color: #227; }
|
19
|
+
.ruby .escape { color: #277; }
|
@@ -32,6 +32,14 @@ No Hotspots were found.
|
|
32
32
|
<b>
|
33
33
|
<%= display_location(item[:location], nil) %>
|
34
34
|
</b>
|
35
|
+
<% if item[:location].file_path %>
|
36
|
+
<% file, line = item[:location].file_path.split(/:/) %>
|
37
|
+
<% if per_file_data[file] %>
|
38
|
+
<small>«
|
39
|
+
<b><a href="<%= file.gsub(%r{/}, '_') %>.html<%= (line.nil? ? '' : "#line#{line}") %>">annotate</a></b>
|
40
|
+
»</small>
|
41
|
+
<% end %>
|
42
|
+
<% end %>
|
35
43
|
<br/><br/>
|
36
44
|
<!-- TODO HOTSPOTS for metric fu nice metric_link method -->
|
37
45
|
<% item[:details].each do |metric, info| %>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<h3>Rails Best Practices Results</h3>
|
2
2
|
|
3
|
-
<p><a href="http://github.com/flyerhzm/rails_best_practices">rails_best_practices</a>
|
3
|
+
<p><a href="http://github.com/flyerhzm/rails_best_practices">rails_best_practices</a> is a code metric tool for rails projects.</p>
|
4
4
|
|
5
5
|
<% graph_name = 'rails_best_practices' %>
|
6
6
|
<% if MetricFu.configuration.graph_engine == :gchart %>
|
@@ -88,6 +88,7 @@ describe MetricFu::Template do
|
|
88
88
|
before(:each) do
|
89
89
|
config = mock("configuration")
|
90
90
|
config.stub!(:platform).and_return('universal-darwin-9.0')
|
91
|
+
config.stub!(:darwin_txmt_protocol_no_thanks).and_return(false)
|
91
92
|
MetricFu.stub!(:configuration).and_return(config)
|
92
93
|
end
|
93
94
|
|
@@ -112,6 +113,22 @@ describe MetricFu::Template do
|
|
112
113
|
+ "/expanded/filename&line=6'>filename:6</a>")
|
113
114
|
end
|
114
115
|
|
116
|
+
describe "but no thanks for txtmt" do
|
117
|
+
before(:each) do
|
118
|
+
config = mock("configuration")
|
119
|
+
config.stub!(:platform).and_return('universal-darwin-9.0')
|
120
|
+
config.stub!(:darwin_txmt_protocol_no_thanks).and_return(true)
|
121
|
+
MetricFu.stub!(:configuration).and_return(config)
|
122
|
+
File.should_receive(:expand_path).and_return('filename')
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should return a file protocol link" do
|
126
|
+
name = "filename"
|
127
|
+
result = @template.send(:link_to_filename, name)
|
128
|
+
result.should == "<a href='file://filename'>filename</a>"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
115
132
|
describe "and given link text" do
|
116
133
|
it "should use the submitted link text" do
|
117
134
|
File.stub!(:expand_path).with('filename').and_return('/expanded/filename')
|
@@ -126,6 +143,7 @@ describe MetricFu::Template do
|
|
126
143
|
before(:each) do
|
127
144
|
config = mock("configuration")
|
128
145
|
config.should_receive(:platform).and_return('other')
|
146
|
+
config.stub!(:darwin_txmt_protocol_no_thanks).and_return(false)
|
129
147
|
MetricFu.stub!(:configuration).and_return(config)
|
130
148
|
File.should_receive(:expand_path).and_return('filename')
|
131
149
|
end
|
@@ -157,5 +175,3 @@ describe MetricFu::Template do
|
|
157
175
|
end
|
158
176
|
|
159
177
|
end
|
160
|
-
|
161
|
-
|
data/spec/base/report_spec.rb
CHANGED
@@ -34,6 +34,7 @@ describe MetricFu::Report do
|
|
34
34
|
template_class.should_receive(:new).and_return(template_class)
|
35
35
|
MetricFu.should_receive(:template_class).and_return(template_class)
|
36
36
|
template_class.should_receive(:report=)
|
37
|
+
template_class.should_receive(:per_file_data=)
|
37
38
|
template_class.should_receive(:write)
|
38
39
|
@report.save_templatized_report
|
39
40
|
end
|
@@ -43,7 +44,13 @@ describe MetricFu::Report do
|
|
43
44
|
it 'should add a passed hash to the report_hash instance variable' do
|
44
45
|
report_type = mock('report_type')
|
45
46
|
report_type.should_receive(:to_s).and_return('type')
|
46
|
-
|
47
|
+
|
48
|
+
report_inst = mock('report_inst')
|
49
|
+
report_type.should_receive(:new).and_return(report_inst)
|
50
|
+
|
51
|
+
report_inst.should_receive(:generate_report).and_return({:a => 'b'})
|
52
|
+
report_inst.should_receive(:respond_to?).and_return(false)
|
53
|
+
|
47
54
|
MetricFu.should_receive(:const_get).
|
48
55
|
with('Type').and_return(report_type)
|
49
56
|
report_hash = mock('report_hash')
|
@@ -11,7 +11,7 @@ describe Churn do
|
|
11
11
|
|
12
12
|
it "should be empty on error" do
|
13
13
|
churn = MetricFu::Churn.new
|
14
|
-
churn.instance_variable_set(:@output, "
|
14
|
+
churn.instance_variable_set(:@output, "Churning requires a subversion or git repo")
|
15
15
|
result = churn.analyze
|
16
16
|
result.should == [:churn => {}]
|
17
17
|
end
|
@@ -5,7 +5,7 @@ describe RailsBestPractices do
|
|
5
5
|
it "should gather the raw data" do
|
6
6
|
MetricFu::Configuration.run {}
|
7
7
|
practices = MetricFu::RailsBestPractices.new
|
8
|
-
practices.should_receive(:`).with("rails_best_practices .")
|
8
|
+
practices.should_receive(:`).with("rails_best_practices --without-color .")
|
9
9
|
practices.emit
|
10
10
|
end
|
11
11
|
end
|
@@ -34,10 +34,10 @@ describe RailsBestPractices do
|
|
34
34
|
@results[:problems].size.should == 2
|
35
35
|
@results[:problems].first.should == { :line => "17",
|
36
36
|
:problem => "replace instance variable with local variable",
|
37
|
-
:file => "
|
37
|
+
:file => "app/views/admin/testimonials/_form.html.erb" }
|
38
38
|
@results[:problems][1].should == { :line => "24,45,68,85",
|
39
39
|
:problem => "use before_filter for show,edit,update,destroy",
|
40
|
-
:file => "
|
40
|
+
:file => "app/controllers/admin/campaigns_controller.rb" }
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -47,12 +47,12 @@ describe MetricFu::Rcov do
|
|
47
47
|
|
48
48
|
it "should know which lines were run" do
|
49
49
|
@files["lib/templates/awesome/awesome_template.rb"][:lines].
|
50
|
-
should include({:content=>"
|
50
|
+
should include({:content=>"require 'fileutils'", :was_run=>true})
|
51
51
|
end
|
52
52
|
|
53
53
|
it "should know which lines NOT were run" do
|
54
54
|
@files["lib/templates/awesome/awesome_template.rb"][:lines].
|
55
|
-
should include({:content=>"
|
55
|
+
should include({:content=>" if template_exists?(section)", :was_run=>false})
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -12,7 +12,7 @@ describe Saikuro do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it "should find the filename of a file" do
|
15
|
-
@output[:saikuro][:files].first[:filename].should == 'users_controller.rb'
|
15
|
+
@output[:saikuro][:files].first[:filename].should == 'app/controllers/users_controller.rb'
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should find the name of the classes" do
|
@@ -33,6 +33,22 @@ describe Saikuro do
|
|
33
33
|
@output[:saikuro][:methods].first[:lines].should == 15
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
describe "per_file_info method" do
|
38
|
+
before :all do
|
39
|
+
MetricFu::Configuration.run {}
|
40
|
+
File.stub!(:directory?).and_return(true)
|
41
|
+
@saikuro = MetricFu::Saikuro.new
|
42
|
+
@saikuro.stub!(:metric_directory).and_return(File.join(File.dirname(__FILE__), "..", "resources", "saikuro"))
|
43
|
+
@saikuro.analyze
|
44
|
+
@output = @saikuro.to_h
|
45
|
+
end
|
46
|
+
|
47
|
+
it "doesn't try to get information if the file does not exist" do
|
48
|
+
File.should_receive(:exists?).at_least(:once).and_return(false)
|
49
|
+
@saikuro.per_file_info('ignore_me')
|
50
|
+
end
|
51
|
+
end
|
36
52
|
|
37
53
|
describe "format_directories method" do
|
38
54
|
it "should format the directories" do
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metric_fu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
- 0
|
9
8
|
- 1
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 2.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jake Scruggs
|
@@ -24,7 +24,7 @@ autorequire:
|
|
24
24
|
bindir: bin
|
25
25
|
cert_chain: []
|
26
26
|
|
27
|
-
date:
|
27
|
+
date: 2011-03-01 00:00:00 -06:00
|
28
28
|
default_executable:
|
29
29
|
dependencies:
|
30
30
|
- !ruby/object:Gem::Dependency
|
@@ -51,12 +51,12 @@ dependencies:
|
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
hash:
|
54
|
+
hash: 3
|
55
55
|
segments:
|
56
56
|
- 2
|
57
|
-
-
|
57
|
+
- 3
|
58
58
|
- 0
|
59
|
-
version: 2.
|
59
|
+
version: 2.3.0
|
60
60
|
type: :runtime
|
61
61
|
version_requirements: *id002
|
62
62
|
- !ruby/object:Gem::Dependency
|
@@ -116,12 +116,12 @@ dependencies:
|
|
116
116
|
requirements:
|
117
117
|
- - ">="
|
118
118
|
- !ruby/object:Gem::Version
|
119
|
-
hash:
|
119
|
+
hash: 15
|
120
120
|
segments:
|
121
121
|
- 0
|
122
|
-
-
|
123
|
-
-
|
124
|
-
version: 0.
|
122
|
+
- 6
|
123
|
+
- 4
|
124
|
+
version: 0.6.4
|
125
125
|
type: :runtime
|
126
126
|
version_requirements: *id006
|
127
127
|
- !ruby/object:Gem::Dependency
|
@@ -132,12 +132,12 @@ dependencies:
|
|
132
132
|
requirements:
|
133
133
|
- - ~>
|
134
134
|
- !ruby/object:Gem::Version
|
135
|
-
hash:
|
135
|
+
hash: 19
|
136
136
|
segments:
|
137
137
|
- 0
|
138
|
-
- 2
|
139
138
|
- 3
|
140
|
-
|
139
|
+
- 0
|
140
|
+
version: 0.3.0
|
141
141
|
type: :runtime
|
142
142
|
version_requirements: *id007
|
143
143
|
- !ruby/object:Gem::Dependency
|
@@ -189,9 +189,23 @@ dependencies:
|
|
189
189
|
type: :runtime
|
190
190
|
version_requirements: *id010
|
191
191
|
- !ruby/object:Gem::Dependency
|
192
|
-
name:
|
192
|
+
name: syntax
|
193
193
|
prerelease: false
|
194
194
|
requirement: &id011 !ruby/object:Gem::Requirement
|
195
|
+
none: false
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
hash: 3
|
200
|
+
segments:
|
201
|
+
- 0
|
202
|
+
version: "0"
|
203
|
+
type: :runtime
|
204
|
+
version_requirements: *id011
|
205
|
+
- !ruby/object:Gem::Dependency
|
206
|
+
name: rspec
|
207
|
+
prerelease: false
|
208
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
195
209
|
none: false
|
196
210
|
requirements:
|
197
211
|
- - "="
|
@@ -203,11 +217,11 @@ dependencies:
|
|
203
217
|
- 0
|
204
218
|
version: 1.3.0
|
205
219
|
type: :development
|
206
|
-
version_requirements: *
|
220
|
+
version_requirements: *id012
|
207
221
|
- !ruby/object:Gem::Dependency
|
208
222
|
name: test-construct
|
209
223
|
prerelease: false
|
210
|
-
requirement: &
|
224
|
+
requirement: &id013 !ruby/object:Gem::Requirement
|
211
225
|
none: false
|
212
226
|
requirements:
|
213
227
|
- - ">="
|
@@ -219,11 +233,11 @@ dependencies:
|
|
219
233
|
- 0
|
220
234
|
version: 1.2.0
|
221
235
|
type: :development
|
222
|
-
version_requirements: *
|
236
|
+
version_requirements: *id013
|
223
237
|
- !ruby/object:Gem::Dependency
|
224
238
|
name: googlecharts
|
225
239
|
prerelease: false
|
226
|
-
requirement: &
|
240
|
+
requirement: &id014 !ruby/object:Gem::Requirement
|
227
241
|
none: false
|
228
242
|
requirements:
|
229
243
|
- - ">="
|
@@ -233,7 +247,7 @@ dependencies:
|
|
233
247
|
- 0
|
234
248
|
version: "0"
|
235
249
|
type: :development
|
236
|
-
version_requirements: *
|
250
|
+
version_requirements: *id014
|
237
251
|
description: Code metrics from Flog, Flay, RCov, Saikuro, Churn, Reek, Roodi, Rails' stats task and Rails Best Practices
|
238
252
|
email: jake.scruggs@gmail.com
|
239
253
|
executables: []
|
@@ -296,6 +310,7 @@ files:
|
|
296
310
|
- lib/templates/awesome/css/default.css
|
297
311
|
- lib/templates/awesome/css/integrity.css
|
298
312
|
- lib/templates/awesome/css/reset.css
|
313
|
+
- lib/templates/awesome/css/syntax.css
|
299
314
|
- lib/templates/awesome/flay.html.erb
|
300
315
|
- lib/templates/awesome/flog.html.erb
|
301
316
|
- lib/templates/awesome/hotspots.html.erb
|