mire 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +15 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +134 -0
- data/Rakefile +7 -0
- data/bin/mire +7 -0
- data/lib/mire.rb +14 -0
- data/lib/mire/analyzer.rb +160 -0
- data/lib/mire/cli.rb +63 -0
- data/lib/mire/configuration.rb +44 -0
- data/lib/mire/configuration_example.yml +12 -0
- data/lib/mire/configuration_methods.rb +12 -0
- data/lib/mire/output/base.rb +34 -0
- data/lib/mire/output/occurrence.rb +21 -0
- data/lib/mire/output/unused.rb +32 -0
- data/lib/mire/version.rb +3 -0
- data/mire.gemspec +34 -0
- data/spec/mire/analyzer_spec.rb +147 -0
- data/spec/mire/configuration_spec.rb +32 -0
- data/spec/mire/output/occurrence_spec.rb +56 -0
- data/spec/mire/output/unused_spec.rb +51 -0
- data/spec/spec_helper.rb +4 -0
- metadata +219 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 361606807951fe02817f5a4f71a2810c914f79c7
|
4
|
+
data.tar.gz: bfd3d06e0f857abb7709c5992bb647c1493486e2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5b1c3198a31536633dd44ec35d1f89cdb4f36011b6bdf5071b8d43b1d296f85597eb6d3b3b02f63e1972cd08aee3b47f1aa65970a4cb366df7db5be732544971
|
7
|
+
data.tar.gz: 7759ff2bd79fd0ace1be9375c7058604ec0e2a278134fa059673d393ba1110d07c80539cea7d3d963fdbb17221bb51e7f6650a6f1af3329d79f13c02d8a8fbc3
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
+
# on 2015-04-15 12:03:43 +0200 using RuboCop version 0.23.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: 1
|
9
|
+
Style/CyclomaticComplexity:
|
10
|
+
Max: 10
|
11
|
+
|
12
|
+
# Offense count: 2
|
13
|
+
# Configuration parameters: CountComments.
|
14
|
+
Style/MethodLength:
|
15
|
+
Max: 22
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## master (unreleased)
|
4
|
+
|
5
|
+
## 0.1.1
|
6
|
+
|
7
|
+
* Add configuration option to exclude files / folders from analyzing
|
8
|
+
* Add configuration initializer option
|
9
|
+
* Switch from OptionParser to Slop as option parser
|
10
|
+
* Extend analyzer to handle modifier ifs and special blocks
|
11
|
+
|
12
|
+
## 0.1.0
|
13
|
+
|
14
|
+
Initial version. This version is not stable yet, but feel free to test
|
15
|
+
it and feedback or (even better) pull requests are highly welcome.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 XING EVENTS GmbH
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# mire [ˈmɪʀɛ]
|
2
|
+
|
3
|
+
mire analyzes a Ruby project and helps you to find dependencies, call
|
4
|
+
stacks and unused methods. It parses Ruby and Haml files and collects
|
5
|
+
all method definitions and invocations.
|
6
|
+
|
7
|
+
* [Installation](#installation)
|
8
|
+
* [Usage](#usage)
|
9
|
+
* [Configuration](#configuration)
|
10
|
+
* [Dependencies](#dependencies)
|
11
|
+
* [TODO](#todo)
|
12
|
+
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'mire'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
$ bundle
|
26
|
+
```
|
27
|
+
|
28
|
+
Or install it yourself as:
|
29
|
+
|
30
|
+
```bash
|
31
|
+
$ gem install mire
|
32
|
+
```
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
First you need to analyze the code and create a `.mire_analysis.yml`
|
37
|
+
file.
|
38
|
+
|
39
|
+
```bash
|
40
|
+
bundle exec mire -a
|
41
|
+
```
|
42
|
+
|
43
|
+
A Ruby code like
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class Foo
|
47
|
+
def bar
|
48
|
+
buz
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
will lead to this `.mire_analysis.yml` file.
|
54
|
+
|
55
|
+
```yaml
|
56
|
+
:bar:
|
57
|
+
:definitions:
|
58
|
+
- :class: Foo
|
59
|
+
:method: :bar
|
60
|
+
:file: foo.rb
|
61
|
+
:line: 2
|
62
|
+
:invocations: []
|
63
|
+
:buz:
|
64
|
+
:definition: []
|
65
|
+
:invocations:
|
66
|
+
- :class: Foo
|
67
|
+
:method: :bar
|
68
|
+
:file: foo.rb
|
69
|
+
:line: 3
|
70
|
+
```
|
71
|
+
|
72
|
+
After this the `.mire_analysis.yml` file can be used to find unused
|
73
|
+
methods for example.
|
74
|
+
|
75
|
+
```bash
|
76
|
+
bundle exec mire -u
|
77
|
+
|
78
|
+
Checking for unused methods
|
79
|
+
foo.rb:2 Foo.bar
|
80
|
+
```
|
81
|
+
|
82
|
+
This result can only be taken as a hint for unused methods. For example ERB files are
|
83
|
+
not being analysed yet. Also dynamic method definitions or invocations are not
|
84
|
+
considered. So mire can't find every usage of a method.
|
85
|
+
|
86
|
+
## Configuration
|
87
|
+
|
88
|
+
mire can be configured with a `.mire.yml` file in your project folder.
|
89
|
+
With `mire -i` a initial configuration file is created.
|
90
|
+
|
91
|
+
You can configure which files or folders should be excluded while
|
92
|
+
analyzing the code or when displaying the unused methods.
|
93
|
+
|
94
|
+
```yaml
|
95
|
+
excluded_files:
|
96
|
+
- vendor/**/*
|
97
|
+
|
98
|
+
output:
|
99
|
+
unused:
|
100
|
+
excluded_files:
|
101
|
+
- db/migrate/**/*
|
102
|
+
- spec/**/*
|
103
|
+
- script/**/*
|
104
|
+
- lib/**/*
|
105
|
+
```
|
106
|
+
|
107
|
+
## Dependencies
|
108
|
+
|
109
|
+
Why is [HAML-Lint](https://github.com/brigade/haml-lint) needed?
|
110
|
+
|
111
|
+
HAML-Lint did a great job to write a Ruby code extractor for Haml files.
|
112
|
+
mire is using this extractor.
|
113
|
+
|
114
|
+
## TODO
|
115
|
+
|
116
|
+
The current implementation of mire is really basic. It needs to become
|
117
|
+
more robust and the parsed file types (e.g. `.erb`) needs to be
|
118
|
+
extended.
|
119
|
+
|
120
|
+
## Contributing
|
121
|
+
|
122
|
+
1. Fork it ( https://github.com/xing/mire/fork )
|
123
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
124
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
125
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
126
|
+
5. Create a new Pull Request
|
127
|
+
|
128
|
+
## Authors
|
129
|
+
|
130
|
+
[Nils Gemeinhardt](https://github.com/geniou) and [Marcus Lankenau](https://github.com/mlankenau)
|
131
|
+
|
132
|
+
Copyright (c) 2015 [XING EVENTS GmbH](http://de.amiando.com/)
|
133
|
+
|
134
|
+
Released under the MIT license. For full details see LICENSE included in this distribution.
|
data/Rakefile
ADDED
data/bin/mire
ADDED
data/lib/mire.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
require 'yaml'
|
3
|
+
require 'slop'
|
4
|
+
require 'ruby-progressbar'
|
5
|
+
require 'haml_lint'
|
6
|
+
|
7
|
+
require 'mire/configuration'
|
8
|
+
require 'mire/configuration_methods'
|
9
|
+
require 'mire/analyzer'
|
10
|
+
require 'mire/cli'
|
11
|
+
|
12
|
+
require 'mire/output/base'
|
13
|
+
require 'mire/output/occurrence'
|
14
|
+
require 'mire/output/unused'
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module Mire
|
2
|
+
# analyze all ruby files in a folder and find there usage
|
3
|
+
class Analyzer
|
4
|
+
include ConfigurationMethods
|
5
|
+
|
6
|
+
attr_reader :methods
|
7
|
+
|
8
|
+
BLACKLIST = %i(lambda new inspect to_i to_a [] []= * | ! != !~ % & + -@ / <
|
9
|
+
<< <= <=> == === =~ > >= - __callee__ __send__ initialize
|
10
|
+
method_missing)
|
11
|
+
|
12
|
+
CALLBACKS = %i(after_commit after_create after_destroy after_save
|
13
|
+
after_update after_validation before_commit before_create
|
14
|
+
before_destroy before_save before_update before_validation)
|
15
|
+
|
16
|
+
BLOCKS = %i(task)
|
17
|
+
|
18
|
+
FILE = '.mire_analysis.yml'
|
19
|
+
|
20
|
+
def initialize(files: nil)
|
21
|
+
@namespace = []
|
22
|
+
@methods = {}
|
23
|
+
@files = files || Dir['**/*.{rb,haml}']
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
progress_bar = ProgressBar.create(total: @files.count)
|
28
|
+
@files.each do |file|
|
29
|
+
@method = nil
|
30
|
+
next if excluded_file?(file)
|
31
|
+
@filename = file
|
32
|
+
case file_type(file)
|
33
|
+
when :haml
|
34
|
+
parse_haml_file(file)
|
35
|
+
when :rb
|
36
|
+
parse_file(file)
|
37
|
+
end
|
38
|
+
@filename = nil
|
39
|
+
progress_bar.increment
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def save
|
45
|
+
IO.write(FILE, @methods.to_yaml)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def file_type(filename)
|
51
|
+
filename.split('.').last.to_sym
|
52
|
+
end
|
53
|
+
|
54
|
+
def excluded_files
|
55
|
+
@excluded_files ||= configuration.read(:excluded_files) || []
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_file(filename)
|
59
|
+
file_content = IO.read(filename)
|
60
|
+
ast = Parser::CurrentRuby.parse(file_content)
|
61
|
+
parse(ast)
|
62
|
+
rescue
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_haml_file(filename)
|
67
|
+
file_content = IO.read(filename)
|
68
|
+
parser = HamlLint::Parser.new(file_content)
|
69
|
+
extractor = HamlLint::ScriptExtractor.new(parser)
|
70
|
+
ast = Parser::CurrentRuby.parse(extractor.extract.strip)
|
71
|
+
parse_method(ast)
|
72
|
+
rescue
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def location(ast)
|
77
|
+
{
|
78
|
+
class: @namespace.join('::'),
|
79
|
+
method: @method,
|
80
|
+
file: @filename,
|
81
|
+
line: ast.loc.line
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_method(to, definition: nil, invocation: nil)
|
86
|
+
# TODO: this class check should not be necessary - it looks like the code
|
87
|
+
# is still messing up by determine the method
|
88
|
+
return unless [String, Symbol, NilClass].include?(@method.class)
|
89
|
+
return if BLACKLIST.include?(to.to_sym)
|
90
|
+
|
91
|
+
@methods[to] ||= { definitions: [], invocations: [] }
|
92
|
+
@methods[to][:definitions] << location(definition) if definition
|
93
|
+
@methods[to][:invocations] << location(invocation) if invocation
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_const(ast)
|
97
|
+
fail "#{ast} is not a constant" unless ast.type == :const
|
98
|
+
ast.children.last.to_s
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_method(ast)
|
102
|
+
return unless ast.respond_to?(:type) && ast.respond_to?(:children)
|
103
|
+
if ast.type == :send
|
104
|
+
add_method(ast.children[1], invocation: ast)
|
105
|
+
elsif ast.type == :sym
|
106
|
+
add_method(ast.children[0], invocation: ast)
|
107
|
+
end
|
108
|
+
parse_children(ast, :parse_method)
|
109
|
+
end
|
110
|
+
|
111
|
+
def parse(ast)
|
112
|
+
return unless ast.respond_to?(:type) && ast.respond_to?(:children)
|
113
|
+
parse_method = "parse_#{ast.type}_node"
|
114
|
+
return send(parse_method, ast) if respond_to?(parse_method, true)
|
115
|
+
parse_children(ast)
|
116
|
+
rescue
|
117
|
+
raise "Error while parsing #{@filename}:#{ast.loc.line}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def parse_children(ast, method = :parse)
|
121
|
+
ast.children.each { |c| send(method, c) }
|
122
|
+
end
|
123
|
+
|
124
|
+
def parse_class_node(ast)
|
125
|
+
@namespace.push(get_const(ast.children.first))
|
126
|
+
parse_children(ast)
|
127
|
+
@namespace.pop
|
128
|
+
end
|
129
|
+
alias_method :parse_module_node, :parse_class_node
|
130
|
+
|
131
|
+
def parse_def_node(ast)
|
132
|
+
@method = ast.children[ast.type == :defs ? 1 : 0]
|
133
|
+
add_method(@method, definition: ast)
|
134
|
+
parse_children(ast, :parse_method)
|
135
|
+
end
|
136
|
+
alias_method :parse_defs_node, :parse_def_node
|
137
|
+
|
138
|
+
def parse_send_node(ast)
|
139
|
+
unless (CALLBACKS + %i(scope validate)).include?(ast.children[1])
|
140
|
+
return parse_children(ast)
|
141
|
+
end
|
142
|
+
return unless ast.children[2]
|
143
|
+
|
144
|
+
@method = ast.children[2].children.last
|
145
|
+
parse_children(ast, :parse_method)
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse_block_node(ast)
|
149
|
+
unless BLOCKS.include?(ast.children.first.children[1])
|
150
|
+
return parse_children(ast)
|
151
|
+
end
|
152
|
+
parse_method(ast.children[2])
|
153
|
+
end
|
154
|
+
|
155
|
+
def parse_if_node(ast)
|
156
|
+
parse_method(ast.children[0])
|
157
|
+
parse_children(ast)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/mire/cli.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Mire
|
2
|
+
# Command line interface for mire
|
3
|
+
class CLI
|
4
|
+
class << self
|
5
|
+
def parse # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
6
|
+
slop = Slop.parse do |opts|
|
7
|
+
opts.on('-h', '--help', 'Show this help') do
|
8
|
+
puts opts
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
opts.on('-a', '--analyze', 'Analyze ruby project') do
|
12
|
+
analyze
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
opts.on('-c', '--check TERM', 'Check term and find usages') do |f|
|
16
|
+
check(f)
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
opts.on('-u', '--unused', 'Check for unused methods') do
|
20
|
+
unused
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
opts.on('-i', '--initialize', 'Create initial configuration file') do
|
24
|
+
init
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
puts slop
|
30
|
+
rescue Slop::UnknownOption
|
31
|
+
puts 'Unknown option - use --help to see all options'
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def init
|
37
|
+
Mire::Configuration.copy_example
|
38
|
+
puts "Configuration file #{Mire::Configuration::FILE} created"
|
39
|
+
rescue Mire::Configuration::ExistingFile
|
40
|
+
puts "Existing #{Mire::Configuration::FILE} file found - nothing done"
|
41
|
+
end
|
42
|
+
|
43
|
+
def analyze
|
44
|
+
analyzer = Analyzer.new
|
45
|
+
puts 'Analyzing project'
|
46
|
+
analyzer.run
|
47
|
+
analyzer.save
|
48
|
+
end
|
49
|
+
|
50
|
+
def check(f)
|
51
|
+
puts "Checking term #{f}"
|
52
|
+
occurrence = Output::Occurrence.new
|
53
|
+
puts occurrence.check(f)
|
54
|
+
end
|
55
|
+
|
56
|
+
def unused
|
57
|
+
puts 'Checking for unused methods'
|
58
|
+
occurrence = Output::Unused.new
|
59
|
+
puts occurrence.check
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Mire
|
2
|
+
# load configuration
|
3
|
+
class Configuration
|
4
|
+
FILE = '.mire.yml'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def copy_example
|
8
|
+
fail ExistingFile if File.exist?(FILE)
|
9
|
+
FileUtils.cp(example_file, FILE)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def example_file
|
15
|
+
File.join(File.dirname(__FILE__), 'configuration_example.yml')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@config = if File.exist?(FILE)
|
21
|
+
symbolize_keys(YAML.load_file(FILE))
|
22
|
+
else
|
23
|
+
{}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def read(*args)
|
28
|
+
args.reduce(@config) do |c, key|
|
29
|
+
return nil unless c
|
30
|
+
c[key]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def symbolize_keys(hash)
|
37
|
+
hash.each_with_object({}) do |(key, value), result|
|
38
|
+
result[key.to_sym] = value.is_a?(Hash) ? symbolize_keys(value) : value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class ExistingFile < StandardError; end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Mire
|
2
|
+
module Output
|
3
|
+
# base class of output classes
|
4
|
+
class Base
|
5
|
+
include ConfigurationMethods
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def methods
|
10
|
+
@methods ||= YAML.load_file(Mire::Analyzer::FILE)
|
11
|
+
end
|
12
|
+
|
13
|
+
def location(location)
|
14
|
+
[location_file(location), location_method(location)]
|
15
|
+
.reject { |s| s.to_s.empty? }
|
16
|
+
.join(' ')
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def location_method(location)
|
22
|
+
[location[:class], location[:method]]
|
23
|
+
.reject { |s| s.to_s.empty? }
|
24
|
+
.join('.')
|
25
|
+
end
|
26
|
+
|
27
|
+
def location_file(location)
|
28
|
+
[location[:file], location[:line]]
|
29
|
+
.reject { |s| s.to_s.empty? }
|
30
|
+
.join(':')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Mire
|
2
|
+
module Output
|
3
|
+
# checks for a term and print out the occurrences and the occurrences of
|
4
|
+
# this and so on
|
5
|
+
class Occurrence < Base
|
6
|
+
def check(term, levels: 2, indenting: 0, output: [])
|
7
|
+
term = term.to_sym if term
|
8
|
+
return unless methods[term]
|
9
|
+
methods[term][:invocations].each_with_object(output) do |method, o|
|
10
|
+
o << "#{' ' * indenting * 2}#{location(method)}"
|
11
|
+
|
12
|
+
next unless levels > 0
|
13
|
+
check(method[:method], levels: levels - 1,
|
14
|
+
indenting: indenting + 1,
|
15
|
+
output: o)
|
16
|
+
end
|
17
|
+
output
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Mire
|
2
|
+
module Output
|
3
|
+
# Check for unused methods
|
4
|
+
class Unused < Base
|
5
|
+
def check
|
6
|
+
methods
|
7
|
+
.select { |_, m| m[:invocations].empty? }
|
8
|
+
.map { |_, m| definitions(m[:definitions]) }
|
9
|
+
.flatten
|
10
|
+
.compact
|
11
|
+
.sort
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def definitions(definitions)
|
17
|
+
definitions.map do |d|
|
18
|
+
location(d) unless excluded_file?(d[:file])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def excluded_file?(file)
|
23
|
+
excluded_files.any? { |e| File.fnmatch(e, file, File::FNM_PATHNAME) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def excluded_files
|
27
|
+
@excluded_files ||=
|
28
|
+
configuration.read(:output, :unused, :excluded_files) || []
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/mire/version.rb
ADDED
data/mire.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mire/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'mire'
|
8
|
+
spec.version = Mire::VERSION
|
9
|
+
spec.authors = ['Nils Gemeinhardt', 'Marcus Lankenau']
|
10
|
+
spec.email = ['nils.gemeinhardt@xing.com', 'marcus.lankenau@xing.com']
|
11
|
+
spec.summary = 'Analyzes a Ruby project.'
|
12
|
+
spec.description = <<-TEXT
|
13
|
+
Analyzes a Ruby project and helps you to find dependencies, call stacks and
|
14
|
+
unused methods.
|
15
|
+
TEXT
|
16
|
+
spec.homepage = 'https://github.com/xing/mire'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0")
|
20
|
+
spec.executables = spec.files.grep(/bin\//) { |f| File.basename(f) }
|
21
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_runtime_dependency 'parser'
|
25
|
+
spec.add_runtime_dependency 'slop', '~> 4.0'
|
26
|
+
spec.add_runtime_dependency 'ruby-progressbar'
|
27
|
+
spec.add_runtime_dependency 'haml-lint'
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
29
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
31
|
+
spec.add_development_dependency 'byebug'
|
32
|
+
spec.add_development_dependency 'rubocop'
|
33
|
+
spec.add_development_dependency 'rubocop-rspec'
|
34
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe Mire::Analyzer, type: :class do
|
5
|
+
subject { analyzer.run.save }
|
6
|
+
let(:analyzer) { described_class.new(files: Dir[@file]) }
|
7
|
+
let(:methods) { YAML.load_file(Mire::Analyzer::FILE) }
|
8
|
+
def file(content, extension = :rb)
|
9
|
+
@file = Tempfile.new(['mire', ".#{extension}"]).tap do |file|
|
10
|
+
file << content
|
11
|
+
file.close
|
12
|
+
end
|
13
|
+
end
|
14
|
+
let(:found_methods) { methods.map { |k, _v| k } }
|
15
|
+
|
16
|
+
it 'finds method calls inside method' do
|
17
|
+
file <<-RUBY
|
18
|
+
class Foo
|
19
|
+
def bar
|
20
|
+
buz
|
21
|
+
end
|
22
|
+
end
|
23
|
+
RUBY
|
24
|
+
subject
|
25
|
+
expect(methods[:buz]).not_to be_nil
|
26
|
+
expect(methods[:buz][:invocations]).not_to be_empty
|
27
|
+
expect(methods[:buz][:invocations].first[:class]).to eq('Foo')
|
28
|
+
expect(methods[:buz][:invocations].first[:method]).to eq(:bar)
|
29
|
+
expect(methods[:buz][:invocations].first[:line]).to eq(3)
|
30
|
+
expect(methods[:bar][:definitions]).not_to be_empty
|
31
|
+
expect(methods[:bar][:definitions].first[:class]).to eq('Foo')
|
32
|
+
expect(methods[:bar][:definitions].first[:method]).to eq(:bar)
|
33
|
+
expect(methods[:bar][:definitions].first[:line]).to eq(2)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'ignores excluded files' do
|
37
|
+
file <<-RUBY
|
38
|
+
class Foo
|
39
|
+
def bar
|
40
|
+
buz
|
41
|
+
end
|
42
|
+
end
|
43
|
+
RUBY
|
44
|
+
configuration = Tempfile.new('mire_configuration')
|
45
|
+
configuration << {
|
46
|
+
excluded_files: [
|
47
|
+
@file.path
|
48
|
+
]
|
49
|
+
}.to_yaml
|
50
|
+
configuration.close
|
51
|
+
stub_const('Mire::Configuration::FILE', configuration.path)
|
52
|
+
|
53
|
+
subject
|
54
|
+
expect(methods[:buz]).to be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'finds method calls inside class.method' do
|
58
|
+
file <<-RUBY
|
59
|
+
class Foo
|
60
|
+
def self.bar
|
61
|
+
buz
|
62
|
+
end
|
63
|
+
end
|
64
|
+
RUBY
|
65
|
+
subject
|
66
|
+
expect(found_methods).to match_array(%i(bar buz))
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'finds callbacks' do
|
70
|
+
file <<-RUBY
|
71
|
+
class Foo
|
72
|
+
before_destroy :bar
|
73
|
+
def bar
|
74
|
+
true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
RUBY
|
78
|
+
subject
|
79
|
+
expect(found_methods).to match_array(%i(bar))
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'finds validation calls' do
|
83
|
+
file <<-RUBY
|
84
|
+
class Foo
|
85
|
+
validate :bar
|
86
|
+
end
|
87
|
+
RUBY
|
88
|
+
subject
|
89
|
+
expect(found_methods).to match_array(%i(bar))
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'finds method calls inside blocks - e.g. rake tasks' do
|
93
|
+
file <<-RUBY
|
94
|
+
namespace :foo do
|
95
|
+
task bar: :environment do
|
96
|
+
Foo.bar
|
97
|
+
Foo.bax
|
98
|
+
end
|
99
|
+
task bax: :environment do
|
100
|
+
Foo.bur
|
101
|
+
end
|
102
|
+
end
|
103
|
+
RUBY
|
104
|
+
subject
|
105
|
+
expect(found_methods).to match_array(%i(bar bax bur))
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'finds method calls in modifier ifs' do
|
109
|
+
file <<-RUBY
|
110
|
+
class Foo
|
111
|
+
validate :bar unless bux
|
112
|
+
validate :bar if buy
|
113
|
+
def bax
|
114
|
+
foo if bur
|
115
|
+
end
|
116
|
+
end
|
117
|
+
RUBY
|
118
|
+
subject
|
119
|
+
expect(found_methods).to match_array(%i(bar bux buy bax bur foo))
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'adds also not called methods' do
|
123
|
+
file <<-RUBY
|
124
|
+
class Foo
|
125
|
+
def self.bar
|
126
|
+
true
|
127
|
+
end
|
128
|
+
def baz
|
129
|
+
self.bar
|
130
|
+
end
|
131
|
+
end
|
132
|
+
RUBY
|
133
|
+
subject
|
134
|
+
expect(found_methods).to match_array(%i(bar baz))
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'haml files' do
|
138
|
+
it 'parses haml files' do
|
139
|
+
file(['- if bar', ' %b= foo'].join("\n"), :haml)
|
140
|
+
subject
|
141
|
+
expect(methods[:bar]).not_to be_nil
|
142
|
+
expect(methods[:bar][:invocations]).not_to be_empty
|
143
|
+
expect(methods[:bar][:invocations].first[:file]).to eq(@file.path)
|
144
|
+
expect(methods[:foo]).not_to be_nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe Mire::Configuration, type: :model do
|
5
|
+
before do
|
6
|
+
file = Tempfile.new('mire_configuration')
|
7
|
+
file << configuration.to_yaml
|
8
|
+
file.close
|
9
|
+
|
10
|
+
stub_const('Mire::Configuration::FILE', file.path)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:configuration) do
|
14
|
+
{
|
15
|
+
output: {
|
16
|
+
unused: {
|
17
|
+
excluded_files: [
|
18
|
+
'boo/**/*'
|
19
|
+
]
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'fetches values' do
|
26
|
+
expect(subject.read(:output, :unused, :excluded_files)).to eq(['boo/**/*'])
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns nil for not existing keys' do
|
30
|
+
expect(subject.read(:not, :known, :attribute)).to be_nil
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mire::Output::Occurrence, type: :model do
|
4
|
+
it 'returns occurrences of a given method' do
|
5
|
+
methods = {
|
6
|
+
foo: {
|
7
|
+
definitions: [
|
8
|
+
{
|
9
|
+
class: 'Foo',
|
10
|
+
method: 'bar',
|
11
|
+
file: 'foo.rb',
|
12
|
+
line: '123'
|
13
|
+
}
|
14
|
+
],
|
15
|
+
invocations: [
|
16
|
+
{
|
17
|
+
class: 'Foo',
|
18
|
+
method: 'baz',
|
19
|
+
file: 'foo.rb',
|
20
|
+
line: '10'
|
21
|
+
},
|
22
|
+
{
|
23
|
+
class: 'Foo',
|
24
|
+
method: 'buz',
|
25
|
+
file: 'foo.rb',
|
26
|
+
line: '20'
|
27
|
+
}
|
28
|
+
]
|
29
|
+
},
|
30
|
+
buz: {
|
31
|
+
definitions: [
|
32
|
+
{
|
33
|
+
class: 'Foo',
|
34
|
+
method: 'buz',
|
35
|
+
file: 'foo.rb',
|
36
|
+
line: '1234'
|
37
|
+
}
|
38
|
+
],
|
39
|
+
invocations: [
|
40
|
+
{
|
41
|
+
class: 'Foo',
|
42
|
+
method: 'biz',
|
43
|
+
file: 'foo.rb',
|
44
|
+
line: '30'
|
45
|
+
}
|
46
|
+
]
|
47
|
+
}
|
48
|
+
}
|
49
|
+
allow(subject).to receive(:methods).and_return(methods)
|
50
|
+
expect(subject.check(:foo)).to eq [
|
51
|
+
'foo.rb:10 Foo.baz',
|
52
|
+
'foo.rb:20 Foo.buz',
|
53
|
+
' foo.rb:30 Foo.biz'
|
54
|
+
]
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe Mire::Output::Unused, class: :model do
|
5
|
+
before do
|
6
|
+
methods = {
|
7
|
+
foo: {
|
8
|
+
definitions: [
|
9
|
+
{
|
10
|
+
class: 'Foo',
|
11
|
+
method: 'bar',
|
12
|
+
file: 'foo/bar.rb',
|
13
|
+
line: '123'
|
14
|
+
},
|
15
|
+
{
|
16
|
+
class: 'Boo',
|
17
|
+
method: 'bar',
|
18
|
+
file: 'boo/bar.rb',
|
19
|
+
line: '12'
|
20
|
+
}
|
21
|
+
],
|
22
|
+
invocations: []
|
23
|
+
}
|
24
|
+
}
|
25
|
+
allow(subject).to receive(:methods).and_return(methods)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns methods that are unused' do
|
29
|
+
expect(subject.check).to match_array([
|
30
|
+
'foo/bar.rb:123 Foo.bar',
|
31
|
+
'boo/bar.rb:12 Boo.bar'
|
32
|
+
])
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'ignores files when given in the configuration' do
|
36
|
+
configuration = Tempfile.new('mire_configuration')
|
37
|
+
configuration << {
|
38
|
+
output: {
|
39
|
+
unused: {
|
40
|
+
excluded_files: [
|
41
|
+
'boo/**/*'
|
42
|
+
]
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}.to_yaml
|
46
|
+
configuration.close
|
47
|
+
|
48
|
+
stub_const('Mire::Configuration::FILE', configuration.path)
|
49
|
+
expect(subject.check).to eq(['foo/bar.rb:123 Foo.bar'])
|
50
|
+
end
|
51
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mire
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nils Gemeinhardt
|
8
|
+
- Marcus Lankenau
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-06-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: parser
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: slop
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '4.0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '4.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: ruby-progressbar
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: haml-lint
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: bundler
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '1.7'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '1.7'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rake
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '10.0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '10.0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rspec
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '3.0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '3.0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: byebug
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rubocop
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: rubocop-rspec
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
description: |2
|
155
|
+
Analyzes a Ruby project and helps you to find dependencies, call stacks and
|
156
|
+
unused methods.
|
157
|
+
email:
|
158
|
+
- nils.gemeinhardt@xing.com
|
159
|
+
- marcus.lankenau@xing.com
|
160
|
+
executables:
|
161
|
+
- mire
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files: []
|
164
|
+
files:
|
165
|
+
- ".gitignore"
|
166
|
+
- ".rubocop.yml"
|
167
|
+
- ".rubocop_todo.yml"
|
168
|
+
- CHANGELOG.md
|
169
|
+
- Gemfile
|
170
|
+
- LICENSE.txt
|
171
|
+
- README.md
|
172
|
+
- Rakefile
|
173
|
+
- bin/mire
|
174
|
+
- lib/mire.rb
|
175
|
+
- lib/mire/analyzer.rb
|
176
|
+
- lib/mire/cli.rb
|
177
|
+
- lib/mire/configuration.rb
|
178
|
+
- lib/mire/configuration_example.yml
|
179
|
+
- lib/mire/configuration_methods.rb
|
180
|
+
- lib/mire/output/base.rb
|
181
|
+
- lib/mire/output/occurrence.rb
|
182
|
+
- lib/mire/output/unused.rb
|
183
|
+
- lib/mire/version.rb
|
184
|
+
- mire.gemspec
|
185
|
+
- spec/mire/analyzer_spec.rb
|
186
|
+
- spec/mire/configuration_spec.rb
|
187
|
+
- spec/mire/output/occurrence_spec.rb
|
188
|
+
- spec/mire/output/unused_spec.rb
|
189
|
+
- spec/spec_helper.rb
|
190
|
+
homepage: https://github.com/xing/mire
|
191
|
+
licenses:
|
192
|
+
- MIT
|
193
|
+
metadata: {}
|
194
|
+
post_install_message:
|
195
|
+
rdoc_options: []
|
196
|
+
require_paths:
|
197
|
+
- lib
|
198
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
199
|
+
requirements:
|
200
|
+
- - ">="
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
205
|
+
- - ">="
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: '0'
|
208
|
+
requirements: []
|
209
|
+
rubyforge_project:
|
210
|
+
rubygems_version: 2.2.3
|
211
|
+
signing_key:
|
212
|
+
specification_version: 4
|
213
|
+
summary: Analyzes a Ruby project.
|
214
|
+
test_files:
|
215
|
+
- spec/mire/analyzer_spec.rb
|
216
|
+
- spec/mire/configuration_spec.rb
|
217
|
+
- spec/mire/output/occurrence_spec.rb
|
218
|
+
- spec/mire/output/unused_spec.rb
|
219
|
+
- spec/spec_helper.rb
|