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.
Files changed (39) hide show
  1. checksums.yaml +14 -6
  2. data/CONTRIBUTORS +1 -0
  3. data/Gemfile +1 -1
  4. data/HISTORY.md +13 -0
  5. data/checksum/metric_fu-4.4.1.gem.sha512 +1 -0
  6. data/lib/metric_fu/cli/parser.rb +7 -0
  7. data/lib/metric_fu/data_structures/line_numbers.rb +70 -59
  8. data/lib/metric_fu/data_structures/location.rb +36 -20
  9. data/lib/metric_fu/data_structures/sexp_node.rb +89 -0
  10. data/lib/metric_fu/environment.rb +48 -0
  11. data/lib/metric_fu/io.rb +1 -7
  12. data/lib/metric_fu/metrics/base_template.rb +28 -9
  13. data/lib/metric_fu/metrics/hotspots/analysis/analyzed_problems.rb +8 -44
  14. data/lib/metric_fu/metrics/hotspots/analysis/analyzer_tables.rb +3 -0
  15. data/lib/metric_fu/metrics/hotspots/analysis/problems.rb +6 -2
  16. data/lib/metric_fu/metrics/hotspots/analysis/ranked_problem_location.rb +71 -0
  17. data/lib/metric_fu/metrics/hotspots/analysis/rankings.rb +8 -1
  18. data/lib/metric_fu/metrics/hotspots/hotspot_analyzer.rb +0 -1
  19. data/lib/metric_fu/metrics/hotspots/hotspots.rb +8 -3
  20. data/lib/metric_fu/metrics/hotspots/template_awesome/hotspots.html.erb +12 -12
  21. data/lib/metric_fu/metrics/saikuro/init.rb +3 -0
  22. data/lib/metric_fu/metrics/saikuro/parsing_element.rb +39 -0
  23. data/lib/metric_fu/metrics/saikuro/saikuro.rb +4 -145
  24. data/lib/metric_fu/metrics/saikuro/scratch_file.rb +114 -0
  25. data/lib/metric_fu/version.rb +1 -1
  26. data/spec/metric_fu/configuration_spec.rb +0 -7
  27. data/spec/metric_fu/formatter/html_spec.rb +0 -1
  28. data/spec/metric_fu/formatter/yaml_spec.rb +0 -1
  29. data/spec/metric_fu/metrics/base_template_spec.rb +1 -2
  30. data/spec/metric_fu/metrics/hotspots/analysis/analyzed_problems_spec.rb +8 -16
  31. data/spec/metric_fu/metrics/hotspots/hotspots_spec.rb +26 -69
  32. data/spec/metric_fu/metrics/saikuro/saikuro_spec.rb +5 -5
  33. data/spec/metric_fu/reporting/graphs/engines/gchart_spec.rb +1 -0
  34. data/spec/resources/yml/hotspots/generator.yml +47 -0
  35. data/spec/resources/yml/hotspots/generator_analysis.yml +53 -0
  36. data/spec/run_spec.rb +0 -1
  37. data/spec/support/helper_methods.rb +10 -0
  38. data/spec/support/suite.rb +13 -14
  39. metadata +35 -25
@@ -1,6 +1,8 @@
1
+ MetricFu.metrics_require { 'saikuro/scratch_file' }
2
+ MetricFu.metrics_require { 'saikuro/parsing_element' }
1
3
  module MetricFu
2
4
 
3
- class Saikuro < Generator
5
+ class SaikuroGenerator < MetricFu::Generator
4
6
 
5
7
  def self.metric
6
8
  :saikuro
@@ -102,152 +104,9 @@ module MetricFu
102
104
  end
103
105
 
104
106
  def assemble_files
105
- files = []
106
- Dir.glob("#{metric_directory}/**/*.html").each do |path|
107
- if Saikuro::SFile.is_valid_text_file?(path)
108
- file = Saikuro::SFile.new(path)
109
- if file
110
- files << file
111
- end
112
- end
113
- end
114
- files
107
+ SaikuroScratchFile.assemble_files( Dir.glob("#{metric_directory}/**/*.html") )
115
108
  end
