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
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5b6391c40e351ab9265635b6b35f32662bbe450c
4
- data.tar.gz: eb534c71875d84de4e477332a365de843ec7dfb3
5
- SHA512:
6
- metadata.gz: 7a9d4acd5a6b37a5ce18219ff9e8e363395e7f8c65f0fcaae138f0c6660172eefd49006371df55fd1cad2984e30cd91ccc64aaf6d7d1e170d1c37d2e6d3c9e12
7
- data.tar.gz: c6dd298de619f6628ae7c5b46026bc7819ff38a9912f1bdd1e13da5e7b70fee82446397b3e6eb08fe602db6e3a7a78f395739874d95e357a6efda95e9eaa5acc
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MTFmYmRkMmExNTg1MDBhMDI0MTQ1OTFiMGJkNDc1MTY2OTdlYTJiNQ==
5
+ data.tar.gz: !binary |-
6
+ ZDQ5OGMzN2QwNWE0M2JmMTM0ZTljYTA4OTFhYjBhNzgzZGVkYjYzMA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZTFlNDhmNDE1NjM1ZmMxODlmYjQ1NDZjMDI4MWE4NzBiMDFjNDc5YTBkODc2
10
+ NzZkZTA1ZTA1OWRhYjYyZmZmZDcxOGVmMGE1ODcxY2E1ZWEzMTliNDUxOTgx
11
+ NDdkYTNkNGViYTBmZmFhYWFjYzFlNTk1ZTNkZGRlOTFhY2VmNTc=
12
+ data.tar.gz: !binary |-
13
+ OTY3Y2ZkMGVlZmJjNzM4MDQxZmMyNWIyNjU1OThmZTVmZDI4YTQyZjYxMDJi
14
+ MjYxMGQyYjM4YTcyNjliYTdlMjlkYzFmMjhlYjA0OTA2OTJiNDI1NzUwODM4
15
+ MzZkOTkyMzBmM2Q1N2IwYWNmMjc5MDQ2ZDJlYTFlNjhiNjkwZWI=
@@ -0,0 +1 @@
1
+ George Erickson
data/Gemfile CHANGED
@@ -14,7 +14,7 @@ group :test do
14
14
  # https://github.com/kina/simplecov-rcov-text
15
15
  gem 'simplecov-rcov-text'
16
16
  end
17
- gem "fakefs", :require => "fakefs/safe"
17
+ gem "fakefs", :require => "fakefs/safe", :platform => :ruby
18
18
  gem 'json'
19
19
  end
20
20
  gemspec :path => File.expand_path('..', __FILE__)
data/HISTORY.md CHANGED
@@ -10,6 +10,19 @@ As such, a _Feature_ would map to either major or minor. A _bug fix_ to a patch.
10
10
  * Fixes
11
11
  * Misc
12
12
 
