mire 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .ruby-version
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ require: rubocop-rspec
2
+ inherit_from: .rubocop_todo.yml
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
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
data/bin/mire ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
4
+
5
+ require 'mire'
6
+
7
+ Mire::CLI.parse
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,12 @@
1
+ excluded_files:
2
+ - vendor/**/*
3
+
4
+ output:
5
+ unused:
6
+ excluded_files:
7
+ - app/controllers/**/*
8
+ - config/**/*
9
+ - db/migrate/**/*
10
+ - lib/**/*
11
+ - script/**/*
12
+ - spec/**/*
@@ -0,0 +1,12 @@
1
+ module Mire
2
+ module ConfigurationMethods
3
+
4
+ def configuration
5
+ @configuration ||= Mire::Configuration.new
6
+ end
7
+
8
+ def excluded_file?(file)
9
+ excluded_files.any? { |e| File.fnmatch(e, file, File::FNM_PATHNAME) }
10
+ end
11
+ end
12
+ 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
@@ -0,0 +1,3 @@
1
+ module Mire
2
+ VERSION = '0.1.1'
3
+ end
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
@@ -0,0 +1,4 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'mire'
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