deadlyzer 0.1.0

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
+ SHA256:
3
+ metadata.gz: 44f9e798e87a8ce44f9cc08c7ebe56e3e9d9078f49cb0bcc25955f804c6961d8
4
+ data.tar.gz: d99a693be1a3d66c2cd4d0b459508ad07faafec82b8ff53fba0e8f6ebafc22e4
5
+ SHA512:
6
+ metadata.gz: 07743314cfc763ba0ab1f2125fecfc93200747863453490e6671e5882b471ab2cd111bab3fa631ce9cf5525879438513681ae3a3cb5f16398cec102636bcf1c2
7
+ data.tar.gz: 1423f22fe859022afbf0a9fc9319e75ca49da0452a82d7b768f3411cffb47779f4c81782fe3d6c450b41dc187965391f11de2243183d8b513e3052d553d5e6f4
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Deadlyzer
2
+ Deadlyzer lets you to compare directories and reveal unreferenced, uncalled constats to remove
3
+
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ ```ruby
9
+ gem 'deadlyzer'
10
+ ```
11
+
12
+ And then execute:
13
+
14
+ $ bundle install
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install deadlyzer
19
+
20
+ ## Usage
21
+
22
+ To use deadlyzer you should create deadlyzer.yml file with following content
23
+
24
+ ```yaml
25
+ target:
26
+ path: './app/workers'
27
+ exclude_consts:
28
+ - 'Sidekiq::Worker'
29
+ against:
30
+ path: './app/controllers'
31
+ ```
32
+
33
+ Available options
34
+
35
+ * exclude_const (Array)
36
+ * exclude_dirs (Array)
37
+
38
+ ```sh
39
+ user@desktop: ~$ deadlyzer # to run
40
+ ```
41
+
42
+ ## Development
43
+
44
+ Working principle is simple enough. Gem uses Rubocup and Parser gems to creat Abstract Syntax Tree. Using '(const ...)' pattern to match with nodes on the tree
45
+
46
+ ## Contributing
47
+ Deadlyzer is still under development and created to use personal purpose where problems can solve with finding uncalled constants. If you also need it or would like to contribute it would be great :)
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "deadlyzer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/deadlyzer ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # require "bundler/setup"
4
+ require "deadlyzer"
5
+
6
+ analyzer = Deadlyzer::Analyzer.new(ARGV)
7
+ analyzer.analize!
@@ -0,0 +1,10 @@
1
+ require 'ostruct'
2
+
3
+ module Deadlyzer
4
+ class Analyzer
5
+
6
+ # @api private
7
+ # @since 0.1.0
8
+ class Configurator < OpenStruct; end
9
+ end
10
+ end
@@ -0,0 +1,35 @@
1
+ require 'yaml'
2
+
3
+ module Deadlyzer
4
+ class Analyzer
5
+ module Utils
6
+ def self.included(base)
7
+ base.include InstanceMethods
8
+ end
9
+
10
+ module InstanceMethods
11
+ # @api private
12
+ # @since 0.1.0
13
+ def parse_yaml(path)
14
+ ctx = read_file(path)
15
+
16
+ YAML.load(ctx)
17
+ end
18
+
19
+ # Default name of config file
20
+ # @api private
21
+ # @since 0.1.0
22
+ def default_config_file
23
+ './deadlyzer.yml'
24
+ end
25
+
26
+ # @api private
27
+ # @since 0.1.0
28
+ def read_file(path)
29
+ File.read(File.expand_path(path))
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,140 @@
1
+ require 'deadlyzer/analyzer/utils'
2
+ require 'deadlyzer/analyzer/configurator'
3
+ require 'deadlyzer/constant_loader'
4
+ require 'deadlyzer/ast_parser'
5
+
6
+ require 'spinning_cursor'
7
+ require 'corol'
8
+
9
+ module Deadlyzer
10
+ class Analyzer
11
+ include Deadlyzer::Analyzer::Utils
12
+
13
+ # @api private
14
+ # @since 0.1.0
15
+ attr_reader :config_file
16
+
17
+ # @api private
18
+ # @since 0.1.0
19
+ attr_reader :config
20
+
21
+ # @api private
22
+ # @since 0.1.0
23
+ attr_reader :config_file_ctx
24
+
25
+ # @api private
26
+ # @since 0.1.0
27
+ attr_reader :target_constants
28
+
29
+ # @api private
30
+ # @since 0.1.0
31
+ attr_reader :against_constants
32
+
33
+ # @api private
34
+ # @since 0.1.0
35
+ attr_reader :constants_may_not_used
36
+
37
+ attr_reader :empty_files
38
+
39
+ def initialize(argv = [])
40
+ @config_file = argv.pop || default_config_file
41
+ @empty_files = []
42
+ @constants_may_not_used = []
43
+ end
44
+
45
+ # Main wrapper analize method
46
+ # @since 0.1.0
47
+ def analize!
48
+ SpinningCursor.run do
49
+ banner "Analyzing".yellow
50
+ type :dots
51
+
52
+ action do
53
+ read_configs!
54
+ load_config!
55
+ load_target_constants!
56
+ load_against_constants!
57
+ match!
58
+ send_out!
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+ # @api private
65
+ # @since 0.1.0
66
+ def read_configs!
67
+ @config_file_ctx = parse_yaml(config_file)
68
+ rescue
69
+ puts 'No config file found'
70
+ exit 1
71
+ end
72
+
73
+ # @api private
74
+ # @since 0.1.0
75
+ def load_config!
76
+ @config = Configurator.new(config_file_ctx)
77
+ end
78
+
79
+ # @api private
80
+ # @since 0.1.0
81
+ def load_target_constants!
82
+ cl = ConstantLoader.new(
83
+ directory: config.target.fetch('path', nil),
84
+ excluded_dirs: config.target.fetch('exclude_dirs', nil),
85
+ excluded_consts: config.target.fetch('exclude_consts', nil),
86
+ )
87
+
88
+ empty_files.push cl.empty_files.uniq
89
+ @target_constants = cl.constants.uniq
90
+ end
91
+
92
+ # @api private
93
+ # @since 0.1.0
94
+ def load_against_constants!
95
+ cl = ConstantLoader.new(
96
+ directory: config.against.fetch('path', nil),
97
+ excluded_dirs: config.against.fetch('exclude_dirs', nil),
98
+ excluded_consts: config.against.fetch('exclude_consts', nil),
99
+ )
100
+
101
+ empty_files.push cl.empty_files.uniq
102
+ @against_constants = cl.constants.uniq
103
+ end
104
+
105
+ # @api private
106
+ # @since 0.1.0
107
+ def match!
108
+ match_constants(target: target_constants, against: against_constants)
109
+ constants_may_not_used
110
+ end
111
+
112
+ # @api private
113
+ # @since 0.1.0
114
+ def send_out!
115
+ puts "Files might be empty"
116
+ empty_files.each do |file|
117
+ puts file
118
+ end
119
+
120
+ puts "\nContants may not refered\n"
121
+ constants_may_not_used.each do |constant|
122
+ puts constant
123
+ end
124
+ end
125
+
126
+ # @api private
127
+ # @since 0.1.0
128
+ def match_constants(target:, against:)
129
+ used_constants = target.select do |tc|
130
+ tc.name if against.any? {|ac| ac.name == tc.name}
131
+ end
132
+
133
+ constants = (used_constants + target).group_by(&:itself).transform_values(&:count)
134
+ constants.each do |constant, count|
135
+ constants_may_not_used.push constant if count < 2
136
+ end
137
+ end
138
+
139
+ end
140
+ end
@@ -0,0 +1,36 @@
1
+ module Deadlyzer
2
+ module AST
3
+ class Matcher
4
+ # @api private
5
+ # @since 0.1.0
6
+ DEFAULT_PATTERN = '(const ... )'
7
+
8
+ # @api private
9
+ # @since 0.1.0
10
+ attr_reader :matcher
11
+
12
+ # @api private
13
+ # @since 0.1.0
14
+ attr_reader :pattern
15
+
16
+ def initialize(matcher: nil, pattern: nil)
17
+ @matcher = matcher || default_matcher
18
+ @pattern = pattern || DEFAULT_PATTERN
19
+ end
20
+
21
+ # @api private
22
+ # @since 0.1.0
23
+ def match(node:)
24
+ matcher.new(pattern).match(node)
25
+ end
26
+
27
+ private
28
+ # @api private
29
+ # @since 0.1.0
30
+ def default_matcher
31
+ require 'rubocop'
32
+ RuboCop::NodePattern
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ module Deadlyzer
2
+ module AST
3
+ class Parser
4
+ # AST Parser
5
+ # @api private
6
+ # @since 0.1.0
7
+
8
+ # @api private
9
+ # @since 0.1.0
10
+ attr_reader :parser
11
+
12
+ # @since 0.1.0
13
+ attr_reader :ast
14
+
15
+ # @since 0.1.0
16
+ attr_reader :file
17
+
18
+ def initialize(parser: nil)
19
+ @parser = parser || default_parser
20
+ end
21
+
22
+ # @api private
23
+ # @since 0.1.0
24
+ def parse(code, file = nil)
25
+ processed_source = parse_code(code, file)
26
+ @ast = processed_source.ast
27
+ @file = processed_source.path
28
+ self
29
+ end
30
+
31
+ private
32
+ # @api private
33
+ # @since 0.1.0
34
+ def default_parser
35
+ require 'rubocop'
36
+ RuboCop::ProcessedSource
37
+ end
38
+
39
+ # @api private
40
+ # @since 0.1.0
41
+ def parse_code(code, file)
42
+ parser.new(code, RUBY_VERSION.to_f, file)
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,186 @@
1
+ require 'deadlyzer/ast_parser'
2
+ require 'deadlyzer/ast_matcher'
3
+
4
+ module Deadlyzer
5
+ class ConstantLoader
6
+ CURRENT_DIRECTORY = File.expand_path('.', __dir__)
7
+ DEFAULT_PATTERN = '/**/*.rb'
8
+
9
+ # @api private
10
+ # @since 0.1.0
11
+ attr_reader :directory
12
+
13
+ # @api private
14
+ # @since 0.1.0
15
+ attr_reader :excluded_dirs
16
+
17
+ # @api private
18
+ # @since 0.1.0
19
+ attr_reader :empty_files
20
+
21
+ # @api private
22
+ # @since 0.1.0
23
+ attr_reader :excluded_consts
24
+
25
+ # @api private
26
+ # @since 0.1.0
27
+ attr_reader :exclude
28
+
29
+ # @api private
30
+ # @since 0.1.0
31
+ attr_reader :pattern
32
+
33
+ # @api private
34
+ # @since 0.1.0
35
+ attr_accessor :ast
36
+
37
+ def initialize(directory: nil, excluded_dirs: nil, excluded_consts: nil, pattern: nil)
38
+ @directory = directory
39
+ @excluded_dirs = excluded_dirs || []
40
+ @excluded_consts = excluded_consts || []
41
+ @pattern = pattern || DEFAULT_PATTERN
42
+ @ast = []
43
+ @constants = []
44
+ @empty_files = []
45
+ end
46
+
47
+ # @api private
48
+ # @since 0.1.0
49
+ def constants
50
+ load_ast!
51
+ load_matcher!
52
+ load_constants!
53
+ @constants
54
+ end
55
+
56
+ private
57
+ # @api private
58
+ # @since 0.1.0
59
+ def load_ast!
60
+ files.each do |file|
61
+ # TODO: check momory issue
62
+ parser = Deadlyzer::AST::Parser.new
63
+ ast.push parser.parse(read(file), file)
64
+ end
65
+ end
66
+
67
+ # @api private
68
+ # @since 0.1.0
69
+ def load_matcher!
70
+ @matcher = Deadlyzer::AST::Matcher.new
71
+ end
72
+
73
+ # @api private
74
+ # @since 0.1.0
75
+ def load_constants!
76
+ ast.each do |node|
77
+ @empty_files.push node.file && next if node.ast.nil?
78
+
79
+ file = node.file
80
+ results = determine_consts(@matcher, node.ast)
81
+
82
+ results.each do |result|
83
+ line, name = humanize_const(result)
84
+ @constants.push(Constant.new(file, line, name)) unless excluded_consts.include?(name)
85
+ end
86
+ end
87
+ end
88
+
89
+ # @api private
90
+ # @since 0.1.0
91
+ def determine_consts(matcher, node)
92
+ match = matcher.match(node: node)
93
+ if match
94
+ match == true ? node : [match, node]
95
+ else
96
+ if node.respond_to?(:children) && !node.children.empty?
97
+ node.children.grep(RuboCop::AST::Node).flat_map{|e| determine_consts(matcher, e)}.compact
98
+ end
99
+ end
100
+ end
101
+
102
+ # @api private
103
+ # @since 0.1.0
104
+ def humanize_const(result)
105
+ if result.is_a?(RuboCop::AST::Node)
106
+ range = result.source_range
107
+ result = result.source
108
+ else
109
+ result, node = result
110
+ range = node.source_range
111
+ end
112
+ [range.line, result, :ruby]
113
+ end
114
+
115
+ # @api private
116
+ # @since 0.1.0
117
+ def files
118
+ target_directory = list_dir(path(directory + pattern))
119
+ remove(files: excluded_files, from: target_directory)
120
+ end
121
+
122
+ # @api private
123
+ # @since 0.1.0
124
+ def excluded_files
125
+ excluded_dirs.flat_map {|dir| list_dir(path(dir+ pattern))}.flatten
126
+ end
127
+
128
+ # @api private
129
+ # @since 0.1.0
130
+ # def excluded_constants
131
+
132
+ # end
133
+
134
+ # @api private
135
+ # @since 0.1.0
136
+ def path(dir)
137
+ File.expand_path(dir)
138
+ end
139
+
140
+ # @api private
141
+ # @since 0.1.0
142
+ def list_dir(dir)
143
+ Dir[dir]
144
+ end
145
+
146
+ # @api private
147
+ # @since 0.1.0
148
+ def remove(files:, from:)
149
+ files.each do |file|
150
+ from.delete file
151
+ end
152
+ from
153
+ end
154
+
155
+ # @api private
156
+ # @since 0.1.0
157
+ def read(file)
158
+ File.read file
159
+ end
160
+
161
+ # @api private
162
+ # @since 0.1.0
163
+ class Constant
164
+ require 'coderay'
165
+
166
+ attr_reader :file
167
+ attr_reader :line
168
+ attr_reader :name
169
+ def initialize(file, line, name)
170
+ @file = file
171
+ @line = line
172
+ @name = name
173
+ end
174
+
175
+ def to_s
176
+ [file, line, rubyize(name)].join(':')
177
+ end
178
+
179
+ private
180
+ def rubyize(name)
181
+ " #{CodeRay.scan(name, :ruby).term}"
182
+ end
183
+ end
184
+
185
+ end
186
+ end
@@ -0,0 +1,5 @@
1
+ module Deadlyzer
2
+ # @api private
3
+ # @since 0.1.0
4
+ VERSION = "0.1.0"
5
+ end
data/lib/deadlyzer.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'deadlyzer/analyzer'
2
+ require 'deadlyzer/version'
3
+
4
+ module Deadlyzer
5
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deadlyzer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Farhad
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-05-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '12.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '12.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-nav
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: coderay
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: spinning_cursor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: corol
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Scan find unused constant on other directories, compare and remove
112
+ email:
113
+ - farhad9801@gmail.com
114
+ executables:
115
+ - deadlyzer
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - README.md
120
+ - bin/console
121
+ - bin/setup
122
+ - exe/deadlyzer
123
+ - lib/deadlyzer.rb
124
+ - lib/deadlyzer/analyzer.rb
125
+ - lib/deadlyzer/analyzer/configurator.rb
126
+ - lib/deadlyzer/analyzer/utils.rb
127
+ - lib/deadlyzer/ast_matcher.rb
128
+ - lib/deadlyzer/ast_parser.rb
129
+ - lib/deadlyzer/constant_loader.rb
130
+ - lib/deadlyzer/version.rb
131
+ homepage: https://github.com/0x2C6/deadlyzer
132
+ licenses: []
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 2.2.0
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 3.1.2
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Reveal uncalled constants
153
+ test_files: []