metric_fu 4.4.1 → 4.4.2

Sign up to get free protection for your applications and to get access to all the features.
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