116
109
 
117
110
  end
118
111
 
119
- class Saikuro::SFile
120
-
121
- attr_reader :elements
122
-
123
- def initialize(path)
124
- @path = path
125
- @file_handle = File.open(@path, "r")
126
- @elements = []
127
- get_elements
128
- ensure
129
- @file_handle.close if @file_handle
130
- end
131
-
132
- def self.is_valid_text_file?(path)
133
- File.open(path, "r") do |f|
134
- if f.eof? || !f.readline.match(/--/)
135
- return false
136
- else
137
- return true
138
- end
139
- end
140
- end
141
-
142
- def filename
143
- File.basename(@path, '_cyclo.html')
144
- end
145
-
146
- def filepath
147
- File.dirname(@path)
148
- end
149
-
150
- def to_h
151
- merge_classes
152
- {:classes => @elements}
153
- end
154
-
155
- def get_elements
156
- begin
157
- while (line = @file_handle.readline) do
158
- return [] if line.nil? || line !~ /\S/
159
- element ||= nil
160
- if line.match(/START/)
161
- unless element.nil?
162
- @elements << element
163
- element = nil
164
- end
165
- line = @file_handle.readline
166
- element = Saikuro::ParsingElement.new(line)
167
- elsif line.match(/END/)
168
- @elements << element if element
169
- element = nil
170
- else
171
- element << line if element
172
- end
173
- end
174
- rescue EOFError
175
- nil
176
- end
177
- end
178
-
179
-
180
- def merge_classes
181
- new_elements = []
182
- get_class_names.each do |target_class|
183
- elements = @elements.find_all {|el| el.name == target_class }
184
- complexity = 0
185
- lines = 0
186
- defns = []
187
- elements.each do |el|
188
- complexity += el.complexity.to_i
189
- lines += el.lines.to_i
190
- defns << el.defs
191
- end
192
-
193
- new_element = {:class_name => target_class,
194
- :complexity => complexity,
195
- :lines => lines,
196
- :methods => defns.flatten.map {|d| d.to_h}}
197
- new_element[:methods] = new_element[:methods].
198
- sort_by {|x| x[:complexity] }.
199
- reverse
200
-
201
- new_elements << new_element
202
- end
203
- @elements = new_elements if new_elements
204
- end
205
-
206
- def get_class_names
207
- class_names = []
208
- @elements.each do |element|
209
- unless class_names.include?(element.name)
210
- class_names << element.name
211
- end
212
- end
213
- class_names
214
- end
215
-
216
- end
217
-
218
- class Saikuro::ParsingElement
219
- TYPE_REGEX=/Type:(.*) Name/
220
- NAME_REGEX=/Name:(.*) Complexity/
221
- COMPLEXITY_REGEX=/Complexity:(.*) Lines/
222
- LINES_REGEX=/Lines:(.*)/
223
-
224
- attr_reader :complexity, :lines, :defs, :element_type
225
- attr_accessor :name
226
-
227
- def initialize(line)
228
- @line = line
229
- @element_type = line.match(TYPE_REGEX)[1].strip
230
- @name = line.match(NAME_REGEX)[1].strip
231
- @complexity = line.match(COMPLEXITY_REGEX)[1].strip
232
- @lines = line.match(LINES_REGEX)[1].strip
233
- @defs = []
234
- end
235
-
236
- def <<(line)
237
- @defs << Saikuro::ParsingElement.new(line)
238
- end
239
-
240
- def to_h
241
- base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
242
- unless @defs.empty?
243
- defs = @defs.map do |my_def|
244
- my_def = my_def.to_h
245
- my_def.delete(:defs)
246
- my_def
247
- end
248
- base[:defs] = defs
249
- end
250
- return base
251
- end
252
- end
253
112
  end
