metric_fu 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +13 -5
- data/README +2 -2
- data/Rakefile +1 -1
- data/TODO +3 -1
- data/lib/base/base_template.rb +16 -16
- data/lib/base/churn_analyzer.rb +3 -3
- data/lib/base/code_issue.rb +8 -8
- data/lib/base/configuration.rb +24 -22
- data/lib/base/flay_analyzer.rb +2 -2
- data/lib/base/flog_analyzer.rb +4 -4
- data/lib/base/generator.rb +21 -21
- data/lib/base/graph.rb +15 -10
- data/lib/base/line_numbers.rb +56 -55
- data/lib/base/location.rb +68 -66
- data/lib/base/metric_analyzer.rb +21 -21
- data/lib/base/ranking.rb +23 -22
- data/lib/base/rcov_analyzer.rb +3 -3
- data/lib/base/reek_analyzer.rb +12 -10
- data/lib/base/report.rb +9 -9
- data/lib/base/roodi_analyzer.rb +2 -2
- data/lib/base/saikuro_analyzer.rb +4 -4
- data/lib/base/scoring_strategies.rb +5 -5
- data/lib/base/stats_analyzer.rb +2 -2
- data/lib/base/table.rb +4 -4
- data/lib/generators/churn.rb +1 -1
- data/lib/generators/flay.rb +1 -1
- data/lib/generators/hotspots.rb +4 -4
- data/lib/generators/rails_best_practices.rb +1 -1
- data/lib/generators/rcov.rb +17 -17
- data/lib/generators/reek.rb +2 -2
- data/lib/generators/saikuro.rb +42 -42
- data/lib/generators/stats.rb +6 -6
- data/lib/graphs/engines/bluff.rb +1 -1
- data/lib/graphs/engines/gchart.rb +7 -7
- data/lib/graphs/flog_grapher.rb +3 -3
- data/lib/graphs/grapher.rb +1 -1
- data/lib/metric_fu.rb +2 -2
- data/lib/templates/awesome/churn.html.erb +1 -1
- data/lib/templates/awesome/css/integrity.css +0 -1
- data/lib/templates/awesome/flay.html.erb +2 -2
- data/lib/templates/awesome/flog.html.erb +2 -2
- data/lib/templates/awesome/hotspots.html.erb +4 -4
- data/lib/templates/awesome/index.html.erb +2 -2
- data/lib/templates/awesome/rails_best_practices.html.erb +1 -1
- data/lib/templates/awesome/rcov.html.erb +1 -1
- data/lib/templates/awesome/roodi.html.erb +1 -1
- data/lib/templates/awesome/saikuro.html.erb +3 -3
- data/lib/templates/awesome/stats.html.erb +1 -1
- data/lib/templates/standard/churn.html.erb +2 -2
- data/lib/templates/standard/default.css +4 -4
- data/lib/templates/standard/flay.html.erb +4 -4
- data/lib/templates/standard/flog.html.erb +1 -1
- data/lib/templates/standard/hotspots.html.erb +4 -4
- data/lib/templates/standard/index.html.erb +2 -2
- data/lib/templates/standard/rails_best_practices.html.erb +2 -2
- data/lib/templates/standard/rcov.html.erb +2 -2
- data/lib/templates/standard/reek.html.erb +1 -1
- data/lib/templates/standard/roodi.html.erb +2 -2
- data/lib/templates/standard/saikuro.html.erb +4 -4
- data/lib/templates/standard/stats.html.erb +2 -2
- data/spec/base/base_template_spec.rb +1 -1
- data/spec/base/configuration_spec.rb +36 -36
- data/spec/base/generator_spec.rb +10 -10
- data/spec/base/graph_spec.rb +41 -4
- data/spec/base/line_numbers_spec.rb +22 -22
- data/spec/base/report_spec.rb +9 -9
- data/spec/generators/churn_spec.rb +4 -4
- data/spec/generators/flay_spec.rb +31 -31
- data/spec/generators/flog_spec.rb +18 -18
- data/spec/generators/rails_best_practices_spec.rb +6 -6
- data/spec/generators/rcov_spec.rb +18 -18
- data/spec/generators/reek_spec.rb +10 -10
- data/spec/generators/roodi_spec.rb +2 -2
- data/spec/generators/saikuro_spec.rb +7 -7
- data/spec/generators/stats_spec.rb +6 -6
- data/spec/graphs/engines/gchart_spec.rb +8 -8
- data/spec/graphs/flog_grapher_spec.rb +8 -8
- data/spec/resources/line_numbers/foo.rb +7 -7
- data/spec/resources/line_numbers/module.rb +2 -2
- data/spec/resources/line_numbers/module_surrounds_class.rb +6 -6
- data/spec/resources/saikuro/index_cyclo.html +2 -2
- data/spec/resources/yml/20090630.yml +349 -349
- data/spec/resources/yml/metric_missing.yml +1 -1
- data/tasks/metric_fu.rake +4 -4
- metadata +4 -4
data/lib/base/ranking.rb
CHANGED
@@ -1,33 +1,34 @@
|
|
1
1
|
require 'forwardable'
|
2
|
+
module MetricFu
|
3
|
+
class Ranking
|
4
|
+
extend Forwardable
|
2
5
|
|
3
|
-
|
4
|
-
|
6
|
+
def initialize
|
7
|
+
@items_to_score = {}
|
8
|
+
end
|
5
9
|
|
6
|
-
|
7
|
-
|
8
|
-
|
10
|
+
def top(num=nil)
|
11
|
+
if(num.is_a?(Numeric))
|
12
|
+
sorted_items[0,num]
|
13
|
+
else
|
14
|
+
sorted_items
|
15
|
+
end
|
16
|
+
end
|
9
17
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
sorted_items
|
18
|
+
def percentile(item)
|
19
|
+
index = sorted_items.index(item)
|
20
|
+
worse_item_count = (length - (index+1))
|
21
|
+
worse_item_count.to_f/length
|
15
22
|
end
|
16
|
-
end
|
17
23
|
|
18
|
-
|
19
|
-
|
20
|
-
worse_item_count = (length - (index+1))
|
21
|
-
worse_item_count.to_f/length
|
22
|
-
end
|
24
|
+
def_delegator :@items_to_score, :has_key?, :scored?
|
25
|
+
def_delegators :@items_to_score, :[], :[]=, :length, :each, :delete
|
23
26
|
|
24
|
-
|
25
|
-
def_delegators :@items_to_score, :[], :[]=, :length, :each, :delete
|
27
|
+
private
|
26
28
|
|
27
|
-
|
29
|
+
def sorted_items
|
30
|
+
@sorted_items ||= @items_to_score.sort_by {|item, score| -score}.map {|item, score| item}
|
31
|
+
end
|
28
32
|
|
29
|
-
def sorted_items
|
30
|
-
@sorted_items ||= @items_to_score.sort_by {|item, score| -score}.map {|item, score| item}
|
31
33
|
end
|
32
|
-
|
33
34
|
end
|
data/lib/base/rcov_analyzer.rb
CHANGED
@@ -6,7 +6,7 @@ class RcovAnalyzer
|
|
6
6
|
def columns
|
7
7
|
COLUMNS
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def name
|
11
11
|
:rcov
|
12
12
|
end
|
@@ -22,13 +22,13 @@ class RcovAnalyzer
|
|
22
22
|
def score(metric_ranking, item)
|
23
23
|
ScoringStrategies.identity(metric_ranking, item)
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def generate_records(data, table)
|
27
27
|
return if data==nil
|
28
28
|
data.each do |file_name, info|
|
29
29
|
next if (file_name == :global_percent_run) || (info[:methods].nil?)
|
30
30
|
info[:methods].each do |method_name, percentage_uncovered|
|
31
|
-
location = Location.for(method_name)
|
31
|
+
location = MetricFu::Location.for(method_name)
|
32
32
|
table << {
|
33
33
|
"metric" => :rcov,
|
34
34
|
'file_path' => file_name,
|
data/lib/base/reek_analyzer.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
1
3
|
class ReekAnalyzer
|
2
4
|
include ScoringStrategies
|
3
5
|
|
4
|
-
REEK_ISSUE_INFO = {'Uncommunicative Name' =>
|
6
|
+
REEK_ISSUE_INFO = {'Uncommunicative Name' =>
|
5
7
|
{'link' => 'http://wiki.github.com/kevinrutherford/reek/uncommunicative-name', 'info' => 'An Uncommunicative Name is a name that doesn’t communicate its intent well enough.'},
|
6
8
|
'Class Variable' =>
|
7
9
|
{'link' => 'http://wiki.github.com/kevinrutherford/reek/class-variable', 'info' => 'Class variables form part of the global runtime state, and as such make it easy for one part of the system to accidentally or inadvertently depend on another part of the system.'},
|
@@ -32,7 +34,7 @@ class ReekAnalyzer
|
|
32
34
|
def columns
|
33
35
|
COLUMNS.map{|column| "#{name}__#{column}"}
|
34
36
|
end
|
35
|
-
|
37
|
+
|
36
38
|
def name
|
37
39
|
:reek
|
38
40
|
end
|
@@ -46,20 +48,20 @@ class ReekAnalyzer
|
|
46
48
|
end
|
47
49
|
|
48
50
|
def score(metric_ranking, item)
|
49
|
-
ScoringStrategies.percentile(metric_ranking, item)
|
51
|
+
ScoringStrategies.percentile(metric_ranking, item)
|
50
52
|
end
|
51
|
-
|
53
|
+
|
52
54
|
def generate_records(data, table)
|
53
55
|
return if data==nil
|
54
56
|
data[:matches].each do |match|
|
55
57
|
file_path = match[:file_path]
|
56
58
|
match[:code_smells].each do |smell|
|
57
|
-
location = Location.for(smell[:method])
|
59
|
+
location = MetricFu::Location.for(smell[:method])
|
58
60
|
smell_type = smell[:type]
|
59
61
|
message = smell[:message]
|
60
62
|
table << {
|
61
|
-
"metric" => name,
|
62
|
-
"file_path" => file_path,
|
63
|
+
"metric" => name, # important
|
64
|
+
"file_path" => file_path, # important
|
63
65
|
# NOTE: ReekAnalyzer is currently different than other analyzers with regard
|
64
66
|
# to column name. Note the COLUMNS constant and #columns method
|
65
67
|
"reek__message" => message,
|
@@ -67,8 +69,8 @@ class ReekAnalyzer
|
|
67
69
|
"reek__value" => parse_value(message),
|
68
70
|
"reek__value_description" => build_value_description(smell_type, message),
|
69
71
|
"reek__comparable_message" => comparable_message(smell_type, message),
|
70
|
-
"class_name" => location.class_name,
|
71
|
-
"method_name" => location.method_name,
|
72
|
+
"class_name" => location.class_name, # important
|
73
|
+
"method_name" => location.method_name, # important
|
72
74
|
}
|
73
75
|
end
|
74
76
|
end
|
@@ -101,7 +103,7 @@ class ReekAnalyzer
|
|
101
103
|
nil
|
102
104
|
end
|
103
105
|
end
|
104
|
-
|
106
|
+
|
105
107
|
def parse_value(message)
|
106
108
|
match = message.match(/\d+/)
|
107
109
|
if(match)
|
data/lib/base/report.rb
CHANGED
@@ -10,25 +10,25 @@ module MetricFu
|
|
10
10
|
#
|
11
11
|
# The Report class is responsible two things:
|
12
12
|
#
|
13
|
-
# It adds information to the yaml report, produced by the system
|
13
|
+
# It adds information to the yaml report, produced by the system
|
14
14
|
# as a whole, for each of the generators used in this test run.
|
15
15
|
#
|
16
16
|
# It also handles passing the information from each generator used
|
17
|
-
# in this test run out to the template class set in
|
17
|
+
# in this test run out to the template class set in
|
18
18
|
# MetricFu::Configuration.
|
19
19
|
class Report
|
20
|
-
|
20
|
+
|
21
21
|
# Renders the result of the report_hash into a yaml serialization
|
22
22
|
# ready for writing out to a file.
|
23
23
|
#
|
24
24
|
# @return YAML
|
25
|
-
# A YAML object containing the results of the report generation
|
25
|
+
# A YAML object containing the results of the report generation
|
26
26
|
# process
|
27
27
|
def to_yaml
|
28
28
|
report_hash.to_yaml
|
29
29
|
end
|
30
30
|
|
31
|
-
|
31
|
+
|
32
32
|
def report_hash #:nodoc:
|
33
33
|
@report_hash ||= {}
|
34
34
|
end
|
@@ -43,7 +43,7 @@ module MetricFu
|
|
43
43
|
@template.report = report_hash
|
44
44
|
@template.write
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
# Adds a hash from a passed report, produced by one of the Generator
|
48
48
|
# classes to the aggregate report_hash managed by this hash.
|
49
49
|
#
|
@@ -55,7 +55,7 @@ module MetricFu
|
|
55
55
|
end
|
56
56
|
|
57
57
|
# Saves the passed in content to the passed in directory. If
|
58
|
-
# a filename is passed in it will be used as the name of the
|
58
|
+
# a filename is passed in it will be used as the name of the
|
59
59
|
# file, otherwise it will default to 'index.html'
|
60
60
|
#
|
61
61
|
# @param content String
|
@@ -76,7 +76,7 @@ module MetricFu
|
|
76
76
|
|
77
77
|
# Checks to discover whether we should try and open the results
|
78
78
|
# of the report in the browser on this system. We only try and open
|
79
|
-
# in the browser if we're on OS X and we're not running in a
|
79
|
+
# in the browser if we're on OS X and we're not running in a
|
80
80
|
# CruiseControl.rb environment. See MetricFu.configuration for more
|
81
81
|
# details about how we make those guesses.
|
82
82
|
#
|
@@ -91,7 +91,7 @@ module MetricFu
|
|
91
91
|
# if we're able to open the browser on this platform.
|
92
92
|
#
|
93
93
|
# @param dir String
|
94
|
-
# The directory path where the 'index.html' we want to open is
|
94
|
+
# The directory path where the 'index.html' we want to open is
|
95
95
|
# stored
|
96
96
|
def show_in_browser(dir)
|
97
97
|
system("open #{dir}/index.html") if open_in_browser?
|
data/lib/base/roodi_analyzer.rb
CHANGED
@@ -6,7 +6,7 @@ class RoodiAnalyzer
|
|
6
6
|
def columns
|
7
7
|
COLUMNS
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def name
|
11
11
|
:roodi
|
12
12
|
end
|
@@ -22,7 +22,7 @@ class RoodiAnalyzer
|
|
22
22
|
def score(metric_ranking, item)
|
23
23
|
ScoringStrategies.percentile(metric_ranking, item)
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def generate_records(data, table)
|
27
27
|
return if data==nil
|
28
28
|
Array(data[:problems]).each do |problem|
|
@@ -6,7 +6,7 @@ class SaikuroAnalyzer
|
|
6
6
|
def columns
|
7
7
|
COLUMNS
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def name
|
11
11
|
:saikuro
|
12
12
|
end
|
@@ -22,16 +22,16 @@ class SaikuroAnalyzer
|
|
22
22
|
def score(metric_ranking, item)
|
23
23
|
ScoringStrategies.identity(metric_ranking, item)
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def generate_records(data, table)
|
27
27
|
return if data == nil
|
28
28
|
data[:files].each do |file|
|
29
29
|
file_name = file[:filename]
|
30
30
|
file[:classes].each do |klass|
|
31
|
-
location = Location.for(klass[:class_name])
|
31
|
+
location = MetricFu::Location.for(klass[:class_name])
|
32
32
|
offending_class = location.class_name
|
33
33
|
klass[:methods].each do |match|
|
34
|
-
offending_method = Location.for(match[:name]).method_name
|
34
|
+
offending_method = MetricFu::Location.for(match[:name]).method_name
|
35
35
|
table << {
|
36
36
|
"metric" => name,
|
37
37
|
"lines" => match[:lines],
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module ScoringStrategies
|
2
2
|
|
3
3
|
def percentile(ranking, item)
|
4
|
-
ranking.percentile(item)
|
4
|
+
ranking.percentile(item) # per project score percentile
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
def identity(ranking, item)
|
8
|
-
ranking[item]
|
8
|
+
ranking[item] # Use the score you got (ex flog score of 20 is not bad even if it is the top one in project)
|
9
9
|
end
|
10
10
|
|
11
11
|
def present(row)
|
12
|
-
1
|
12
|
+
1 # If present it's a one, not present it's a zero - For things like Reek that don't have a number
|
13
13
|
end
|
14
14
|
|
15
15
|
def sum(scores)
|
@@ -24,6 +24,6 @@ module ScoringStrategies
|
|
24
24
|
sum = scores.inject( nil ) { |sum,x| sum ? sum+x : x }
|
25
25
|
(sum.to_f / score_length.to_f)
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
extend self
|
29
29
|
end
|
data/lib/base/stats_analyzer.rb
CHANGED
data/lib/base/table.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
class Table
|
2
|
-
|
2
|
+
|
3
3
|
def initialize(opts = {})
|
4
4
|
@rows = []
|
5
5
|
@columns = opts.fetch(:column_names)
|
6
|
-
|
6
|
+
|
7
7
|
@make_index = opts.fetch(:make_index) {true}
|
8
8
|
@metric_index = {}
|
9
9
|
end
|
@@ -44,7 +44,7 @@ class Table
|
|
44
44
|
end
|
45
45
|
arr
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
def group_by_metric
|
49
49
|
@metric_index.to_a
|
50
50
|
end
|
@@ -64,7 +64,7 @@ class Table
|
|
64
64
|
def to_a
|
65
65
|
@rows
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
def map
|
69
69
|
new_table = Table.new(:column_names => @columns)
|
70
70
|
@rows.map do |row|
|
data/lib/generators/churn.rb
CHANGED
data/lib/generators/flay.rb
CHANGED
data/lib/generators/hotspots.rb
CHANGED
@@ -18,23 +18,23 @@ module MetricFu
|
|
18
18
|
num = nil
|
19
19
|
worst_items = {}
|
20
20
|
if @analyzer
|
21
|
-
worst_items[:files] =
|
21
|
+
worst_items[:files] =
|
22
22
|
@analyzer.worst_files(num).inject([]) do |array, worst_file|
|
23
|
-
array <<
|
23
|
+
array <<
|
24
24
|
{:location => @analyzer.location(:file, worst_file),
|
25
25
|
:details => @analyzer.problems_with(:file, worst_file)}
|
26
26
|
array
|
27
27
|
end
|
28
28
|
worst_items[:classes] = @analyzer.worst_classes(num).inject([]) do |array, class_name|
|
29
29
|
location = @analyzer.location(:class, class_name)
|
30
|
-
array <<
|
30
|
+
array <<
|
31
31
|
{:location => location,
|
32
32
|
:details => @analyzer.problems_with(:class, class_name)}
|
33
33
|
array
|
34
34
|
end
|
35
35
|
worst_items[:methods] = @analyzer.worst_methods(num).inject([]) do |array, method_name|
|
36
36
|
location = @analyzer.location(:method, method_name)
|
37
|
-
array <<
|
37
|
+
array <<
|
38
38
|
{:location => location,
|
39
39
|
:details => @analyzer.problems_with(:method, method_name)}
|
40
40
|
array
|
data/lib/generators/rcov.rb
CHANGED
@@ -35,43 +35,43 @@ module MetricFu
|
|
35
35
|
output_file = MetricFu.rcov[:external] ? MetricFu.rcov[:external] : MetricFu::Rcov.metric_directory + '/rcov.txt'
|
36
36
|
output = File.open(output_file).read
|
37
37
|
output = output.split(NEW_FILE_MARKER)
|
38
|
-
|
38
|
+
|
39
39
|
output.shift # Throw away the first entry - it's the execution time etc.
|
40
|
-
|
40
|
+
|
41
41
|
files = assemble_files(output)
|
42
42
|
|
43
43
|
@global_total_lines = 0
|
44
44
|
@global_total_lines_run = 0
|
45
|
-
|
45
|
+
|
46
46
|
@rcov = add_coverage_percentage(files)
|
47
47
|
end
|
48
48
|
|
49
49
|
def to_h
|
50
50
|
global_percent_run = ((@global_total_lines_run.to_f / @global_total_lines.to_f) * 100)
|
51
51
|
add_method_data
|
52
|
-
{:rcov => @rcov.merge({:global_percent_run => round_to_tenths(global_percent_run) })}
|
52
|
+
{:rcov => @rcov.merge({:global_percent_run => round_to_tenths(global_percent_run) })}
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
private
|
56
|
-
|
56
|
+
|
57
57
|
def add_method_data
|
58
58
|
@rcov.each_pair do |file_path, info|
|
59
59
|
file_contents = ""
|
60
60
|
coverage = []
|
61
|
-
|
61
|
+
|
62
62
|
info[:lines].each_with_index do |line, index|
|
63
63
|
file_contents << "#{line[:content]}\n"
|
64
64
|
coverage << line[:was_run]
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
begin
|
68
|
-
line_numbers = LineNumbers.new(file_contents)
|
68
|
+
line_numbers = MetricFu::LineNumbers.new(file_contents)
|
69
69
|
rescue StandardError => e
|
70
70
|
raise e unless e.message =~ /you shouldn't be able to get here/
|
71
71
|
puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level Rcov information for this file."
|
72
72
|
next
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
method_coverage_map = {}
|
76
76
|
coverage.each_with_index do |covered, index|
|
77
77
|
line_number = index + 1
|
@@ -84,16 +84,16 @@ module MetricFu
|
|
84
84
|
method_coverage_map[method_name][:uncovered] += 1 if !covered
|
85
85
|
end
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
@rcov[file_path][:methods] = {}
|
89
|
-
|
89
|
+
|
90
90
|
method_coverage_map.each do |method_name, coverage_data|
|
91
91
|
@rcov[file_path][:methods][method_name] = (coverage_data[:uncovered] / coverage_data[:total].to_f) * 100.0
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
end
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
def assemble_files(output)
|
98
98
|
files = {}
|
99
99
|
output.each_slice(2) {|out| files[out.first.strip] = out.last}
|
@@ -111,16 +111,16 @@ module MetricFu
|
|
111
111
|
end
|
112
112
|
files
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
def add_coverage_percentage(files)
|
116
116
|
files.each_pair do |fname, content|
|
117
117
|
lines = content[:lines]
|
118
118
|
@global_total_lines_run += lines_run = lines.find_all {|line| line[:was_run] == true }.length
|
119
119
|
@global_total_lines += total_lines = lines.length
|
120
120
|
percent_run = ((lines_run.to_f / total_lines.to_f) * 100).round
|
121
|
-
files[fname][:percent_run] = percent_run
|
121
|
+
files[fname][:percent_run] = percent_run
|
122
122
|
end
|
123
123
|
end
|
124
|
-
|
124
|
+
|
125
125
|
end
|
126
126
|
end
|