13
+ ### MetricFu [4.4.2 / 2013-09-25](https://github.com/metricfu/metric_fu/compare/v4.4.1...v4.4.2)
14
+
15
+ * Features
16
+ * Add --debug-info command line switch to get debug info for Issues. (calveto, #118)
17
+ * Fixes
18
+ * Return valid line locations for code with either no AST or nil nodes (Benjamin Fleischer, #137)
19
+ * Only use FakeFS on MRI. Avoid intermittent failures on JRuby or Rubinius (Benjamin Fleischer, #135)
20
+ * Hotspots no longer serialize actual classes to YAML. (Benjamin Fleischer, #128)
21
+ * Misc
22
+ * Extract SexpNode class from LineNumbers to handle Sexp Processing (Benjamin Fleischer, #137)
23
+ * Separate out Hotspot ranked problem location and misc code improvements (Benjamin Fleischer, #128)
24
+ * Identify directories with code to analyze by checking if they exist. (No longer use :rails? as a proxy for checking if we should run on 'app'). (George Erickson, #129)
25
+
13
26
  ### MetricFu [4.4.1 / 2013-08-29](https://github.com/metricfu/metric_fu/compare/v4.4.0...v4.4.1)
14
27
 
15
28
  * Features
@@ -0,0 +1 @@
1
+ bbcd106dba00bcf0c1bfe8562d012a82f0945d8388bcd768598b872c35655668bfc9d0c1e7b0d67369713902dd9f1dd6fba9b83b21d79fe8ee774a2f8fa2defe
@@ -65,6 +65,7 @@ module MetricFu
65
65
  p.on_tail("-h", "--help", "Show this message") {puts p ; exit}
66
66
  short = @used_short.include?("v") ? "-V" : "-v"
67
67
  p.on_tail(short, "--version", "Print version") {puts @version ; exit} unless @version.nil?
68
+ p.on_tail("--debug-info", "Print debug info") { debug_info; exit }
68
69
  @default_values = @result.clone # save default values to reset @result in subsequent calls
69
70
  end
70
71
 
@@ -78,6 +79,12 @@ module MetricFu
78
79
  @result
79
80
  end
80
81
 
82
+ def debug_info
83
+ extend(MetricFu::Environment)
84
+ require 'pp'
85
+ pp debug_info
86
+ end
87
+
81
88
  # Build a nicely formatted list of built-in
82
89
  # formatter keys and their descriptions
83
90
  # @see MetricFu::Formatter::BUILTIN_FORMATS
@@ -1,63 +1,39 @@
1
- # MetricFu::LineNumber
2
- # (see #initialize)
3
- require 'ruby_parser'
1
+ MetricFu.data_structures_require { 'sexp_node' }
4
2
  module MetricFu
5
3
  class LineNumbers
4
+ attr_reader :file_path
6
5
 
7
6
  # Parses ruby code to collect line numbers for class, module, and method definitions.
8
7
  # Used by metrics that don't provide line numbers for class, module, or methods problems
9
8
  # @param contents [String] a string of ruby code
10
9
  # @param file_path [String] the file path for the contents, defaults to empty string
11
10
  def initialize(contents,file_path='')
11
+ @file_path = file_path
12
+ @locations = {}
12
13
  if contents.to_s.size.zero?
13
14
  mf_log "NON PARSEABLE INPUT: File is empty at path #{file_path.inspect}\n\t#{caller.join("\n\t")}"
14
15
  else
15
- rp = RubyParser.new
16
- @locations = {}
17
- file_sexp = rp.parse(contents)
18
- case file_sexp[0]
19
- when nil
20
- mf_log "No ruby code found in #{file_path}"
21
- when :class
22
- process_class(file_sexp)
23
- when :module
24
- process_module(file_sexp)
25
- else
26
- mf_debug "SEXP: Parsing line numbers for classes in sexp type #{file_sexp[0].inspect}"
27
- mf_debug " in #{file_path}"
28
- file_sexp.each_of_type(:module) { |sexp| process_class(sexp) }
29
- file_sexp.each_of_type(:class) { |sexp| process_class(sexp) }
30
- end
16
+ parse_code(contents)
31
17
  end
32
- rescue Exception => e
33
- #catch errors for files ruby_parser fails on
34
- mf_log "RUBY PARSE FAILURE: #{e.class}\t#{e.message}\tFILE:#{file_path}\tSEXP:#{file_sexp.inspect}\n\tCONTENT:#{contents.inspect}\n\t#{e.backtrace}"
35
- @locations
36
18
  end
37
19
 
38
- # @param line_number [String]
20
+ # @param line_number [Integer]
39
21
  # @return [Boolean] if the given line number is in a method
40
- def in_method? line_number
41
- !!@locations.detect do |method_name, line_number_range|
42
- line_number_range.include?(line_number)
43
- end
22
+ def in_method?(line_number)
23
+ not method_at_line(line_number) == :no_method_at_line
44
24
  end
45
25
 
46
26
  # @param line_number [Integer]
47
27
  # @return [String, nil] the method which includes that line number, if any
48
- # For all collected locations, find the first location where the line_number_range
49
- # includes the line_number
50
- # If a location is found, return its first element
51
- # Else return nil
52
- def method_at_line line_number
53
- found_method_and_range = @locations.detect do |method_name, line_number_range|
28
+ # For all collected locations, find the first location
29
+ # where the line_number_range includes the line_number.
30
+ # If a location is found, return the method name (first element)
31
+ # Else return :no_method_at_line
32
+ def method_at_line(line_number)
33
+ default_proc = ->{ [:no_method_at_line] }
34
+ @locations.detect(default_proc) do |method_name, line_number_range|
54
35
  line_number_range.include?(line_number)
55
- end
56
- if found_method_and_range
57
- found_method_and_range.first
58
- else
59
- nil
60
- end
36
+ end.first
61
37
  end
62
38
 
63
39
  # @param method [String] the method name being queried
@@ -69,33 +45,68 @@ module MetricFu
69
45
 
70
46
  private
71
47
 
72
- def process_module(sexp)
73
- module_name = sexp[1]
74
- sexp.each_of_type(:class) do |exp|
75
- process_class(exp, module_name)
76
- hide_methods_from_next_round(exp)
48
+ def parse_code(contents)
49
+ file_sexp = MetricFu::SexpNode.parse(contents)
50
+ file_sexp && process_ast(file_sexp)
51
+ rescue => e
52
+ #catch errors for files ruby_parser fails on
53
+ mf_log "RUBY PARSE FAILURE: #{e.class}\t#{e.message}\tFILE:#{file_path}\tSEXP:#{file_sexp.inspect}\n\tCONTENT:#{contents.inspect}\n\t#{e.backtrace}"
54
+ end
55
+
56
+ def process_ast(file_sexp)
57
+ node = MetricFu::SexpNode.new(file_sexp)
58
+ case node.node_type
59
+ when nil
60
+ mf_log "No ruby code found in #{file_path}"
61
+ when :class
62
+ process_class(node)
63
+ when :module
64
+ process_module(node)
65
+ else
66
+ mf_debug "SEXP: Parsing line numbers for classes in sexp type #{node.node_type.inspect}"
67
+ mf_debug " in #{file_path}"
68
+ node.each_module {|child_node| process_class(child_node) }
69
+ node.each_class {|child_node| process_class(child_node) }
70
+ end
71
+ end
72
+
73
+ def process_module(module_node)
74
+ module_name = module_node.name
75
+ module_node.each_class do |class_node|
76
+ process_class(class_node, module_name)
77
+ class_node.hide_methods_from_next_round
77
78
  end
78
- process_class(sexp)
79
+ process_class(module_node)
79
80
  end
80
81
 
81
- def process_class(sexp, module_name=nil)
82
- class_name = sexp[1]
83
- process_class_self_blocks(sexp, class_name)
84
- module_name_string = module_name ? "#{module_name}::" : nil
85
- sexp.each_of_type(:defn) { |s| @locations["#{module_name_string}#{class_name}##{s[1]}"] = (s.line)..(s.last.line) }
86
- sexp.each_of_type(:defs) { |s| @locations["#{module_name_string}#{class_name}::#{s[2]}"] = (s.line)..(s.last.line) }
82
+ def process_class(class_node, module_name=nil)
83
+ class_name = class_node.name
84
+ process_singleton_methods(class_node, class_name)
85
+ process_instance_methods( class_node, class_name, module_name)
86
+ process_class_methods( class_node, class_name, module_name)
87
87
  end
88
88
 
89
- def process_class_self_blocks(sexp, class_name)
90
- sexp.each_of_type(:sclass) do |sexp_in_class_self_block|
91
- sexp_in_class_self_block.each_of_type(:defn) { |s| @locations["#{class_name}::#{s[1]}"] = (s.line)..(s.last.line) }
92
- hide_methods_from_next_round(sexp_in_class_self_block)
89
+ def process_singleton_methods(class_node, class_name)
90
+ class_node.each_singleton_class do |singleton_node|
91
+ singleton_node.each_singleton_method do |singleton_method_node|
92
+ singleton_method_name = singleton_method_node.full_name(class_name)
93
+ @locations[singleton_method_name] = singleton_method_node.line_range
94
+ end
93
95
  end
94
96
  end
95
97
 
96
- def hide_methods_from_next_round(sexp)
97
- sexp.find_and_replace_all(:defn, :ignore_me)
98
- sexp.find_and_replace_all(:defs, :ignore_me)
98
+ def process_instance_methods(class_node, class_name, module_name)
99
+ class_node.each_instance_method do |instance_method_node|
100
+ instance_method_name = instance_method_node.full_name(module_name, class_name)
101
+ @locations[instance_method_name] = instance_method_node.line_range
102
+ end
103
+ end
104
+
105
+ def process_class_methods(class_node, class_name, module_name)
106
+ class_node.each_class_method do |class_method_node|
107
+ class_method_name = class_method_node.full_name(module_name, class_name)
108
+ @locations[class_method_name] = class_method_node.line_range
109
+ end
99
110
  end
100
111
 
101
112
  end
@@ -3,34 +3,58 @@ module MetricFu
3
3
  include Comparable
4
4
 
5
5
  attr_accessor :file_path, :file_name, :line_number,
6
- :class_name, :method_name, :simple_method_name, :hash
6
+ :class_name, :method_name, :simple_method_name, :hash, :hash_key
7
7
 
8
8
  def self.get(file_path, class_name, method_name)
9
9
  location = new(file_path, class_name, method_name)
10
10
  @@locations ||= {}
11
- @@locations.fetch(location.to_key) do
12
- @@locations[location.to_key] = location
11
+ @@locations.fetch(location.hash_key) do
12
+ @@locations[location.hash_key] = location
13
13
  location.finalize
14
14
  location
15
15
  end
16
16
  end
17
17
 
18
18
  def initialize(file_path, class_name, method_name)
19
- @file_path = file_path
19
+ @file_path = file_path
20
20
  @file_name, @line_number = file_path.to_s.split(/:/)
21
- @class_name = class_name
22
- @method_name = method_name
23
- @simple_method_name = @method_name.to_s.sub(@class_name.to_s,'')
24
- @hash = to_key.hash
21
+ @class_name = class_name
22
+ @method_name = method_name
23
+ @simple_method_name = @method_name.to_s.sub(@class_name.to_s,'')
24
+ @hash_key = to_key
25
+ @hash = @hash_key.hash
25
26
  end
26
27
 
27
- # TODO - we need this method (and hash accessor above) as a temporary hack where we're using Location as a hash key
28
+ def to_hash
29
+ hash = {
30
+ "class_name" => class_name,
31
+ "method_name" => method_name,
32
+ "file_path" => file_path,
33
+ 'file_name' => file_name,
34
+ 'line_number' => line_number,
35
+ 'hash_key' => hash_key,
36
+ }
37
+
38
+ if method_name.to_s.size > 0
39
+ hash = hash.merge("simple_method_name" => simple_method_name)
40
+ else
41
+ hash
42
+ end
43
+ end
44
+
45
+ # defining :eql? and :hash to use Location as a hash key
28
46
  def eql?(other)
29
- # REMOVED per https://github.com/jscruggs/metric_fu/pull/67/files
30
- # [self.file_path.to_s, self.class_name.to_s, self.method_name.to_s] == [other.file_path.to_s, other.class_name.to_s, other.method_name.to_s]
31
47
  @hash == other.hash
32
48
  end
33
- # END we need these methods as a temporary hack where we're using Location as a hash key
49
+
50
+ def <=>(other)
51
+ self.hash <=> other.hash
52
+ end
53
+
54
+ # Generates the @hash key
55
+ def to_key
56
+ [@file_path, @class_name, @method_name].inspect
57
+ end
34
58
 
35
59
  def self.for(class_or_method_name)
36
60
  class_or_method_name = strip_modules(class_or_method_name)
@@ -61,14 +85,6 @@ module MetricFu
61
85
  self.get(nil, class_name, method_name)
62
86
  end
63
87
 
64
- def <=>(other)
65
- [self.file_path.to_s, self.class_name.to_s, self.method_name.to_s] <=> [other.file_path.to_s, other.class_name.to_s, other.method_name.to_s]
66
- end
67
-
68
- def to_key
69
- [@file_path, @class_name, @method_name]
70
- end
71
-
72
88
  def finalize
73
89
  @file_path &&= @file_path.clone
74
90
  @file_name &&= @file_name.clone
@@ -0,0 +1,89 @@
1
+ require 'ruby_parser'
2
+ module MetricFu
3
+ SexpNode = Struct.new(:sexp) do
4
+ # @return file_sexp
5
+ def self.parse(contents)
6
+ rp = RubyParser.new
7
+ rp.parse(contents)
8
+ end
9
+ def nil?
10
+ sexp.nil?
11
+ end
12
+ def node_type
13
+ sexp[0]
14
+ end
15
+ def name
16
+ sexp[1]
17
+ end
18
+ def each_of_type(type,node_class=SexpNode)
19
+ sexp.each_of_type(type) do |child_sexp|
20
+ yield node_class.new(child_sexp)
21
+ end
22
+ end
23
+ def each_module(&block)
24
+ each_of_type(:module,&block)
25
+ end
26
+ def each_class(&block)
27
+ each_of_type(:class,&block)
28
+ end
29
+ def each_singleton_class(&block)
30
+ each_of_type(:sclass,SingletonMethodNode,&block)
31
+ end
32
+ def each_instance_method(&block)
33
+ each_of_type(:defn,InstanceMethodNode,&block)
34
+ end
35
+ def each_class_method(&block)
36
+ each_of_type(:defs,ClassMethodNode,&block)
37
+ end
38
+ def first_line
39
+ sexp.line
40
+ end
41
+ def last_line
42
+ sexp.last.line
43
+ end
44
+ def line_range
45
+ (first_line..last_line)
46
+ end
47
+ def hide_methods_from_next_round
48
+ sexp.find_and_replace_all(:defn, :ignore_me)
49
+ sexp.find_and_replace_all(:defs, :ignore_me)
50
+ end
51
+ def full_method_name(method_separator, class_name, module_name=nil)
52
+ [module_namespace(module_name), class_name, method_separator, name].join
53
+ end
54
+ def module_namespace(module_name=nil)
55
+ if module_name.nil?
56
+ nil
57
+ else
58
+ [module_name,class_method_separator].join
59
+ end
60
+ end
61
+ def instance_method_separator
62
+ '#'
63
+ end
64
+ def class_method_separator
65
+ '::'
66
+ end
67
+ end
68
+ class ClassMethodNode < SexpNode
69
+ def name
70
+ sexp[2]
71
+ end
72
+ def full_name(module_name, class_name)
73
+ full_method_name(class_method_separator, class_name, module_name)
74
+ end
75
+ end
76
+ class InstanceMethodNode < SexpNode
77
+ def full_name(module_name, class_name)
78
+ full_method_name(instance_method_separator, class_name, module_name)
79
+ end
80
+ end
81
+ class SingletonMethodNode < SexpNode
82
+ def full_name(class_name)
83
+ full_method_name(class_method_separator, class_name)
84
+ end
85
+ def each_singleton_method(&block)
86
+ each_of_type(:defn,SingletonMethodNode,&block)
87
+ end
88
+ end
89
+ end
@@ -1,4 +1,5 @@
1
1
  require 'redcard'
2
+ require 'rbconfig'
2
3
  MetricFu.logging_require { 'mf_debugger' }
3
4
  module MetricFu
4
5
  module Environment
@@ -66,10 +67,57 @@ module MetricFu
66
67
  false
67
68
  end
68
69
  end
70
+
69
71
  def platform #:nodoc:
70
72
  return RUBY_PLATFORM
71
73
  end
72
74
 
75
+ def version
76
+ MetricFu::VERSION
77
+ end
78
+
79
+ def environment_details
80
+ @environment_details ||= {
81
+ 'VERBOSE' => $VERBOSE.inspect,
82
+ 'External Encoding' => Encoding.default_external.to_s,
83
+ 'Internal Encoding' => Encoding.default_internal.to_s,
84
+ 'Host Architecture' => RbConfig::CONFIG['build'],
85
+ 'Ruby Prefix' => RbConfig::CONFIG['prefix'],
86
+ 'Ruby Options' => ENV.keys.grep(/RUBYOPT/).map{|key| "#{key}=#{ENV[key]}" }.join(', '),
87
+ }
88
+ end
89
+
90
+ # To consider
91
+ # $LOADED_FEATURES
92
+ # $LOAD_PATH
93
+ def ruby_details
94
+ @ruby_details ||= {
95
+ 'Engine' => ruby_flavor,
96
+ 'Version' => ruby_version,
97
+ 'Patchlevel' => (defined?(RUBY_PATCHLEVEL) && RUBY_PATCHLEVEL),
98
+ 'Ripper Support' => supports_ripper?,
99
+ 'Rubygems Version' => Gem::VERSION,
100
+ 'Long Description' => (defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : platform),
101
+ }
102
+ end
103
+
104
+ def library_details
105
+ @library_details ||= {
106
+ 'Version' => version,
107
+ 'Verbose Mode' => verbose,
108
+ 'Enabled Metrics' => MetricFu::Metric.enabled_metrics.map(&:name),
109
+ # 'Dependencies' => `gem dependency metric_fu`, # TODO how should we handle this?
110
+ }
111
+ end
112
+
113
+ def debug_info
114
+ @debug_info ||= {
115
+ 'Ruby' => ruby_details,
116
+ 'Environment' => environment_details,
117
+ 'MetricFu' => library_details,
118
+ }
119
+ end
120
+
73
121
  def osx?
74
122
  @osx ||= platform.include?('darwin')
75
123
  end