@@ -0,0 +1,114 @@
1
+ MetricFu.metrics_require { 'saikuro/parsing_element' }
2
+ module MetricFu
3
+ class SaikuroScratchFile
4
+
5
+ def self.assemble_files(glob)
6
+ files = []
7
+ glob.each do |path|
8
+ if is_valid_text_file?(path)
9
+ file = new(path)
10
+ if file
11
+ files << file
12
+ end
13
+ end
14
+ end
15
+ files
16
+ end
17
+
18
+ attr_reader :elements
19
+
20
+ def initialize(path)
21
+ @path = path
22
+ @file_handle = File.open(@path, "r")
23
+ @elements = []
24
+ get_elements
25
+ ensure
26
+ @file_handle.close if @file_handle
27
+ end
28
+
29
+ def self.is_valid_text_file?(path)
30
+ File.open(path, "r") do |f|
31
+ if f.eof? || !f.readline.match(/--/)
32
+ return false
33
+ else
34
+ return true
35
+ end
36
+ end
37
+ end
38
+
39
+ def filename
40
+ File.basename(@path, '_cyclo.html')
41
+ end
42
+
43
+ def filepath
44
+ File.dirname(@path)
45
+ end
46
+
47
+ def to_h
48
+ merge_classes
49
+ {:classes => @elements}
50
+ end
51
+
52
+ def get_elements
53
+ begin
54
+ while (line = @file_handle.readline) do
55
+ return [] if line.nil? || line !~ /\S/
56
+ element ||= nil
57
+ if line.match(/START/)
58
+ unless element.nil?
59
+ @elements << element
60
+ element = nil
61
+ end
62
+ line = @file_handle.readline
63
+ element = MetricFu::SaikuroParsingElement.new(line)
64
+ elsif line.match(/END/)
65
+ @elements << element if element
66
+ element = nil
67
+ else
68
+ element << line if element
69
+ end
70
+ end
71
+ rescue EOFError
72
+ nil
73
+ end
74
+ end
75
+
76
+
77
+ def merge_classes
78
+ new_elements = []
79
+ get_class_names.each do |target_class|
80
+ elements = @elements.find_all {|el| el.name == target_class }
81
+ complexity = 0
82
+ lines = 0
83
+ defns = []
84
+ elements.each do |el|
85
+ complexity += el.complexity.to_i
86
+ lines += el.lines.to_i
87
+ defns << el.defs
88
+ end
89
+
90
+ new_element = {:class_name => target_class,
91
+ :complexity => complexity,
92
+ :lines => lines,
93
+ :methods => defns.flatten.map {|d| d.to_h}}
94
+ new_element[:methods] = new_element[:methods].
95
+ sort_by {|x| x[:complexity] }.
96
+ reverse
97
+
98
+ new_elements << new_element
99
+ end
100
+ @elements = new_elements if new_elements
101
+ end
102
+
103
+ def get_class_names
104
+ class_names = []
105
+ @elements.each do |element|
106
+ unless class_names.include?(element.name)
107
+ class_names << element.name
108
+ end
109
+ end
110
+ class_names
111
+ end
112
+
113
+ end
114
+ end
@@ -1,3 +1,3 @@
1
1
  module MetricFu
2
- VERSION = "4.4.1"
2
+ VERSION = "4.4.2"
3
3
  end
@@ -225,13 +225,6 @@ describe MetricFu::Configuration do
225
225
  end
226
226
  end
227
227
 
228
- describe '#set_code_dirs ' do
229
- it 'should set the @code_dirs instance var to ["app", "lib"]' do
230
- directory('code_dirs').
231
- should == ['app','lib']
232
- end
233
- end
234
-
235
228
  it 'should set @rails_best_practices to {}' do
236
229
  load_metric 'rails_best_practices'
237
230
  expect(MetricFu::Metric.get_metric(:rails_best_practices).run_options).to eql({})
@@ -1,5 +1,4 @@
1
1
  require "spec_helper"
2
- require 'fakefs/safe'
3
2
 
4
3
  describe MetricFu::Formatter::HTML do
5
4
 
@@ -1,5 +1,4 @@
1
1
  require "spec_helper"
2
- require 'fakefs/safe'
3
2
 
4
3
  describe MetricFu::Formatter::YAML do
5
4
 
