metric_fu 4.4.1 → 4.4.2
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.
- checksums.yaml +14 -6
- data/CONTRIBUTORS +1 -0
- data/Gemfile +1 -1
- data/HISTORY.md +13 -0
- data/checksum/metric_fu-4.4.1.gem.sha512 +1 -0
- data/lib/metric_fu/cli/parser.rb +7 -0
- data/lib/metric_fu/data_structures/line_numbers.rb +70 -59
- data/lib/metric_fu/data_structures/location.rb +36 -20
- data/lib/metric_fu/data_structures/sexp_node.rb +89 -0
- data/lib/metric_fu/environment.rb +48 -0
- data/lib/metric_fu/io.rb +1 -7
- data/lib/metric_fu/metrics/base_template.rb +28 -9
- data/lib/metric_fu/metrics/hotspots/analysis/analyzed_problems.rb +8 -44
- data/lib/metric_fu/metrics/hotspots/analysis/analyzer_tables.rb +3 -0
- data/lib/metric_fu/metrics/hotspots/analysis/problems.rb +6 -2
- data/lib/metric_fu/metrics/hotspots/analysis/ranked_problem_location.rb +71 -0
- data/lib/metric_fu/metrics/hotspots/analysis/rankings.rb +8 -1
- data/lib/metric_fu/metrics/hotspots/hotspot_analyzer.rb +0 -1
- data/lib/metric_fu/metrics/hotspots/hotspots.rb +8 -3
- data/lib/metric_fu/metrics/hotspots/template_awesome/hotspots.html.erb +12 -12
- data/lib/metric_fu/metrics/saikuro/init.rb +3 -0
- data/lib/metric_fu/metrics/saikuro/parsing_element.rb +39 -0
- data/lib/metric_fu/metrics/saikuro/saikuro.rb +4 -145
- data/lib/metric_fu/metrics/saikuro/scratch_file.rb +114 -0
- data/lib/metric_fu/version.rb +1 -1
- data/spec/metric_fu/configuration_spec.rb +0 -7
- data/spec/metric_fu/formatter/html_spec.rb +0 -1
- data/spec/metric_fu/formatter/yaml_spec.rb +0 -1
- data/spec/metric_fu/metrics/base_template_spec.rb +1 -2
- data/spec/metric_fu/metrics/hotspots/analysis/analyzed_problems_spec.rb +8 -16
- data/spec/metric_fu/metrics/hotspots/hotspots_spec.rb +26 -69
- data/spec/metric_fu/metrics/saikuro/saikuro_spec.rb +5 -5
- data/spec/metric_fu/reporting/graphs/engines/gchart_spec.rb +1 -0
- data/spec/resources/yml/hotspots/generator.yml +47 -0
- data/spec/resources/yml/hotspots/generator_analysis.yml +53 -0
- data/spec/run_spec.rb +0 -1
- data/spec/support/helper_methods.rb +10 -0
- data/spec/support/suite.rb +13 -14
- metadata +35 -25
data/lib/metric_fu/io.rb
CHANGED
@@ -48,13 +48,7 @@ module MetricFu
|
|
48
48
|
|
49
49
|
# Add the 'app' directory if we're running within rails.
|
50
50
|
def set_code_dirs(config)
|
51
|
-
|
52
|
-
# shouldn't we just check if the directories exist?
|
53
|
-
if config.rails?
|
54
|
-
@directories['code_dirs'] = %w(app lib)
|
55
|
-
else
|
56
|
-
@directories['code_dirs'] = %w(lib)
|
57
|
-
end
|
51
|
+
@directories['code_dirs'] = %w(app lib).select{|dir| Dir.exists?(dir) }
|
58
52
|
end
|
59
53
|
|
60
54
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'pathname'
|
2
1
|
require 'erb'
|
3
2
|
module MetricFu
|
4
3
|
|
@@ -7,7 +6,7 @@ module MetricFu
|
|
7
6
|
# methods to make templating a bit easier. However, classes do not
|
8
7
|
# have to inherit from here in order to provide a template. The only
|
9
8
|
# requirement for a template class is that it provides a #write method
|
10
|
-
# to actually write out the template. See
|
9
|
+
# to actually write out the template. See AwesomeTemplate for an
|
11
10
|
# example.
|
12
11
|
class Template
|
13
12
|
attr_accessor :result, :per_file_data, :formatter, :output_directory
|
@@ -27,8 +26,21 @@ module MetricFu
|
|
27
26
|
# @return String
|
28
27
|
# The erb evaluated string
|
29
28
|
def erbify(section)
|
30
|
-
|
31
|
-
|
29
|
+
template_file = template(section)
|
30
|
+
erb = erb_template_source(template_file)
|
31
|
+
erb.result(binding)
|
32
|
+
rescue => e
|
33
|
+
message = "Error: #{e.class}; message #{e.message}. "
|
34
|
+
message << "Failed evaluating erb template "
|
35
|
+
message << "for section #{section} and template #{template_file}."
|
36
|
+
raise message
|
37
|
+
end
|
38
|
+
|
39
|
+
def erb_template_source(template_file)
|
40
|
+
erb_doc = File.read(template_file)
|
41
|
+
erb = ERB.new(erb_doc)
|
42
|
+
erb.filename = template_file
|
43
|
+
erb
|
32
44
|
end
|
33
45
|
|
34
46
|
# Copies an instance variable mimicing the name of the section
|
@@ -64,9 +76,11 @@ module MetricFu
|
|
64
76
|
File.join(template_directory, section.to_s + ".html.erb")
|
65
77
|
end
|
66
78
|
end
|
79
|
+
|
67
80
|
def template_dir(metric)
|
68
81
|
File.join(MetricFu.metrics_dir, metric, metric_template_dir)
|
69
82
|
end
|
83
|
+
|
70
84
|
# e.g. template_awesome, template_standard
|
71
85
|
def metric_template_dir
|
72
86
|
template_name = self.class.name.sub('Template', '')[/^([A-Z][a-z]+)+/].downcase
|
@@ -106,7 +120,8 @@ module MetricFu
|
|
106
120
|
# @return String
|
107
121
|
# The contents of the css file
|
108
122
|
def inline_css(css)
|
109
|
-
|
123
|
+
css_file = File.join(template_directory, css)
|
124
|
+
open(css_file) {|f| f.read }
|
110
125
|
end
|
111
126
|
|
112
127
|
# Provides a link to open a file through the textmate protocol
|
@@ -121,7 +136,9 @@ module MetricFu
|
|
121
136
|
# @return String
|
122
137
|
# An anchor link to a textmate reference or a file reference
|
123
138
|
def link_to_filename(name, line = nil, link_content = nil)
|
124
|
-
|
139
|
+
href = file_url(name, line)
|
140
|
+
link_text = link_content(name, line, link_content)
|
141
|
+
"<a href='#{href}'>#{link_text}</a>"
|
125
142
|
end
|
126
143
|
|
127
144
|
def round_to_tenths(decimal)
|
@@ -140,9 +157,9 @@ module MetricFu
|
|
140
157
|
end
|
141
158
|
|
142
159
|
def display_location(location)
|
143
|
-
class_name, method_name = location.class_name, location.method_name
|
160
|
+
class_name, method_name = location.fetch('class_name'), location.fetch('method_name')
|
144
161
|
str = ""
|
145
|
-
str += link_to_filename(location.file_name, location.line_number)
|
162
|
+
str += link_to_filename(location.fetch('file_name'), location.fetch('line_number'))
|
146
163
|
str += " : " if method_name || class_name
|
147
164
|
if(method_name)
|
148
165
|
str += "#{method_name}"
|
@@ -177,6 +194,7 @@ module MetricFu
|
|
177
194
|
def remove_leading_slash(filename)
|
178
195
|
filename.gsub(/^\//, '')
|
179
196
|
end
|
197
|
+
|
180
198
|
def render_as_txmt_protocol? # :nodoc:
|
181
199
|
if MetricFu.configuration.osx?
|
182
200
|
!MetricFu::Formatter::Templates.option('darwin_txmt_protocol_no_thanks')
|
@@ -184,6 +202,7 @@ module MetricFu
|
|
184
202
|
false
|
185
203
|
end
|
186
204
|
end
|
205
|
+
|
187
206
|
def render_as_mvim_protocol? # :nodoc:
|
188
207
|
if MetricFu.configuration.osx?
|
189
208
|
!MetricFu::Formatter::Templates.option('darwin_mvim_protocol_no_thanks')
|
@@ -227,7 +246,7 @@ module MetricFu
|
|
227
246
|
end
|
228
247
|
|
229
248
|
def snake_case_to_title_case(string)
|
230
|
-
|
249
|
+
string.split('_').collect{|word| word[0] = word[0..0].upcase; word}.join(" ")
|
231
250
|
end
|
232
251
|
|
233
252
|
# belive me, I tried to meta program this with an inherited hook
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module MetricFu
|
2
2
|
class HotspotAnalyzedProblems
|
3
|
-
|
3
|
+
MetricFu.metrics_require { "hotspots/analysis/ranked_problem_location" }
|
4
4
|
|
5
5
|
def initialize(hotspot_rankings, analyzer_tables)
|
6
6
|
@hotspot_rankings = hotspot_rankings
|
7
7
|
@analyzer_tables = analyzer_tables
|
8
8
|
end
|
9
|
+
|
9
10
|
def worst_items
|
10
11
|
worst_items = {}
|
11
12
|
worst_items[:files] = worst(@hotspot_rankings.worst_files, :file)
|
@@ -18,53 +19,16 @@ module MetricFu
|
|
18
19
|
|
19
20
|
# @param rankings [Array<MetricFu::HotspotRankings>]
|
20
21
|
# @param granularity [Symbol] one of :class, :method, :file
|
21
|
-
def worst(rankings,granularity)
|
22
|
+
def worst(rankings, granularity)
|
22
23
|
rankings.map do |ranked_item_name|
|
23
|
-
|
24
|
-
|
25
|
-
{:location => location, :details => details}
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# @todo redo as item,value, options = {}
|
30
|
-
# Note that the other option for 'details' is :detailed (this isn't
|
31
|
-
# at all clear from this method itself
|
32
|
-
def problems_with(item, value)
|
33
|
-
sub_table = get_sub_table(item, value)
|
34
|
-
#grouping = Ruport::Data::Grouping.new(sub_table, :by => 'metric')
|
35
|
-
grouping = get_grouping(sub_table, :by => 'metric')
|
36
|
-
MetricFu::HotspotProblems.new(grouping).problems
|
37
|
-
end
|
38
|
-
|
39
|
-
def location(item, value)
|
40
|
-
sub_table = get_sub_table(item, value)
|
41
|
-
assert_sub_table_has_data(item, sub_table, value)
|
42
|
-
first_row = sub_table[0]
|
43
|
-
case item
|
44
|
-
when :class
|
45
|
-
MetricFu::Location.get(first_row.file_path, first_row.class_name, nil)
|
46
|
-
when :method
|
47
|
-
MetricFu::Location.get(first_row.file_path, first_row.class_name, first_row.method_name)
|
48
|
-
when :file
|
49
|
-
MetricFu::Location.get(first_row.file_path, nil, nil)
|
50
|
-
else
|
51
|
-
raise ArgumentError, "Item must be :class, :method, or :file"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def assert_sub_table_has_data(item, sub_table, value)
|
56
|
-
if (sub_table.length==0)
|
57
|
-
raise MetricFu::AnalysisError, "The #{item.to_s} '#{value.to_s}' does not have any rows in the analysis table"
|
24
|
+
sub_table = get_sub_table(granularity, ranked_item_name)
|
25
|
+
MetricFu::HotspotRankedProblemLocation.new(sub_table, granularity)
|
58
26
|
end
|
59
27
|
end
|
60
28
|
|
61
|
-
def get_sub_table(
|
62
|
-
tables = @analyzer_tables.tables_for(
|
63
|
-
tables[
|
64
|
-
end
|
65
|
-
|
66
|
-
def get_grouping(table, opts)
|
67
|
-
MetricFu::HotspotGroupings.new(table, opts).get_grouping
|
29
|
+
def get_sub_table(granularity, ranked_item_name)
|
30
|
+
tables = @analyzer_tables.tables_for(granularity)
|
31
|
+
tables[ranked_item_name]
|
68
32
|
end
|
69
33
|
|
70
34
|
end
|
@@ -44,6 +44,7 @@ module MetricFu
|
|
44
44
|
}
|
45
45
|
end
|
46
46
|
|
47
|
+
# COLLECT AND PROCESS RAW HOTSPOT METRICS
|
47
48
|
def build_lookups!
|
48
49
|
@class_and_method_to_file ||= {}
|
49
50
|
# Build a mapping from [class,method] => filename
|
@@ -58,6 +59,7 @@ module MetricFu
|
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
62
|
+
# COLLECT AND PROCESS RAW HOTSPOT METRICS
|
61
63
|
def process_rows!
|
62
64
|
# Correct incorrect rows in the table
|
63
65
|
table.each do |row|
|
@@ -74,6 +76,7 @@ module MetricFu
|
|
74
76
|
end
|
75
77
|
|
76
78
|
|
79
|
+
# COLLECT AND PROCESS RAW HOTSPOT METRICS
|
77
80
|
def fix_row_file_path!(row)
|
78
81
|
# We know that Saikuro rows are broken
|
79
82
|
# next unless row['metric'] == :saikuro
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module MetricFu
|
2
2
|
class HotspotProblems
|
3
3
|
|
4
|
-
def initialize(
|
5
|
-
@grouping =
|
4
|
+
def initialize(sub_table)
|
5
|
+
@grouping = group_by(sub_table, 'metric')
|
6
6
|
end
|
7
7
|
|
8
8
|
def problems
|
@@ -13,5 +13,9 @@ module MetricFu
|
|
13
13
|
problems
|
14
14
|
end
|
15
15
|
|
16
|
+
def group_by(sub_table, by = 'metric')
|
17
|
+
MetricFu::HotspotGroupings.new(sub_table, :by => by).get_grouping
|
18
|
+
end
|
19
|
+
|
16
20
|
end
|
17
21
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module MetricFu
|
2
|
+
class HotspotRankedProblemLocation
|
3
|
+
MetricFu.data_structures_require { 'location' }
|
4
|
+
attr_reader :sub_table, :granularity
|
5
|
+
def initialize(sub_table, granularity)
|
6
|
+
@sub_table = sub_table
|
7
|
+
@granularity = granularity
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_hash
|
11
|
+
{
|
12
|
+
'location' => location.to_hash,
|
13
|
+
'details' => stringify_keys(problems),
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def stringify_keys(hash)
|
18
|
+
result = {}
|
19
|
+
hash.each do |key, value|
|
20
|
+
result[key.to_s] = value
|
21
|
+
end
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# @todo redo as item,value, options = {}
|
27
|
+
# Note that the other option for 'details' is :detailed (this isn't
|
28
|
+
# at all clear from this method itself
|
29
|
+
def problems
|
30
|
+
@problems ||= MetricFu::HotspotProblems.new(sub_table).problems
|
31
|
+
end
|
32
|
+
|
33
|
+
def location
|
34
|
+
@location ||= case granularity
|
35
|
+
when :class then class_location
|
36
|
+
when :method then method_location
|
37
|
+
when :file then file_location
|
38
|
+
else raise ArgumentError, "Item must be :class, :method, or :file"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def file_path
|
43
|
+
first_row.file_path
|
44
|
+
end
|
45
|
+
def class_name
|
46
|
+
first_row.class_name
|
47
|
+
end
|
48
|
+
def method_name
|
49
|
+
first_row.method_name
|
50
|
+
end
|
51
|
+
def file_location
|
52
|
+
MetricFu::Location.get(file_path, nil, nil)
|
53
|
+
end
|
54
|
+
def method_location
|
55
|
+
MetricFu::Location.get(file_path, class_name, method_name)
|
56
|
+
end
|
57
|
+
def class_location
|
58
|
+
MetricFu::Location.get(file_path, class_name, nil)
|
59
|
+
end
|
60
|
+
def first_row
|
61
|
+
assert_sub_table_has_data
|
62
|
+
@first_row ||= sub_table[0]
|
63
|
+
end
|
64
|
+
def assert_sub_table_has_data
|
65
|
+
if (sub_table.length==0)
|
66
|
+
raise MetricFu::AnalysisError, "The #{item.to_s} '#{value.to_s}' does not have any rows in the analysis table"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -39,8 +39,15 @@ module MetricFu
|
|
39
39
|
|
40
40
|
def calculate_score_for_granularity(analyzer, granularity)
|
41
41
|
metric_ranking = calculate_metric_scores(granularity, analyzer)
|
42
|
-
|
42
|
+
|
43
|
+
add_to_master_ranking(
|
44
|
+
ranking(granularity),
|
45
|
+
metric_ranking,
|
46
|
+
analyzer
|
47
|
+
)
|
43
48
|
end
|
49
|
+
|
50
|
+
# CALCULATES METRIC HOTSPOT SCORES / RANKINGS PER map/reduce in HOTSPOT subclasses
|
44
51
|
def calculate_metric_scores(granularity, analyzer)
|
45
52
|
metric_ranking = MetricFu::Ranking.new
|
46
53
|
metric_violations = @tool_tables[analyzer.name]
|
@@ -12,15 +12,20 @@ module MetricFu
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def emit
|
15
|
-
|
15
|
+
# no-op
|
16
16
|
end
|
17
17
|
|
18
18
|
def analyze
|
19
|
-
|
19
|
+
analyzer = MetricFu::HotspotAnalyzer.new(MetricFu.result.result_hash)
|
20
|
+
@hotspots = analyzer.hotspots
|
20
21
|
end
|
21
22
|
|
22
23
|
def to_h
|
23
|
-
{:hotspots =>
|
24
|
+
result = {:hotspots => {}}
|
25
|
+
@hotspots.each do |granularity, hotspots|
|
26
|
+
result[:hotspots][granularity.to_s] = hotspots.map(&:to_hash)
|
27
|
+
end
|
28
|
+
result
|
24
29
|
end
|
25
30
|
end
|
26
31
|
|
@@ -2,11 +2,11 @@
|
|
2
2
|
<p>Meta analysis of your metrics to find hotspots in your code.</p>
|
3
3
|
<br/>
|
4
4
|
|
5
|
-
<% if
|
5
|
+
<% if @hotspots.nil? || @hotspots.size.zero? %>
|
6
6
|
No Hotspots were found.
|
7
|
-
<%
|
7
|
+
<% else %>
|
8
8
|
|
9
|
-
<% granularities = [
|
9
|
+
<% granularities = %w[files classes methods] %>
|
10
10
|
<table>
|
11
11
|
<tr valign="top">
|
12
12
|
<% granularities.each do |granularity| %>
|
@@ -15,21 +15,21 @@
|
|
15
15
|
<% end %>
|
16
16
|
</tr>
|
17
17
|
|
18
|
-
<%
|
18
|
+
<% hotspots = [] %>
|
19
19
|
<% granularities.each_index do |index| %>
|
20
20
|
<% granularity = granularities[index] %>
|
21
|
-
<%
|
21
|
+
<% hotspots << @hotspots[granularity] %>
|
22
22
|
<% end %>
|
23
23
|
|
24
|
-
<%
|
24
|
+
<% hotspots_length = 0 %>
|
25
25
|
<% columns = [0, 1, 2] %>
|
26
|
-
<% while (
|
26
|
+
<% while (hotspots_length < hotspots[0].length || hotspots_length < hotspots[1].length || hotspots_length < hotspots[2].length) do %>
|
27
27
|
<tr valign="top">
|
28
28
|
<% columns.each do |column| %>
|
29
|
-
<% item =
|
29
|
+
<% item = hotspots[column].length >= hotspots_length ? hotspots[column][hotspots_length] : nil %>
|
30
30
|
<% if item %>
|
31
|
-
<% location = item
|
32
|
-
<% file, line = location.file_name, location.line_number %>
|
31
|
+
<% location = item.fetch('location') %>
|
32
|
+
<% file, line = location.fetch('file_name'), location.fetch('line_number') %>
|
33
33
|
<td>
|
34
34
|
<b>
|
35
35
|
<%= display_location(location) %>
|
@@ -43,7 +43,7 @@
|
|
43
43
|
<% end %>
|
44
44
|
<br/><br/>
|
45
45
|
<!-- TODO HOTSPOTS for metric fu nice metric_link method -->
|
46
|
-
<% item
|
46
|
+
<% item.fetch('details').each do |metric, info| %>
|
47
47
|
<%#= metric_link(@stat, metric, h(metric.to_s.capitalize)) + ": " + h(info)%><br/>
|
48
48
|
<%= "#{metric.to_s.capitalize}: #{info}" %><br/>
|
49
49
|
<% end %>
|
@@ -52,7 +52,7 @@
|
|
52
52
|
<td> </td>
|
53
53
|
<% end %>
|
54
54
|
<% end %>
|
55
|
-
<%
|
55
|
+
<% hotspots_length += 1 %>
|
56
56
|
</tr>
|
57
57
|
<% end %>
|
58
58
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MetricFu
|
2
|
+
class SaikuroParsingElement
|
3
|
+
|
4
|
+
TYPE_REGEX=/Type:(.*) Name/
|
5
|
+
NAME_REGEX=/Name:(.*) Complexity/
|
6
|
+
COMPLEXITY_REGEX=/Complexity:(.*) Lines/
|
7
|
+
LINES_REGEX=/Lines:(.*)/
|
8
|
+
|
9
|
+
attr_reader :complexity, :lines, :defs, :element_type
|
10
|
+
attr_accessor :name
|
11
|
+
|
12
|
+
def initialize(line)
|
13
|
+
@line = line
|
14
|
+
@element_type = line.match(TYPE_REGEX)[1].strip
|
15
|
+
@name = line.match(NAME_REGEX)[1].strip
|
16
|
+
@complexity = line.match(COMPLEXITY_REGEX)[1].strip
|
17
|
+
@lines = line.match(LINES_REGEX)[1].strip
|
18
|
+
@defs = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def <<(line)
|
22
|
+
@defs << MetricFu::SaikuroParsingElement.new(line)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
|
27
|
+
unless @defs.empty?
|
28
|
+
defs = @defs.map do |my_def|
|
29
|
+
my_def = my_def.to_h
|
30
|
+
my_def.delete(:defs)
|
31
|
+
my_def
|
32
|
+
end
|
33
|
+
base[:defs] = defs
|
34
|
+
end
|
35
|
+
return base
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|