├── checksum
├── .gitkeep
├── metric_fu-4.2.0.gem.sha512
├── metric_fu-4.2.1.gem.sha512
├── metric_fu-4.3.0.gem.sha512
├── metric_fu-4.3.1.gem.sha512
├── metric_fu-4.4.0.gem.sha512
├── metric_fu-4.4.1.gem.sha512
├── metric_fu-4.4.2.gem.sha512
├── metric_fu-4.4.3.gem.sha512
├── metric_fu-4.4.4.gem.sha512
├── metric_fu-4.5.0.gem.sha512
├── metric_fu-4.5.1.gem.sha512
├── metric_fu-4.5.2.gem.sha512
├── metric_fu-4.6.0.gem.sha512
├── metric_fu-4.7.0.gem.sha512
├── metric_fu-4.7.1.gem.sha512
├── metric_fu-4.7.2.gem.sha512
├── metric_fu-4.7.3.gem.sha512
├── metric_fu-4.7.4.gem.sha512
├── metric_fu-4.8.0.gem.sha512
├── metric_fu-4.9.0.gem.sha512
├── metric_fu-4.10.0.gem.sha512
├── metric_fu-4.11.0.gem.sha512
├── metric_fu-4.11.1.gem.sha512
├── metric_fu-4.11.2.gem.sha512
├── metric_fu-4.11.3.gem.sha512
├── metric_fu-4.11.4.gem.sha512
├── metric_fu-4.12.0.gem.sha512
└── metric_fu-4.13.0.gem.sha512
├── spec
├── dummy
│ ├── .gitkeep
│ ├── lib
│ │ ├── .gitkeep
│ │ └── bad_encoding.rb
│ ├── spec
│ │ └── .gitkeep
│ └── .gitignore
├── fixtures
│ ├── metric_missing.yml
│ ├── exit0.sh
│ ├── exit1.sh
│ ├── hotspots
│ │ ├── stats.yml
│ │ ├── reek.yml
│ │ ├── roodi.yml
│ │ ├── saikuro.yml
│ │ ├── three_metrics_on_same_file.yml
│ │ ├── several_metrics.yml
│ │ ├── generator.yml
│ │ └── generator_analysis.yml
│ ├── line_numbers
│ │ ├── two_classes.rb
│ │ ├── module.rb
│ │ ├── module_surrounds_class.rb
│ │ └── foo.rb
│ ├── coverage.rb
│ ├── coverage-153.rb
│ ├── saikuro_sfiles
│ │ └── thing.rb_cyclo.html
│ └── saikuro
│ │ └── app
│ │ └── controllers
│ │ ├── sessions_controller.rb_cyclo.html
│ │ └── users_controller.rb_cyclo.html
├── support
│ ├── samples
│ │ └── reek
│ │ │ └── alfa.rb
│ ├── .metrics
│ ├── timeout.rb
│ ├── suite.rb
│ ├── deferred_garbaged_collection.rb
│ ├── test_fixtures.rb
│ ├── matcher_create_file.rb
│ ├── helper_methods.rb
│ └── matcher_create_files.rb
├── metric_fu
│ ├── metrics
│ │ ├── hotspots
│ │ │ ├── analysis
│ │ │ │ ├── table_spec.rb
│ │ │ │ └── ranking_spec.rb
│ │ │ ├── hotspot_analyzer_spec.rb
│ │ │ ├── hotspot_spec.rb
│ │ │ └── generator_spec.rb
│ │ ├── flay
│ │ │ ├── configuration_spec.rb
│ │ │ └── grapher_spec.rb
│ │ ├── reek
│ │ │ ├── configuration_spec.rb
│ │ │ └── grapher_spec.rb
│ │ ├── churn
│ │ │ └── configuration_spec.rb
│ │ ├── roodi
│ │ │ ├── configuration_spec.rb
│ │ │ └── grapher_spec.rb
│ │ ├── flog
│ │ │ └── configuration_spec.rb
│ │ ├── rcov
│ │ │ ├── hotspot_spec.rb
│ │ │ ├── configuration_spec.rb
│ │ │ ├── generator_spec.rb
│ │ │ ├── grapher_spec.rb
│ │ │ └── rcov_line_spec.rb
│ │ ├── cane
│ │ │ └── configuration_spec.rb
│ │ ├── saikuro
│ │ │ ├── configuration_spec.rb
│ │ │ └── generator_spec.rb
│ │ ├── rails_best_practices
│ │ │ ├── generator_spec.rb
│ │ │ ├── configuration_spec.rb
│ │ │ └── grapher_spec.rb
│ │ └── stats
│ │ │ └── grapher_spec.rb
│ ├── loader_spec.rb
│ ├── templates
│ │ ├── metrics_template_spec.rb
│ │ ├── report_spec.rb
│ │ └── configuration_spec.rb
│ ├── utility_spec.rb
│ ├── gem_version_spec.rb
│ ├── calculate_spec.rb
│ ├── reporting
│ │ ├── graphs
│ │ │ ├── grapher_spec.rb
│ │ │ └── graph_spec.rb
│ │ └── result_spec.rb
│ ├── reporter_spec.rb
│ ├── metric_spec.rb
│ ├── formatter
│ │ ├── configuration_spec.rb
│ │ └── yaml_spec.rb
│ └── formatter_spec.rb
├── shared
│ └── configured.rb
├── spec_helper.rb
├── capture_warnings.rb
└── metric_fu_spec.rb
├── .rspec
├── .metrics
├── etc
├── erd.png
└── README.md
├── .yardopts
├── lib
└── metric_fu
│ ├── templates
│ ├── _report_footer.html.erb
│ ├── _graph.html.erb
│ ├── index.html.erb
│ ├── css
│ │ ├── bluff.css
│ │ ├── rcov.css
│ │ ├── syntax.css
│ │ ├── default.css
│ │ ├── reset.css
│ │ └── buttons.css
│ ├── javascripts
│ │ ├── utils.js
│ │ ├── bluff_graph.js
│ │ └── highcharts_graph.js
│ ├── configuration.rb
│ ├── report.html.erb
│ ├── report.rb
│ └── layout.html.erb
│ ├── errors
│ └── analysis_error.rb
│ ├── version.rb
│ ├── calculate.rb
│ ├── metrics
│ ├── hotspots
│ │ ├── analysis
│ │ │ ├── groupings.rb
│ │ │ ├── problems.rb
│ │ │ ├── scoring_strategies.rb
│ │ │ ├── grouping.rb
│ │ │ ├── record.rb
│ │ │ ├── ranking.rb
│ │ │ ├── analyzed_problems.rb
│ │ │ ├── table.rb
│ │ │ └── ranked_problem_location.rb
│ │ ├── metric.rb
│ │ ├── generator.rb
│ │ ├── hotspot_analyzer.rb
│ │ └── report.html.erb
│ ├── reek
│ │ ├── metric.rb
│ │ ├── report.html.erb
│ │ └── grapher.rb
│ ├── roodi
│ │ ├── metric.rb
│ │ ├── report.html.erb
│ │ ├── hotspot.rb
│ │ ├── grapher.rb
│ │ └── generator.rb
│ ├── rcov
│ │ ├── external_client.rb
│ │ ├── grapher.rb
│ │ ├── rcov_line.rb
│ │ ├── hotspot.rb
│ │ ├── report.html.erb
│ │ ├── metric.rb
│ │ └── generator.rb
│ ├── churn
│ │ ├── metric.rb
│ │ ├── hotspot.rb
│ │ ├── generator.rb
│ │ └── report.html.erb
│ ├── flay
│ │ ├── metric.rb
│ │ ├── grapher.rb
│ │ ├── report.html.erb
│ │ ├── generator.rb
│ │ └── hotspot.rb
│ ├── saikuro
│ │ ├── metric.rb
│ │ ├── parsing_element.rb
│ │ ├── hotspot.rb
│ │ └── report.html.erb
│ ├── flog
│ │ ├── metric.rb
│ │ ├── hotspot.rb
│ │ ├── report.html.erb
│ │ └── grapher.rb
│ ├── stats
│ │ ├── hotspot.rb
│ │ ├── metric.rb
│ │ ├── grapher.rb
│ │ └── report.html.erb
│ ├── rails_best_practices
│ │ ├── metric.rb
│ │ ├── report.html.erb
│ │ ├── grapher.rb
│ │ └── generator.rb
│ └── cane
│ │ ├── metric.rb
│ │ ├── grapher.rb
│ │ └── violations.rb
│ ├── formatter
│ ├── yaml.rb
│ └── syntax.rb
│ ├── cli
│ ├── client.rb
│ └── helper.rb
│ ├── logging
│ └── mf_debugger.rb
│ ├── reporter.rb
│ ├── formatter.rb
│ ├── logger.rb
│ ├── reporting
│ ├── graphs
│ │ ├── grapher.rb
│ │ └── graph.rb
│ └── result.rb
│ ├── utility.rb
│ ├── constantize.rb
│ ├── tasks
│ └── metric_fu.rake
│ └── gem_run.rb
├── AUTHORS
├── bin
├── metric_fu
├── mf-cane
├── mf-flay
├── mf-reek
├── mf-churn
├── mf-roodi
└── mf-saikuro
├── .rubocop.yml
├── gem_tasks
├── rubocop.rake
├── yard.rake
└── usage_test.rake
├── .gitignore
├── .travis.yml
├── .github
└── workflows
│ └── ruby.yml
├── config
└── roodi_config.yml
├── appveyor.yml
├── Rakefile
├── MIT-LICENSE
├── Guardfile
├── CONTRIBUTORS
├── certs
└── bf4.pem
├── Gemfile
├── .rubocop_todo.yml
├── DEV.md
└── CONTRIBUTING.md
/checksum/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/lib/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
2 | --profile
--------------------------------------------------------------------------------
/spec/dummy/spec/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/.gitignore:
--------------------------------------------------------------------------------
1 | tmp
2 |
--------------------------------------------------------------------------------
/spec/fixtures/metric_missing.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
--------------------------------------------------------------------------------
/spec/support/samples/reek/alfa.rb:
--------------------------------------------------------------------------------
1 | def foo(echo); end
2 |
--------------------------------------------------------------------------------
/.metrics:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Put configuration code here
4 |
--------------------------------------------------------------------------------
/spec/fixtures/exit0.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | which ruby
3 | exit 0
4 |
--------------------------------------------------------------------------------
/spec/fixtures/exit1.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | which ruby
3 | exit 1
4 |
--------------------------------------------------------------------------------
/etc/erd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metricfu/metric_fu/HEAD/etc/erd.png
--------------------------------------------------------------------------------
/.yardopts:
--------------------------------------------------------------------------------
1 | --no-private
2 | --readme README.md
3 | lib/metric_fu.rb
4 | lib/metric_fu/**/*.rb
5 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/_report_footer.html.erb:
--------------------------------------------------------------------------------
1 |
Generated on <%= MetricFu.current_time %>
--------------------------------------------------------------------------------
/lib/metric_fu/errors/analysis_error.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class AnalysisError < RuntimeError
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/spec/fixtures/hotspots/stats.yml:
--------------------------------------------------------------------------------
1 | :stats:
2 | :codeLOC: 4222
3 | :testLOC: 2111
4 | :code_to_test_ratio: 2
5 |
--------------------------------------------------------------------------------
/spec/support/.metrics:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # For testing that this file gets loaded
4 | $metric_file_loaded = true
5 |
--------------------------------------------------------------------------------
/spec/dummy/lib/bad_encoding.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | class SomeClass
3 | def initialize(_a, _b, _c)
4 | "hey, invalid ASCII\xED"
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/checksum/metric_fu-4.2.0.gem.sha512:
--------------------------------------------------------------------------------
1 | 9dab16044285faa2d79afc40e701fb35d7e41d658d147bb161f616676f3258e069c1e250c1d854460e67ca1674ae286265f43dcd50255416c0e6d8e9e919b516
--------------------------------------------------------------------------------
/checksum/metric_fu-4.2.1.gem.sha512:
--------------------------------------------------------------------------------
1 | 529b99cf3f5fb314e9a7684ad38198e7509ec13926ded779bfef2a4b968b0538fc72248606a3b0b1bdead31163e04641bd6f565d5db62bb168ee80b854e9c580
--------------------------------------------------------------------------------
/checksum/metric_fu-4.3.0.gem.sha512:
--------------------------------------------------------------------------------
1 | 7a5e74c8b010bd9629e9cbdcf297cc1d75295338bb34a327a9ed57a742c5fc57107608bd148e31e5355263dc113841249be8719877a68fcb42271212edd71f13
--------------------------------------------------------------------------------
/checksum/metric_fu-4.3.1.gem.sha512:
--------------------------------------------------------------------------------
1 | 8ceae4f1bb2719d8ecbe2c7b3e929fe477cbe4ea6582297092a1bc09a6bef9e423f3a33c619b62372b208f956c4d8f9839257cd491475b58c06a2c8341eafc77
--------------------------------------------------------------------------------
/checksum/metric_fu-4.4.0.gem.sha512:
--------------------------------------------------------------------------------
1 | c01694dc4a374806bd6cfaf5091808d223db1ea07a85d90a9749ed8e603c3abee283a36a6aacde81072b7c0ec2eec64f85d977caa6cd69687a532040a2137bd4
--------------------------------------------------------------------------------
/checksum/metric_fu-4.4.1.gem.sha512:
--------------------------------------------------------------------------------
1 | bbcd106dba00bcf0c1bfe8562d012a82f0945d8388bcd768598b872c35655668bfc9d0c1e7b0d67369713902dd9f1dd6fba9b83b21d79fe8ee774a2f8fa2defe
--------------------------------------------------------------------------------
/checksum/metric_fu-4.4.2.gem.sha512:
--------------------------------------------------------------------------------
1 | 1beb22d0d940c4d5297efbfa5a2c6ad46a5f64fd68a0695bc1d81a7b3d85fdafc9004ede390272f2602a2a87f306137fccf53e8225b9dd9943e87ef5c7103c78
--------------------------------------------------------------------------------
/checksum/metric_fu-4.4.3.gem.sha512:
--------------------------------------------------------------------------------
1 | 6d7c86a7231183dc5cf3d13b03fa4174873ffa6202d0b3fa09b485acc0e076951d6c906a99816fa9908fed75fa513cfba1ce29fb3911f881a93fb0922a70ff8f
--------------------------------------------------------------------------------
/checksum/metric_fu-4.4.4.gem.sha512:
--------------------------------------------------------------------------------
1 | 287c0e2d61b94453f05b6c6b8d4666c49337022af56d4b3066ff6f9856747ef6ce5830b121bed2cfbe747716049c88ac1c9edc19a4cccc9b408454107899c663
--------------------------------------------------------------------------------
/checksum/metric_fu-4.5.0.gem.sha512:
--------------------------------------------------------------------------------
1 | 60fdd77882c1eac658fec94a3207b00d3556e082ee9a01ce4097d8fa297df32d66de4ec2fc780b7566dc331975ad4469143dd8eb0dcd8df985c01405ce57c580
--------------------------------------------------------------------------------
/checksum/metric_fu-4.5.1.gem.sha512:
--------------------------------------------------------------------------------
1 | a1ff91abb134992e958518efddea3faaee87d987d4f7d6190a065eff5647514b9e58d16c78db061f42a53429f1647f47f3efda1bc75f746a342b9ba50bf72b1c
--------------------------------------------------------------------------------
/checksum/metric_fu-4.5.2.gem.sha512:
--------------------------------------------------------------------------------
1 | cecb4dabfef6d7f955f394a9af5a8755cae4269196dfe6f4447f0d465426a8c4bb777572d3dc8404de352c0e64dd74562b6b8e65687ca79bdd247d3fbafe6e69
--------------------------------------------------------------------------------
/checksum/metric_fu-4.6.0.gem.sha512:
--------------------------------------------------------------------------------
1 | b9488463cdbac040cc3f4fdff3ec18385b3129876464ae14de2078c1742a1be7951ba40b7f2efba54809cb8e7766fc53ff63c2672ffd25452ddca2f9a9387ac4
--------------------------------------------------------------------------------
/checksum/metric_fu-4.7.0.gem.sha512:
--------------------------------------------------------------------------------
1 | 7abc57b0cf4a342710a2dc53f9113c4797b0260912e68f386888692a72b84631bf81cee47aa9deba81ea3ddb07f8302b08279562c0802a678d78b7cc3c32ad38
--------------------------------------------------------------------------------
/checksum/metric_fu-4.7.1.gem.sha512:
--------------------------------------------------------------------------------
1 | c7769b05094f900a0b48c1e8b9e4df67372223e0540d97c94109eb00af5acf32cb20615c8c5adcf7352a3ee1ad1b3106b5b20b87ebccc3dfc86a21ca5709490b
--------------------------------------------------------------------------------
/checksum/metric_fu-4.7.2.gem.sha512:
--------------------------------------------------------------------------------
1 | ad47170c438b84335051bae41480a00dec428e1f755a70813364c6232f43d16c15c55813ffb555e5236189360fe84bfcbe194e6cd54b304d3b2688985b3609ac
--------------------------------------------------------------------------------
/checksum/metric_fu-4.7.3.gem.sha512:
--------------------------------------------------------------------------------
1 | 9a3c76074181ca4ec770f1a52f652268e44af10648cf82cb641672d2075d92133751ad275ad1ba417c473ba8095d872df762eb379e2e48fd38ce1db06b4ea1e7
--------------------------------------------------------------------------------
/checksum/metric_fu-4.7.4.gem.sha512:
--------------------------------------------------------------------------------
1 | 26caa046f537a1a81415bf5f238a7cd36a5b1c1f3f2e1008276e0fdbb35d08925fbf3f2bdd95477f75f716088639630cebec9ca5d300f03d6cd6cb3ae1c437dc
--------------------------------------------------------------------------------
/checksum/metric_fu-4.8.0.gem.sha512:
--------------------------------------------------------------------------------
1 | d46528ba18e30ce561e3d0dfb4245ee2e4b7ff72a895cff24cb4bb4a86ace4ed3f759eba96b26aa9e512ebd253d78d9697423bf468c09a09fb764597977f26c2
--------------------------------------------------------------------------------
/checksum/metric_fu-4.9.0.gem.sha512:
--------------------------------------------------------------------------------
1 | 6a59eb643743ccb2d34c7de915a0c448c6409a8b31a76b5bc8315fced802e5e186f8d97a7973c95ba306ac17d7de59d015cd93c50bda56180a952c91ac56c216
--------------------------------------------------------------------------------
/checksum/metric_fu-4.10.0.gem.sha512:
--------------------------------------------------------------------------------
1 | e71e2ed626a7b3ee170116c5e72d846ba36039b0f016eb31ca87c9815534f27d5c85cf9bb42308bb2b3f539d123aa3ffea8fb7692c9a1c6f2b616a3511bf11f1
--------------------------------------------------------------------------------
/checksum/metric_fu-4.11.0.gem.sha512:
--------------------------------------------------------------------------------
1 | 5402db0fe4fb45627ce37d071b0d0d0ec4d173320cfffb251e3e0406556e828ac97fc416a4ddd9387b39aa9e374687ebf900f94cb0a578d771b12ae4af8506c6
--------------------------------------------------------------------------------
/checksum/metric_fu-4.11.1.gem.sha512:
--------------------------------------------------------------------------------
1 | 2d38eac5067748ee4417ab7088302a9e6c465e5163712cef9a1998bcb7048bf74c1b5c1facaccd8af042d2362693e4774de5b8c2bc6dbae5613eb91920ec58aa
--------------------------------------------------------------------------------
/checksum/metric_fu-4.11.2.gem.sha512:
--------------------------------------------------------------------------------
1 | c31f9343f44972b7b614d97cf33d457ad11518b408fb5e8e009eff8ee3c88353bd1dab14ac143a313433b7920b96f89d34753a0cd910364a70396ead3dc39980
--------------------------------------------------------------------------------
/checksum/metric_fu-4.11.3.gem.sha512:
--------------------------------------------------------------------------------
1 | 2290ac18a8467b1ec187273cd94afdb3c2cb187dcf1c58152b99acf54e4249746512ef55604a8b4dc348ad106b7b25ea4a5f225fd2e9659367d20340407578d5
--------------------------------------------------------------------------------
/checksum/metric_fu-4.11.4.gem.sha512:
--------------------------------------------------------------------------------
1 | 9a74861bf6d89f703786e562cdff96eea727aac874e0d9615d4ac8af57b2c97d15e2121de8d978b7097b56bd72b40744c9a27be9d3d12f9ecf75b66ac565e3ef
--------------------------------------------------------------------------------
/checksum/metric_fu-4.12.0.gem.sha512:
--------------------------------------------------------------------------------
1 | 5af713f345549aa574af94d5790ec5360d61aa9ff9dce867da339f5a86e1d2d39c23d025434dd98fc04e0912b6a6da64f271663242a5aa75cbf15cac1c9c73e1
--------------------------------------------------------------------------------
/checksum/metric_fu-4.13.0.gem.sha512:
--------------------------------------------------------------------------------
1 | e57d3944216f0d2f9ff06ddabae68cc240510df5a3b346739d866c9dc9e86d5bc7e341efd13f5bc34f5d06bdb90cf455a4491dfc65d2f197bc0ca616ea911f7c
--------------------------------------------------------------------------------
/spec/fixtures/line_numbers/two_classes.rb:
--------------------------------------------------------------------------------
1 | class Foo
2 | def stuff
3 | 5
4 | end
5 | end
6 |
7 | class Bar
8 | def stuff
9 | 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/_graph.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/hotspots/analysis/table_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "hotspots/analysis/table" }
3 |
4 | describe MetricFu::Table do
5 | it "needs tests"
6 | end
7 |
--------------------------------------------------------------------------------
/spec/fixtures/line_numbers/module.rb:
--------------------------------------------------------------------------------
1 | module KickAss
2 |
3 | def get_beat_up?
4 | [1,2,3].inject {|m,o| m = m + o}
5 | true
6 | end
7 |
8 | def fight
9 | "poorly"
10 | end
11 | end
--------------------------------------------------------------------------------
/spec/fixtures/coverage.rb:
--------------------------------------------------------------------------------
1 | class SomeClass
2 | def method_1
3 | 1 + 1
4 | end
5 |
6 | def method_2(value)
7 | value * value
8 | end
9 |
10 | def method_3
11 | "über"
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Jake Scruggs
2 | Sean Soper
3 | Andre Arko
4 | Petrik de Heus
5 | Grant McInnes
6 | Nick Quaranto
7 | Édouard Brière
8 | Carl Youngblood
9 | Richard Huang
10 | Dan Mayer
11 | Benjamin Fleischer
12 | Robin Curry
13 |
--------------------------------------------------------------------------------
/lib/metric_fu/version.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class Version
3 | MAJOR = "4"
4 | MINOR = "13"
5 | PATCH = "0"
6 | PRE = ""
7 | end
8 | VERSION = [Version::MAJOR, Version::MINOR, Version::PATCH].join(".")
9 | end
10 |
--------------------------------------------------------------------------------
/spec/fixtures/line_numbers/module_surrounds_class.rb:
--------------------------------------------------------------------------------
1 | module StuffModule
2 |
3 | class ThingClass
4 |
5 | def do_it
6 | "do it"
7 | end
8 |
9 | end
10 |
11 | def blah
12 | "blah blah"
13 | end
14 |
15 | end
--------------------------------------------------------------------------------
/bin/metric_fu:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'metric_fu'
3 | require 'metric_fu/cli/client'
4 |
5 | cli = MetricFu::Cli::Client.new
6 | # see http://obtiva.com/blog/185-fun-with-ruby-it-s-a-trap
7 | trap("INT") { cli.shutdown }
8 |
9 | cli.run
10 |
--------------------------------------------------------------------------------
/lib/metric_fu/calculate.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | module Calculate
3 | module_function
4 |
5 | def integer_percent(num, total)
6 | return 0 if total.zero?
7 | (Float(num) / Float(total) * 100).round
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/bin/mf-cane:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'rubygems'
3 |
4 | require 'metric_fu/gem_run'
5 | options = {
6 | gem_name: 'cane',
7 | metric_name: 'cane',
8 | args: ARGV.dup,
9 | }
10 | STDOUT.puts MetricFu::GemRun.new(options).run.output
11 |
--------------------------------------------------------------------------------
/bin/mf-flay:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'rubygems'
3 |
4 | require 'metric_fu/gem_run'
5 | options = {
6 | gem_name: 'flay',
7 | metric_name: 'flay',
8 | args: ARGV.dup,
9 | }
10 | STDOUT.puts MetricFu::GemRun.new(options).run.output
11 |
--------------------------------------------------------------------------------
/bin/mf-reek:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'rubygems'
3 |
4 | require 'metric_fu/gem_run'
5 | options = {
6 | gem_name: 'reek',
7 | metric_name: 'reek',
8 | args: ARGV.dup,
9 | }
10 | STDOUT.puts MetricFu::GemRun.new(options).run.output
11 |
--------------------------------------------------------------------------------
/bin/mf-churn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'rubygems'
3 |
4 | require 'metric_fu/gem_run'
5 | options = {
6 | gem_name: 'churn',
7 | metric_name: 'churn',
8 | args: ARGV.dup,
9 | }
10 | STDOUT.puts MetricFu::GemRun.new(options).run.output
11 |
--------------------------------------------------------------------------------
/bin/mf-roodi:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'rubygems'
3 |
4 | require 'metric_fu/gem_run'
5 | options = {
6 | gem_name: 'roodi',
7 | metric_name: 'roodi',
8 | args: ARGV.dup,
9 | }
10 | STDOUT.puts MetricFu::GemRun.new(options).run.output
11 |
--------------------------------------------------------------------------------
/spec/support/timeout.rb:
--------------------------------------------------------------------------------
1 | require "timeout"
2 | # No spec should run longer than this
3 | # (define here to support the closure)
4 | p "setting timeout #{timeout = 15.0} seconds"
5 | RSpec.configuration.around(:each) do |example|
6 | Timeout.timeout(timeout, &example)
7 | end
8 |
--------------------------------------------------------------------------------
/bin/mf-saikuro:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'rubygems'
3 |
4 | require 'metric_fu/gem_run'
5 | options = {
6 | gem_name: 'metric_fu-Saikuro',
7 | metric_name: 'saikuro',
8 | args: ARGV.dup,
9 | }
10 | STDOUT.puts MetricFu::GemRun.new(options).run.output
11 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= MetricFu.metric_name %> Results
2 |
3 | <% metric_links.each do |link| %>
4 | -
5 | <%= link %>
6 |
7 | <% end %>
8 |
9 |
10 | <%= render_partial 'report_footer' %>
11 |
--------------------------------------------------------------------------------
/spec/fixtures/coverage-153.rb:
--------------------------------------------------------------------------------
1 | # from https://github.com/metricfu/metric_fu/issues/153
2 | class A
3 | def m(arg1)
4 | p "this is my method" # Assume that none of the line covered in this method
5 | if arg1 > 5
6 | p "more than 5"
7 | else
8 | p "not more than 5"
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from:
2 | - config/rubocop.yml
3 | - .rubocop_todo.yml
4 |
5 | # Files you want to exclude
6 | AllCops:
7 | Exclude:
8 | - bin/*
9 | - gem_tasks/*
10 | - lib/tasks/*
11 | - Gemfile
12 | - Rakefile
13 | - bundle/**/*
14 | - vendor/bundle/**/*
15 | - spec/fixtures/line_numbers/*
16 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/css/bluff.css:
--------------------------------------------------------------------------------
1 | .bluff-tooltip {
2 | background: #fff;
3 | border: 1px solid #d1edf5;
4 | padding: 8px 8px 6px;
5 | }
6 | .bluff-tooltip .color {
7 | display: block;
8 | height: 4px;
9 | width: 30px;
10 | margin: 0 0 4px;
11 | overflow: hidden;
12 | }
13 | .bluff-tooltip .data {
14 | font-weight: bold;
15 | }
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/analysis/groupings.rb:
--------------------------------------------------------------------------------
1 | MetricFu.metrics_require { "hotspots/analysis/grouping" }
2 | module MetricFu
3 | class HotspotGroupings
4 | def initialize(table, opts)
5 | @table, @opts = table, opts
6 | end
7 |
8 | def get_grouping
9 | MetricFu::Grouping.new(@table, @opts)
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/gem_tasks/rubocop.rake:
--------------------------------------------------------------------------------
1 | if ENV["FULL_BUILD"] != "true" # skip on Travis
2 | require "rubocop/rake_task"
3 | RuboCop::RakeTask.new(:rubocop) do |task|
4 | task.patterns = ["lib", "spec"]
5 | task.formatters = ["progress"]
6 | task.options = ["--display-cop-names"]
7 | task.fail_on_error = false
8 | task.verbose = false
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/javascripts/utils.js:
--------------------------------------------------------------------------------
1 | function createGraphElement(elementType) {
2 | var graphContainer = document.getElementById("graph_container");
3 |
4 | if(graphContainer) {
5 | var graphElement = document.createElement(elementType);
6 | graphElement.setAttribute("id", "graph");
7 | graphContainer.appendChild(graphElement);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .rvmrc*
3 | *.gem
4 | *.rbc
5 | .bundle
6 | .config
7 | .yardoc
8 | Gemfile.lock
9 | InstalledFiles
10 | _yardoc
11 | coverage
12 | doc/
13 | lib/bundler/man
14 | pkg
15 | rdoc
16 | spec/reports
17 | test/tmp
18 | test/version_tmp
19 | tmp
20 | *.swp
21 | tags
22 | .idea
23 |
24 | # rvm
25 | .ruby-version
26 | .ruby-gemset
27 |
28 | .overcommit.yml
--------------------------------------------------------------------------------
/spec/fixtures/saikuro_sfiles/thing.rb_cyclo.html:
--------------------------------------------------------------------------------
1 | -- START Mod --
2 | Type:Module Name:Mod Complexity:2 Lines:7
3 | Type:Def Name:self.included Complexity:2 Lines:5
4 | -- START --
5 | Type:Class Name: Complexity:0 Lines:1
6 | -- END --
7 | Type:Def Name:update Complexity:1 Lines:1
8 | -- END Mod --
9 | -- START Thing --
10 | Type:Class Name:Thing Complexity:0 Lines:2
11 | -- END Thing --
12 |
--------------------------------------------------------------------------------
/spec/metric_fu/loader_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MetricFu do
4 | it "loads the .metrics file" do
5 | # Global only for testing that this file gets loaded
6 | $metric_file_loaded = false
7 | MetricFu.with_run_dir "spec/support" do
8 | MetricFu.loader.load_user_configuration
9 | end
10 |
11 | expect($metric_file_loaded).to be_truthy
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/hotspots/hotspot_analyzer_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "hotspots/hotspot_analyzer" }
3 |
4 | describe MetricFu::HotspotAnalyzer do
5 | it "should have its own tests regarding how it orchestrates the analysis of results, rankings, tables, and analyzed_problems"
6 |
7 | it "#hotspots aka worst_items"
8 |
9 | it "#analyzed_problems"
10 | end
11 |
--------------------------------------------------------------------------------
/spec/metric_fu/templates/metrics_template_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MetricFu::Templates::MetricsTemplate do
4 | let(:template) { Templates::MetricsTemplate.new }
5 |
6 | describe "#html_filename" do
7 | it "returns the hashed filename ending with .html" do
8 | expect(template.html_filename("some_file.rb")).to eq("10580a1fcbe74a931db8210462a584.html")
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricHotspots < Metric
3 | def name
4 | :hotspots
5 | end
6 |
7 | # TODO remove explicit Churn dependency
8 | def default_run_options
9 | { start_date: "1 year ago", minimum_churn_count: 10 }
10 | end
11 |
12 | def has_graph?
13 | false
14 | end
15 |
16 | def enable
17 | super
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/spec/fixtures/line_numbers/foo.rb:
--------------------------------------------------------------------------------
1 | class Foo
2 |
3 | def self.awesome
4 | 1 + 1
5 | end
6 |
7 | def what
8 | 12
9 | end
10 |
11 | def hello
12 | puts "Hi There"
13 | end
14 |
15 |
16 | def awesome
17 | 3+4
18 | end
19 |
20 | class << self
21 | def neat
22 | "23 skido"
23 | end
24 | end
25 |
26 | private
27 |
28 | def whoop
29 | "w00t"
30 | end
31 | end
32 |
33 | #comment
--------------------------------------------------------------------------------
/lib/metric_fu/formatter/yaml.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | module Formatter
3 | class YAML
4 | include MetricFu::Io
5 |
6 | DEFAULT_PATH = "report.yml"
7 |
8 | def initialize(opts = {})
9 | @options = opts
10 | @path_or_io = @options[:output] || DEFAULT_PATH
11 | end
12 |
13 | def finish
14 | write_output(MetricFu.result.as_yaml, @path_or_io)
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/fixtures/saikuro/app/controllers/sessions_controller.rb_cyclo.html:
--------------------------------------------------------------------------------
1 | -- START --
2 | Type:Global Name: Complexity:0 Lines:1
3 | -- END --
4 | -- START SessionsController --
5 | Type:Class Name:SessionsController Complexity:6 Lines:40
6 | Type:Def Name:new Complexity:2 Lines:2
7 | Type:Def Name:create Complexity:2 Lines:19
8 | Type:Def Name:destroy Complexity:1 Lines:4
9 | Type:Def Name:note_failed_signin Complexity:1 Lines:3
10 | -- END SessionsController --
11 |
--------------------------------------------------------------------------------
/spec/fixtures/hotspots/reek.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :reek:
3 | :matches:
4 | - :file_path: lib/client/client.rb
5 | :code_smells:
6 | - :type: Large Class
7 | :message: has at least 27 methods
8 | :method: Devver::Client
9 | - :type: Long Method
10 | :message: has approx 6 statements
11 | :method: Devver::Client#client_requested_sync
12 | - :type: Large Class
13 | :message: has at least 20 methods
14 | :method: Devver::Foo
15 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/flay/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for flay" do
5 | it_behaves_like "configured" do
6 | it "should set @flay to {:dirs_to_flay => @code_dirs}" do
7 | load_metric "flay"
8 | expect(MetricFu::Metric.get_metric(:flay).run_options).to eq(
9 | dirs_to_flay: ["lib"], minimum_score: nil
10 | )
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/reek/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for reek" do
5 | it_behaves_like "configured" do
6 | it "should set @reek to {:dirs_to_reek => @code_dirs}" do
7 | load_metric "reek"
8 | expect(MetricFu::Metric.get_metric(:reek).run_options).to eq(
9 | config_file_pattern: nil, dirs_to_reek: ["lib"]
10 | )
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | bundler_args: --path vendor/bundle --jobs=3 --retry=3
3 | script: bundle exec rspec
4 | cache: bundler
5 |
6 | before_install:
7 | - gem install bundler
8 | - bundle config --local without local_development yard guard
9 | rvm:
10 | # 2.1, not 2.1.0 until fixed https://github.com/travis-ci/travis-ci/issues/2220
11 | - 2.5
12 | - 2.6
13 | - 2.7
14 | - 3.0
15 | - jruby
16 | matrix:
17 | allow_failures:
18 | - rvm: jruby
19 | fast_finish: true
20 |
--------------------------------------------------------------------------------
/spec/metric_fu/utility_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "metric_fu/utility"
3 |
4 | describe MetricFu::Utility do
5 | it "strips ANSI escape codes from text" do
6 | text = "\e[31m./app/models/account.rb:64 - Found = in conditional. It should probably be an ==\e[0m"
7 | output = "./app/models/account.rb:64 - Found = in conditional. It should probably be an =="
8 |
9 | result = MetricFu::Utility.strip_escape_codes(text)
10 | expect(result).to eq(output)
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/reek/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricReek < Metric
3 | def name
4 | :reek
5 | end
6 |
7 | def default_run_options
8 | {
9 | dirs_to_reek: MetricFu::Io::FileSystem.directory("code_dirs"),
10 | config_file_pattern: nil,
11 | }
12 | end
13 |
14 | def has_graph?
15 | true
16 | end
17 |
18 | def enable
19 | super
20 | end
21 |
22 | def activate
23 | super
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/javascripts/bluff_graph.js:
--------------------------------------------------------------------------------
1 | createGraphElement("canvas");
2 |
3 | var chart = new Bluff.Line("graph", "1000x600");
4 | chart.theme_37signals();
5 | chart.tooltips = true;
6 | chart.title_font_size = "24px";
7 | chart.legend_font_size = "12px";
8 | chart.marker_font_size = "10px";
9 | chart.title = graph_title;
10 | for(var i = 0; i < graph_series.length; i++) {
11 | var serie = graph_series[i];
12 | chart.data(serie.name, serie.data);
13 | }
14 | chart.labels = graph_labels;
15 | chart.draw();
16 |
--------------------------------------------------------------------------------
/spec/metric_fu/gem_version_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.lib_require { "gem_version" }
3 |
4 | describe MetricFu::GemVersion do
5 | it "has a list of gem deps" do
6 | gem_version = MetricFu::GemVersion.new
7 | gem_deps = gem_version.gem_runtime_dependencies.map(&:name)
8 | MetricFu::Metric.metrics.reject { |metric| metric.name == :hotspots || metric.name == :stats }.map(&:name).map(&:to_s).each do |metric_name|
9 | expect(gem_deps).to include(metric_name)
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/churn/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for churn" do
5 | it_behaves_like "configured" do
6 | it "should set @churn to {}" do
7 | load_metric "churn"
8 | expect(MetricFu::Metric.get_metric(:churn).run_options).to eq(
9 | start_date: '"1 year ago"', minimum_churn_count: 10, ignore_files: [], data_directory: MetricFu::Io::FileSystem.scratch_directory("churn")
10 | )
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/roodi/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricRoodi < Metric
3 | def name
4 | :roodi
5 | end
6 |
7 | def default_run_options
8 | { dirs_to_roodi: MetricFu::Io::FileSystem.directory("code_dirs"),
9 | roodi_config: "#{MetricFu::Io::FileSystem.directory('root_directory')}/config/roodi_config.yml" }
10 | end
11 |
12 | def has_graph?
13 | true
14 | end
15 |
16 | def enable
17 | super
18 | end
19 |
20 | def activate
21 | super
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/fixtures/hotspots/roodi.yml:
--------------------------------------------------------------------------------
1 | :roodi:
2 | :total:
3 | - Found 164 errors.
4 | :problems:
5 | - :line: "158"
6 | :file: lib/client/client.rb
7 | :problem: Method name "process" cyclomatic complexity is 10. It should be 8 or less.
8 | - :line: "232"
9 | :file: lib/client/client.rb
10 | :problem: Method name "process_ready" cyclomatic complexity is 15. It should be 8 or less.
11 | - :line: "288"
12 | :file: lib/client/foobar.rb
13 | :problem: Method name "send_tests" cyclomatic complexity is 10. It should be 8 or less.
14 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/roodi/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for roodi" do
5 | it_behaves_like "configured" do
6 | it "should set @roodi to {:dirs_to_roodi => @code_dirs}" do
7 | load_metric "roodi"
8 | expect(MetricFu::Metric.get_metric(:roodi).run_options).to eq(
9 | dirs_to_roodi: directory("code_dirs"),
10 | roodi_config: "#{directory('root_directory')}/config/roodi_config.yml"
11 | )
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/hotspots/hotspot_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "hotspots/hotspot" }
3 |
4 | describe MetricFu::Hotspot do
5 | before do
6 | enable_hotspots
7 | end
8 |
9 | it "returns an array of of the analyzers that subclass it" do
10 | expected_analyzers = [ReekHotspot, RoodiHotspot,
11 | FlogHotspot, ChurnHotspot, SaikuroHotspot,
12 | FlayHotspot, StatsHotspot, RcovHotspot]
13 |
14 | expect(MetricFu::Hotspot.analyzers.size).to eq(expected_analyzers.size)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/flog/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for flog" do
5 | it_behaves_like "configured" do
6 | if MetricFu.configuration.mri?
7 | it "should set @flog to {:dirs_to_flog => @code_dirs}" do
8 | load_metric "flog"
9 | expect(MetricFu::Metric.get_metric(:flog).run_options).to eq(
10 | all: true,
11 | continue: true,
12 | dirs_to_flog: ["lib"],
13 | quiet: true
14 | )
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/css/rcov.css:
--------------------------------------------------------------------------------
1 | .rcov_code td {
2 | border-bottom: 1px solid #ddd ;
3 | padding: 0;
4 | margin: 0;
5 | }
6 | .rcov_code tr {
7 | border: 0px;
8 | padding:0px;
9 | margin: 0px;
10 | }
11 | .rcov_code pre {
12 | border: 0px;
13 | padding: 0px;
14 | margin: 0px;
15 | }
16 | .rcov_run {}
17 | .rcov_not_run {
18 | background-color: #d88;
19 | }
20 | .rcov_run a, .rcov_not_run a {
21 | text-decoration: none;
22 | }
23 | .rcov_run a {
24 | color: #333;
25 | }
26 | .rcov_not_run a {
27 | color: #000;
28 | }
29 | .rcov_overflow {
30 | overflow: auto;
31 | font-size: 50%;
32 | }
33 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rcov/external_client.rb:
--------------------------------------------------------------------------------
1 | # Reads and writes external coverage files as BINARY
2 | module MetricFu
3 | class RCovTestCoverageClient
4 | def initialize(coverage_file)
5 | @file_path = Pathname(coverage_file)
6 | @file_path.dirname.mkpath
7 | end
8 |
9 | def post_results(payload)
10 | mf_log "Saving coverage payload to #{@file_path}"
11 | dump(payload)
12 | end
13 |
14 | def load
15 | File.binread(@file_path)
16 | end
17 |
18 | def dump(payload)
19 | File.open(@file_path, "wb") { |file| file.write(payload) }
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/gem_tasks/yard.rake:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | begin
4 | require "yard"
5 | rescue LoadError
6 | else
7 | namespace :yard do
8 | YARD::Rake::YardocTask.new(:doc) do |t|
9 | t.stats_options = ["--list-undoc"]
10 | end
11 |
12 | desc "start a gem server"
13 | task :server do
14 | sh "bundle exec yard server --gems"
15 | end
16 |
17 | desc "use Graphviz to generate dot graph"
18 | task :graph do
19 | output_file = "doc/erd.dot"
20 | sh "bundle exec yard graph --protected --full --dependencies > #{output_file}"
21 | puts "open doc/erd.dot if you have graphviz installed"
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/churn/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricChurn < Metric
3 | def name
4 | :churn
5 | end
6 |
7 | def default_run_options
8 | {
9 | start_date: '"1 year ago"',
10 | minimum_churn_count: 10,
11 | ignore_files: [],
12 | data_directory: MetricFu::Io::FileSystem.scratch_directory(name)
13 | }
14 | end
15 |
16 | def has_graph?
17 | false
18 | end
19 |
20 | def enable
21 | super
22 | end
23 |
24 | def activate
25 | activate_library("churn/calculator")
26 | super
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/.github/workflows/ruby.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | test:
11 |
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | ruby-version: ['2.5', '2.6', '2.7', '3.0']
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up Ruby
20 | uses: ruby/setup-ruby@v1
21 | with:
22 | ruby-version: ${{ matrix.ruby-version }}
23 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically
24 | - name: Run tests
25 | run: bundle exec rake
26 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/analysis/problems.rb:
--------------------------------------------------------------------------------
1 | MetricFu.metrics_require { "hotspots/analysis/groupings" }
2 | module MetricFu
3 | class HotspotProblems
4 | def initialize(sub_table)
5 | @grouping = group_by(sub_table, "metric")
6 | end
7 |
8 | def problems
9 | problems = {}
10 | @grouping.each do |metric, table|
11 | problems[metric] = MetricFu::Hotspot.analyzer_for_metric(metric).present_group(table)
12 | end
13 | problems
14 | end
15 |
16 | def group_by(sub_table, by = "metric")
17 | MetricFu::HotspotGroupings.new(sub_table, by: by).get_grouping
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/analysis/scoring_strategies.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | module HotspotScoringStrategies
3 | module_function
4 |
5 | # per project score percentile
6 | def percentile(ranking, item)
7 | ranking.percentile(item)
8 | end
9 |
10 | # Use the score you got
11 | # (ex flog score of 20 is not bad even if it is the top one in project)
12 | def identity(ranking, item)
13 | ranking.fetch(item)
14 | end
15 |
16 | def sum(scores)
17 | scores.inject(&:+)
18 | end
19 |
20 | def average(scores)
21 | sum(scores).to_f / scores.size.to_f
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/config/roodi_config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | CaseMissingElseCheck: {}
3 | ClassLineCountCheck:
4 | line_count: 300
5 | ClassNameCheck:
6 | pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
7 | CyclomaticComplexityBlockCheck:
8 | complexity: 4
9 | CyclomaticComplexityMethodCheck:
10 | complexity: 8
11 | EmptyRescueBodyCheck: {}
12 | ForLoopCheck: {}
13 | MethodLineCountCheck:
14 | line_count: 20
15 | MethodNameCheck:
16 | pattern: !ruby/regexp /^[_a-z<>=\[\]|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
17 | ModuleLineCountCheck:
18 | line_count: 300
19 | ModuleNameCheck:
20 | pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
21 | ParameterNumberCheck:
22 | parameter_count: 5
23 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/analysis/grouping.rb:
--------------------------------------------------------------------------------
1 | %w(table).each do |path|
2 | MetricFu.metrics_require { "hotspots/analysis/#{path}" }
3 | end
4 | module MetricFu
5 | class Grouping
6 | def initialize(table, opts)
7 | column_name = opts.fetch(:by)
8 | hash = {}
9 | if column_name.to_sym == :metric # special optimized case
10 | hash = table.group_by_metric
11 | else
12 | raise "Unexpected column_name #{column_name}"
13 | end
14 | @arr = hash.to_a
15 | end
16 |
17 | def each
18 | @arr.each do |value, rows|
19 | yield value, rows
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/metric_fu/cli/client.rb:
--------------------------------------------------------------------------------
1 | require "metric_fu"
2 | require "metric_fu/cli/helper"
3 | require "metric_fu/cli/parser"
4 | module MetricFu
5 | module Cli
6 | class Client
7 | def initialize
8 | @helper = MetricFu::Cli::Helper.new
9 | end
10 |
11 | def shutdown
12 | @helper.shutdown
13 | end
14 |
15 | def run(argv = ARGV.dup)
16 | options = @helper.process_options(argv)
17 | mf_debug "Got options #{options.inspect}"
18 | if options[:run]
19 | @helper.run(options)
20 | else
21 | STDOUT.puts @helper.usage
22 | end
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/flay/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricFlay < Metric
3 | def name
4 | :flay
5 | end
6 |
7 | def default_run_options
8 | { dirs_to_flay: MetricFu::Io::FileSystem.directory("code_dirs"),
9 | # MetricFu has been setting the minimum score as 100 for
10 | # a long time. This is a really big number, considering
11 | # the default is 16. Setting it to nil to use the Flay default.
12 | minimum_score: nil,
13 | }
14 | end
15 |
16 | def has_graph?
17 | true
18 | end
19 |
20 | def enable
21 | super
22 | end
23 |
24 | def activate
25 | super
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/saikuro/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricSaikuro < Metric
3 | def name
4 | :saikuro
5 | end
6 |
7 | def default_run_options
8 | {
9 | output_directory: MetricFu::Io::FileSystem.scratch_directory(name),
10 | input_directory: MetricFu::Io::FileSystem.directory("code_dirs"),
11 | cyclo: "",
12 | filter_cyclo: "0",
13 | warn_cyclo: "5",
14 | error_cyclo: "7",
15 | formater: "text",
16 | }
17 | end
18 |
19 | def has_graph?
20 | false
21 | end
22 |
23 | def enable
24 | super
25 | end
26 |
27 | def activate
28 | super
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/flog/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricFlog < Metric
3 | def name
4 | :flog
5 | end
6 |
7 | def default_run_options
8 | { dirs_to_flog: MetricFu::Io::FileSystem.directory("code_dirs"), continue: true, all: true, quiet: true }
9 | end
10 |
11 | def has_graph?
12 | true
13 | end
14 |
15 | def enable
16 | if MetricFu.configuration.mri?
17 | super
18 | else
19 | MetricFu.logger.debug("Flog is only available in MRI due to flog tasks")
20 | end
21 | end
22 |
23 | def activate
24 | activate_library "flog"
25 | activate_library "flog_cli"
26 | super
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/stats/hotspot.rb:
--------------------------------------------------------------------------------
1 | class MetricFu::StatsHotspot < MetricFu::Hotspot
2 | COLUMNS = %w{stat_name stat_value}
3 |
4 | def columns
5 | COLUMNS
6 | end
7 |
8 | def name
9 | :stats
10 | end
11 |
12 | def map_strategy
13 | :absent
14 | end
15 |
16 | def reduce_strategy
17 | :absent
18 | end
19 |
20 | def score_strategy
21 | :absent
22 | end
23 |
24 | def generate_records(data, table)
25 | return if data == nil
26 | data.each do |key, value|
27 | next if value.is_a?(Array)
28 | table << {
29 | "metric" => name,
30 | "stat_name" => key,
31 | "stat_value" => value
32 | }
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/rcov/hotspot_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "hotspots/metric" }
3 | MetricFu.metrics_require { "hotspots/hotspot" }
4 | MetricFu.metrics_require { "hotspots/analysis/record" }
5 | MetricFu.metrics_require { "rcov/hotspot" }
6 |
7 | describe MetricFu::RcovHotspot do
8 | describe "map" do
9 | let(:zero_row) do
10 | MetricFu::Record.new({ "percentage_uncovered" => 0.0 }, nil)
11 | end
12 |
13 | let(:non_zero_row) do
14 | MetricFu::Record.new({ "percentage_uncovered" => 0.75 }, nil)
15 | end
16 |
17 | it { expect(subject.map(zero_row)).to eql(0.0) }
18 | it { expect(subject.map(non_zero_row)).to eql(0.75) }
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/analysis/record.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class Record
3 | attr_reader :data
4 |
5 | def initialize(data, _columns)
6 | @data = data
7 | end
8 |
9 | def method_missing(name, *args, &block)
10 | key = name.to_s
11 | if key == "fetch"
12 | @data.send(name, *args, &block)
13 | elsif @data.has_key?(key)
14 | @data[key]
15 | else
16 | super(name, *args, &block)
17 | end
18 | end
19 |
20 | def []=(key, value)
21 | @data[key] = value
22 | end
23 |
24 | def [](key)
25 | @data[key]
26 | end
27 |
28 | def has_key?(key)
29 | @data.has_key?(key)
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/spec/metric_fu/templates/report_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.lib_require { "templates/report" }
3 |
4 | describe MetricFu::Templates::Report do
5 | # TODO: This test only shows how the code works and that it doesn't blow up.
6 | # Perhaps it should test something more specific?
7 | it "Reads in a source file, and produces an annotated HTML report" do
8 | lines = { "2" => [{ type: :reek, description: "Bad Param Names" }] }
9 | source_file = File.join(MetricFu.root_dir, "spec", "dummy", "lib", "bad_encoding.rb")
10 | report = MetricFu::Templates::Report.new(source_file, lines)
11 | expect {
12 | rendered_report = report.render
13 | }.not_to raise_error
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/gem_tasks/usage_test.rake:
--------------------------------------------------------------------------------
1 | ROOT_PATH = File.expand_path("..", File.dirname(__FILE__))
2 | require File.join(ROOT_PATH, 'spec', 'support', 'usage_test')
3 | LIB_PATH = File.join(ROOT_PATH, 'lib')
4 | BIN_PATH = File.join(ROOT_PATH, 'bin')
5 | EXAMPLE_FILES = [
6 | File.join(ROOT_PATH, 'README.md'),
7 | File.join(ROOT_PATH, 'DEV.md')
8 | ]
9 | task "load_path" do
10 | $LOAD_PATH.unshift(LIB_PATH)
11 | $VERBOSE = nil
12 | ENV['PATH'] = "#{BIN_PATH}:#{ENV['PATH']}"
13 | ENV['CC_BUILD_ARTIFACTS'] = 'turn_off_browser_opening'
14 | end
15 | desc "Test that documentation usage works"
16 | task "usage_test" => %w[load_path] do
17 | usage_test = UsageTest.new
18 | usage_test.test_files(EXAMPLE_FILES)
19 | end
20 |
--------------------------------------------------------------------------------
/spec/support/suite.rb:
--------------------------------------------------------------------------------
1 | require "fileutils"
2 | def directory(name)
3 | MetricFu::Io::FileSystem.directory(name)
4 | end
5 |
6 | def scratch_directory(name)
7 | File.join(MetricFu::Io::FileSystem.artifact_dir, "scratch", name)
8 | end
9 |
10 | def artifact_test_dir
11 | File.join(MetricFu::APP_ROOT, "tmp", "metric_fu", "test")
12 | end
13 |
14 | # Let's shift the output directories so that we don't interfere with
15 | # existing historical metric data.
16 | MetricFu::Io::FileSystem.artifact_dir = artifact_test_dir
17 | MetricFu::Io::FileSystem.set_directories
18 |
19 | def setup_fs
20 | cleanup_fs
21 | MetricFu::Io::FileSystem.set_directories
22 | end
23 |
24 | def cleanup_fs
25 | FileUtils.rm_rf(artifact_test_dir)
26 | end
27 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | image:
3 | - Visual Studio 2019
4 |
5 | skip_tags: true
6 |
7 | environment:
8 | matrix:
9 | - RUBY_VERSION: 25
10 | - RUBY_VERSION: 25-x64
11 | - RUBY_VERSION: 26
12 | - RUBY_VERSION: 26-x64
13 | - RUBY_VERSION: 27
14 | - RUBY_VERSION: 27-x64
15 |
16 | install:
17 | - set PATH=C:\Ruby%RUBY_VERSION%\bin;%PATH%
18 | - gem install bundler
19 | - bundle config force_ruby_platform true
20 | - bundle install --path=vendor/bundle --retry=3 --jobs=3
21 |
22 | cache:
23 | - vendor/bundle
24 |
25 | build: off
26 |
27 | before_test:
28 | - ruby -v
29 | - gem -v
30 | - bundle -v
31 |
32 | test_script:
33 | - bundle exec rspec
34 | - bundle exec ruby -Ilib bin/metric_fu --no-open
35 |
--------------------------------------------------------------------------------
/spec/fixtures/hotspots/saikuro.yml:
--------------------------------------------------------------------------------
1 | :saikuro:
2 | :files:
3 | - :classes:
4 | - :complexity: 0
5 | :methods: []
6 | :lines: 3
7 | :class_name: Shorty
8 | - :complexity: 19
9 | :methods:
10 | - :complexity: 9
11 | :lines: 6
12 | :name: Shorty::Supr#self.handle_full_or_hash_option
13 | - :complexity: 1
14 | :lines: 9
15 | :name: Shorty::Supr#initialize
16 | :lines: 92
17 | :class_name: Shorty::Supr
18 | :filename: supr.rb
19 | - :classes:
20 | - :complexity: 12
21 | :methods:
22 | - :complexity: 8
23 | :lines: 10
24 | :name: Shorty::Bitly#info
25 | :lines: 104
26 | :class_name: Shorty::Bitly
27 | :filename: bitly.rb
28 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rails_best_practices/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricRailsBestPractices < Metric
3 | def name
4 | :rails_best_practices
5 | end
6 |
7 | def default_run_options
8 | {
9 | silent: true,
10 | exclude: []
11 | }
12 | end
13 |
14 | def has_graph?
15 | true
16 | end
17 |
18 | def enable
19 | if MetricFu.configuration.supports_ripper?
20 | super if MetricFu.configuration.rails?
21 | else
22 | MetricFu.logger.debug("Rails Best Practices is only available in MRI 1.9. It requires ripper")
23 | end
24 | end
25 |
26 | def activate
27 | activate_library("rails_best_practices")
28 | super
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/roodi/report.html.erb:
--------------------------------------------------------------------------------
1 | Roodi Results
2 |
3 | Roodi parses your Ruby code and warns you about design issues you have based on the checks that is has configured.
4 |
5 | <%= render_partial 'graph', {:graph_name => 'roodi'} %>
6 |
7 |
8 |
9 | | File Path |
10 | Warning |
11 |
12 | <% count = 0 %>
13 | <% @roodi[:problems].each do |problem| %>
14 |
15 | | <%= link_to_filename(problem[:file], problem[:line]) %> |
16 | <%= problem[:problem] %> |
17 |
18 | <% count += 1 %>
19 | <% end %>
20 |
21 |
22 | <%= render_partial 'report_footer' %>
23 |
--------------------------------------------------------------------------------
/etc/README.md:
--------------------------------------------------------------------------------
1 | # Scripts
2 |
3 | `git clone https://gist.github.com/7209165.git scripts`
4 |
5 | Generate list of contributors in descending number of code changes
6 |
7 | ```sh
8 | ruby scripts/git-rank-contributors.rb > CONTRIBUTORS
9 | ```
10 |
11 | Generate entity diagram (gotta ensure all the code runs)
12 |
13 | ```sh
14 | ruby -Ilib -rmetric_fu -rmetric_fu/cli/client -rmetric_fu/metrics/rcov/simplecov_formatter -e "cli = MetricFu::Cli::Client.new; begin; MetricFu.configuration.configure_metric(:rcov){|rcov| rcov.coverage_file = MetricFu.run_path.join('coverage/rcov/rcov.txt'); rcov.enable; rcov.activate}; cli.run; rescue SystemExit; end; ARGV.clear; ARGV.concat(%w[metric_fu MetricFu SimpleCov::Formatter::MetricFu]); load 'scripts/erd.rb'" && mv erd.* etc/
15 | ```
16 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/css/syntax.css:
--------------------------------------------------------------------------------
1 | table { background: #fff; color: #000; }
2 | .ruby .normal { color: #000; }
3 | .ruby .comment { color: #005; font-style: italic; }
4 | .ruby .keyword { color: #A44; font-weight: bold; }
5 | .ruby .method { color: #44f; }
6 | .ruby .class { color: #b1713d; }
7 | .ruby .module { color: #050; }
8 | .ruby .punct { color: #668; font-weight: bold; }
9 | .ruby .symbol { color: #00f; }
10 | .ruby .string { color: #4a4; }
11 | .ruby .char { color: #F07; }
12 | .ruby .ident { color: #000; }
13 | .ruby .constant { color: #b1713d; }
14 | .ruby .regex { color: #B66; background: #FEF; }
15 | .ruby .number { color: #F99; }
16 | .ruby .attribute { color: #f84; }
17 | .ruby .global { color: #7FB; }
18 | .ruby .expr { color: #227; }
19 | .ruby .escape { color: #277; }
--------------------------------------------------------------------------------
/spec/metric_fu/calculate_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.lib_require { "calculate" }
3 |
4 | describe MetricFu::Calculate do
5 | describe "returns a percent rounded to the nearest integer" do
6 | specify "3 / 10 == 30" do
7 | expect(MetricFu::Calculate.integer_percent(3, 10)).to eq(30)
8 | end
9 | specify "3.0 / 10 == 30" do
10 | expect(MetricFu::Calculate.integer_percent(3.0, 10)).to eq(30)
11 | end
12 | it "raises an ArgumentError on non-numeric input" do
13 | expect {
14 | MetricFu::Calculate.integer_percent("", 10)
15 | }.to raise_error(ArgumentError)
16 | end
17 | it "returns 0 when the denominator is 0" do
18 | expect(MetricFu::Calculate.integer_percent(3, 0)).to eq(0)
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/analysis/ranking.rb:
--------------------------------------------------------------------------------
1 | require "forwardable"
2 | module MetricFu
3 | class Ranking
4 | extend Forwardable
5 |
6 | def initialize
7 | @items_to_score = {}
8 | end
9 |
10 | def top
11 | sorted_items
12 | end
13 |
14 | def percentile(item)
15 | index = sorted_items.index(item)
16 | worse_item_count = (length - (index + 1))
17 | worse_item_count.to_f / length
18 | end
19 |
20 | def_delegator :@items_to_score, :has_key?, :scored?
21 | def_delegators :@items_to_score, :[], :[]=, :length, :each, :delete, :fetch
22 |
23 | private
24 |
25 | def sorted_items
26 | @sorted_items ||= @items_to_score.sort_by { |_item, score| -score }.map { |item, _score| item }
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/css/default.css:
--------------------------------------------------------------------------------
1 | table {
2 | margin-top: 20px;
3 | border-collapse: collapse;
4 | border: 1px solid #666;
5 | background: #fff;
6 | margin-bottom: 20px;
7 | }
8 |
9 | table tr.light {
10 | background: #fff;
11 | }
12 |
13 | table tr.dark {
14 | background: #f9f9f9;
15 | }
16 |
17 | table tr:hover {
18 | background: #FFFFC0;
19 | }
20 |
21 | table td, table th {
22 | padding: 4px;
23 | font-size: 11px;
24 | }
25 | table th {
26 | text-align: center;
27 | color: #337022;
28 | background: #DDFFCC;
29 | font-weight: bold;
30 | border: #99D688 1px solid;
31 | }
32 |
33 | table td {
34 | border: #d0d0d0 1px solid;
35 | }
36 |
37 | table td.score {
38 | text-align: right;
39 | }
40 |
41 | .warning {
42 | background: yellow;
43 | }
44 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/cane/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for cane" do
5 | it_behaves_like "configured" do
6 | if MetricFu.configuration.mri?
7 | it "should set @cane to " +
8 | ':dirs_to_cane => @code_dirs, :abc_max => 15, :line_length => 80, :no_doc => "n", :no_readme => "y"' do
9 | load_metric "cane"
10 | expect(MetricFu::Metric.get_metric(:cane).run_options).to eq(
11 |
12 | dirs_to_cane: directory("code_dirs"),
13 | filetypes: ["rb"],
14 | abc_max: 15,
15 | line_length: 80,
16 | no_doc: "n",
17 | no_readme: "n"
18 | )
19 | end
20 | end
21 | end # end it_behaves
22 | end
23 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/stats/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricStats < Metric
3 | def name
4 | :stats
5 | end
6 |
7 | def default_run_options
8 | {
9 | # returns a list of directories that contains the glob of files that have the file_pattern in the file names
10 | additional_test_directories: [{ glob_pattern: File.join(".", "spec", "**", "*_spec.rb"), file_pattern: "spec" }],
11 | additional_app_directories: [{ glob_pattern: File.join(".", "engines", "**", "*.rb"), file_pattern: "" }],
12 | }
13 | end
14 |
15 | def has_graph?
16 | true
17 | end
18 |
19 | def enable
20 | super
21 | end
22 |
23 | def activate
24 | activate_library "code_metrics"
25 | super
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rails_best_practices/report.html.erb:
--------------------------------------------------------------------------------
1 | Rails Best Practices Results
2 |
3 | rails_best_practices is a code metric tool for rails projects.
4 |
5 | <%= render_partial 'graph', {:graph_name => 'rails_best_practices'} %>
6 |
7 |
8 |
9 | | File Path |
10 | Warning |
11 |
12 | <% count = 0 %>
13 | <% @rails_best_practices[:problems].each do |problem| %>
14 |
15 | | <%= link_to_filename(problem[:file], problem[:line]) %> |
16 | ><%= problem[:problem] %> |
17 |
18 | <% count += 1 %>
19 | <% end %>
20 |
21 |
22 | <%= render_partial 'report_footer' %>
23 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/roodi/hotspot.rb:
--------------------------------------------------------------------------------
1 | class MetricFu::RoodiHotspot < MetricFu::Hotspot
2 | COLUMNS = %w{problems}
3 |
4 | def columns
5 | COLUMNS
6 | end
7 |
8 | def name
9 | :roodi
10 | end
11 |
12 | def map_strategy
13 | :present
14 | end
15 |
16 | def reduce_strategy
17 | :sum
18 | end
19 |
20 | def score_strategy
21 | :percentile
22 | end
23 |
24 | def generate_records(data, table)
25 | return if data == nil
26 | Array(data[:problems]).each do |problem|
27 | table << {
28 | "metric" => name,
29 | "problems" => problem[:problem],
30 | "file_path" => problem[:file]
31 | }
32 | end
33 | end
34 |
35 | def present_group(group)
36 | occurences = group.size
37 | "found #{occurences} design problems"
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/fixtures/saikuro/app/controllers/users_controller.rb_cyclo.html:
--------------------------------------------------------------------------------
1 | -- START UsersController --
2 | Type:Class Name:UsersController Complexity:25 Lines:111
3 | Type:Def Name:signup Complexity:1 Lines:3
4 | Type:Def Name:new Complexity:1 Lines:2
5 | Type:Def Name:edit Complexity:1 Lines:3
6 | Type:Def Name:update Complexity:2 Lines:8
7 | Type:Def Name:index Complexity:1 Lines:2
8 | Type:Def Name:create Complexity:4 Lines:15
9 | Type:Def Name:thank_you Complexity:2 Lines:10
10 | Type:Def Name:destroy Complexity:1 Lines:3
11 | Type:Def Name:add_primary_site Complexity:1 Lines:4
12 | Type:Def Name:users_have_changed Complexity:3 Lines:8
13 | Type:Def Name:after_create_page Complexity:2 Lines:6
14 | Type:Def Name:sanitize_params Complexity:3 Lines:3
15 | Type:Def Name:authorize_user Complexity:3 Lines:5
16 | -- END UsersController --
17 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/flay/grapher.rb:
--------------------------------------------------------------------------------
1 | MetricFu.reporting_require { "graphs/grapher" }
2 | module MetricFu
3 | class FlayGrapher < Grapher
4 | attr_accessor :flay_score, :labels
5 |
6 | def self.metric
7 | :flay
8 | end
9 |
10 | def initialize
11 | super
12 | @flay_score = []
13 | @labels = {}
14 | end
15 |
16 | def get_metrics(metrics, date)
17 | if metrics && metrics[:flay]
18 | @flay_score.push(metrics[:flay][:total_score].to_i)
19 | @labels.update(@labels.size => date)
20 | end
21 | end
22 |
23 | def title
24 | "Flay: duplication"
25 | end
26 |
27 | def data
28 | [
29 | ["flay", @flay_score.join(",")]
30 | ]
31 | end
32 |
33 | def output_filename
34 | "flay.js"
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/metric_fu/logging/mf_debugger.rb:
--------------------------------------------------------------------------------
1 | warn "MfDebugger if deprecated. Please use MetricFu.logger"
2 | MetricFu.lib_require { "logger" }
3 | module MfDebugger
4 | extend self
5 | class Logger
6 | def self.debug_on
7 | warn "MfDebugger if deprecated. Please use MetricFu.logger"
8 | MetricFu.logger.debug_on
9 | end
10 | def self.debug_on=(bool)
11 | warn "MfDebugger if deprecated. Please use MetricFu.logger"
12 | MetricFu.logger.level = bool ? "debug" : "info"
13 | end
14 | def self.log(msg, &_block)
15 | warn "MfDebugger if deprecated. Please use MetricFu.logger"
16 | MetricFu.logger.info msg
17 | end
18 | def self.debug(msg, &_block)
19 | warn "MfDebugger if deprecated. Please use MetricFu.logger"
20 | MetricFu.logger.debug msg
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/cane/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricCane < Metric
3 | def name
4 | :cane
5 | end
6 |
7 | def default_run_options
8 | {
9 | dirs_to_cane: MetricFu::Io::FileSystem.directory("code_dirs"),
10 | abc_max: 15,
11 | line_length: 80,
12 | no_doc: "n",
13 | no_readme: "n",
14 | filetypes: ["rb"]
15 | }
16 | end
17 |
18 | def has_graph?
19 | true
20 | end
21 |
22 | def enable
23 | if MetricFu.configuration.supports_ripper? && !MetricFu.configuration.ruby18?
24 | super
25 | else
26 | MetricFu.logger.debug("Cane is only available in MRI. It requires ripper and 1.9 hash syntax support")
27 | end
28 | end
29 |
30 | def activate
31 | super
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/rcov/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for rcov" do
5 | it_behaves_like "configured" do
6 | it "should set rcov run_options" do
7 | load_metric "rcov"
8 | expect(
9 | MetricFu::Metric.get_metric(:rcov).run_options
10 | ).to eq(
11 |
12 | environment: "test",
13 | external: nil,
14 | test_files: Dir["{spec,test}/**/*_{spec,test}.rb"],
15 | rcov_opts: [
16 | "--sort coverage",
17 | "--no-html",
18 | "--text-coverage",
19 | "--no-color",
20 | "--profile",
21 | "--exclude-only '.*'",
22 | '--include-file "\Aapp,\Alib"',
23 | "-Ispec"
24 | ],
25 | )
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/roodi/grapher.rb:
--------------------------------------------------------------------------------
1 | MetricFu.reporting_require { "graphs/grapher" }
2 | module MetricFu
3 | class RoodiGrapher < Grapher
4 | attr_accessor :roodi_count, :labels
5 |
6 | def self.metric
7 | :roodi
8 | end
9 |
10 | def initialize
11 | super
12 | @roodi_count = []
13 | @labels = {}
14 | end
15 |
16 | def get_metrics(metrics, date)
17 | if metrics && metrics[:roodi]
18 | @roodi_count.push(metrics[:roodi][:problems].size)
19 | @labels.update(@labels.size => date)
20 | end
21 | end
22 |
23 | def title
24 | "Roodi: design problems"
25 | end
26 |
27 | def data
28 | [
29 | ["roodi", @roodi_count.join(",")]
30 | ]
31 | end
32 |
33 | def output_filename
34 | "roodi.js"
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rcov/grapher.rb:
--------------------------------------------------------------------------------
1 | MetricFu.reporting_require { "graphs/grapher" }
2 | module MetricFu
3 | class RcovGrapher < Grapher
4 | attr_accessor :rcov_percent, :labels
5 |
6 | def self.metric
7 | :rcov
8 | end
9 |
10 | def initialize
11 | super
12 | self.rcov_percent = []
13 | self.labels = {}
14 | end
15 |
16 | def get_metrics(metrics, date)
17 | if metrics && metrics[:rcov]
18 | rcov_percent.push(metrics[:rcov][:global_percent_run])
19 | labels.update(labels.size => date)
20 | end
21 | end
22 |
23 | def title
24 | "Rcov: code coverage"
25 | end
26 |
27 | def data
28 | [
29 | ["rcov", @rcov_percent.join(",")]
30 | ]
31 | end
32 |
33 | def output_filename
34 | "rcov.js"
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/metric_fu/reporter.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class Reporter
3 | def initialize(formatters = nil)
4 | @formatters = Array(formatters)
5 | end
6 |
7 | def start
8 | notify :start
9 | end
10 |
11 | def finish
12 | notify :finish
13 | end
14 |
15 | def start_metric(metric)
16 | mf_log "** STARTING METRIC #{metric}"
17 | notify :start_metric, metric
18 | end
19 |
20 | def finish_metric(metric)
21 | mf_log "** ENDING METRIC #{metric}"
22 | notify :finish_metric, metric
23 | end
24 |
25 | def display_results
26 | notify :display_results
27 | end
28 |
29 | protected
30 |
31 | def notify(event, *args)
32 | @formatters.each do |formatter|
33 | formatter.send(event, *args) if formatter.respond_to?(event)
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | using_git = File.exist?(File.expand_path('../.git/', __FILE__))
3 | if using_git
4 | require 'bundler/setup'
5 | end
6 | require 'rake'
7 | require 'simplecov'
8 |
9 | Dir['./gem_tasks/*.rake'].each do |task|
10 | import(task)
11 | end
12 |
13 | require 'rspec/core/rake_task'
14 | desc "Run all specs in spec directory"
15 | RSpec::Core::RakeTask.new(:spec) do |t|
16 | t.verbose = false
17 |
18 | t.pattern = "spec/**/*_spec.rb"
19 | # we require spec_helper so we don't get an RSpec warning about
20 | # examples being defined before configuration.
21 | t.ruby_opts = "-I./spec -r./spec/capture_warnings -rspec_helper"
22 | t.rspec_opts = %w[--format progress] if (ENV['FULL_BUILD'] || !using_git)
23 | end
24 |
25 | require File.expand_path File.join(File.dirname(__FILE__),'lib/metric_fu')
26 |
27 | task :default => :spec
28 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/cane/grapher.rb:
--------------------------------------------------------------------------------
1 | MetricFu.reporting_require { "graphs/grapher" }
2 | module MetricFu
3 | class CaneGrapher < Grapher
4 | attr_accessor :cane_violations, :labels
5 |
6 | def self.metric
7 | :cane
8 | end
9 |
10 | def initialize
11 | super
12 | @cane_violations = []
13 | @labels = {}
14 | end
15 |
16 | def get_metrics(metrics, date)
17 | if metrics && metrics[:cane]
18 | @cane_violations.push(metrics[:cane][:total_violations].to_i)
19 | @labels.update(@labels.size => date)
20 | end
21 | end
22 |
23 | def title
24 | "Cane: code quality threshold violations"
25 | end
26 |
27 | def data
28 | [
29 | ["cane", @cane_violations.join(",")]
30 | ]
31 | end
32 |
33 | def output_filename
34 | "cane.js"
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/support/deferred_garbaged_collection.rb:
--------------------------------------------------------------------------------
1 | class DeferredGarbageCollection
2 | DEFERRED_GC_THRESHOLD = (ENV["DEFER_GC"] || 15.0).to_f
3 |
4 | @@last_gc_run = Time.now
5 |
6 | def self.start
7 | GC.disable if DEFERRED_GC_THRESHOLD > 0
8 | end
9 |
10 | def self.reconsider
11 | if DEFERRED_GC_THRESHOLD > 0 && Time.now - @@last_gc_run >= DEFERRED_GC_THRESHOLD
12 | GC.enable
13 | GC.start
14 | GC.disable
15 | @@last_gc_run = Time.now
16 | end
17 | end
18 |
19 | def self.configure(config)
20 | return if defined?(JRUBY_VERSION)
21 | config.before(:all) do
22 | DeferredGarbageCollection.start
23 | end
24 |
25 | config.after(:all) do
26 | DeferredGarbageCollection.reconsider
27 | end
28 | end
29 | end
30 |
31 | RSpec.configure do |config|
32 | DeferredGarbageCollection.configure(config)
33 | end
34 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/javascripts/highcharts_graph.js:
--------------------------------------------------------------------------------
1 | createGraphElement("div");
2 |
3 | if(document.getElementById('graph')) {
4 | var chart = new Highcharts.Chart({
5 | chart: {
6 | animation: false,
7 | renderTo: 'graph'
8 | },
9 | legend: {
10 | align: 'center',
11 | verticalAlign: 'top',
12 | y: 25
13 | },
14 | plotOptions: {
15 | line: {
16 | animation: false,
17 | lineWidth: 3,
18 | marker: {
19 | radius: 6
20 | },
21 | pointPlacement: 'on'
22 | }
23 | },
24 | title: {
25 | text: graph_title
26 | },
27 | xAxis: {
28 | categories: graph_labels,
29 | tickmarkPlacement: 'on'
30 | },
31 | yAxis: {
32 | maxPadding: 0,
33 | min: 0,
34 | minPadding: 0
35 | },
36 | series: graph_series
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/css/reset.css:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3 | Code licensed under the BSD License:
4 | http://developer.yahoo.net/yui/license.txt
5 | version: 2.5.2
6 | */
7 | html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
8 |
--------------------------------------------------------------------------------
/spec/support/test_fixtures.rb:
--------------------------------------------------------------------------------
1 | require "pathname"
2 | class TestFixtures
3 | attr_reader :fixtures_path
4 |
5 | def initialize
6 | @loaded_data = {}
7 | @fixtures_path = Pathname(MetricFu.root_dir).join("spec", "fixtures")
8 | end
9 |
10 | def load_metric(path)
11 | retrieve_data(path) do |path|
12 | YAML.load_file(fixture_path(path))
13 | end
14 | end
15 |
16 | def load_file(path)
17 | retrieve_data(path) do |path|
18 | File.read(fixture_path(path))
19 | end
20 | end
21 |
22 | def fixture_path(path)
23 | fixtures_path.join(*Array(path))
24 | end
25 |
26 | private
27 |
28 | def retrieve_data(path)
29 | @loaded_data.fetch(path) do
30 | @loaded_data[path] = yield(path)
31 | end
32 | end
33 | end
34 | FIXTURE = TestFixtures.new
35 | HOTSPOT_DATA = ->(paths) {
36 | FIXTURE.load_metric(["hotspots"].concat(Array(paths)))
37 | }
38 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/hotspots/analysis/ranking_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "hotspots/analysis/ranking" }
3 |
4 | describe MetricFu::Ranking do
5 | context "with many items" do
6 | specify "#top" do
7 | ranking = Ranking.new
8 | ranking[:a] = 10
9 | ranking[:b] = 50
10 | ranking[:c] = 1
11 | expect(ranking.top).to eq([:b, :a, :c])
12 | end
13 |
14 | specify "lowest item is at 0 percentile" do
15 | ranking = Ranking.new
16 | ranking[:a] = 10
17 | ranking[:b] = 50
18 | expect(ranking.percentile(:a)).to eq(0)
19 | end
20 |
21 | specify "highest item is at high percentile" do
22 | ranking = Ranking.new
23 | ranking[:a] = 10
24 | ranking[:b] = 50
25 | ranking[:c] = 0
26 | ranking[:d] = 5
27 | expect(ranking.percentile(:b)).to eq(0.75)
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/configuration.rb:
--------------------------------------------------------------------------------
1 | MetricFu.lib_require { "templates/metrics_template" }
2 | module MetricFu::Templates
3 | class Configuration
4 | FILE_PREFIX = "file:/"
5 |
6 | def initialize
7 | @options = {}
8 | @options[:template_class] = MetricFu::Templates::MetricsTemplate
9 | @options[:darwin_txmt_protocol_no_thanks] = true
10 | # turning off syntax_highlighting may avoid some UTF-8 issues
11 | @options[:syntax_highlighting] = true
12 | @options[:link_prefix] = FILE_PREFIX
13 | end
14 |
15 | [:template_class, :link_prefix, :syntax_highlighting, :darwin_txmt_protocol_no_thanks].each do |option|
16 | define_method("#{option}=") do |arg|
17 | @options[option] = arg
18 | end
19 | end
20 |
21 | def option(name)
22 | @options.fetch(name.to_sym) { raise "No such template option: #{name}" }
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/metric_fu/reporting/graphs/grapher_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe "Bluff graphers responding to #graph!" do
4 | before do
5 | setup_fs
6 | end
7 | after do
8 | cleanup_fs
9 | end
10 | it "should write chart file" do
11 | graphs = {}
12 | available_graphs = MetricFu::Metric.enabled_metrics.select(&:has_graph?).map(&:name)
13 | available_graphs.each do |graph|
14 | grapher_name = graph.to_s.gsub("MetricFu::", "").gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
15 | grapher_name = grapher_name + "Grapher"
16 | graphs[graph] = MetricFu.const_get(grapher_name).new
17 | end
18 | graphs.each do |key, val|
19 | val.graph!
20 | output_dir = File.expand_path(File.join(MetricFu::Io::FileSystem.directory("output_directory")))
21 | expect { File.read(File.join(output_dir, "#{key.to_s.downcase}.js")) }.not_to raise_error
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/reek/report.html.erb:
--------------------------------------------------------------------------------
1 | Reek Results
2 |
3 | Reek detects common code smells in ruby code.
4 |
5 | <%= render_partial 'graph', {:graph_name => 'reek'} %>
6 |
7 |
8 |
9 | | File Path |
10 | Method |
11 | Description |
12 | Type |
13 |
14 | <% count = 0 %>
15 | <% @reek[:matches].each do |match| %>
16 | <% match[:code_smells].each do |smell| %>
17 |
18 | | <%= link_to_filename(match[:file_path]) %> |
19 |
20 | <%= smell[:method] %>
21 | |
22 |
23 | <%= smell[:message] %>
24 | |
25 |
26 | <%= smell[:type] %>
27 | |
28 |
29 | <% count += 1 %>
30 | <% end %>
31 | <% end %>
32 |
33 |
34 |
35 | <%= render_partial 'report_footer' %>
36 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/stats/grapher.rb:
--------------------------------------------------------------------------------
1 | MetricFu.reporting_require { "graphs/grapher" }
2 | module MetricFu
3 | class StatsGrapher < Grapher
4 | attr_accessor :loc_counts, :lot_counts, :labels
5 |
6 | def self.metric
7 | :stats
8 | end
9 |
10 | def initialize
11 | super
12 | self.loc_counts = []
13 | self.lot_counts = []
14 | self.labels = {}
15 | end
16 |
17 | def get_metrics(metrics, date)
18 | if metrics && metrics[:stats]
19 | loc_counts.push(metrics[:stats][:codeLOC].to_i)
20 | lot_counts.push(metrics[:stats][:testLOC].to_i)
21 | labels.update(labels.size => date)
22 | end
23 | end
24 |
25 | def title
26 | "Stats: LOC & LOT"
27 | end
28 |
29 | def data
30 | [
31 | ["LOC", @loc_counts.join(",")],
32 | ["LOT", @lot_counts.join(",")],
33 | ]
34 | end
35 |
36 | def output_filename
37 | "stats.js"
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/spec/support/matcher_create_file.rb:
--------------------------------------------------------------------------------
1 | RSpec::Matchers.define :create_file do |expected|
2 | match do |block|
3 | @before = exists?(expected)
4 | block.call
5 | @after = exists?(expected)
6 | !@before && @after
7 | end
8 |
9 | failure_message do |_block|
10 | existed_before_message expected do
11 | "The file #{expected.inspect} was not created"
12 | end
13 | end
14 |
15 | failure_message_when_negated do |_block|
16 | existed_before_message expected do
17 | "The file #{expected.inspect} was created"
18 | end
19 | end
20 |
21 | def supports_block_expectations?
22 | true
23 | end
24 |
25 | def exists?(expected)
26 | # Allows us to use wildcard checks for existence.
27 | !Dir.glob(expected).empty?
28 | end
29 |
30 | def existed_before_message(expected)
31 | if @before
32 | "The file #{expected.inspect} existed before, so this test doesn't work"
33 | else
34 | yield
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/flay/report.html.erb:
--------------------------------------------------------------------------------
1 | Flay Results
2 |
3 | Flay analyzes ruby code for structural similarities.
4 |
5 | <%= render_partial 'graph', {:graph_name => 'flay'} %>
6 |
7 | Total Score (lower is better): <%= @flay[:total_score] %>
8 | Scores less than <%= MetricFu::Metric.get_metric('flay').run_options[:minimum_score] %> are not shown or part of the total
9 |
10 |
11 |
12 | | Files |
13 | Matches |
14 |
15 | <% count = 0 %>
16 | <% @flay[:matches].each do |match| %>
17 |
18 |
19 | <% match[:matches].each do |file| %>
20 | <%= link_to_filename(file[:name], file[:line]) %>
21 | <% end %>
22 | |
23 | <%= match[:reason] %> |
24 |
25 | <% count += 1 %>
26 | <% end %>
27 |
28 |
29 | <%= render_partial 'report_footer' %>
30 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/report.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | Analyzed File Report
7 |
8 |
9 |
10 | <% @data.each_with_index do |line, idx| %>
11 | <% line_number = (idx + 1).to_s %>
12 |
13 |
14 | <% if @lines.has_key?(line_number) %>
15 |
16 | <% @lines[line_number].each do |problem| %>
17 | - <%= "#{problem[:description]} » #{problem[:type]}" %>
18 | <% end %>
19 |
20 | <% else %>
21 |
22 | <% end %>
23 | |
24 |
25 | <%= line_for_display(line, line_number) %>
26 | |
27 |
28 | <% end %>
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/spec/support/helper_methods.rb:
--------------------------------------------------------------------------------
1 | def enable_hotspots
2 | MetricFu.configure
3 | hotspot_metrics = MetricFu::Metric.metrics.map(&:name)
4 | hotspot_metrics.each do |metric_name|
5 | path = "#{metric_name}/hotspot"
6 | begin
7 | MetricFu.metrics_require { path }
8 | rescue LoadError
9 | # No hotspot, but that's ok
10 | end
11 | end
12 | end
13 |
14 | def metric_not_activated?(metric_name)
15 | MetricFu.configuration.configure_metrics
16 | metric = MetricFu::Metric.get_metric(metric_name.intern)
17 | if (metric.activate rescue false) # may fail if ripper not supported
18 | false
19 | else
20 | p "Skipping #{metric_name} tests, not activated"
21 | true
22 | end
23 | end
24 |
25 | def breaks_when?(bool)
26 | p "Skipping tests in #{caller[0]}. They unnecessarily break the build." if bool
27 | bool
28 | end
29 |
30 | def compare_paths(path1, path2)
31 | expect(File.join(MetricFu.root_dir, path1)).to eq(File.join(MetricFu.root_dir, path2))
32 | end
33 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/churn/hotspot.rb:
--------------------------------------------------------------------------------
1 | class MetricFu::ChurnHotspot < MetricFu::Hotspot
2 | COLUMNS = %w{times_changed}
3 |
4 | def columns
5 | COLUMNS
6 | end
7 |
8 | def name
9 | :churn
10 | end
11 |
12 | def map_strategy
13 | :present
14 | end
15 |
16 | def reduce_strategy
17 | :sum
18 | end
19 |
20 | def score_strategy
21 | :calculate_score
22 | end
23 |
24 | def calculate_score(metric_ranking, item)
25 | flat_churn_score = 0.50
26 | metric_ranking.scored?(item) ? flat_churn_score : 0
27 | end
28 |
29 | def generate_records(data, table)
30 | return if data == nil
31 | Array(data[:changes]).each do |change|
32 | table << {
33 | "metric" => :churn,
34 | "times_changed" => change[:times_changed],
35 | "file_path" => change[:file_path]
36 | }
37 | end
38 | end
39 |
40 | def present_group(group)
41 | "detected high level of churn (changed #{group[0].times_changed} times)"
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/churn/generator.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class ChurnGenerator < Generator
3 | def self.metric
4 | :churn
5 | end
6 |
7 | ###
8 | # options available are what can be passed to churn_calculator
9 | # https://github.com/danmayer/churn#library-options
10 | ###
11 | def emit
12 | @output = run(options)
13 | end
14 |
15 | def analyze
16 | if @output.nil? || @output.size.zero?
17 | @churn = { churn: {} }
18 | else
19 | @churn = @output
20 | end
21 | @churn
22 | end
23 |
24 | # ensure hash only has the :churn key
25 | def to_h
26 | { churn: @churn[:churn] }
27 | end
28 |
29 | # @param args [Hash] churn metric run options
30 | # @return [Hash] churn results
31 | def run(args)
32 | # @note passing in false to report will return a hash
33 | # instead of the default String
34 | ::Churn::ChurnCalculator.new(args).report(false)
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/rcov/generator_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "rcov/generator" }
3 | require "shared/test_coverage"
4 |
5 | describe MetricFu::RcovGenerator, "configured as rcov" do
6 | it_behaves_like "rcov test coverage generator", :rcov do
7 | describe "emit" do
8 | before :each do
9 | options = { external: nil }
10 | @test_coverage = MetricFu::RcovGenerator.new(@default_options.merge(options))
11 | end
12 |
13 | it "should set the RAILS_ENV" do
14 | expect(MetricFu::Utility).to receive(:rm_rf).with(MetricFu::RcovGenerator.metric_directory, verbose: false)
15 | expect(MetricFu::Utility).to receive(:mkdir_p).with(MetricFu::RcovGenerator.metric_directory)
16 | options = { environment: "metrics", external: nil }
17 | @test_coverage = MetricFu::RcovGenerator.new(@default_options.merge(options))
18 | expect(@test_coverage.command).to include("RAILS_ENV=metrics")
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rcov/rcov_line.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class RCovLine
3 | attr_accessor :content, :was_run
4 |
5 | def initialize(content, was_run)
6 | @content = content
7 | @was_run = was_run
8 | end
9 |
10 | def to_h
11 | { content: @content, was_run: @was_run }
12 | end
13 |
14 | def covered?
15 | @was_run == 1
16 | end
17 |
18 | def missed?
19 | @was_run == 0
20 | end
21 |
22 | def ignored?
23 | @was_run.nil?
24 | end
25 |
26 | def self.line_coverage(lines)
27 | lines.map { |line| line[:was_run] }
28 | end
29 |
30 | def self.covered_lines(line_coverage)
31 | line_coverage.count(1)
32 | end
33 |
34 | def self.missed_lines(line_coverage)
35 | line_coverage.count(0)
36 | end
37 |
38 | def self.ignored_lines(line_coverage)
39 | line_coverage.count(nil)
40 | end
41 |
42 | def css_class
43 | return "rcov_not_run" if missed?
44 |
45 | "rcov_run"
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rails_best_practices/grapher.rb:
--------------------------------------------------------------------------------
1 | MetricFu.reporting_require { "graphs/grapher" }
2 | module MetricFu
3 | class RailsBestPracticesGrapher < Grapher
4 | attr_accessor :rails_best_practices_count, :labels
5 |
6 | def self.metric
7 | :rails_best_practices
8 | end
9 |
10 | def initialize
11 | super
12 | @rails_best_practices_count = []
13 | @labels = {}
14 | end
15 |
16 | def get_metrics(metrics, date)
17 | if metrics && metrics[:rails_best_practices]
18 | size = (metrics[:rails_best_practices][:problems] || []).size
19 | @rails_best_practices_count.push(size)
20 | @labels.update(@labels.size => date)
21 | end
22 | end
23 |
24 | def title
25 | "Rails Best Practices: design problems"
26 | end
27 |
28 | def data
29 | [
30 | ["rails_best_practices", @rails_best_practices_count.join(",")]
31 | ]
32 | end
33 |
34 | def output_filename
35 | "rails_best_practices.js"
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/generator.rb:
--------------------------------------------------------------------------------
1 | MetricFu.metrics_require { "hotspots/hotspot_analyzer" }
2 | module MetricFu
3 | class HotspotsGenerator < Generator
4 | def self.metric
5 | :hotspots
6 | end
7 |
8 | def initialize(options = {})
9 | MetricFu::Metric.enabled_metrics.each do |metric|
10 | require_hotspot(metric.name)
11 | end
12 | super
13 | end
14 |
15 | def emit
16 | # no-op
17 | end
18 |
19 | def analyze
20 | analyzer = MetricFu::HotspotAnalyzer.new(MetricFu.result.result_hash)
21 | @hotspots = analyzer.hotspots
22 | end
23 |
24 | def to_h
25 | result = { hotspots: {} }
26 | @hotspots.each do |granularity, hotspots|
27 | result[:hotspots][granularity.to_s] = hotspots.map(&:to_hash)
28 | end
29 | result
30 | end
31 |
32 | private
33 |
34 | def require_hotspot(metric_name)
35 | require "metric_fu/metrics/#{metric_name}/hotspot"
36 | rescue LoadError
37 | mf_debug "*** No hotspot for #{metric_name}"
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/spec/fixtures/hotspots/three_metrics_on_same_file.yml:
--------------------------------------------------------------------------------
1 | :saikuro:
2 | :files:
3 | - :classes:
4 | - :complexity: 19
5 | :methods:
6 | - :complexity: 1
7 | :lines: 9
8 | :name: Client#client_requested_sync
9 | - :complexity: 1
10 | :lines: 9
11 | :name: Client#method_that_is_not_mentioned_elsewhere_in_stats
12 | :lines: 92
13 | :class_name: Devver::Client
14 | :filename: client.rb
15 | :reek:
16 | :matches:
17 | - :file_path: lib/client/client.rb
18 | :code_smells:
19 | - :type: Large Class
20 | :message: has at least 27 methods
21 | :method: Devver::Client
22 | - :type: Long Method
23 | :message: has approx 6 statements
24 | :method: Devver::Client#client_requested_sync
25 | :flog:
26 | :total: 1817.6
27 | :pages:
28 | - :path: /lib/client/client.rb
29 | :highest_score: 37.9
30 | :average_score: 13.6
31 | :scanned_methods:
32 | - :operators:
33 | - :operator: "[]"
34 | :score: 11.1
35 | :score: 37.9
36 | :name: Client#client_requested_sync
37 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/saikuro/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for saikuro" do
5 | it_behaves_like "configured" do
6 | it "should set @saikuro to { :output_directory => @scratch_directory + '/saikuro',
7 | :input_directory => @code_dirs,
8 | :cyclo => '',
9 | :filter_cyclo => '0',
10 | :warn_cyclo => '5',
11 | :error_cyclo => '7',
12 | :formater => 'text' }" do
13 | load_metric "saikuro"
14 | expect(MetricFu::Metric.get_metric(:saikuro).run_options).to eq(
15 | output_directory: "#{scratch_directory}/saikuro",
16 | input_directory: ["lib"],
17 | cyclo: "",
18 | filter_cyclo: "0",
19 | warn_cyclo: "5",
20 | error_cyclo: "7",
21 | formater: "text"
22 | )
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008,2009,2010,2011 Jake Scruggs
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/saikuro/parsing_element.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class SaikuroParsingElement
3 | TYPE_REGEX = /Type:(.*) Name/
4 | NAME_REGEX = /Name:(.*) Complexity/
5 | COMPLEXITY_REGEX = /Complexity:(.*) Lines/
6 | LINES_REGEX = /Lines:(.*)/
7 |
8 | attr_reader :complexity, :lines, :defs, :element_type
9 | attr_accessor :name
10 |
11 | def initialize(line)
12 | @line = line
13 | @element_type = line.match(TYPE_REGEX)[1].strip
14 | @name = line.match(NAME_REGEX)[1].strip
15 | @complexity = line.match(COMPLEXITY_REGEX)[1].strip
16 | @lines = line.match(LINES_REGEX)[1].strip
17 | @defs = []
18 | end
19 |
20 | def <<(line)
21 | @defs << MetricFu::SaikuroParsingElement.new(line)
22 | end
23 |
24 | def to_h
25 | base = { name: @name, complexity: @complexity.to_i, lines: @lines.to_i }
26 | unless @defs.empty?
27 | defs = @defs.map do |my_def|
28 | my_def = my_def.to_h
29 | my_def.delete(:defs)
30 | my_def
31 | end
32 | base[:defs] = defs
33 | end
34 | base
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/support/matcher_create_files.rb:
--------------------------------------------------------------------------------
1 | RSpec::Matchers.define :create_files do |expected|
2 | match do |block|
3 | @before = false
4 | @after = true
5 | expected.each do |file|
6 | @before |= exists?(file)
7 | end
8 | block.call
9 | expected.each do |file|
10 | @after &= exists?(file)
11 | end
12 | !@before && @after
13 | end
14 |
15 | failure_message do |_block|
16 | existed_before_message expected do
17 | "One or more files in [#{expected.inspect}] was not created."
18 | end
19 | end
20 |
21 | failure_message_when_negated do |_block|
22 | existed_before_message expected do
23 | "The files in [#{expected.inspect}] were created."
24 | end
25 | end
26 |
27 | def supports_block_expectations?
28 | true
29 | end
30 |
31 | def exists?(expected)
32 | # Allows us to use wildcard checks for existence.
33 | !Dir.glob(expected).empty?
34 | end
35 |
36 | def existed_before_message(expected)
37 | if @before
38 | "One or more files in [#{expected.inspect}] existed before, so this test doesn't work"
39 | else
40 | yield
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/spec/shared/configured.rb:
--------------------------------------------------------------------------------
1 | shared_examples "configured" do
2 | def get_new_config
3 | ENV["CC_BUILD_ARTIFACTS"] = nil
4 | @config = MetricFu.configuration
5 | @config.reset
6 | MetricFu.configuration.configure_metric(:rcov) do |rcov|
7 | rcov.enabled = true
8 | end
9 | MetricFu.configure
10 | allow(MetricFu::Io::FileSystem).to receive(:create_directories) # no need to create directories for the tests
11 | @config
12 | end
13 |
14 | def directory(name)
15 | MetricFu::Io::FileSystem.directory(name)
16 | end
17 |
18 | def base_directory
19 | directory("base_directory")
20 | end
21 |
22 | def output_directory
23 | directory("output_directory")
24 | end
25 |
26 | def scratch_directory
27 | directory("scratch_directory")
28 | end
29 |
30 | def template_directory
31 | directory("template_directory")
32 | end
33 |
34 | def template_class
35 | MetricFu::Formatter::Templates.option("template_class")
36 | end
37 |
38 | def metric_fu_root
39 | directory("root_directory")
40 | end
41 |
42 | def load_metric(metric)
43 | load File.join(MetricFu.metrics_dir, metric, "metric.rb")
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | guard :bundler do
2 | watch("Gemfile")
3 | watch("Gemfile.lock")
4 | watch(%w{.+.gemspec\z})
5 | end
6 |
7 | guard :rspec, cli: File.read(".rspec").split.push("--fail-fast").join(" "), keep_failed: false do
8 | # Run all specs if configuration is modified
9 | watch(".rspec") { "spec" }
10 | watch("Guardfile") { "spec" }
11 | watch("Gemfile.lock") { "spec" }
12 | watch("spec/spec_helper.rb") { "spec" }
13 |
14 | # Run all specs if supporting files are modified
15 | watch(%r{\Aspec/(?:fixtures|lib|support|shared)/.+\.rb\z}) { "spec" }
16 |
17 | # Run unit specs if associated lib code is modified
18 | watch(%r{\Alib/(.+)\.rb\z}) { |m| "spec/#{m[1]}_spec.rb" }
19 | watch("lib/#{File.basename(File.expand_path('../', __FILE__))}.rb") { "spec" }
20 |
21 | # Run a spec if it is modified
22 | watch(%r{\Aspec/.+_spec\.rb$\z})
23 | end
24 |
25 | # TODO enable once we're ready to handle all the violations :)
26 | # guard :rubocop, cli: %w[--config config/rubocop.yml] do
27 | # watch(%r{.+\.(?:rb|rake)\z})
28 | # watch(%r{\Aconfig/rubocop\.yml\z}) { |m| File.dirname(m[0]) }
29 | # watch(%r{(?:.+/)?\.rubocop\.yml\z}) { |m| File.dirname(m[0]) }
30 | # end
31 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/flog/hotspot.rb:
--------------------------------------------------------------------------------
1 | class MetricFu::FlogHotspot < MetricFu::Hotspot
2 | COLUMNS = %w{score}
3 |
4 | def columns
5 | COLUMNS
6 | end
7 |
8 | def name
9 | :flog
10 | end
11 |
12 | def map_strategy
13 | :score
14 | end
15 |
16 | def reduce_strategy
17 | :average
18 | end
19 |
20 | def score_strategy
21 | :identity
22 | end
23 |
24 | def generate_records(data, table)
25 | return if data == nil
26 | Array(data[:method_containers]).each do |method_container|
27 | Array(method_container[:methods]).each do |entry|
28 | file_path = entry[1][:path].sub(%r{^/}, "") if entry[1][:path]
29 | location = MetricFu::Location.for(entry.first)
30 | table << {
31 | "metric" => name,
32 | "score" => entry[1][:score],
33 | "file_path" => file_path,
34 | "class_name" => location.class_name,
35 | "method_name" => location.method_name
36 | }
37 | end
38 | end
39 | end
40 |
41 | def present_group(group)
42 | occurences = group.size
43 | complexity = get_mean(group.column("score"))
44 | "#{'average ' if occurences > 1}complexity is %.1f" % complexity
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/stats/report.html.erb:
--------------------------------------------------------------------------------
1 | Lines of Code/Tests Metric Results
2 |
3 | <%= render_partial 'graph', {:graph_name => 'stats'} %>
4 |
5 | Lines of Code/Tests Metrics Results
6 |
7 |
8 | | Lines of Code |
9 | Lines of Test |
10 | Code to test ratio |
11 |
12 |
13 | | <%= @stats[:codeLOC] %> |
14 | <%= @stats[:testLOC] %> |
15 | 1:<%= @stats[:code_to_test_ratio] %> |
16 |
17 |
18 |
19 |
20 |
21 | | Name |
22 | Lines |
23 | LOC |
24 | Classes |
25 | Methods |
26 | Methods per class |
27 | LOC per method |
28 |
29 | <% count = 0 %>
30 | <% @stats[:lines].each do |line| %>
31 |
32 | | <%= line[:name] %> |
33 | <%= line[:lines] %> |
34 | <%= line[:loc] %> |
35 | <%= line[:classes] %> |
36 | <%= line[:methods] %> |
37 | <%= line[:methods_per_class] %> |
38 | <%= line[:loc_per_method] %> |
39 |
40 | <% count += 1 %>
41 | <% end %>
42 |
43 |
44 | <%= render_partial 'report_footer' %>
45 |
--------------------------------------------------------------------------------
/lib/metric_fu/formatter.rb:
--------------------------------------------------------------------------------
1 | require "metric_fu/constantize"
2 | module MetricFu
3 | module Formatter
4 | BUILTIN_FORMATS = {
5 | "html" => ["MetricFu::Formatter::HTML", "Generates a templated HTML report using the configured template class and graph engine."],
6 | "yaml" => ["MetricFu::Formatter::YAML", "Generates the raw output as yaml"]
7 | }
8 | DEFAULT = [[:html]]
9 |
10 | class << self
11 | include MetricFu::Constantize
12 |
13 | def class_for(format)
14 | if (builtin = BUILTIN_FORMATS[format.to_s])
15 | constantize(builtin[0])
16 | else
17 | constantize(format.to_s)
18 | end
19 | end
20 | end
21 |
22 | module Templates
23 | MetricFu.lib_require { "templates/metrics_template" }
24 |
25 | module_function
26 |
27 | def templates_configuration=(templates_configuration)
28 | @templates_configuration = templates_configuration
29 | end
30 |
31 | def option(name)
32 | templates_configuration.option(name)
33 | end
34 |
35 | def templates_configuration
36 | @templates_configuration ||= MetricFu::Templates::Configuration.new
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/analysis/analyzed_problems.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class HotspotAnalyzedProblems
3 | MetricFu.metrics_require { "hotspots/analysis/ranked_problem_location" }
4 |
5 | def initialize(hotspot_rankings, analyzer_tables)
6 | @hotspot_rankings = hotspot_rankings
7 | @analyzer_tables = analyzer_tables
8 | end
9 |
10 | def worst_items
11 | worst_items = {}
12 | worst_items[:files] = worst(@hotspot_rankings.worst_files, :file)
13 | worst_items[:classes] = worst(@hotspot_rankings.worst_classes, :class)
14 | worst_items[:methods] = worst(@hotspot_rankings.worst_methods, :method)
15 | worst_items
16 | end
17 |
18 | private
19 |
20 | # @param rankings [Array]
21 | # @param granularity [Symbol] one of :class, :method, :file
22 | def worst(rankings, granularity)
23 | rankings.map do |ranked_item_name|
24 | sub_table = get_sub_table(granularity, ranked_item_name)
25 | MetricFu::HotspotRankedProblemLocation.new(sub_table, granularity)
26 | end
27 | end
28 |
29 | def get_sub_table(granularity, ranked_item_name)
30 | tables = @analyzer_tables.tables_for(granularity)
31 | tables[ranked_item_name]
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rcov/hotspot.rb:
--------------------------------------------------------------------------------
1 | class MetricFu::RcovHotspot < MetricFu::Hotspot
2 | COLUMNS = %w{percentage_uncovered}
3 |
4 | def columns
5 | COLUMNS
6 | end
7 |
8 | def name
9 | :rcov
10 | end
11 |
12 | def map_strategy
13 | :percentage_uncovered
14 | end
15 |
16 | def reduce_strategy
17 | :average
18 | end
19 |
20 | def score_strategy
21 | :identity
22 | end
23 |
24 | def generate_records(data, table)
25 | return if data == nil
26 | data.each do |file_name, info|
27 | next if (file_name == :global_percent_run) || (info[:methods].nil?)
28 | info[:methods].each do |method_name, percentage_uncovered|
29 | location = MetricFu::Location.for(method_name)
30 | table << {
31 | "metric" => :rcov,
32 | "file_path" => file_name,
33 | "class_name" => location.class_name,
34 | "method_name" => location.method_name,
35 | "percentage_uncovered" => percentage_uncovered
36 | }
37 | end
38 | end
39 | end
40 |
41 | def present_group(group)
42 | occurences = group.size
43 | average_code_uncoverage = get_mean(group.column("percentage_uncovered"))
44 | "#{'average ' if occurences > 1}uncovered code is %.1f%" % average_code_uncoverage
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/CONTRIBUTORS:
--------------------------------------------------------------------------------
1 | Benjamin Fleischer
2 | Jake Scruggs
3 | Édouard Brière
4 | Richard Huang
5 | Grant McInnes
6 | Petrik
7 | Dan Mayer
8 | Robin Curry
9 | Sean Soper
10 | Delwyn de Villiers
11 | Martin Gotink
12 | Carl Youngblood
13 | Andre Arko
14 | Chris Griego
15 | Sathish
16 | Michael Stark
17 | Nick Quaranto
18 | dan sinclair
19 | joshuacronemeyer
20 | jscruggs
21 | David Chelimsky
22 | adrien
23 | Jay Zeschin
24 | Greg Allen
25 | Alex Rothenberg
26 | calveto
27 | Avdi Grimm
28 | Kevin Rutherford
29 | Randy Souza
30 | GeorgeErickson
31 | Josef Šimánek
32 | Andy Gregorowicz
33 | Tarsoly András
34 | Benjamin Fleischer / Paul Swagerty
35 | Nick Veys
36 | unknown
37 | Micah Geisel
38 | Extrovert
39 | Ben Turner
40 | Andrew Timberlake
41 | KAKUTANI Shintaro
42 | Guilherme Souza
43 | Przemysław Dąbek
44 | carlost
45 | David Barri
46 | Beau Fabry
47 | Chris Mason
48 | Andrew Selder
49 | iain
50 | Mark Wilden
51 | Alessandro Dias
52 | Lars E. Hoeg
53 | Eric Wollesen
54 | Adam Bair
55 | Jinzhu
56 | Matthew Van Horn
57 | Diego Carrion
58 | saltracer
59 | benlovell
60 | ff-cviradiya
61 | khall
62 | Stefan Huber
63 | Todd A. Jacobs
64 | jayzes
65 | Hans Hasselberg
66 | Scyllinice
67 | Bitdeli Chef
68 | René Föhring
69 | Matthew Gordon
70 | factorylabs
71 | Mike Ball
72 | Guilherme Simões
73 | Paul Elliott
74 | Joel Nimety
75 | Chris Ian Fiel
76 | Mike Szyndel
77 |
--------------------------------------------------------------------------------
/spec/metric_fu/reporter_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.lib_require { "reporter" }
3 |
4 | describe MetricFu::Reporter do
5 | context "given a single formatter" do
6 | before do
7 | @formatter = double("formatter")
8 | allow(@formatter).to receive(:to_a).and_return([@formatter])
9 | @reporter = Reporter.new(@formatter)
10 | end
11 |
12 | it "notifies the formatter" do
13 | expect(@formatter).to receive(:start)
14 | expect(@formatter).to receive(:finish)
15 | @reporter.start
16 | @reporter.finish
17 | end
18 |
19 | it "only sends notifications when supported by formatter" do
20 | allow(@formatter).to receive(:respond_to?).with(:display_results).and_return(false)
21 | expect(@formatter).not_to receive(:display_results)
22 | @reporter.display_results
23 | end
24 | end
25 |
26 | context "given multiple formatters" do
27 | before do
28 | @formatters = [double("formatter"), double("formatter")]
29 | @reporter = Reporter.new(@formatters)
30 | end
31 |
32 | it "notifies all formatters" do
33 | @formatters.each do |formatter|
34 | expect(formatter).to receive(:start)
35 | expect(formatter).to receive(:finish)
36 | end
37 | @reporter.start
38 | @reporter.finish
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rcov/report.html.erb:
--------------------------------------------------------------------------------
1 |
4 | Rcov Code Coverage Results
5 |
6 | C0 code coverage information.
7 |
8 | <%= render_partial 'graph', {:graph_name => 'rcov'} %>
9 |
10 | Total Coverage: <%= @rcov.delete(:global_percent_run) %>%
11 |
12 |
13 | | File Path |
14 | Percent run |
15 |
16 | <% count = 0 %>
17 | <% @rcov.sort_by{ |k,v| v[:percent_run] }.each do |fname, file| %>
18 |
19 | | <%= fname %> |
20 | <%= file[:percent_run] %> |
21 |
22 | <% count += 1 %>
23 | <% end %>
24 |
25 |
26 | <% @rcov.sort_by{ |k,v| v[:percent_run] }.each do |fname, file| %>
27 | <%= fname %>
28 |
29 |
30 | <% file[:lines].each_with_index do |line, index| %>
31 |
32 | <% rcov_line = RCovLine.new(line[:content], line[:was_run]) %>
33 | <%= link_to_filename(fname, index + 1, "#{line[:content]}") %> |
34 |
35 | <% end %>
36 |
37 |
38 | <% end %>
39 |
40 | <%= render_partial 'report_footer' %>
41 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/rails_best_practices/generator_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "rails_best_practices/generator" }
3 |
4 | describe RailsBestPracticesGenerator do
5 | break if metric_not_activated?(:rails_best_practices)
6 |
7 | describe "emit method" do
8 | let(:analyzer) { ::RailsBestPractices::Analyzer.new(".", "silent" => true) }
9 | context "RailsBestPractices provides the expected API" do
10 | it { expect(analyzer).to respond_to :analyze }
11 | it { expect(analyzer).to respond_to :errors }
12 | end
13 | end
14 |
15 | describe "analyze method" do
16 | let(:error) { ::RailsBestPractices::Core::Error.new }
17 | context "RailsBestPractices provdies the expected API" do
18 | it { expect(error).to respond_to :filename }
19 | it { expect(error).to respond_to :line_number }
20 | it { expect(error).to respond_to :message }
21 | it { expect(error).to respond_to :url }
22 | end
23 | end
24 |
25 | describe "to_h method" do
26 | it "should put things into a hash" do
27 | MetricFu::Configuration.run {}
28 | practices = MetricFu::RailsBestPracticesGenerator.new
29 | practices.instance_variable_set(:@rails_best_practices_results, "the_practices")
30 | expect(practices.to_h[:rails_best_practices]).to eq("the_practices")
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/flay/generator.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class FlayGenerator < Generator
3 | def self.metric
4 | :flay
5 | end
6 |
7 | def emit
8 | args = "#{minimum_duplication_mass} #{dirs_to_flay}"
9 | @output = run!(args)
10 | end
11 |
12 | def analyze
13 | @matches = @output.chomp.split("\n\n").map { |m| m.split("\n ") }
14 | end
15 |
16 | def to_h
17 | { flay: calculate_result(@matches) }
18 | end
19 |
20 | # TODO: move into analyze method
21 | def calculate_result(matches)
22 | total_score = matches.shift.first.split("=").last.strip
23 | target = []
24 | matches.each do |problem|
25 | reason = problem.shift.strip
26 | lines_info = problem.map do |full_line|
27 | name, line = full_line.split(":").map(&:strip)
28 | { name: name, line: line }
29 | end
30 | target << [reason: reason, matches: lines_info]
31 | end
32 | {
33 | total_score: total_score,
34 | matches: target.flatten
35 | }
36 | end
37 |
38 | private
39 |
40 | def minimum_duplication_mass
41 | flay_mass = options[:minimum_score]
42 | return "" unless flay_mass
43 |
44 | "--mass #{flay_mass} "
45 | end
46 |
47 | def dirs_to_flay
48 | options[:dirs_to_flay].join(" ")
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/flay/hotspot.rb:
--------------------------------------------------------------------------------
1 | class MetricFu::FlayHotspot < MetricFu::Hotspot
2 | COLUMNS = %w{flay_reason flay_matching_reason}
3 |
4 | def columns
5 | COLUMNS
6 | end
7 |
8 | def name
9 | :flay
10 | end
11 |
12 | def map_strategy
13 | :present
14 | end
15 |
16 | def reduce_strategy
17 | :sum
18 | end
19 |
20 | def score_strategy
21 | :percentile
22 | end
23 |
24 | def generate_records(data, table)
25 | return if data == nil
26 | Array(data[:matches]).each do |match|
27 | problems = match[:reason]
28 | matching_reason = problems.gsub(/^[0-9]+\) /, "").gsub(/\:[0-9]+/, "")
29 | files = []
30 | locations = []
31 | match[:matches].each do |file_match|
32 | file_path = file_match[:name].sub(%r{^/}, "")
33 | locations << "#{file_path}:#{file_match[:line]}"
34 | files << file_path
35 | end
36 | files = files.uniq
37 | files.each do |file|
38 | table << {
39 | "metric" => name,
40 | "file_path" => file,
41 | "flay_reason" => problems + " files: #{locations.join(', ')}",
42 | "flay_matching_reason" => matching_reason
43 | }
44 | end
45 | end
46 | end
47 |
48 | def present_group(group)
49 | occurences = group.size
50 | "found #{occurences} code duplications"
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/roodi/generator.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class RoodiGenerator < Generator
3 | def self.metric
4 | :roodi
5 | end
6 |
7 | def emit
8 | files_to_analyze = options[:dirs_to_roodi].map { |dir| Dir[File.join(dir, "**/*.rb")] }
9 | files = remove_excluded_files(files_to_analyze.flatten)
10 | config = options[:roodi_config] ? "-config=#{options[:roodi_config]}" : ""
11 | args = "#{config} #{files.join(' ')}"
12 | @output = run!(args)
13 | end
14 |
15 | def analyze
16 | @output = MetricFu::Utility.strip_escape_codes(@output)
17 | @matches = @output.chomp.split("\n").map { |m| m.split(" - ") }
18 | total = @matches.pop
19 | @matches.reject! { |array| array.size < 2 }
20 | @matches.map! do |match|
21 | file, line = match[0].split(":")
22 | problem = match[1]
23 | { file: file, line: line, problem: problem }
24 | end
25 | @roodi_results = { total: total, problems: @matches }
26 | end
27 |
28 | def to_h
29 | { roodi: @roodi_results }
30 | end
31 |
32 | def per_file_info(out)
33 | @matches.each do |match|
34 | out[match[:file]] ||= {}
35 | out[match[:file]][match[:line]] ||= []
36 | out[match[:file]][match[:line]] << { type: :roodi, description: match[:problem] }
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/certs/bf4.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDmjCCAoKgAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMQ8wDQYDVQQDDAZnaXRo
3 | dWIxITAfBgoJkiaJk/IsZAEZFhFiZW5qYW1pbmZsZWlzY2hlcjETMBEGCgmSJomT
4 | 8ixkARkWA2NvbTAeFw0xNTAxMjIxMzAyNTNaFw0xNjAxMjIxMzAyNTNaMEkxDzAN
5 | BgNVBAMMBmdpdGh1YjEhMB8GCgmSJomT8ixkARkWEWJlbmphbWluZmxlaXNjaGVy
6 | MRMwEQYKCZImiZPyLGQBGRYDY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
7 | CgKCAQEA7V1VZBU7Aft01XAoK8I8tdClfv3H/NIauiV0jfyNtXtZEWwaZ6ooZNLk
8 | 8kmIUsO2xI7I/B3es6w7le9q9xdEowlYjiR/X/yazNvufu5kpM4f6Ri1AKN8xvPk
9 | LFlR8aOAd9LptcusYDfE+BjhmAvnLTgMGODcDLJIaJzLJaRywTLUuFv4digpFwCm
10 | Zps9VheJnL4hkgI5BDn6DVjxHSCMRnccQM/kX9L34lbP9KkHXXEtQgkQYpElHbnd
11 | MtR753aPeLfOBxSGzsso+6Lhe+fz8huD05mzgWaEZN40e6M7dA9FRSsEzL32ZOad
12 | 0z13MZWj3Yg5srV/cZvzCDCdVvRphwIDAQABo4GMMIGJMAkGA1UdEwQCMAAwCwYD
13 | VR0PBAQDAgSwMB0GA1UdDgQWBBQvUrPExdvmdz0Vau0dH3hRh1YQfDAnBgNVHREE
14 | IDAegRxnaXRodWJAYmVuamFtaW5mbGVpc2NoZXIuY29tMCcGA1UdEgQgMB6BHGdp
15 | dGh1YkBiZW5qYW1pbmZsZWlzY2hlci5jb20wDQYJKoZIhvcNAQEFBQADggEBAEWo
16 | g1soMaRTT/OfFklTuP+odV0w+2qJSfJhOY5bIebDjqxb9BN7hZJ9L6WXhcXCvl6r
17 | kuXjpcC05TIv1DoWWaSjGK2ADmEBDNVhaFepYidAYuUQN4+ZjVH/gS9V9OkBcE8h
18 | 3ZwRv+9RkXM0uY1FwuGI6jgbgPeR1AkkfJnhOPohkG+VN5bFo9aK/Stw8Nwhuuiz
19 | axCPD3cmaJBguufRXSMC852SDiBT7AtI4Gl2Fyr+0M5TzXHKbQ9xRBxwfE1bWDd6
20 | lEs7ndJ1/vd/Hy0zQ1tIRWyql+ITLhqMi161Pw5flsYpQvPlRLR5pGJ4eD0/JdKE
21 | ZG9WSFH7QcGLY65mEYc=
22 | -----END CERTIFICATE-----
23 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/cane/violations.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | module CaneViolations
3 | class AbcComplexity
4 | def self.parse(violation_list)
5 | violation_list.split(/\n/).map do |violation|
6 | file, method, complexity = violation.split
7 | { file: file, method: method, complexity: complexity }
8 | end
9 | end
10 | end
11 |
12 | class LineStyle
13 | def self.parse(violation_list)
14 | violation_list.split(/\n/).map do |violation|
15 | line, description = violation.split(/\s{2,}/).reject { |x|x.strip == "" }
16 | { line: line, description: description }
17 | end
18 | end
19 | end
20 |
21 | class Comment
22 | def self.parse(violation_list)
23 | violation_list.split(/\n/).map do |violation|
24 | line, class_name = violation.split
25 | { line: line, class_name: class_name }
26 | end
27 | end
28 | end
29 |
30 | class Documentation
31 | def self.parse(violation_list)
32 | violation_list.split(/\n/).map do |violation|
33 | { description: violation.strip }
34 | end
35 | end
36 | end
37 |
38 | class Others
39 | def self.parse(violation_list)
40 | violation_list.split(/\n/).map do |violation|
41 | { description: violation.strip }
42 | end
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/saikuro/hotspot.rb:
--------------------------------------------------------------------------------
1 | class MetricFu::SaikuroHotspot < MetricFu::Hotspot
2 | COLUMNS = %w{lines complexity}
3 |
4 | def columns
5 | COLUMNS
6 | end
7 |
8 | def name
9 | :saikuro
10 | end
11 |
12 | def map_strategy
13 | :complexity
14 | end
15 |
16 | def reduce_strategy
17 | :average
18 | end
19 |
20 | def score_strategy
21 | :identity
22 | end
23 |
24 | def generate_records(data, table)
25 | return if data == nil
26 | data[:files].each do |file|
27 | file_name = file[:filename]
28 | file[:classes].each do |klass|
29 | location = MetricFu::Location.for(klass[:class_name])
30 | offending_class = location.class_name
31 | klass[:methods].each do |match|
32 | offending_method = MetricFu::Location.for(match[:name]).method_name
33 | table << {
34 | "metric" => name,
35 | "lines" => match[:lines],
36 | "complexity" => match[:complexity],
37 | "class_name" => offending_class,
38 | "method_name" => offending_method,
39 | "file_path" => file_name,
40 | }
41 | end
42 | end
43 | end
44 | end
45 |
46 | def present_group(group)
47 | occurences = group.size
48 | complexity = get_mean(group.column("complexity"))
49 | "#{'average ' if occurences > 1}complexity is %.1f" % complexity
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # add lib to the load path just like rubygems does
2 | $:.unshift File.expand_path("../../lib", __FILE__)
3 | require "simplecov"
4 |
5 | require "date"
6 | require "test_construct"
7 | require "json"
8 | require "pry-nav"
9 |
10 | require "metric_fu"
11 | include MetricFu
12 | def mf_log(msg); mf_debug(msg); end
13 |
14 | # Requires supporting ruby files with custom matchers and macros, etc,
15 | # in spec/support/ and its subdirectories.
16 | Dir[MetricFu.root_dir + "/spec/support/**/*.rb"].each { |f| require f }
17 |
18 | RSpec.configure do |config|
19 | config.filter_run focus: true
20 | config.run_all_when_everything_filtered = true
21 | # Skip specs tagged `:slow` unless SLOW_SPECS is set
22 | config.filter_run_excluding :slow unless ENV["SLOW_SPECS"]
23 | # End specs on first failure if FAIL_FAST is set
24 | config.fail_fast = ENV.include?("FAIL_FAST")
25 | config.order = :rand
26 | config.color = true
27 | config.expect_with :rspec do |expectations|
28 | expectations.syntax = :expect
29 | end
30 | config.mock_with :rspec do |mocks|
31 | mocks.syntax = :expect
32 | mocks.verify_partial_doubles = true
33 | end
34 |
35 | # :suite after/before all specs
36 | # :each every describe block
37 | # :all every it block
38 |
39 | config.after(:suite) do
40 | cleanup_fs
41 | end
42 |
43 | config.append_after do
44 | MetricFu.reset
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/spec/metric_fu/metric_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MetricFu::Metric do
4 | before do
5 | @metric = MetricFu::Metric.get_metric(:flog)
6 | # @original_options = @metric.run_options.dup
7 | end
8 |
9 | # it "can have its run_options over-written" do
10 | # new_options = {:foo => 'bar'}
11 | # @metric.run_options = new_options
12 | # expect(@original_options).to_not eq(new_options)
13 | # expect(@metric.run_options).to eq(new_options)
14 | # end
15 |
16 | # it "can have its run_options modified" do
17 | # new_options = {:foo => 'bar'}
18 | # @metric.run_options.merge!(new_options)
19 | # expect(@metric.run_options).to eq(@original_options.merge(new_options))
20 | # end
21 |
22 | context "given a valid configurable option" do
23 | before do
24 | allow(@metric).to receive(:default_run_options).and_return(foo: "baz")
25 | end
26 |
27 | it "can be configured as an attribute" do
28 | @metric.foo = "qux"
29 | expect(@metric.run_options[:foo]).to eq("qux")
30 | end
31 | end
32 |
33 | context "given an invalid configurable option" do
34 | before do
35 | allow(@metric).to receive(:default_run_options).and_return({})
36 | end
37 |
38 | it "raises an error" do
39 | expect { @metric.foo = "bar" }.to raise_error(RuntimeError, /not a valid configuration option/)
40 | end
41 | end
42 |
43 | after do
44 | @metric.configured_run_options.clear
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rcov/metric.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class MetricRcov < Metric
3 | def name
4 | :rcov
5 | end
6 |
7 | def default_run_options
8 | {
9 | environment: "test",
10 | test_files: Dir["{spec,test}/**/*_{spec,test}.rb"],
11 | rcov_opts: rcov_opts,
12 | external: nil,
13 | }
14 | end
15 |
16 | def coverage_file=(coverage_file)
17 | configured_run_options.update(external: coverage_file)
18 | end
19 |
20 | def has_graph?
21 | true
22 | end
23 |
24 | def enable
25 | if external_coverage_file?
26 | super
27 | else
28 | mf_debug("RCov is not available. See README")
29 | end
30 | end
31 |
32 | def activate
33 | super
34 | end
35 |
36 | def external_coverage_file?
37 | if coverage_file = run_options[:external]
38 | File.exist?(coverage_file) ||
39 | mf_log("Configured RCov file #{coverage_file.inspect} does not exist")
40 | else
41 | false
42 | end
43 | end
44 |
45 | private
46 |
47 | def rcov_opts
48 | rcov_opts = [
49 | "--sort coverage",
50 | "--no-html",
51 | "--text-coverage",
52 | "--no-color",
53 | "--profile",
54 | "--exclude-only '.*'",
55 | '--include-file "\Aapp,\Alib"'
56 | ]
57 | rcov_opts << "-Ispec" if File.exist?("spec")
58 | rcov_opts
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rails_best_practices/generator.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | class RailsBestPracticesGenerator < Generator
3 | def self.metric
4 | :rails_best_practices
5 | end
6 |
7 | def initialize(options = {})
8 | super(MetricFu::Utility.stringify_keys(options))
9 | end
10 |
11 | def emit
12 | mf_debug "** Rails Best Practices"
13 | path = "."
14 | analyzer = ::RailsBestPractices::Analyzer.new(path, options)
15 | analyzer.analyze
16 | @output = analyzer.errors
17 | end
18 |
19 | def analyze
20 | @problems = @output.collect do |problem|
21 | {
22 | file: problem.filename,
23 | line: problem.line_number,
24 | problem: problem.message,
25 | url: problem.url
26 | }
27 | end
28 | total = ["Found #{@problems.count} errors."]
29 | @rails_best_practices_results = { total: total, problems: @problems }
30 | end
31 |
32 | def to_h
33 | { rails_best_practices: @rails_best_practices_results }
34 | end
35 |
36 | def per_file_info(out)
37 | @rails_best_practices_results[:problems].each do |problem|
38 | next if problem[:file] == "" || problem[:problem].nil?
39 |
40 | lines = problem[:line].split(/\s*,\s*/)
41 | lines.each do |line|
42 | out[problem[:file]][line] << { type: :rails_best_practices, description: problem[:problem] }
43 | end
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/reek/grapher.rb:
--------------------------------------------------------------------------------
1 | MetricFu.reporting_require { "graphs/grapher" }
2 | module MetricFu
3 | class ReekGrapher < Grapher
4 | attr_accessor :reek_count, :labels
5 |
6 | def self.metric
7 | :reek
8 | end
9 |
10 | def initialize
11 | super
12 | @reek_count = {}
13 | @labels = {}
14 | end
15 |
16 | def get_metrics(metrics, date)
17 | if metrics && metrics[:reek]
18 | counter = @labels.size
19 | @labels.update(@labels.size => date)
20 |
21 | metrics[:reek][:matches].each do |reek_chunk|
22 | reek_chunk[:code_smells].each do |code_smell|
23 | # speaking of code smell...
24 | @reek_count[code_smell[:type]] = [] if @reek_count[code_smell[:type]].nil?
25 | if @reek_count[code_smell[:type]][counter].nil?
26 | @reek_count[code_smell[:type]][counter] = 1
27 | else
28 | @reek_count[code_smell[:type]][counter] += 1
29 | end
30 | end
31 | end
32 | end
33 | end
34 |
35 | def title
36 | "Reek: code smells"
37 | end
38 |
39 | def data
40 | @reek_count.map do |name, count|
41 | [name, nil_counts_to_zero(count).join(",")]
42 | end
43 | end
44 |
45 | def output_filename
46 | "reek.js"
47 | end
48 |
49 | private
50 |
51 | def nil_counts_to_zero(counts)
52 | counts.map { |count| count || 0 }
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/lib/metric_fu/formatter/syntax.rb:
--------------------------------------------------------------------------------
1 | require "coderay"
2 | MetricFu.lib_require { "utility" }
3 | # CodeRay options
4 | # used to analyze source code, because object Tokens is a list of tokens with specified types.
5 | # :tab_width – tabulation width in spaces. Default: 8
6 | # :css – how to include the styles (:class и :style). Default: :class)
7 | #
8 | # :wrap – wrap result in html tag :page, :div, :span or not to wrap (nil)
9 | #
10 | # :line_numbers – how render line numbers (:table, :inline, :list or nil)
11 | #
12 | # :line_number_start – first line number
13 | #
14 | # :bold_every – make every n-th line number bold. Default: 10
15 | module MetricFu
16 | module Formatter
17 | class Syntax
18 | def initialize
19 | @options = { css: :class, style: :alpha }
20 | @line_number_options = { line_numbers: :inline, line_number_start: 0 }
21 | end
22 |
23 | def highlight(ruby_text, line_number)
24 | tokens = tokenize(ruby_text)
25 | tokens.div(highlight_options(line_number))
26 | end
27 |
28 | def highlight_options(line_number)
29 | line_number = line_number.to_i
30 | if line_number > 0
31 | @options.merge(@line_number_options.merge(line_number_start: line_number))
32 | else
33 | @options
34 | end
35 | end
36 |
37 | private
38 |
39 | def tokenize(ruby_text)
40 | ascii_text = MetricFu::Utility.clean_ascii_text(ruby_text)
41 | tokens = CodeRay.scan(ascii_text, :ruby)
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/analysis/table.rb:
--------------------------------------------------------------------------------
1 | %w(record).each do |path|
2 | MetricFu.metrics_require { "hotspots/analysis/#{path}" }
3 | end
4 |
5 | module MetricFu
6 | class Table
7 | include Enumerable
8 |
9 | def initialize(opts = {})
10 | @rows = []
11 | @columns = opts.fetch(:column_names)
12 |
13 | @make_index = opts.fetch(:make_index) { true }
14 | @metric_index = {}
15 | end
16 |
17 | def <<(row)
18 | record = nil
19 | if row.is_a?(MetricFu::Record)
20 | record = row
21 | else
22 | record = MetricFu::Record.new(row, @columns)
23 | end
24 | @rows << record
25 | updated_key_index(record) if @make_index
26 | end
27 |
28 | def each
29 | @rows.each do |row|
30 | yield row
31 | end
32 | end
33 |
34 | def size
35 | length
36 | end
37 |
38 | def length
39 | @rows.length
40 | end
41 |
42 | def [](index)
43 | @rows[index]
44 | end
45 |
46 | def column(column_name)
47 | arr = []
48 | @rows.each do |row|
49 | arr << row[column_name]
50 | end
51 | arr
52 | end
53 |
54 | def group_by_metric
55 | @metric_index.to_a
56 | end
57 |
58 | private
59 |
60 | def updated_key_index(record)
61 | if record.has_key?("metric")
62 | @metric_index[record.metric] ||= MetricFu::Table.new(column_names: @columns, make_index: false)
63 | @metric_index[record.metric] << record
64 | end
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/spec/metric_fu/formatter/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for formatters" do
5 | it_behaves_like "configured" do
6 | describe "#configure_formatter" do
7 | before(:each) { get_new_config }
8 |
9 | context "given a built-in formatter" do
10 | before do
11 | @config.configure_formatter("html")
12 | end
13 |
14 | it "adds to the list of formatters" do
15 | expect(@config.formatters.first).to be_an_instance_of(MetricFu::Formatter::HTML)
16 | end
17 | end
18 |
19 | context "given a custom formatter by class name" do
20 | before do
21 | stub_const("MyCustomFormatter", Class.new { def initialize(*); end })
22 | @config.configure_formatter("MyCustomFormatter")
23 | end
24 |
25 | it "adds to the list of formatters" do
26 | expect(@config.formatters.first).to be_an_instance_of(MyCustomFormatter)
27 | end
28 | end
29 |
30 | context "given multiple formatters" do
31 | before do
32 | stub_const("MyCustomFormatter", Class.new { def initialize(*); end })
33 | @config.configure_formatter("html")
34 | @config.configure_formatter("yaml")
35 | @config.configure_formatter("MyCustomFormatter")
36 | end
37 |
38 | it "adds each to the list of formatters" do
39 | expect(@config.formatters.count).to eq(3)
40 | end
41 | end
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/metric_fu/formatter_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MetricFu::Formatter do
4 | describe "formatter class loading" do
5 | context "given a built-in formatter (string)" do
6 | subject { MetricFu::Formatter.class_for("html") }
7 |
8 | it "returns the formatter class" do
9 | expect(subject).to eq(MetricFu::Formatter::HTML)
10 | end
11 | end
12 |
13 | context "given a built-in formatter (symbol)" do
14 | subject { MetricFu::Formatter.class_for(:yaml) }
15 |
16 | it "returns the formatter class" do
17 | expect(subject).to eq(MetricFu::Formatter::YAML)
18 | end
19 | end
20 |
21 | context "given an unknown built-in formatter" do
22 | subject { MetricFu::Formatter.class_for(:unknown) }
23 |
24 | it "raises an error" do
25 | expect { subject }.to raise_error(NameError)
26 | end
27 | end
28 |
29 | context "given a custom formatter that exists" do
30 | subject { MetricFu::Formatter.class_for("MyCustomFormatter") }
31 |
32 | before do
33 | stub_const("MyCustomFormatter", Class.new { def initialize(*); end })
34 | end
35 |
36 | it "returns the formatter class" do
37 | expect(subject).to eq(MyCustomFormatter)
38 | end
39 | end
40 |
41 | context "given a custom formatter that doesnt exist" do
42 | subject { MetricFu::Formatter.class_for("MyNonExistentCustomFormatter") }
43 |
44 | it "raises an error" do
45 | expect { subject }.to raise_error(NameError)
46 | end
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/metric_fu/logger.rb:
--------------------------------------------------------------------------------
1 | require "logger"
2 | require "forwardable"
3 | module MetricFu
4 | def self.logger
5 | @logger ||= ::MetricFu::Logger.new($stdout)
6 | end
7 |
8 | class Logger
9 | extend Forwardable
10 | MfLogger = ::Logger
11 |
12 | def initialize(stdout)
13 | @logger = MfLogger.new(stdout)
14 | self.debug_on = false
15 | self.formatter = ->(_severity, _time, _progname, msg) { "#{msg}\n" }
16 | self.level = "info"
17 | end
18 |
19 | def debug_on=(bool)
20 | self.level = bool ? "debug" : "info"
21 | end
22 |
23 | def debug_on
24 | @logger.level == MfLogger::DEBUG
25 | end
26 |
27 | def_delegators :@logger, :info, :warn, :error, :fatal, :unknown
28 |
29 | LEVELS = {
30 | "debug" => MfLogger::DEBUG,
31 | "info" => MfLogger::INFO,
32 | "warn" => MfLogger::WARN,
33 | "error" => MfLogger::ERROR,
34 | "fatal" => MfLogger::FATAL,
35 | "unknown" => MfLogger::UNKNOWN,
36 | }
37 |
38 | def level=(level)
39 | @logger.level = LEVELS.fetch(level.to_s.downcase) { level }
40 | end
41 |
42 | def formatter=(formatter)
43 | @logger.formatter = formatter
44 | end
45 |
46 | def log(msg)
47 | @logger.info "*" * 5 + msg.to_s
48 | end
49 |
50 | def debug(msg)
51 | @logger.debug "*" * 5 + msg.to_s
52 | end
53 | end
54 | end
55 | # For backward compatibility
56 | def mf_debug(msg, &block)
57 | MetricFu.logger.debug(msg, &block)
58 | end
59 |
60 | def mf_log(msg, &block)
61 | MetricFu.logger.log(msg, &block)
62 | end
63 |
--------------------------------------------------------------------------------
/spec/capture_warnings.rb:
--------------------------------------------------------------------------------
1 | # https://raw.githubusercontent.com/metric_fu/metric_fu/master/spec/capture_warnings.rb
2 | require "rubygems" if RUBY_VERSION =~ /^1\.8/
3 | require "bundler/setup"
4 | require "rspec/core"
5 | require "rspec/expectations"
6 | require "tempfile"
7 | require "fileutils"
8 |
9 | stderr_file = Tempfile.new("app.stderr")
10 | app_root ||= Dir.pwd
11 | output_dir = File.join(app_root, "tmp")
12 | FileUtils.mkdir_p(output_dir)
13 | bundle_dir = File.join(app_root, "bundle")
14 |
15 | RSpec.configure do |config|
16 | config.before(:suite) do
17 | $stderr.reopen(stderr_file.path)
18 | $VERBOSE = true
19 | end
20 |
21 | config.after(:suite) do
22 | stderr_file.rewind
23 | lines = stderr_file.read.split("\n").uniq
24 | stderr_file.close!
25 |
26 | $stderr.reopen(STDERR)
27 |
28 | app_warnings, other_warnings = lines.partition { |line|
29 | line.include?(app_root) && !line.include?(bundle_dir)
30 | }
31 |
32 | if app_warnings.any?
33 | puts <<-WARNINGS
34 | #{'-' * 30} app warnings: #{'-' * 30}
35 |
36 | #{app_warnings.join("\n")}
37 |
38 | #{'-' * 75}
39 | WARNINGS
40 | end
41 |
42 | if other_warnings.any?
43 | output_file = File.join(output_dir, "warnings.txt")
44 | File.write(output_file, other_warnings.join("\n") << "\n")
45 | puts
46 | puts "Non-app warnings written to tmp/warnings.txt"
47 | puts
48 | end
49 |
50 | # fail the build...
51 | if app_warnings.any?
52 | abort "Failing build due to app warnings: #{app_warnings.inspect}"
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/fixtures/hotspots/several_metrics.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :reek:
3 | :matches:
4 | - :file_path: lib/client/client.rb
5 | :code_smells:
6 | - :type: Large Class
7 | :message: has at least 27 methods
8 | :method: Devver::Client
9 | - :type: Long Method
10 | :message: has approx 6 statements
11 | :method: Devver::Client#client_requested_sync
12 | :flog:
13 | :method_containers:
14 | - :highest_score: 61.5870319141946
15 | :path: /lib/client/client.rb
16 | :methods:
17 | Client#client_requested_sync:
18 | :path: /lib/client/client.rb
19 | :score: 37.9270319141946
20 | :operators:
21 | :+: 1.70000000000001
22 | :/: 1.80000000000001
23 | :method_at_line: 1.90000000000001
24 | :puts: 1.70000000000001
25 | :assignment: 33.0000000000001
26 | :in_method?: 1.70000000000001
27 | :message: 1.70000000000001
28 | :branch: 12.6
29 | :<<: 3.40000000000001
30 | :each: 1.50000000000001
31 | :lit_fixnum: 1.45
32 | :raise: 1.80000000000001
33 | :each_pair: 1.3
34 | :*: 1.60000000000001
35 | :to_f: 2.00000000000001
36 | :each_with_index: 3.00000000000001
37 | :[]: 22.3000000000001
38 | :new: 1.60000000000001
39 | :average_score: 11.1209009055421
40 | :total_score: 1817.6
41 | :name: Client#client_requested_sync
42 | :churn:
43 | :changes:
44 | - :file_path: lib/client/client.rb
45 | :times_changed: 54
46 | - :file_path: lib/client/foo.rb
47 | :times_changed: 52
48 |
--------------------------------------------------------------------------------
/spec/fixtures/hotspots/generator.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :reek:
3 | :matches:
4 | - :file_path: lib/client/client.rb:42
5 | :code_smells:
6 | - :type: Large Class
7 | :message: has at least 27 methods
8 | :method: Devver::Client
9 | - :type: Long Method
10 | :message: has approx 6 statements
11 | :method: Devver::Client#client_requested_sync
12 | :flog:
13 | :method_containers:
14 | - :highest_score: 61.5870319141946
15 | :path: /lib/client/client.rb
16 | :methods:
17 | Client#client_requested_sync:
18 | :path: /lib/client/client.rb:42
19 | :score: 37.9270319141946
20 | :operators:
21 | :+: 1.70000000000001
22 | :/: 1.80000000000001
23 | :method_at_line: 1.90000000000001
24 | :puts: 1.70000000000001
25 | :assignment: 33.0000000000001
26 | :in_method?: 1.70000000000001
27 | :message: 1.70000000000001
28 | :branch: 12.6
29 | :<<: 3.40000000000001
30 | :each: 1.50000000000001
31 | :lit_fixnum: 1.45
32 | :raise: 1.80000000000001
33 | :each_pair: 1.3
34 | :*: 1.60000000000001
35 | :to_f: 2.00000000000001
36 | :each_with_index: 3.00000000000001
37 | :[]: 22.3000000000001
38 | :new: 1.60000000000001
39 | :average_score: 11.1209009055421
40 | :total_score: 1817.6
41 | :name: Client#client_requested_sync
42 | :churn:
43 | :changes:
44 | - :file_path: lib/client/client.rb
45 | :times_changed: 54
46 | - :file_path: lib/client/foo.rb
47 | :times_changed: 52
48 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/report.rb:
--------------------------------------------------------------------------------
1 | MetricFu.lib_require { "formatter/syntax" }
2 | MetricFu.lib_require { "templates/template" }
3 |
4 | # Creates an HTML document for a given analyzed file,
5 | # with scored metrics annotating the relevant line.
6 | module MetricFu
7 | module Templates
8 | class Report < MetricFu::Template
9 | # @param file [String] the analyzed file to annotate
10 | # @param lines [Hash] of line number [String] keyed to an list [[Array] of metrics for that line. Each metric in the list is a hash containing the keys :type => metric_name, :descrption => metric_score
11 | # @example file and lines
12 | # file: "lib/metric_fu/gem_version.rb
13 | # lines: {"30"=>[{:type=>:flog, :description=>"Score of 22.43"}], "42"=>[{:type=>:flog, :description=>"Score of 8.64"}]}
14 | def initialize(file, lines)
15 | @file = file
16 | @lines = lines
17 | @data = File.open(file, "rb") { |f| f.readlines }
18 | end
19 |
20 | def render
21 | erbify("report")
22 | end
23 |
24 | def convert_ruby_to_html(ruby_text, line_number)
25 | MetricFu::Formatter::Syntax.new.highlight(ruby_text, line_number)
26 | end
27 |
28 | def line_for_display(line, line_number)
29 | if MetricFu::Formatter::Templates.option("syntax_highlighting")
30 | line_for_display = convert_ruby_to_html(line, line_number)
31 | else
32 | "#{line_number}#{line}"
33 | end
34 | end
35 |
36 | def template_directory
37 | File.dirname(__FILE__)
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/spec/metric_fu_spec.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | require "spec_helper"
3 |
4 | describe MetricFu do
5 | before do
6 | MetricFu.run_dir = Dir.pwd
7 | MetricFu.report_name = nil
8 | end
9 |
10 | describe ".with_run_dir" do
11 | it "temporarily uses a specified `run_dir`" do
12 | expect(MetricFu.run_dir).to eq(Dir.pwd)
13 |
14 | MetricFu.with_run_dir "spec/dummy" do
15 | expect(MetricFu.run_dir).to eq("spec/dummy")
16 | end
17 |
18 | expect(MetricFu.run_dir).to eq(Dir.pwd)
19 | end
20 | end
21 |
22 | specify "the default report_name is the run directory base name" do
23 | MetricFu.with_run_dir "spec/support" do
24 | expect(MetricFu.report_name).to eq("support")
25 | end
26 | end
27 |
28 | specify "the user can set the report_name" do
29 | original_report_name = MetricFu.report_name
30 |
31 | MetricFu.report_name = "override"
32 | expect(MetricFu.report_name).to eq("override")
33 |
34 | MetricFu.report_name = original_report_name
35 | end
36 |
37 | it "has a global report time (corresponding to the time of the VCS code state)" do
38 | expect(MetricFu.report_time - Time.now).to be_within(0.1).of(0)
39 | end
40 |
41 | it "has a global current time (corresponding to report generation time)" do
42 | expect(MetricFu.current_time - Time.now).to be_within(0.1).of(0)
43 | end
44 |
45 | it "has a global report id" do
46 | expect(MetricFu.report_id).to eq(Time.now.strftime("%Y%m%d"))
47 | end
48 |
49 | it "has a global report fingerprint (corresponding to VCS code state)" do
50 | expect(MetricFu.report_fingerprint.to_i - Time.now.to_i).to be_within(0.1).of(0)
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | source "https://rubygems.org"
3 |
4 | if RUBY_VERSION >= '1.9.3'
5 | gem "rubocop", platforms: :mri, groups: [:test, :local_development]
6 | gem "activesupport", "~> 4.2"
7 |
8 | if RUBY_VERSION > '2.3'
9 | gem "json", ">= 2.0"
10 | else
11 | gem "json", "~> 1.7"
12 | end
13 | if RUBY_VERSION =~ /^2\.0\..*/
14 | gem "unparser", "0.2.4"
15 | elsif RUBY_VERSION =~ /^1\.9\.3.*/
16 | gem "unparser", "0.2.4"
17 | gem "json_pure", "2.0.1"
18 | gem "mime-types", "2.99.3"
19 | gem "rest-client", "1.8.0"
20 | gem "addressable", "2.4.0"
21 | gem "ffi", "1.9.14" # windows support
22 | end
23 | elsif RUBY_VERSION =~ /^1\.9\.2.*/
24 | # because of https://github.com/railsbp/rails_best_practices/blob/master/rails_best_practices.gemspec
25 | gem "activesupport", "~> 3.2"
26 | # because of https://github.com/troessner/reek/issues/334
27 | gem "reek", "~> 1.4.0"
28 | # rbp -> as -> i18n
29 | gem "i18n", "0.6.11"
30 | gem "parallel", "= 1.3.3" # 1.3.4 disallows 1.9.2
31 | gem "unparser", "0.1.5"
32 | gem "json_pure", "2.0.1"
33 | gem "mime-types", "2.99.3"
34 | gem "rest-client", "1.8.0"
35 | gem "json", "~> 1.7"
36 | gem "addressable", "2.4.0"
37 | gem "rainbow", "2.1.0"
38 | gem "ffi", "1.9.14" # windows support
39 | end
40 |
41 | gemspec path: File.expand_path("..", __FILE__)
42 |
43 | platform :jruby do
44 | group :jruby do
45 | gem "jruby-openssl", "~> 0.9.17"
46 | end
47 | end
48 |
49 | group :test, :local_development do
50 | gem "pry"
51 | gem "pry-nav"
52 | end
53 |
54 | # Added by devtools
55 | group :development do
56 | gem "rake", "~> 10.1.0"
57 | gem "yard", "~> 0.8.7", group: :yard
58 | end
59 |
--------------------------------------------------------------------------------
/spec/fixtures/hotspots/generator_analysis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | files:
3 | - location:
4 | class_name:
5 | method_name:
6 | file_path: lib/client/client.rb:42
7 | file_name: lib/client/client.rb
8 | line_number: '42'
9 | hash_key: '["lib/client/client.rb:42", nil, nil]'
10 | details:
11 | flog: complexity is 37.9
12 | reek: found 2 code smells
13 | - location:
14 | class_name:
15 | method_name:
16 | file_path: lib/client/client.rb
17 | file_name: lib/client/client.rb
18 | line_number:
19 | hash_key: '["lib/client/client.rb", nil, nil]'
20 | details:
21 | churn: detected high level of churn (changed 54 times)
22 | - location:
23 | class_name:
24 | method_name:
25 | file_path: lib/client/foo.rb
26 | file_name: lib/client/foo.rb
27 | line_number:
28 | hash_key: '["lib/client/foo.rb", nil, nil]'
29 | details:
30 | churn: detected high level of churn (changed 52 times)
31 | classes:
32 | - location:
33 | class_name: Client
34 | method_name:
35 | file_path: lib/client/client.rb:42
36 | file_name: lib/client/client.rb
37 | line_number: '42'
38 | hash_key: '["lib/client/client.rb:42", "Client", nil]'
39 | details:
40 | flog: complexity is 37.9
41 | reek: found 2 code smells
42 | methods:
43 | - location:
44 | class_name: Client
45 | method_name: Client#client_requested_sync
46 | file_path: lib/client/client.rb:42
47 | file_name: lib/client/client.rb
48 | line_number: '42'
49 | hash_key: '["lib/client/client.rb:42", "Client", "Client#client_requested_sync"]'
50 | simple_method_name: '#client_requested_sync'
51 | details:
52 | flog: complexity is 37.9
53 | reek: found 1 code smells
54 |
--------------------------------------------------------------------------------
/lib/metric_fu/reporting/graphs/grapher.rb:
--------------------------------------------------------------------------------
1 | require "multi_json"
2 | module MetricFu
3 | class Grapher
4 | @graphers = []
5 | # @return all subclassed graphers [Array]
6 | def self.graphers
7 | @graphers
8 | end
9 |
10 | def self.inherited(subclass)
11 | @graphers << subclass
12 | end
13 |
14 | def self.get_grapher(metric)
15 | graphers.find { |grapher|grapher.metric.to_s == metric.to_s }
16 | end
17 |
18 | attr_accessor :output_directory
19 |
20 | def initialize(opts = {})
21 | self.output_directory = opts[:output_directory]
22 | end
23 |
24 | def output_directory
25 | @output_directory || MetricFu::Io::FileSystem.directory("output_directory")
26 | end
27 |
28 | def get_metrics(_metrics, _sortable_prefix)
29 | not_implemented
30 | end
31 |
32 | def graph!
33 | labels = MultiJson.dump(@labels)
34 | content = <<-EOS
35 | var graph_title = '#{title}';
36 | #{build_data(data)}
37 | var graph_labels = #{labels};
38 | EOS
39 | File.open(File.join(output_directory, output_filename), "w") { |f| f << content }
40 | end
41 |
42 | def title
43 | not_implemented
44 | end
45 |
46 | def date
47 | not_implemented
48 | end
49 |
50 | def output_filename
51 | not_implemented
52 | end
53 |
54 | private
55 |
56 | def build_data(data)
57 | "var graph_series = [" << Array(data).map do |label, datum|
58 | "{name: '#{label}', data: [#{datum}]}"
59 | end.join(",") << "];"
60 | end
61 |
62 | def not_implemented
63 | raise "#{__LINE__} in #{__FILE__} from #{caller[0]}"
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/spec/metric_fu/reporting/graphs/graph_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.reporting_require { "graphs/graph" }
3 |
4 | describe MetricFu do
5 | describe "responding to #graph" do
6 | it "should return an instance of Graph" do
7 | expect(MetricFu.graph).to be_a(Graph)
8 | end
9 | end
10 | end
11 |
12 | describe MetricFu::Graph do
13 | before(:each) do
14 | @graph = MetricFu::Graph.new
15 | end
16 |
17 | describe "setting the date on the graph" do
18 | # TODO better test
19 | it "should set the date once for one data point" do
20 | metric_file = "metric_fu/tmp/_data/20101105.yml"
21 | expect(MetricFu::Utility).to receive(:glob).and_return([metric_file].sort)
22 | expect(MetricFu::Utility).to receive(:load_yaml).with(metric_file).and_return("Metrics")
23 | double_grapher = double
24 | expect(double_grapher).to receive(:get_metrics).with("Metrics", "11/5")
25 | expect(double_grapher).to receive(:graph!)
26 |
27 | @graph.graphers = [double_grapher]
28 | @graph.generate
29 | end
30 |
31 | # TODO better test
32 | it "should set the date when the data directory isn't in the default place" do
33 | metric_file = "/some/kind/of/weird/directory/somebody/configured/_data/20101105.yml"
34 | expect(MetricFu::Utility).to receive(:glob).and_return([metric_file].sort)
35 | expect(MetricFu::Utility).to receive(:load_yaml).with(metric_file).and_return("Metrics")
36 | double_grapher = double
37 | expect(double_grapher).to receive(:get_metrics).with("Metrics", "11/5")
38 | expect(double_grapher).to receive(:graph!)
39 |
40 | @graph.graphers = [double_grapher]
41 | @graph.generate
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/metric_fu/reporting/result_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.reporting_require { "result" }
3 |
4 | describe MetricFu do
5 | describe "#result" do
6 | it "should return an instance of Result" do
7 | expect(MetricFu.result.instance_of?(Result)).to be(true)
8 | end
9 | end
10 | end
11 |
12 | describe MetricFu::Result do
13 | before(:each) do
14 | @result = MetricFu::Result.new
15 | end
16 |
17 | describe "#as_yaml" do
18 | it "should call #result_hash" do
19 | result_hash = double("result_hash")
20 | expect(result_hash).to receive(:to_yaml)
21 |
22 | expect(@result).to receive(:result_hash).and_return(result_hash)
23 | @result.as_yaml
24 | end
25 | end
26 |
27 | describe "#result_hash" do
28 | end
29 |
30 | describe "#add" do
31 | it "should add a passed hash to the result_hash instance variable" do
32 | result_type = double("result_type")
33 | allow(result_type).to receive(:to_s).and_return("type")
34 |
35 | result_inst = double("result_inst")
36 | expect(result_type).to receive(:new).and_return(result_inst)
37 |
38 | expect(result_inst).to receive(:generate_result).and_return(a: "b")
39 | expect(result_inst).to receive(:respond_to?).and_return(false)
40 |
41 | expect(MetricFu::Generator).to receive(:get_generator).
42 | with(result_type).and_return(result_type)
43 | result_hash = double("result_hash")
44 | expect(result_hash).to receive(:merge!).with(a: "b")
45 | expect(@result).to receive(:result_hash).and_return(result_hash)
46 | expect(@result).to receive(:metric_options_for_result_type).with(result_type)
47 | @result.add(result_type)
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/churn/report.html.erb:
--------------------------------------------------------------------------------
1 | Source Control Churn Results
2 | Files that change a lot in your project may be bad a sign.
3 | This task uses your source control log to identify those files.
4 |
5 |
6 |
7 | | File Path |
8 | Times Changed |
9 |
10 | <% count = 0 %>
11 | <% if @churn[:changes] %>
12 | <% @churn[:changes].each do |change| %>
13 |
14 | | <%= link_to_filename(change[:file_path]) %> |
15 | <%= change[:times_changed] %> |
16 |
17 | <% count += 1 %>
18 | <% end %>
19 | <% end %>
20 |
21 |
22 | <% if @churn[:class_churn] %>
23 | Classes that change a lot in your project may be bad a sign.
24 |
25 |
26 | | File Path |
27 | Times Changed |
28 |
29 | <% count = 0 %>
30 | <% @churn[:class_churn].each do |change| %>
31 |
32 | | <%= link_to_filename(change['klass']['file']) %> <%= change['klass']['klass'] %> |
33 | <%= change['times_changed'] %> |
34 |
35 | <% count += 1 %>
36 | <% end %>
37 |
38 | <% end %>
39 |
40 | <% if @churn[:method_churn] %>
41 | Methods that change a lot in your project may be bad a sign.
42 |
43 |
44 | | File Path |
45 | Times Changed |
46 |
47 | <% count = 0 %>
48 | <% @churn[:method_churn].each do |change| %>
49 |
50 | | <%= link_to_filename(change['method']['file']) %> <%= change['method']['method'] %> |
51 | <%= change['times_changed'] %> |
52 |
53 | <% count += 1 %>
54 | <% end %>
55 |
56 | <% end %>
57 |
58 | <%= render_partial 'report_footer' %>
59 |
--------------------------------------------------------------------------------
/spec/metric_fu/formatter/yaml_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.formatter_require { "yaml" }
3 |
4 | describe MetricFu::Formatter::YAML do
5 | before do
6 | setup_fs
7 |
8 | config = MetricFu.configuration
9 |
10 | if config.mri?
11 | @metric1 = :cane
12 | else
13 | @metric1 = :stats
14 | config.templates_configuration do |c|
15 | c.syntax_highlighting = false
16 | end
17 | end
18 | allow(MetricFu::Metric.get_metric(@metric1)).to receive(:run_external).and_return("")
19 | @metric2 = :hotspots
20 | MetricFu.result.add(@metric1)
21 | MetricFu.result.add(@metric2)
22 | end
23 |
24 | context "In general" do
25 | it "creates a report yaml file" do
26 | expect {
27 | MetricFu::Formatter::YAML.new.finish
28 | }.to create_file("#{directory('base_directory')}/report.yml")
29 | end
30 | end
31 |
32 | context "given a custom output file" do
33 | before do
34 | @output = "customreport.yml"
35 | end
36 |
37 | it "creates a report yaml file to the custom output path" do
38 | expect {
39 | MetricFu::Formatter::YAML.new(output: @output).finish
40 | }.to create_file("#{directory('base_directory')}/customreport.yml")
41 | end
42 | end
43 |
44 | context "given a custom output stream" do
45 | before do
46 | @output = $stdout
47 | end
48 |
49 | it "creates a report yaml in the custom stream" do
50 | out = MetricFu::Utility.capture_output {
51 | MetricFu::Formatter::YAML.new(output: @output).finish
52 | }
53 | expect(out).to include ":#{@metric1}:"
54 | expect(out).to include ":#{@metric2}:"
55 | end
56 | end
57 |
58 | after do
59 | cleanup_fs
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/layout.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | metrics
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
30 |
31 | <%= @html %>
32 |
33 |
34 | <% graph_engine = MetricFu.configuration.graph_engine.to_s %>
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.rubocop_todo.yml:
--------------------------------------------------------------------------------
1 | # This configuration was generated by `rubocop --auto-gen-config`
2 | # on 2015-01-27 12:05:28 -0600 using RuboCop version 0.28.0.
3 | # The point is for the user to remove these configuration records
4 | # one by one as the offenses are removed from the code base.
5 | # Note that changes in the inspected code, or installation of new
6 | # versions of RuboCop, may require this file to be generated again.
7 |
8 | # Offense count: 2
9 | # Configuration parameters: AlignWith, SupportedStyles.
10 | Lint/EndAlignment:
11 | Enabled: false
12 |
13 | # Offense count: 2
14 | Lint/EnsureReturn:
15 | Enabled: false
16 |
17 | # Offense count: 1
18 | Lint/RescueException:
19 | Enabled: false
20 |
21 | # Offense count: 10
22 | Lint/ShadowingOuterLocalVariable:
23 | Enabled: false
24 |
25 | # Offense count: 1
26 | Lint/UselessAccessModifier:
27 | Enabled: false
28 |
29 | # Offense count: 27
30 | Lint/UselessAssignment:
31 | Enabled: false
32 |
33 | # Offense count: 44
34 | Metrics/AbcSize:
35 | Max: 56
36 |
37 | # Offense count: 393
38 | # Configuration parameters: AllowURI, URISchemes.
39 | Metrics/LineLength:
40 | Max: 709
41 |
42 | # Offense count: 3
43 | Metrics/PerceivedComplexity:
44 | Max: 13
45 |
46 | # Offense count: 4
47 | # Cop supports --auto-correct.
48 | # Configuration parameters: EnforcedStyle, SupportedStyles.
49 | Style/AndOr:
50 | Enabled: false
51 |
52 | # Offense count: 2
53 | Style/EmptyElse:
54 | Enabled: false
55 |
56 | # Offense count: 2
57 | # Configuration parameters: NamePrefix, NamePrefixBlacklist.
58 | Style/PredicateName:
59 | Enabled: false
60 |
61 | # Offense count: 1
62 | Style/RescueModifier:
63 | Enabled: false
64 |
65 | # Offense count: 4
66 | # Cop supports --auto-correct.
67 | # Configuration parameters: AllowAsExpressionSeparator.
68 | Style/Semicolon:
69 | Enabled: false
70 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/flay/grapher_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "flay/grapher" }
3 |
4 | describe FlayGrapher do
5 | before :each do
6 | @flay_grapher = MetricFu::FlayGrapher.new
7 | MetricFu.configuration
8 | end
9 |
10 | it "should respond to flay_score and labels" do
11 | expect(@flay_grapher).to respond_to(:flay_score)
12 | expect(@flay_grapher).to respond_to(:labels)
13 | end
14 |
15 | describe "responding to #initialize" do
16 | it "should initialise flay_score and labels" do
17 | expect(@flay_grapher.flay_score).to eq([])
18 | expect(@flay_grapher.labels).to eq({})
19 | end
20 | end
21 |
22 | describe "responding to #get_metrics" do
23 | context "when metrics were not generated" do
24 | before(:each) do
25 | @metrics = FIXTURE.load_metric("metric_missing.yml")
26 | @date = "1/2"
27 | end
28 |
29 | it "should not push to flay_score" do
30 | expect(@flay_grapher.flay_score).not_to receive(:push)
31 | @flay_grapher.get_metrics(@metrics, @date)
32 | end
33 |
34 | it "should not update labels with the date" do
35 | expect(@flay_grapher.labels).not_to receive(:update)
36 | @flay_grapher.get_metrics(@metrics, @date)
37 | end
38 | end
39 |
40 | context "when metrics have been generated" do
41 | before(:each) do
42 | @metrics = FIXTURE.load_metric("20090630.yml")
43 | @date = "1/2"
44 | end
45 |
46 | it "should push to flay_score" do
47 | expect(@flay_grapher.flay_score).to receive(:push).with(476)
48 | @flay_grapher.get_metrics(@metrics, @date)
49 | end
50 |
51 | it "should update labels with the date" do
52 | expect(@flay_grapher.labels).to receive(:update).with(0 => "1/2")
53 | @flay_grapher.get_metrics(@metrics, @date)
54 | end
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/rcov/grapher_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "rcov/grapher" }
3 |
4 | describe RcovGrapher do
5 | before :each do
6 | @rcov_grapher = MetricFu::RcovGrapher.new
7 | MetricFu.configuration
8 | end
9 |
10 | it "should respond to rcov_percent and labels" do
11 | expect(@rcov_grapher).to respond_to(:rcov_percent)
12 | expect(@rcov_grapher).to respond_to(:labels)
13 | end
14 |
15 | describe "responding to #initialize" do
16 | it "should initialise rcov_percent and labels" do
17 | expect(@rcov_grapher.rcov_percent).to eq([])
18 | expect(@rcov_grapher.labels).to eq({})
19 | end
20 | end
21 |
22 | describe "responding to #get_metrics" do
23 | context "when metrics were not generated" do
24 | before(:each) do
25 | @metrics = FIXTURE.load_metric("metric_missing.yml")
26 | @date = "1/2"
27 | end
28 |
29 | it "should not push to rcov_percent" do
30 | expect(@rcov_grapher.rcov_percent).not_to receive(:push)
31 | @rcov_grapher.get_metrics(@metrics, @date)
32 | end
33 |
34 | it "should not update labels with the date" do
35 | expect(@rcov_grapher.labels).not_to receive(:update)
36 | @rcov_grapher.get_metrics(@metrics, @date)
37 | end
38 | end
39 |
40 | context "when metrics have been generated" do
41 | before(:each) do
42 | @metrics = FIXTURE.load_metric("20090630.yml")
43 | @date = "1/2"
44 | end
45 |
46 | it "should push to rcov_percent" do
47 | expect(@rcov_grapher.rcov_percent).to receive(:push).with(49.6)
48 | @rcov_grapher.get_metrics(@metrics, @date)
49 | end
50 |
51 | it "should update labels with the date" do
52 | expect(@rcov_grapher.labels).to receive(:update).with(0 => "1/2")
53 | @rcov_grapher.get_metrics(@metrics, @date)
54 | end
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/roodi/grapher_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "roodi/grapher" }
3 |
4 | describe RoodiGrapher do
5 | before :each do
6 | @roodi_grapher = MetricFu::RoodiGrapher.new
7 | MetricFu.configuration
8 | end
9 |
10 | it "should respond to roodi_count and labels" do
11 | expect(@roodi_grapher).to respond_to(:roodi_count)
12 | expect(@roodi_grapher).to respond_to(:labels)
13 | end
14 |
15 | describe "responding to #initialize" do
16 | it "should initialise roodi_count and labels" do
17 | expect(@roodi_grapher.roodi_count).to eq([])
18 | expect(@roodi_grapher.labels).to eq({})
19 | end
20 | end
21 |
22 | describe "responding to #get_metrics" do
23 | context "when metrics were not generated" do
24 | before(:each) do
25 | @metrics = FIXTURE.load_metric("metric_missing.yml")
26 | @date = "1/2"
27 | end
28 |
29 | it "should not push to roodi_count" do
30 | expect(@roodi_grapher.roodi_count).not_to receive(:push)
31 | @roodi_grapher.get_metrics(@metrics, @date)
32 | end
33 |
34 | it "should not update labels with the date" do
35 | expect(@roodi_grapher.labels).not_to receive(:update)
36 | @roodi_grapher.get_metrics(@metrics, @date)
37 | end
38 | end
39 |
40 | context "when metrics have been generated" do
41 | before(:each) do
42 | @metrics = FIXTURE.load_metric("20090630.yml")
43 | @date = "1/2"
44 | end
45 |
46 | it "should push to roodi_count" do
47 | expect(@roodi_grapher.roodi_count).to receive(:push).with(13)
48 | @roodi_grapher.get_metrics(@metrics, @date)
49 | end
50 |
51 | it "should update labels with the date" do
52 | expect(@roodi_grapher.labels).to receive(:update).with(0 => "1/2")
53 | @roodi_grapher.get_metrics(@metrics, @date)
54 | end
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/metric_fu/reporting/result.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | # MetricFu.result memoizes access to a Result object, that will be
3 | # used throughout the lifecycle of the MetricFu app.
4 | def self.result
5 | @result ||= Result.new
6 | end
7 |
8 | # = Result
9 | #
10 | # The Result class is responsible for one thing:
11 | #
12 | # It tracks the results generated by each metric used in this test run.
13 | class Result
14 | # Renders the result of the result_hash into a yaml serialization
15 | # ready for writing out to a file.
16 | #
17 | # @return YAML
18 | # A YAML object containing the results of the result generation
19 | # process
20 | def as_yaml
21 | result_hash.to_yaml
22 | end
23 |
24 | def per_file_data
25 | @per_file_data ||= Hash.new do |hash, filename|
26 | hash[filename] = Hash.new do |h, line|
27 | h[line] = Array.new
28 | end
29 | end
30 | end
31 |
32 | def result_hash #:nodoc:
33 | @result_hash ||= {}
34 | end
35 |
36 | # Adds a hash from a passed result, produced by one of the Generator
37 | # classes to the aggregate result_hash managed by this hash.
38 | #
39 | # @param result_type Hash
40 | # The hash to add to the aggregate result_hash
41 | def add(result_type)
42 | mf_debug "result requested #{result_type}"
43 | metric_options = metric_options_for_result_type(result_type)
44 | generator_class = MetricFu::Generator.get_generator(result_type)
45 | mf_debug "result class found #{generator_class}"
46 | generator = generator_class.new(metric_options)
47 |
48 | result_hash.merge!(generator.generate_result)
49 |
50 | generator.per_file_info(per_file_data) if generator.respond_to?(:per_file_info)
51 | end
52 |
53 | private
54 |
55 | def metric_options_for_result_type(result_type)
56 | MetricFu::Metric.get_metric(result_type).run_options
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/spec/metric_fu/templates/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for templates" do
5 | it_behaves_like "configured" do
6 | describe "when there is no CC_BUILD_ARTIFACTS environment variable" do
7 | before(:each) do
8 | ENV["CC_BUILD_ARTIFACTS"] = nil
9 | get_new_config
10 | end
11 |
12 | it "should set @template_directory to the lib/templates relative " + "to @metric_fu_root_directory" do
13 | expected_template_dir = MetricFu.root.join("lib", "templates").to_s
14 | expect(template_directory).to eq(expected_template_dir)
15 | end
16 |
17 | it "should set @template_class to MetricFu::Templates::MetricsTemplate by default" do
18 | expect(template_class).to eq(MetricFu::Templates::MetricsTemplate)
19 | end
20 |
21 | describe "when a templates configuration is given" do
22 | before do
23 | class DummyTemplate; end
24 |
25 | @config.templates_configuration do |config|
26 | config.template_class = DummyTemplate
27 | config.link_prefix = "http:/"
28 | config.syntax_highlighting = false
29 | config.darwin_txmt_protocol_no_thanks = false
30 | end
31 | end
32 |
33 | it "should set given template_class" do
34 | expect(template_class).to eq(DummyTemplate)
35 | end
36 |
37 | it "should set given link_prefix" do
38 | expect(MetricFu::Formatter::Templates.option("link_prefix")).to eq("http:/")
39 | end
40 |
41 | it "should set given darwin_txmt_protocol_no_thanks" do
42 | expect(MetricFu::Formatter::Templates.option("darwin_txmt_protocol_no_thanks")).to be_falsey
43 | end
44 |
45 | it "should set given syntax_highlighting" do
46 | expect(MetricFu::Formatter::Templates.option("syntax_highlighting")).to be_falsey
47 | end
48 | end
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/flog/report.html.erb:
--------------------------------------------------------------------------------
1 | Flog Results
2 | Flog measures code complexity.
3 |
4 | <%= render_partial 'graph', {:graph_name => 'flog'} %>
5 |
6 | Total Flog score for all methods: <%= round_to_tenths @flog[:total]%>
7 | Average Flog score for all methods: <%= round_to_tenths @flog[:average]%>
8 |
9 |
10 |
11 | | File |
12 | Total score |
13 | Methods |
14 | Average score |
15 | Highest score |
16 |
17 | <% @flog[:method_containers].each do |method_container| %>
18 |
19 | | <%= method_container[:path] %> |
20 | <%= round_to_tenths method_container[:total_score] %> |
21 | <%= method_container[:methods].size %> |
22 | <%= round_to_tenths method_container[:average_score] %> |
23 | <%= round_to_tenths method_container[:highest_score] %> |
24 |
25 | <% end %>
26 |
27 |
28 | <% @flog[:method_containers].each do |method_container| %>
29 | <%= link_to_filename(method_container[:path]) %>
30 |
31 | <% method_container[:methods].each do |full_method_name, method_info| %>
32 | <% path, line = method_info[:path].split(":") if method_info[:path] %>
33 | <%= link_to_filename(path, line, full_method_name) %>
34 | Total Score: <%= round_to_tenths method_info[:score]%>
35 |
36 |
37 | | Score |
38 | Operator |
39 |
40 | <% method_info[:operators].each do |operator, score| %>
41 |
42 | | <%= round_to_tenths score %> |
43 | <%= operator %> |
44 |
45 | <% end %>
46 |
47 | <% end %>
48 | <% end %>
49 |
50 | <%= render_partial 'report_footer' %>
51 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/rcov/generator.rb:
--------------------------------------------------------------------------------
1 | MetricFu.lib_require { "utility" }
2 | MetricFu.lib_require { "calculate" }
3 | MetricFu.data_structures_require { "line_numbers" }
4 | require_relative "rcov_format_coverage"
5 | require_relative "rcov_line"
6 | require_relative "external_client"
7 |
8 | module MetricFu
9 | class RcovGenerator < MetricFu::Generator
10 | def self.metric
11 | :rcov
12 | end
13 |
14 | def emit
15 | if run_rcov?
16 | mf_debug "** Running the specs/tests in the [#{options[:environment]}] environment"
17 | mf_debug "** #{command}"
18 | `#{command}`
19 | end
20 | end
21 |
22 | def command
23 | @command ||= default_command
24 | end
25 |
26 | def command=(command)
27 | @command = command
28 | end
29 |
30 | def reset_output_location
31 | MetricFu::Utility.rm_rf(metric_directory, verbose: false)
32 | MetricFu::Utility.mkdir_p(metric_directory)
33 | end
34 |
35 | def default_command
36 | require "rake"
37 | reset_output_location
38 | test_files = FileList[*options[:test_files]].join(" ")
39 | rcov_opts = options[:rcov_opts].join(" ")
40 | %(RAILS_ENV=#{options[:environment]} rcov #{test_files} #{rcov_opts} >> #{default_output_file})
41 | end
42 |
43 | def analyze
44 | rcov_text = load_output
45 | formatter = MetricFu::RCovFormatCoverage.new(rcov_text)
46 | @rcov = formatter.to_h
47 | end
48 |
49 | def to_h
50 | {
51 | rcov: @rcov
52 | }
53 | end
54 |
55 | private
56 |
57 | # We never run rcov anymore
58 | def run_rcov?
59 | false
60 | end
61 |
62 | def load_output
63 | MetricFu::RCovTestCoverageClient.new(output_file).load
64 | end
65 |
66 | def output_file
67 | options.fetch(:external)
68 | end
69 |
70 | # Only used if run_rcov? is true
71 | def default_output_file
72 | output_file || File.join(metric_directory, "rcov.txt")
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/lib/metric_fu/templates/css/buttons.css:
--------------------------------------------------------------------------------
1 | /* --------------------------------------------------------------
2 |
3 | buttons.css
4 | * Gives you some great CSS-only buttons.
5 |
6 | Created by Kevin Hale [particletree.com]
7 | * particletree.com/features/rediscovering-the-button-element
8 |
9 | See Readme.txt in this folder for instructions.
10 |
11 | -------------------------------------------------------------- */
12 |
13 | button {
14 | display:block;
15 | float:left;
16 | margin:0 0.583em 0.667em 0;
17 | padding:5px 10px 5px 7px; /* Links */
18 |
19 | border:1px solid #dedede;
20 | border-top:1px solid #eee;
21 | border-left:1px solid #eee;
22 |
23 | background-color:#f5f5f5;
24 | font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
25 | font-size:100%;
26 | line-height:130%;
27 | text-decoration:none;
28 | font-weight:bold;
29 | color:#565656;
30 | cursor:pointer;
31 | }
32 | button {
33 | width:auto;
34 | overflow:visible;
35 | padding:4px 10px 3px 7px; /* IE6 */
36 | }
37 | button[type] {
38 | padding:4px 10px 4px 7px; /* Firefox */
39 | line-height:17px; /* Safari */
40 | }
41 | *:first-child+html button[type] {
42 | padding:4px 10px 3px 7px; /* IE7 */
43 | }
44 | button img {
45 | margin:0 3px -3px 0 !important;
46 | padding:0;
47 | border:none;
48 | width:16px;
49 | height:16px;
50 | float:none;
51 | }
52 |
53 |
54 | /* Button colors
55 | -------------------------------------------------------------- */
56 |
57 | /* Standard */
58 | button:hover {
59 | background-color:#dff4ff;
60 | border:1px solid #c2e1ef;
61 | color:#336699;
62 | }
63 |
64 | /* Positive */
65 | body .positive {
66 | color:#529214;
67 | }
68 | button.positive:hover {
69 | background-color:#E6EFC2;
70 | border:1px solid #C6D880;
71 | color:#529214;
72 | }
73 |
74 | /* Negative */
75 | body .negative {
76 | color:#d12f19;
77 | }
78 | button.negative:hover {
79 | background:#fbe3e4;
80 | border:1px solid #fbc2c4;
81 | color:#d12f19;
82 | }
83 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/rails_best_practices/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "shared/configured"
3 |
4 | describe MetricFu::Configuration, "for rails_best_practices" do
5 | it_behaves_like "configured" do
6 | describe "if #rails? is true " do
7 | before(:each) do
8 | @config = MetricFu.configuration
9 | allow(@config).to receive(:rails?).and_return(true)
10 | @config.reset
11 | MetricFu.configure
12 | %w(rails_best_practices).each do |metric|
13 | load_metric metric
14 | end
15 | end
16 |
17 | describe "#set_graphs " do
18 | it "should set the graphs to include rails_best_practices" do
19 | expect(MetricFu::Metric.get_metric(:rails_best_practices).has_graph?).to be_truthy
20 | end
21 | end
22 |
23 | it "should default @rails_best_practices to { :silent => true }" do
24 | load_metric "rails_best_practices"
25 | rbp = MetricFu::MetricRailsBestPractices.new
26 | expect(rbp.run_options).to eq(exclude: [], silent: true)
27 | end
28 |
29 | it "can configure @rails_best_practices 'exclude' using the sugar" do
30 | load_metric "rails_best_practices"
31 | rbp = MetricFu::Metric.get_metric(:rails_best_practices)
32 | rbp.exclude = ["config/chef"]
33 | expect(rbp.run_options).to eq(
34 | exclude: ["config/chef"],
35 | silent: true
36 | )
37 |
38 | end
39 | end
40 |
41 | describe "if #rails? is false " do
42 | before(:each) do
43 | get_new_config
44 | allow(@config).to receive(:rails?).and_return(false)
45 | %w(rails_best_practices).each do |metric|
46 | load_metric metric
47 | end
48 | end
49 |
50 | it "should set the registered code_dirs to ['lib']" do
51 | expect(directory("code_dirs")).to eq(["lib"])
52 | end
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/lib/metric_fu/cli/helper.rb:
--------------------------------------------------------------------------------
1 | require "metric_fu"
2 | require "metric_fu/cli/parser"
3 | MetricFu.lib_require { "run" }
4 | # see https://github.com/grosser/pru/blob/master/bin/pru
5 | module MetricFu
6 | module Cli
7 | def self.immediate_shutdown!
8 | exit(1)
9 | end
10 | def self.complete!
11 | exit(0)
12 | end
13 | class Helper
14 | def initialize
15 | @metric_fu = MetricFu::Run.new
16 | end
17 |
18 | def run(options = {})
19 | @metric_fu.run(options)
20 | complete
21 | end
22 |
23 | def version
24 | MetricFu::VERSION
25 | end
26 |
27 | def shutdown
28 | out "\nShutting down. Bye"
29 | MetricFu::Cli.immediate_shutdown!
30 | end
31 |
32 | def banner
33 | "MetricFu: A Fistful of code metrics"
34 | end
35 |
36 | def usage
37 | <<-EOS
38 | #{banner}
39 | Use --help for help
40 | EOS
41 | end
42 |
43 | def executable_name
44 | "metric_fu"
45 | end
46 |
47 | def metrics
48 | MetricFu::Metric.metrics.map(&:name).sort_by(&:to_s)
49 | end
50 |
51 | def process_options(argv = [])
52 | options = MetricFu::Cli::MicroOptParse::Parser.new do |p|
53 | p.banner = banner
54 | p.version = version
55 | p.option :run, "Run all metrics with defaults", default: true
56 | metrics.each do |metric|
57 | p.option metric.to_sym, "Enables or disables #{metric}", default: true # , :value_in_set => [true, false]
58 | end
59 | p.option :open, "Open report in browser (if supported by formatter)", default: true
60 | end.process!(argv)
61 | options
62 | end
63 |
64 | private
65 |
66 | def out(text)
67 | STDOUT.puts text
68 | end
69 |
70 | def error(text)
71 | STDERR.puts text
72 | end
73 |
74 | def complete
75 | out "all done"
76 | MetricFu::Cli.complete!
77 | end
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/lib/metric_fu/utility.rb:
--------------------------------------------------------------------------------
1 | require "yaml"
2 | require "fileutils"
3 | module MetricFu
4 | module Utility
5 | module_function
6 |
7 | ESCAPE_CODES_PATTERN = Regexp.new('\e\[(?:\d;)?\d{1,2}m')
8 |
9 | # Removes non-ASCII characters
10 | def clean_ascii_text(text)
11 | if text.respond_to?(:encode)
12 | # avoids invalid multi-byte escape error
13 | ascii_text = text.encode("ASCII", invalid: :replace, undef: :replace, replace: "")
14 | # see http://www.ruby-forum.com/topic/183413
15 | pattern = Regexp.new('[\x80-\xff]', nil, "n")
16 | ascii_text.gsub(pattern, "")
17 | else
18 | text
19 | end
20 | end
21 |
22 | def stringify_keys(hash)
23 | result = {}
24 | hash.each do |key, value|
25 | result[key.to_s] = value
26 | end
27 | result
28 | end
29 |
30 | def strip_escape_codes(text)
31 | text.gsub(ESCAPE_CODES_PATTERN, "")
32 | end
33 |
34 | def rm_rf(*args)
35 | FileUtils.rm_rf(*args)
36 | end
37 |
38 | def mkdir_p(dir, **args)
39 | FileUtils.mkdir_p(dir, **args)
40 | end
41 |
42 | def glob(*args)
43 | Dir.glob(*args)
44 | end
45 |
46 | def load_yaml(file)
47 | YAML.load_file(file)
48 | end
49 |
50 | def binread(file)
51 | File.binread(file)
52 | end
53 |
54 | # From episode 029 of Ruby Tapas by Avdi
55 | # https://rubytapas.dpdcart.com/subscriber/post?id=88
56 | def capture_output(stream = STDOUT, &_block)
57 | old_stdout = stream.clone
58 | pipe_r, pipe_w = IO.pipe
59 | pipe_r.sync = true
60 | output = ""
61 | reader = Thread.new do
62 | begin
63 | loop do
64 | output << pipe_r.readpartial(1024)
65 | end
66 | rescue EOFError
67 | end
68 | end
69 | stream.reopen(pipe_w)
70 | yield
71 | ensure
72 | stream.reopen(old_stdout)
73 | pipe_w.close
74 | reader.join
75 | pipe_r.close
76 | return output
77 | end
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/saikuro/report.html.erb:
--------------------------------------------------------------------------------
1 | Saikuro Results
2 | Saikuro analyzes ruby code for cyclomatic complexity.
3 |
4 | Analyzed Methods
5 |
6 |
7 | | Method Name |
8 | Complexity |
9 | # Lines |
10 |
11 | <% @saikuro[:methods].each do |method| %>
12 |
13 | | <%= method[:name] %> |
14 | <%= method[:complexity] %> |
15 | <%= method[:lines] %> |
16 |
17 | <% end %>
18 |
19 |
20 |
21 |
22 | Analyzed Classes
23 |
24 |
25 | | Class Name |
26 | Complexity |
27 | # Lines |
28 |
29 | <% @saikuro[:classes].each do |klass| %>
30 |
31 | | <%= klass[:name] %> |
32 | <%= klass[:complexity] %> |
33 | <%= klass[:lines] %> |
34 |
35 | <% end %>
36 |
37 |
38 |
39 | Analyzed Files
40 | <% @saikuro[:files].each do |file| %>
41 | <% file[:classes].each do |klass| %>
42 | <% if !klass[:methods].empty? %>
43 | <%=link_to_filename(file[:filename])%>
44 | Class : <%= klass[:class_name] %>
45 | Total complexity : <%= klass[:complexity] %>
46 | Total lines : <%= klass[:lines] %>
47 |
48 |
49 | | Method |
50 | Complexity |
51 | # Lines |
52 |
53 | <% klass[:methods].each do |method| %>
54 |
55 | |
56 | <%= method[:name] %>
57 | |
58 |
59 | <%= method[:complexity] %>
60 | |
61 |
62 | <%= method[:lines] %>
63 | |
64 |
65 | <% end %>
66 |
67 | <% end %>
68 | <% end %>
69 | <% end %>
70 |
71 | <%= render_partial 'report_footer' %>
72 |
--------------------------------------------------------------------------------
/lib/metric_fu/constantize.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | module Constantize
3 | # Copied from ActiveSupport and modified so as not to introduce a dependency.
4 | # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L220
5 | def constantize(camel_cased_word)
6 | tries ||= 2
7 | names = camel_cased_word.split("::")
8 | names.shift if names.empty? || names.first.empty?
9 |
10 | names.inject(Object) do |constant, name|
11 | if constant == Object
12 | constant.const_get(name)
13 | else
14 | candidate = constant.const_get(name)
15 | next candidate if constant.const_defined?(name, false)
16 | next candidate unless Object.const_defined?(name)
17 |
18 | # Go down the ancestors to check it it's owned
19 | # directly before we reach Object or the end of ancestors.
20 | constant = constant.ancestors.inject do |const, ancestor|
21 | break const if ancestor == Object
22 | break ancestor if ancestor.const_defined?(name, false)
23 | const
24 | end
25 |
26 | # owner is in Object, so raise
27 | constant.const_get(name, false)
28 | end
29 | end
30 | rescue NameError
31 | # May need to attempt to require the file and try again.
32 | begin
33 | require underscore(camel_cased_word)
34 | rescue LoadError => e
35 | mf_log e.message
36 | end
37 |
38 | if (tries -= 1).zero?
39 | raise
40 | else
41 | retry
42 | end
43 | end
44 |
45 | # Copied from active_support
46 | # https://github.com/rails/rails/blob/51cd6bb829c418c5fbf75de1dfbb177233b1b154/activesupport/lib/active_support/inflector/methods.rb#L88
47 | def underscore(camel_cased_word)
48 | word = camel_cased_word.to_s.dup
49 | word.gsub!(/::/, "/")
50 | word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
51 | word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
52 | word.tr!("-", "_")
53 | word.downcase!
54 | word
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/metric_fu/tasks/metric_fu.rake:
--------------------------------------------------------------------------------
1 | require "rake"
2 | require "metric_fu/run"
3 | namespace :metrics do
4 | def options_tip(task_name)
5 | "with options, for example: rake metrics:#{task_name}['cane: {abc_max: 81}']"
6 | end
7 | desc "Generate all metrics reports, or #{options_tip('all')}"
8 | task :all, [:options] do |_t, args|
9 | MetricFu.run(process_options(args.options))
10 | end
11 |
12 | desc "Run only specified ;-separated metrics, for example, metrics:only[cane;flog] or #{options_tip('only')}"
13 | task :only, [:metrics, :options] do |_t, args|
14 | requested_metrics = args.metrics.to_s.split(";").map(&:strip)
15 | enabled_metrics = MetricFu::Metric.enabled_metrics.map(&:name)
16 | metrics_to_run = enabled_metrics.select { |metric| requested_metrics.include?(metric.to_s) }
17 | MetricFu.run_only(metrics_to_run, process_options(args.options))
18 | end
19 |
20 | MetricFu::Metric.enabled_metrics.each do |metric|
21 | name = metric.name
22 | desc "Generate report for #{name}, or #{options_tip('cane')}"
23 | task name, [:options] do |_t, args|
24 | MetricFu.run_only(name, process_options(args.options))
25 | end
26 | end
27 |
28 | private
29 |
30 | # from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/keys.rb
31 | class Hash
32 | # Destructively, recursively convert all keys to symbols, as long as they respond
33 | # to +to_sym+.
34 | def recursively_symbolize_keys!
35 | keys.each do |key|
36 | value = delete(key)
37 | new_key = key.intern # rescue
38 | self[new_key] = (value.is_a?(Hash) ? value.dup.recursively_symbolize_keys! : value)
39 | end
40 | self
41 | end
42 | end
43 |
44 | def process_options(options)
45 | return {} if options.nil? or options.empty?
46 | options = YAML.load(options)
47 | if options.is_a?(Hash)
48 | p "Got options #{options.recursively_symbolize_keys!.inspect}"
49 | options
50 | else
51 | raise "Invalid options #{options.inspect}, is a #{options.class}, should be a Hash"
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/analysis/ranked_problem_location.rb:
--------------------------------------------------------------------------------
1 | MetricFu.lib_require { "errors/analysis_error" }
2 | MetricFu.metrics_require { "hotspots/analysis/problems" }
3 | module MetricFu
4 | class HotspotRankedProblemLocation
5 | MetricFu.data_structures_require { "location" }
6 | attr_reader :sub_table, :granularity
7 | def initialize(sub_table, granularity)
8 | @sub_table = sub_table
9 | @granularity = granularity
10 | end
11 |
12 | def to_hash
13 | {
14 | "location" => location.to_hash,
15 | "details" => MetricFu::Utility.stringify_keys(problems),
16 | }
17 | end
18 |
19 | # @todo redo as item,value, options = {}
20 | # Note that the other option for 'details' is :detailed (this isn't
21 | # at all clear from this method itself
22 | def problems
23 | @problems ||= MetricFu::HotspotProblems.new(sub_table).problems
24 | end
25 |
26 | def location
27 | @location ||= case granularity
28 | when :class then class_location
29 | when :method then method_location
30 | when :file then file_location
31 | else raise ArgumentError, "Item must be :class, :method, or :file"
32 | end
33 | end
34 |
35 | def file_path
36 | first_row.file_path
37 | end
38 |
39 | def class_name
40 | first_row.class_name
41 | end
42 |
43 | def method_name
44 | first_row.method_name
45 | end
46 |
47 | def file_location
48 | MetricFu::Location.get(file_path, nil, nil)
49 | end
50 |
51 | def method_location
52 | MetricFu::Location.get(file_path, class_name, method_name)
53 | end
54 |
55 | def class_location
56 | MetricFu::Location.get(file_path, class_name, nil)
57 | end
58 |
59 | def first_row
60 | assert_sub_table_has_data
61 | @first_row ||= sub_table[0]
62 | end
63 |
64 | def assert_sub_table_has_data
65 | if (sub_table.length == 0)
66 | raise MetricFu::AnalysisError, "The #{item} '#{value}' does not have any rows in the analysis table"
67 | end
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/hotspots/generator_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "hotspots/generator" }
3 |
4 | describe MetricFu::HotspotsGenerator do
5 | describe "analyze method" do
6 | it "should be empty on error" do
7 | hotspots = MetricFu::HotspotsGenerator.new
8 | hotspots.instance_variable_set(:@analyzer, nil)
9 | result = hotspots.analyze
10 | expect(result).to eq(files: [], classes: [], methods: [])
11 | end
12 |
13 | it "should put the changes into a hash" do
14 | hotspots = MetricFu::HotspotsGenerator.new
15 | hotspots.analyze
16 | result = hotspots.to_h[:hotspots]
17 | expected = HOTSPOT_DATA["generator_analysis.yml"]
18 | # ensure expected granularities
19 | expect(result.keys).to eq(expected.keys)
20 |
21 | # for each granularity's location details
22 | result.each do |granularity, location_details|
23 | # map 2d array for this granularity of [details, location]
24 | expected_result = expected.fetch(granularity).map { |ld| [ld.fetch("details"), ld.fetch("location")] }
25 | # verify all the location details for this granularity match elements of expected_result
26 | location_details.each do |location_detail|
27 | location = location_detail.fetch("location")
28 | details = location_detail.fetch("details")
29 | # get the location_detail array where the where the locations (second element) match
30 | expected_location_details = expected_result.rassoc(location)
31 | # get the details (first element) from the expected location_details array
32 | expected_details = expected_location_details[0]
33 | expect(details).to eq(expected_details)
34 | end
35 | end
36 | end
37 |
38 | # really testing the output of analyzed_problems#worst_items
39 | it "should return the worst item granularities: files, classes, methods" do
40 | hotspots = MetricFu::HotspotsGenerator.new
41 | yaml = HOTSPOT_DATA["generator.yml"]
42 | analyzer = HotspotAnalyzer.new(yaml)
43 | expect(hotspots.analyze.keys).to eq([:files, :classes, :methods])
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/metric_fu/reporting/graphs/graph.rb:
--------------------------------------------------------------------------------
1 | module MetricFu
2 | def self.graph
3 | @graph ||= Graph.new
4 | end
5 |
6 | class Graph
7 | attr_accessor :graphers
8 |
9 | def initialize
10 | self.graphers = []
11 | end
12 |
13 | def add(metric_name, _graph_engine, output_directory = MetricFu::Io::FileSystem.directory("output_directory"))
14 | grapher = MetricFu::Grapher.get_grapher(metric_name).
15 | new.tap { |g| g.output_directory = output_directory }
16 | graphers.push grapher
17 | rescue NameError => e
18 | mf_log "#{e.message} called in MetricFu::Graph.add with #{graph_type}"
19 | end
20 |
21 | def generate
22 | return if graphers.empty?
23 | mf_log "Generating graphs"
24 | generate_graphs_for_files
25 | graph!
26 | rescue NameError => e
27 | mf_log "#{e.message} called in MetricFu::Graph generate"
28 | end
29 |
30 | private
31 |
32 | def metric_files
33 | MetricFu::Utility.glob(
34 | File.join(MetricFu::Io::FileSystem.directory("data_directory"), "*.yml")
35 | ).sort
36 | end
37 |
38 | def generate_graphs_for_files
39 | metric_files.each do |metric_file|
40 | generate_graphs_for_file(metric_file)
41 | end
42 | end
43 |
44 | def generate_graphs_for_file(metric_file)
45 | mf_log "Generating graphs for #{metric_file}"
46 | date_parts = year_month_day_from_filename(metric_file)
47 | metrics = MetricFu::Utility.load_yaml(metric_file)
48 |
49 | build_graph(metrics, "#{date_parts[:m]}/#{date_parts[:d]}")
50 | rescue NameError => e
51 | mf_log "#{e.message} called in MetricFu::Graph.generate with #{metric_file}"
52 | end
53 |
54 | def build_graph(metrics, sortable_prefix)
55 | graphers.each do |grapher|
56 | grapher.get_metrics(metrics, sortable_prefix)
57 | end
58 | end
59 |
60 | def graph!
61 | graphers.each(&:graph!)
62 | end
63 |
64 | def year_month_day_from_filename(path_to_file_with_date)
65 | date = path_to_file_with_date.match(/\/(\d+).yml$/)[1]
66 | { y: date[0..3].to_i, m: date[4..5].to_i, d: date[6..7].to_i }
67 | end
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/hotspot_analyzer.rb:
--------------------------------------------------------------------------------
1 | require "yaml"
2 | MetricFu.metrics_require do
3 | [
4 | "hotspots/hotspot",
5 | "hotspots/analysis/analyzer_tables",
6 | "hotspots/analysis/analyzed_problems",
7 | "hotspots/analysis/rankings"
8 | ]
9 | end
10 |
11 | module MetricFu
12 | class HotspotAnalyzer
13 | COMMON_COLUMNS = %w{metric}
14 | GRANULARITIES = %w{file_path class_name method_name}
15 |
16 | def tool_analyzers
17 | MetricFu::Hotspot.analyzers
18 | end
19 |
20 | def initialize(result_hash)
21 | # we can't depend on the result
22 | # returning a parsed yaml file as a hash?
23 | result_hash = YAML::load(result_hash) if result_hash.is_a?(String)
24 | setup(result_hash)
25 | end
26 |
27 | def hotspots
28 | analyzed_problems.worst_items
29 | end
30 |
31 | def analyzed_problems
32 | @analyzed_problems = MetricFu::HotspotAnalyzedProblems.new(@rankings, @analyzer_tables)
33 | end
34 |
35 | private
36 |
37 | # TODO clarify each of these steps in setup
38 | # extract into its own method
39 | # remove unnecessary constants,
40 | # turn into methods
41 | def setup(result_hash)
42 | # TODO There is likely a clash that will happen between
43 | # column names eventually. We should probably auto-prefix
44 | # them (e.g. "roodi_problem")
45 | analyzer_columns = COMMON_COLUMNS + GRANULARITIES + tool_analyzers.map(&:columns).flatten
46 | # though the tool_analyzers aren't returned, they are processed in
47 | # various places here, then by the analyzer tables
48 | # then by the rankings
49 | # to ultimately generate the hotspots
50 | @analyzer_tables = MetricFu::AnalyzerTables.new(analyzer_columns)
51 | tool_analyzers.each do |analyzer|
52 | analyzer.generate_records(result_hash[analyzer.name], @analyzer_tables.table)
53 | end
54 | @analyzer_tables.generate_records
55 | @rankings = MetricFu::HotspotRankings.new(@analyzer_tables.tool_tables)
56 | @rankings.calculate_scores(tool_analyzers, GRANULARITIES)
57 | # TODO does it not need to return something here?
58 | analyzed_problems
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/reek/grapher_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "reek/grapher" }
3 |
4 | describe ReekGrapher do
5 | before :each do
6 | @reek_grapher = MetricFu::ReekGrapher.new
7 | MetricFu.configuration
8 | end
9 |
10 | it "should respond to reek_count and labels" do
11 | expect(@reek_grapher).to respond_to(:reek_count)
12 | expect(@reek_grapher).to respond_to(:labels)
13 | end
14 |
15 | describe "responding to #initialize" do
16 | it "should initialise reek_count and labels" do
17 | expect(@reek_grapher.reek_count).to eq({})
18 | expect(@reek_grapher.labels).to eq({})
19 | end
20 | end
21 |
22 | describe "responding to #get_metrics" do
23 | context "when metrics were not generated" do
24 | before(:each) do
25 | @metrics = FIXTURE.load_metric("metric_missing.yml")
26 | @date = "1/2"
27 | end
28 |
29 | it "should not set a hash of code smells to reek_count" do
30 | @reek_grapher.get_metrics(@metrics, @date)
31 | expect(@reek_grapher.reek_count).to eq({})
32 | end
33 |
34 | it "should not update labels with the date" do
35 | expect(@reek_grapher.labels).not_to receive(:update)
36 | @reek_grapher.get_metrics(@metrics, @date)
37 | end
38 | end
39 |
40 | context "when metrics have been generated" do
41 | before(:each) do
42 | @metrics = FIXTURE.load_metric("20090630.yml")
43 | @date = "1/2"
44 | end
45 |
46 | it "should set a hash of code smells to reek_count" do
47 | @reek_grapher.get_metrics(@metrics, @date)
48 | expect(@reek_grapher.reek_count).to eq(
49 | "Uncommunicative Name" => [27],
50 | "Feature Envy" => [20],
51 | "Utility Function" => [15],
52 | "Long Method" => [26],
53 | "Nested Iterators" => [12],
54 | "Control Couple" => [4],
55 | "Duplication" => [48],
56 | "Large Class" => [1]
57 | )
58 | end
59 |
60 | it "should update labels with the date" do
61 | expect(@reek_grapher.labels).to receive(:update).with(0 => "1/2")
62 | @reek_grapher.get_metrics(@metrics, @date)
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/hotspots/report.html.erb:
--------------------------------------------------------------------------------
1 | Hotspot Results
2 | Meta analysis of your metrics to find hotspots in your code.
3 |
4 |
5 | <% if @hotspots.nil? || @hotspots.size.zero? %>
6 | No Hotspots were found.
7 | <% else %>
8 |
9 | <% granularities = %w[files classes methods] %>
10 |
11 |
12 | <% granularities.each do |granularity| %>
13 | |
14 | <%= granularity.to_s.capitalize %> |
15 | <% end %>
16 |
17 |
18 | <% hotspots = [] %>
19 | <% granularities.each_index do |index| %>
20 | <% granularity = granularities[index] %>
21 | <% hotspots << @hotspots[granularity] %>
22 | <% end %>
23 |
24 | <% hotspots_length = 0 %>
25 | <% columns = [0, 1, 2] %>
26 | <% while (hotspots_length < hotspots[0].length || hotspots_length < hotspots[1].length || hotspots_length < hotspots[2].length) do %>
27 |
28 | <% columns.each do |column| %>
29 | <% item = hotspots[column].length >= hotspots_length ? hotspots[column][hotspots_length] : nil %>
30 | <% if item %>
31 | <% location = item.fetch('location') %>
32 | <% file, line = location.fetch('file_name'), location.fetch('line_number') %>
33 |
34 |
35 | <%= display_location(location) %>
36 |
37 | <% unless per_file_data[file].empty? %>
38 | «
39 | ">annotate
40 | »
41 | <% end %>
42 |
43 |
44 | <% item.fetch('details').each do |metric, info| %>
45 | <%#= metric_link(@stat, metric, h(metric.to_s.capitalize)) + ": " + h(info)%>
46 | <%= "#{metric.to_s.capitalize}: #{info}" %>
47 | <% end %>
48 | |
49 | <% else %>
50 | |
51 | <% end %>
52 | <% end %>
53 | <% hotspots_length += 1 %>
54 |
55 | <% end %>
56 |
57 |
58 | <% end %>
59 |
60 | <%= render_partial 'report_footer' %>
61 |
--------------------------------------------------------------------------------
/lib/metric_fu/gem_run.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require "open3"
3 | require "shellwords"
4 | require "metric_fu"
5 | MetricFu.lib_require { "logger" }
6 | MetricFu.lib_require { "gem_version" }
7 | module MetricFu
8 | class GemRun
9 | attr_reader :output, :gem_name, :library_name, :version, :arguments
10 | def initialize(arguments = {})
11 | @gem_name = arguments.fetch(:gem_name)
12 | @library_name = arguments.fetch(:metric_name)
13 | @version = arguments.fetch(:version) { MetricFu::GemVersion.for(library_name) }
14 | args = arguments.fetch(:args)
15 | @arguments = args.respond_to?(:scan) ? Shellwords.shellwords(args) : args
16 | @output = ""
17 | @errors = []
18 | end
19 |
20 | def summary
21 | "RubyGem #{gem_name}, library #{library_name}, version #{version}, arguments #{arguments}"
22 | end
23 |
24 | def run
25 | @output = execute
26 | end
27 |
28 | def execute
29 | mf_debug "Running #{summary}"
30 | captured_output = ""
31 | captured_errors = ""
32 | thread = ""
33 | Open3.popen3("#{library_name}", *arguments) do |_stdin, stdout, stderr, wait_thr|
34 | captured_output << stdout.read.chomp
35 | captured_errors << stderr.read.chomp
36 | thread = wait_thr
37 | end
38 | rescue StandardError => run_error
39 | handle_run_error(run_error)
40 | rescue SystemExit => system_exit
41 | handle_system_exit(system_exit)
42 | ensure
43 | print_errors
44 | return captured_output, captured_errors, thread.value
45 | end
46 |
47 | def handle_run_error(run_error)
48 | @errors << "ERROR: #{run_error.inspect}"
49 | end
50 |
51 | def handle_system_exit(system_exit)
52 | status = system_exit.success? ? "SUCCESS" : "FAILURE"
53 | message = "#{status} with code #{system_exit.status}: " <<
54 | "#{system_exit.message}: #{system_exit.backtrace.inspect}"
55 | if status == "SUCCESS"
56 | mf_debug message
57 | else
58 | @errors << message
59 | end
60 | end
61 |
62 | def print_errors
63 | return if @errors.empty?
64 | STDERR.puts "ERRORS running #{summary}"
65 | @errors.each do |error|
66 | STDERR.puts "\t" << error
67 | end
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/rails_best_practices/grapher_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "rails_best_practices/grapher" }
3 |
4 | describe RailsBestPracticesGrapher do
5 | before :each do
6 | @stats_grapher = MetricFu::RailsBestPracticesGrapher.new
7 | MetricFu.configuration
8 | end
9 |
10 | it "should respond to rails_best_practices_count and labels" do
11 | expect(@stats_grapher).to respond_to(:rails_best_practices_count)
12 | expect(@stats_grapher).to respond_to(:labels)
13 | end
14 |
15 | describe "responding to #initialize" do
16 | it "should initialise rails_best_practices_count and labels" do
17 | expect(@stats_grapher.rails_best_practices_count).to eq([])
18 | expect(@stats_grapher.labels).to eq({})
19 | end
20 | end
21 |
22 | describe "responding to #get_metrics" do
23 | context "when metrics were not generated" do
24 | before(:each) do
25 | @metrics = FIXTURE.load_metric("metric_missing.yml")
26 | @date = "01022003"
27 | end
28 |
29 | it "should not push to rails_best_practices_count" do
30 | expect(@stats_grapher.rails_best_practices_count).not_to receive(:push)
31 | @stats_grapher.get_metrics(@metrics, @date)
32 | end
33 |
34 | it "should not update labels with the date" do
35 | expect(@stats_grapher.labels).not_to receive(:update)
36 | @stats_grapher.get_metrics(@metrics, @date)
37 | end
38 | end
39 |
40 | context "when metrics have been generated" do
41 | before(:each) do
42 | @metrics = FIXTURE.load_metric("20090630.yml")
43 | @date = "01022003"
44 | end
45 |
46 | it "should push to rails_best_practices_count" do
47 | expect(@stats_grapher.rails_best_practices_count).to receive(:push).with(2)
48 | @stats_grapher.get_metrics(@metrics, @date)
49 | end
50 |
51 | it "should push 0 to rails_best_practices_count when no problems were found" do
52 | expect(@stats_grapher.rails_best_practices_count).to receive(:push).with(0)
53 | @stats_grapher.get_metrics({ rails_best_practices: {} }, @date)
54 | end
55 |
56 | it "should update labels with the date" do
57 | expect(@stats_grapher.labels).to receive(:update).with(0 => "01022003")
58 | @stats_grapher.get_metrics(@metrics, @date)
59 | end
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/DEV.md:
--------------------------------------------------------------------------------
1 | ## Contracted Interfaces
2 |
3 | ```ruby
4 | MetricFu.run_dir #=> Dir.pwd
5 | MetricFu.run_dir = 'some_path'
6 | MetricFu.run_path #=> Pathname(Dir.pwd)
7 | MetricFu.root_dir
8 | MetricFu.loader.load_user_configuration
9 | MetricFu.loader.loaded_files
10 | MetricFu.lib_require { 'utility' }
11 | MetricFu.lib_dir #=> metric_fu/lib
12 | MetricFu.lib_require('metrics') { 'flog/metric' }
13 | MetricFu.metrics_require {'flog/metric' }
14 | MetricFu.metrics_dir #=> metric_fu/lib/metrics
15 | MetricFu.formatter_require { 'html' }
16 | MetricFu.formatter_dir #=> metric_fu/lib/formatter
17 | MetricFu.reporting_require { 'result' }
18 | MetricFu.reporting_dir #=> metric_fu/lib/reporting
19 | MetricFu.logging_require { 'mf_debugger' }
20 | MetricFu.lib_require { 'logger' }
21 | MetricFu.logging_dir #=> metric_fu/lib/logging
22 | MetricFu.errors_require { 'analysis_error' }
23 | MetricFu.errors_dir #=> metric_fu/lib/errors
24 | MetricFu.data_structures_require { 'line_numbers' }
25 | MetricFu.data_structures_dir #=> metric_fu/lib/data_structures
26 | MetricFu.tasks_require { } # Doesn't work as expected. Don't use
27 | MetricFu.tasks_dir #=> metric_fu/lib/tasks
28 |
29 | MetricFu.configuration #=> MetricFu::Configuration.new
30 | MetricFu.configuration.configure_metrics # for each metric, yield to block or runs enable, activate
31 | MetricFu.configuration.configure_metric(:flog) do |metric|
32 | metric.run_options #=> metric.default_run_options.merge(metric.configured_run_options)
33 | metric.enable
34 | metric.enabled = true
35 | metric.activate
36 | metric.activated = true
37 | metric.name #=> :flog
38 | end
39 | ```
40 |
41 | ## Templates
42 |
43 | ```ruby
44 | # Given
45 | template = MetricFu::Templates::MetricsTemplate.new
46 |
47 | # Render _report_footer.html.erb partial:
48 |
49 | template.render_partial('report_footer')
50 |
51 | # Render _graph.html.erb partial and set a graph_name instance variable:
52 |
53 | template.render_partial 'graph', {:graph_name => 'reek'}
54 | ```
55 |
56 | ## Testing
57 |
58 | `bundle exec rspec`
59 |
60 | ## Forking
61 |
62 | ## Issues / Pull Requests
63 |
64 | * see [CONTRIBUTING](CONTRIBUTING.md)
65 |
66 | ## Building
67 |
68 | `rake build` or `rake install`
69 |
70 | ## Releasing
71 |
72 | 0. Run `rake usage_test` to make sure the examples are still valid
73 | 1. Update lib/metric_fu/version.rb
74 | 2. Update HISTORY.md
75 | 3. Update CONTRIBUTORS and erd per README in etc
76 | 3. `rake release`
77 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/stats/grapher_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "stats/grapher" }
3 |
4 | describe StatsGrapher do
5 | before :each do
6 | @stats_grapher = MetricFu::StatsGrapher.new
7 | MetricFu.configuration
8 | end
9 |
10 | it "should respond to loc_counts and lot_counts and labels" do
11 | expect(@stats_grapher).to respond_to(:loc_counts)
12 | expect(@stats_grapher).to respond_to(:lot_counts)
13 | expect(@stats_grapher).to respond_to(:labels)
14 | end
15 |
16 | describe "responding to #initialize" do
17 | it "should initialise loc_counts and lot_counts and labels" do
18 | expect(@stats_grapher.loc_counts).to eq([])
19 | expect(@stats_grapher.lot_counts).to eq([])
20 | expect(@stats_grapher.labels).to eq({})
21 | end
22 | end
23 |
24 | describe "responding to #get_metrics" do
25 | context "when metrics were not generated" do
26 | before(:each) do
27 | @metrics = FIXTURE.load_metric("metric_missing.yml")
28 | @date = "01022003"
29 | end
30 |
31 | it "should not push to loc_counts" do
32 | expect(@stats_grapher.loc_counts).not_to receive(:push)
33 | @stats_grapher.get_metrics(@metrics, @date)
34 | end
35 |
36 | it "should not push to lot_counts" do
37 | expect(@stats_grapher.lot_counts).not_to receive(:push)
38 | @stats_grapher.get_metrics(@metrics, @date)
39 | end
40 |
41 | it "should not update labels with the date" do
42 | expect(@stats_grapher.labels).not_to receive(:update)
43 | @stats_grapher.get_metrics(@metrics, @date)
44 | end
45 | end
46 |
47 | context "when metrics have been generated" do
48 | before(:each) do
49 | @metrics = FIXTURE.load_metric("20090630.yml")
50 | @date = "01022003"
51 | end
52 |
53 | it "should push to loc_counts" do
54 | expect(@stats_grapher.loc_counts).to receive(:push).with(15935)
55 | @stats_grapher.get_metrics(@metrics, @date)
56 | end
57 |
58 | it "should push to lot_counts" do
59 | expect(@stats_grapher.lot_counts).to receive(:push).with(7438)
60 | @stats_grapher.get_metrics(@metrics, @date)
61 | end
62 |
63 | it "should update labels with the date" do
64 | expect(@stats_grapher.labels).to receive(:update).with(0 => "01022003")
65 | @stats_grapher.get_metrics(@metrics, @date)
66 | end
67 | end
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | How to contribute:
2 |
3 | ## Bug reports / Issues
4 |
5 | * Is something broken or not working as expected? Check for an existing issue or [create a new one](https://github.com/metricfu/metric_fu/issues/new)
6 | * See [Quick guide to writing good bug reports](https://github.com/metricfu/metric_fu/wiki/Issues:-Quick-guide-to-writing-good-bug-reports)
7 | * IMPORTANT: Include the output of `metric_fu --debug-info`
8 |
9 | ## Code
10 |
11 | 1. Fork and clone the repo: `git clone git://github.com/yourghname/metric_fu.git && cd metric_fu`
12 | 2. Install the gem dependencies: `bundle install`
13 | 3. Make the changes you want and back them up with tests.
14 | * Run the tests (`bundle exec rspec`)
15 | * Run metric_fu on itself (`bundle exec ruby -Ilib bin/metric_fu`)
16 | 4. Update the HISTORY.md file with your changes and give yourself credit
17 | 5. Commit and create a pull request with details as to what has been changed and why
18 | * Use well-described, small (atomic) commits.
19 | * Include links to any relevant github issues.
20 | * *Don't* change the VERSION file.
21 | 6. Extra Credit: [Confirm it runs and tests pass on the rubies specified in the travis config](.travis.yml). I will otherwise confirm it runs on these.
22 |
23 | How I handle pull requests:
24 |
25 | * If the tests pass and the pull request looks good, I will merge it.
26 | * If the pull request needs to be changed,
27 | * you can change it by updating the branch you generated the pull request from
28 | * either by adding more commits, or
29 | * by force pushing to it
30 | * I can make any changes myself and manually merge the code in.
31 |
32 | ### Commit Messages
33 |
34 | * [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
35 | * [http://stopwritingramblingcommitmessages.com/](http://stopwritingramblingcommitmessages.com/)
36 | * [ThoughtBot style guide](https://github.com/thoughtbot/guides/tree/master/style#git)
37 |
38 | ### About Pull Requests (PR's)
39 |
40 | * [All Your Open Source Code Are Belong To Us](http://www.benjaminfleischer.com/2013/07/30/all-your-open-source-code-are-belong-to-us/)
41 | * [Using Pull Requests](https://help.github.com/articles/using-pull-requests)
42 | * [Github pull requests made easy](http://www.element84.com/github-pull-requests-made-easy.html)
43 |
44 | ## Documentation
45 |
46 | * If relevant, you may update [the metric_fu website](https://github.com/metricfu/metricfu.github.com) in a separate pull request to that repo
47 | * Update the wiki
48 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/rcov/rcov_line_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "metric_fu/metrics/rcov/rcov_line"
3 |
4 | describe MetricFu::RCovLine do
5 | describe "#to_h" do
6 | it "returns a hash with the content and was_run" do
7 | rcov_line = RCovLine.new("some content", 1)
8 |
9 | expect(rcov_line.to_h).to eq(content: "some content", was_run: 1)
10 | end
11 | end
12 |
13 | describe "#covered?" do
14 | it "returns true if was_run is 1" do
15 | rcov_line = RCovLine.new("", 1)
16 |
17 | expect(rcov_line.covered?).to eq(true)
18 | end
19 |
20 | it "returns false if was_run is 0" do
21 | rcov_line = RCovLine.new("", 0)
22 |
23 | expect(rcov_line.covered?).to eq(false)
24 | end
25 |
26 | it "returns false if was_run is nil" do
27 | rcov_line = RCovLine.new("", nil)
28 |
29 | expect(rcov_line.covered?).to eq(false)
30 | end
31 | end
32 |
33 | describe "#missed?" do
34 | it "returns true if was_run is 0" do
35 | rcov_line = RCovLine.new("", 0)
36 |
37 | expect(rcov_line.missed?).to eq(true)
38 | end
39 |
40 | it "returns false if was_run is 1" do
41 | rcov_line = RCovLine.new("", 1)
42 |
43 | expect(rcov_line.missed?).to eq(false)
44 | end
45 |
46 | it "returns false if was_run is nil" do
47 | rcov_line = RCovLine.new("", nil)
48 |
49 | expect(rcov_line.missed?).to eq(false)
50 | end
51 | end
52 |
53 | describe "#ignored?" do
54 | it "returns true if was_run is nil" do
55 | rcov_line = RCovLine.new("", nil)
56 |
57 | expect(rcov_line.ignored?).to eq(true)
58 | end
59 |
60 | it "returns false if was_run is 1" do
61 | rcov_line = RCovLine.new("", 1)
62 |
63 | expect(rcov_line.ignored?).to eq(false)
64 | end
65 |
66 | it "returns false if was_run is 0" do
67 | rcov_line = RCovLine.new("", 0)
68 |
69 | expect(rcov_line.ignored?).to eq(false)
70 | end
71 | end
72 |
73 | describe "#css_class" do
74 | it "returns 'rcov_run' for an ignored line" do
75 | rcov_line = RCovLine.new("", nil)
76 | expect(rcov_line.css_class).to eq("rcov_run")
77 | end
78 |
79 | it "returns 'rcov_not_run' for a missed line" do
80 | rcov_line = RCovLine.new("", 0)
81 | expect(rcov_line.css_class).to eq("rcov_not_run")
82 | end
83 |
84 | it "returns 'rcov_run' for a covered line" do
85 | rcov_line = RCovLine.new("", 1)
86 | expect(rcov_line.css_class).to eq("rcov_run")
87 | end
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/lib/metric_fu/metrics/flog/grapher.rb:
--------------------------------------------------------------------------------
1 | MetricFu.reporting_require { "graphs/grapher" }
2 | module MetricFu
3 | class FlogGrapher < Grapher
4 | attr_accessor :flog_average, :labels, :top_five_percent_average
5 |
6 | def self.metric
7 | :flog
8 | end
9 |
10 | def initialize
11 | super
12 | @flog_average = []
13 | @labels = {}
14 | @top_five_percent_average = []
15 | end
16 |
17 | def get_metrics(metrics, date)
18 | if metrics && metrics[:flog]
19 | @top_five_percent_average.push(calc_top_five_percent_average(metrics))
20 | @flog_average.push(metrics[:flog][:average])
21 | @labels.update(@labels.size => date)
22 | end
23 | end
24 |
25 | def title
26 | "Flog: code complexity"
27 | end
28 |
29 | def data
30 | [
31 | ["average", @flog_average.join(",")],
32 | ["top 5% average", @top_five_percent_average.join(",")]
33 | ]
34 | end
35 |
36 | def output_filename
37 | "flog.js"
38 | end
39 |
40 | private
41 |
42 | def calc_top_five_percent_average(metrics)
43 | return calc_top_five_percent_average_legacy(metrics) if metrics[:flog][:pages]
44 |
45 | method_scores = metrics[:flog][:method_containers].inject([]) do |method_scores, container|
46 | method_scores += container[:methods].values.map { |v| v[:score] }
47 | end
48 | method_scores.sort!.reverse!
49 |
50 | number_of_methods_that_is_five_percent = (method_scores.size * 0.05).ceil
51 |
52 | total_for_five_percent =
53 | method_scores[0...number_of_methods_that_is_five_percent].inject(0) { |total, score| total += score }
54 | if number_of_methods_that_is_five_percent == 0
55 | 0.0
56 | else
57 | total_for_five_percent / number_of_methods_that_is_five_percent.to_f
58 | end
59 | end
60 |
61 | def calc_top_five_percent_average_legacy(metrics)
62 | methods = metrics[:flog][:pages].inject([]) { |methods, page| methods << page[:scanned_methods] }
63 | methods.flatten!
64 | methods = methods.sort_by { |method| method[:score] }.reverse
65 |
66 | number_of_methods_that_is_five_percent = (methods.size * 0.05).ceil
67 |
68 | total_for_five_percent =
69 | methods[0...number_of_methods_that_is_five_percent].inject(0) { |total, method| total += method[:score] }
70 | if number_of_methods_that_is_five_percent == 0
71 | 0.0
72 | else
73 | total_for_five_percent / number_of_methods_that_is_five_percent.to_f
74 | end
75 | end
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/spec/metric_fu/metrics/saikuro/generator_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | MetricFu.metrics_require { "saikuro/generator" }
3 |
4 | describe MetricFu::SaikuroGenerator do
5 | STUB_TEST_DATA = lambda do |generator|
6 | # set test data dir; ensure it doesn't get cleared
7 | def generator.metric_directory
8 | FIXTURE.fixtures_path.join("saikuro").to_s
9 | end
10 | def generator.clear_scratch_files!
11 | # no-op
12 | end
13 | end
14 | describe "to_h method" do
15 | before do
16 | options = {}
17 | saikuro = MetricFu::SaikuroGenerator.new(options)
18 | STUB_TEST_DATA[saikuro]
19 |
20 | saikuro.analyze
21 | @output = saikuro.to_h
22 | end
23 |
24 | it "should find the filename of a file" do
25 | expect(@output[:saikuro][:files].first[:filename]).to eq("app/controllers/users_controller.rb")
26 | end
27 |
28 | it "should find the name of the classes" do
29 | expect(@output[:saikuro][:classes].first[:name]).to eq("UsersController")
30 | expect(@output[:saikuro][:classes][1][:name]).to eq("SessionsController")
31 | end
32 |
33 | it "should put the most complex method first" do
34 | expect(@output[:saikuro][:methods].first[:name]).to eq("UsersController#create")
35 | expect(@output[:saikuro][:methods].first[:complexity]).to eq(4)
36 | end
37 |
38 | it "should find the complexity of a method" do
39 | expect(@output[:saikuro][:methods].first[:complexity]).to eq(4)
40 | end
41 |
42 | it "should find the lines of a method" do
43 | expect(@output[:saikuro][:methods].first[:lines]).to eq(15)
44 | end
45 | end
46 |
47 | describe "per_file_info method" do
48 | before :all do
49 | options = {}
50 | @saikuro = MetricFu::SaikuroGenerator.new(options)
51 | STUB_TEST_DATA[@saikuro]
52 | @saikuro.analyze
53 | @output = @saikuro.to_h
54 | end
55 |
56 | it "doesn't try to get information if the file does not exist" do
57 | expect(@saikuro).to receive(:file_not_exists?).at_least(:once).and_return(true)
58 | @saikuro.per_file_info("ignore_me")
59 | end
60 | end
61 |
62 | describe MetricFu::SaikuroScratchFile do
63 | describe "getting elements from a Saikuro result file" do
64 | it "should parse nested START/END sections" do
65 | path = FIXTURE.fixtures_path.join("saikuro_sfiles", "thing.rb_cyclo.html").to_s
66 | sfile = MetricFu::SaikuroScratchFile.new path
67 | expect(sfile.elements.map(&:complexity).sort).to eql(["0", "0", "2"])
68 | end
69 | end
70 | end
71 | end
72 |
--------------------------------------------------------------------------------