@@ -10,11 +10,10 @@ describe MetricFu::Template do
10
10
  describe "#erbify" do
11
11
  it 'should evaluate a erb doc' do
12
12
  section = 'section'
13
- File.stub(:read).and_return('foo')
14
13
  erb = double('erb')
15
14
  erb.should_receive(:result)
16
- ERB.should_receive(:new).with('foo').and_return(erb)
17
15
  @template.should_receive(:template).and_return('foo')
16
+ @template.should_receive(:erb_template_source).with('foo').and_return(erb)
18
17
  @template.send(:erbify, section)
19
18
  end
20
19
  end
@@ -37,16 +37,14 @@ describe MetricFu::HotspotAnalyzedProblems do
37
37
  :flog => "complexity is 37.9"
38
38
  }
39
39
  # TODO Unsure if we want to make problems_with and location public or private at this point
40
- @analyzed_problems.method(:problems_with).call(:class, "Client").should == expected
41
- expect(@worst_items[:classes].first[:details]).to eq(expected)
40
+ expect(@worst_items[:classes].first.problems).to eq(expected)
42
41
  end
43
42
 
44
43
  it "gives all issues for a method" do
45
44
  expected = {
46
45
  :reek => "found 1 code smells",
47
46
  :flog => "complexity is 37.9"}
48
- @analyzed_problems.method(:problems_with).call(:method, "Client#client_requested_sync").should == expected
49
- expect(@worst_items[:methods].first[:details]).to eq(expected)
47
+ expect(@worst_items[:methods].first.problems).to eq(expected)
50
48
  end
51
49
 
52
50
  it "gives all issues for a file" do
@@ -54,32 +52,28 @@ describe MetricFu::HotspotAnalyzedProblems do
54
52
  :reek => "found 2 code smells" ,
55
53
  :flog => "complexity is 37.9",
56
54
  :churn => "detected high level of churn (changed 54 times)"}
57
- @analyzed_problems.method(:problems_with).call(:file, "lib/client/client.rb").should == expected
58
- expect(@worst_items[:files].first[:details]).to eq(expected)
55
+ expect(@worst_items[:files].first.problems).to eq(expected)
59
56
  end
60
57
 
61
58
  it "provide location for a method" do
62
59
  expected = MetricFu::Location.new("lib/client/client.rb",
63
60
  "Client",
64
61
  "Client#client_requested_sync")
65
- @analyzed_problems.method(:location).call(:method, "Client#client_requested_sync").should == expected
66
- expect(@worst_items[:methods].first[:location]).to eq(expected)
62
+ expect(@worst_items[:methods].first.location).to eq(expected)
67
63
  end
68
64
 
69
65
  it "provides location for a class" do
70
66
  expected = MetricFu::Location.new("lib/client/client.rb",
71
67
  "Client",
72
68
  nil)
73
- @analyzed_problems.method(:location).call(:class, "Client").should == expected
74
- expect(@worst_items[:classes].first[:location]).to eq(expected)
69
+ expect(@worst_items[:classes].first.location).to eq(expected)
75
70
  end
76
71
 
77
72
  it "provides location for a file" do
78
73
  expected = MetricFu::Location.new("lib/client/client.rb",
79
74
  nil,
80
75
  nil)
81
- @analyzed_problems.method(:location).call(:file, "lib/client/client.rb").should == expected
82
- expect(@worst_items[:files].first[:location]).to eq(expected)
76
+ expect(@worst_items[:files].first.location).to eq(expected)
83
77
  end
84
78
 
85
79
  end
@@ -96,16 +90,14 @@ describe MetricFu::HotspotAnalyzedProblems do
96
90
  expected = {
97
91
  :saikuro => "complexity is 1.0"
98
92
  }
99
- @analyzed_problems.method(:problems_with).call(:method, "Supr#initialize").should == expected
100
- expect(@worst_items[:methods].last[:details]).to eq(expected)
93
+ expect(@worst_items[:methods].last.problems).to eq(expected)
101
94
  end
102
95
 
103
96
  it "gives average complexity for class" do
