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