104
97
  expected = {
105
98
  :saikuro => "average complexity is 5.0"
106
99
  }
107
- @analyzed_problems.method(:problems_with).call(:class, "Supr").should == expected
108
- expect(@worst_items[:classes].last[:details]).to eq(expected)
100
+ expect(@worst_items[:classes].last.problems).to eq(expected)
109
101
  end
110
102
 
111
103
  end
@@ -1,4 +1,3 @@
1
- require 'multi_json'
2
1
  require "spec_helper"
3
2
 
4
3
  describe MetricFu::HotspotsGenerator do
@@ -7,90 +6,48 @@ describe MetricFu::HotspotsGenerator do
7
6
  before :each do
8
7
  MetricFu::Configuration.run {}
9
8
  File.stub(:directory?).and_return(true)
10
- @yaml =<<END
11
- ---
12
- :reek:
13
- :matches:
14
- - :file_path: lib/client/client.rb
15
- :code_smells:
16
- - :type: Large Class
17
- :message: has at least 27 methods
18
- :method: Devver::Client
19
- - :type: Long Method
20
- :message: has approx 6 statements
21
- :method: Devver::Client#client_requested_sync
22
- :flog:
23
- :method_containers:
24
- - :highest_score: 61.5870319141946
25
- :path: /lib/client/client.rb
26
- :methods:
27
- Client#client_requested_sync:
28
- :path: /lib/client/client.rb
29
- :score: 37.9270319141946
30
- :operators:
31
- :+: 1.70000000000001
32
- :/: 1.80000000000001
33
- :method_at_line: 1.90000000000001
34
- :puts: 1.70000000000001
35
- :assignment: 33.0000000000001
36
- :in_method?: 1.70000000000001
37
- :message: 1.70000000000001
38
- :branch: 12.6
39
- :<<: 3.40000000000001
40
- :each: 1.50000000000001
41
- :lit_fixnum: 1.45
42
- :raise: 1.80000000000001
43
- :each_pair: 1.3
44
- :*: 1.60000000000001
45
- :to_f: 2.00000000000001
46
- :each_with_index: 3.00000000000001
47
- :[]: 22.3000000000001
48
- :new: 1.60000000000001
49
- :average_score: 11.1209009055421
50
- :total_score: 1817.6
51
- :name: Client#client_requested_sync
52
- :churn:
53
- :changes:
54
- - :file_path: lib/client/client.rb
55
- :times_changed: 54
56
- - :file_path: lib/client/foo.rb
57
- :times_changed: 52
58
- END
9
+ @yaml = metric_data('hotspots/generator.yml')
59
10
  end
60
11
 
61
12
  it "should be empty on error" do
62
13
  hotspots = MetricFu::HotspotsGenerator.new
63
14
  hotspots.instance_variable_set(:@analyzer, nil)
64
15
  result = hotspots.analyze
65
- result.should == {}
66
- end
67
-
68
- it "should return yaml results" do
69
- hotspots = MetricFu::HotspotsGenerator.new
70
- analyzer = HotspotAnalyzer.new(@yaml)
71
- hotspots.instance_variable_set(:@analyzer, analyzer)
72
- result = hotspots.analyze
73
- expected = MultiJson.load("{\"methods\":[{\"location\":{\"class_name\":\"Client\",\"method_name\":\"Client#client_requested_sync\",\"file_path\":\"lib/client/client.rb\",\"hash\":7919384682,\"simple_method_name\":\"#client_requested_sync\"},\"details\":{\"reek\":\"found 1 code smells\",\"flog\":\"complexity is 37.9\"}}],\"classes\":[{\"location\":{\"class_name\":\"Client\",\"method_name\":null,\"file_path\":\"lib/client/client.rb\",\"hash\":7995629750},\"details\":{\"reek\":\"found 2 code smells\",\"flog\":\"complexity is 37.9\"}}],\"files\":[{\"location\":{\"class_name\":null,\"method_name\":null,\"file_path\":\"lib/client/client.rb\",\"hash\":-5738801681},\"details\":{\"reek\":\"found 2 code smells\",\"flog\":\"complexity is 37.9\",\"churn\":\"detected high level of churn (changed 54 times)\"}},{\"location\":{\"class_name\":null,\"method_name\":null,\"file_path\":\"lib/client/foo.rb\",\"hash\":-7081271905},\"details\":{\"churn\":\"detected high level of churn (changed 52 times)\"}}]}")
74
- compare_hashes(MultiJson.load(MultiJson.dump(hotspots.to_h[:hotspots])), expected)
16
+ result.should == {:files => [], :classes => [], :methods => []}
75
17
  end
76
18
 
77
19
  it "should put the changes into a hash" do
20
+ MetricFu.result.should_receive(:result_hash).and_return(@yaml)
78
21
  hotspots = MetricFu::HotspotsGenerator.new
79
- analyzer = HotspotAnalyzer.new(@yaml)
80
- hotspots.instance_variable_set(:@analyzer, analyzer)
81
22
  hotspots.analyze
82
- expected = MultiJson.load("{\"methods\":[{\"location\":{\"class_name\":\"Client\",\"method_name\":\"Client#client_requested_sync\",\"file_path\":\"lib/client/client.rb\",\"hash\":7919384682,\"simple_method_name\":\"#client_requested_sync\"},\"details\":{\"reek\":\"found 1 code smells\",\"flog\":\"complexity is 37.9\"}}],\"classes\":[{\"location\":{\"class_name\":\"Client\",\"method_name\":null,\"file_path\":\"lib/client/client.rb\",\"hash\":7995629750},\"details\":{\"reek\":\"found 2 code smells\",\"flog\":\"complexity is 37.9\"}}],\"files\":[{\"location\":{\"class_name\":null,\"method_name\":null,\"file_path\":\"lib/client/client.rb\",\"hash\":-5738801681},\"details\":{\"reek\":\"found 2 code smells\",\"flog\":\"complexity is 37.9\",\"churn\":\"detected high level of churn (changed 54 times)\"}},{\"location\":{\"class_name\":null,\"method_name\":null,\"file_path\":\"lib/client/foo.rb\",\"hash\":-7081271905},\"details\":{\"churn\":\"detected high level of churn (changed 52 times)\"}}]}")
83
- compare_hashes(MultiJson.load(MultiJson.dump(hotspots.to_h[:hotspots])), expected)
23
+ result = hotspots.to_h[:hotspots]
24
+ expected = metric_data('hotspots/generator_analysis.yml')
25
+ # ensure expected granularities
26
+ expect(result.keys).to eq(expected.keys)
27
+
28
+ # for each granularity's location details
29
+ result.each do |granularity,location_details|
30
+ # map 2d array for this granularity of [details, location]
31
+ expected_result = expected.fetch(granularity).map {|ld| [ld.fetch('details'), ld.fetch('location')] }
32
+ # verify all the location details for this granularity match elements of expected_result
33
+ location_details.each do |location_detail|
34
+ location = location_detail.fetch('location')
35
+ details = location_detail.fetch('details')
36
+ # get the location_detail array where the where the locations (second element) match
37
+ expected_location_details = expected_result.rassoc(location)
38
+ # get the details (first element) from the expected location_details array
39
+ expected_details = expected_location_details[0]
40
+ expect(details).to eq(expected_details)
41
+ end
42
+ end
84
43
  end
44
+
85
45
  # really testing the output of analyzed_problems#worst_items
86
46
  it "should return the worst item granularities: files, classes, methods" do
87
47
  hotspots = MetricFu::HotspotsGenerator.new
88
48
  analyzer = HotspotAnalyzer.new(@yaml)
89
- hotspots.instance_variable_set(:@analyzer, analyzer)
90
- hotspots.analyze.keys.should =~ [:files, :classes, :methods]
49
+ expect(hotspots.analyze.keys).to eq([:files, :classes, :methods])
91
50
  end
92
51
  end
93
- end
94
- def compare_hashes(got,expected)
95
- got.hash == expected.hash
52
+
96
53
  end