leftovers 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,14 @@
3
3
  module Leftovers
4
4
  ruby_version = Gem::Version.new(RUBY_VERSION)
5
5
  unless ruby_version >= Gem::Version.new('2.5')
6
+ require 'set'
7
+ module SetCaseEq
8
+ refine ::Set do
9
+ def ===(value)
10
+ include?(value)
11
+ end
12
+ end
13
+ end
6
14
  module StringDeletePrefixSuffix
7
15
  refine ::String do
8
16
  def delete_prefix!(str)
@@ -24,33 +32,5 @@ module Leftovers
24
32
  end
25
33
  end
26
34
  end
27
-
28
- require 'yaml'
29
- module YAMLSymbolizeNames
30
- refine YAML.singleton_class do
31
- alias_method :safe_load_without_symbolize_names, :safe_load
32
- def safe_load(path, *args, symbolize_names: false, **kwargs)
33
- if symbolize_names
34
- symbolize_names!(safe_load_without_symbolize_names(path, *args, **kwargs))
35
- else
36
- safe_load_without_symbolize_names(path, *args, **kwargs)
37
- end
38
- end
39
-
40
- private
41
-
42
- def symbolize_names!(obj) # rubocop:disable Metrics/MethodLength
43
- case obj
44
- when Hash
45
- obj.keys.each do |key| # rubocop:disable Style/HashEachMethods # each_key never finishes.
46
- obj[key.to_sym] = symbolize_names!(obj.delete(key))
47
- end
48
- when Array
49
- obj.map! { |ea| symbolize_names!(ea) }
50
- end
51
- obj
52
- end
53
- end
54
- end
55
35
  end
56
36
  end
@@ -6,25 +6,31 @@ require_relative 'version'
6
6
 
7
7
  module Leftovers
8
8
  class CLI
9
- attr_reader :argv, :stdout, :stderr
10
-
11
9
  def initialize(argv: [], stdout: $stdout, stderr: $stderr)
12
10
  @argv = argv
13
11
  @stdout = stdout
14
12
  @stderr = stderr
13
+ end
15
14
 
16
- parse_options
15
+ def run
16
+ catch(:leftovers_exit) do
17
+ Leftovers.reset
18
+ parse_options
17
19
 
18
- exit Leftovers.run(stdout: stdout, stderr: stderr)
20
+ Leftovers.run(stdout: stdout, stderr: stderr)
21
+ end
19
22
  end
20
23
 
24
+ private
25
+
26
+ attr_reader :argv, :stdout, :stderr
27
+
21
28
  def parse_options # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
22
29
  opts = OptionParser.new
23
30
  Leftovers.parallel = true
24
31
  Leftovers.progress = true
25
32
 
26
33
  opts.banner = 'Usage: leftovers [options]'
27
- opts.on('-q', '--quiet', 'Silences output') { Leftovers.quiet = true }
28
34
  opts.on('--[no-]parallel', 'Run in parallel or not, default --parallel') do |p|
29
35
  Leftovers.parallel = p
30
36
  end
@@ -32,16 +38,16 @@ module Leftovers
32
38
  Leftovers.progress = p
33
39
  end
34
40
  opts.on('-v', '--version', 'Returns the current version') do
35
- stdout.puts(Leftovers::Version)
36
- exit
41
+ stdout.puts(Leftovers::VERSION)
42
+ Leftovers.exit
37
43
  end
38
44
  opts.on('--dry-run', 'Output files that will be looked at') do
39
- stdout.puts(Leftovers::FileList.new.to_a.map(&:relative_path).join("\n"))
40
- exit
45
+ Leftovers::FileList.new.each { |f| stdout.puts f.relative_path }
46
+ Leftovers.exit
41
47
  end
42
48
  opts.on('-h', '--help', 'Shows this message') do
43
49
  stdout.puts(opts.help)
44
- exit
50
+ Leftovers.exit
45
51
  end
46
52
 
47
53
  opts.parse(argv)
@@ -31,9 +31,9 @@ module Leftovers
31
31
 
32
32
  def collect_file_list(list)
33
33
  if Leftovers.parallel?
34
- Parallel.each(list, finish: method(:finish_parallel), &method(:collect_file))
34
+ Parallel.each(list, finish: method(:finish_file), &method(:collect_file))
35
35
  else
36
- list.each { |file| finish_parallel(nil, nil, collect_file(file)) }
36
+ list.each { |file| finish_file(nil, nil, collect_file(file)) }
37
37
  end
38
38
  end
39
39
 
@@ -46,11 +46,11 @@ module Leftovers
46
46
 
47
47
  def print_progress
48
48
  Leftovers.print(
49
- "checked #{@count} files, collected #{@count_calls} calls, #{@count_definitions} definitions\r" # rubocop:disable Layout/LineLength
49
+ "\e[2Kchecked #{@count} files, collected #{@count_calls} calls, #{@count_definitions} definitions\r" # rubocop:disable Layout/LineLength
50
50
  )
51
51
  end
52
52
 
53
- def finish_parallel(_, _, result) # rubocop:disable Metrics/MethodLength
53
+ def finish_file(_, _, result) # rubocop:disable Metrics/MethodLength
54
54
  @count += 1
55
55
  @count_calls += result[:calls].length
56
56
  @count_definitions += result[:definitions].length
@@ -6,16 +6,12 @@ require_relative 'rule'
6
6
  module Leftovers
7
7
  class Config
8
8
  # :nocov:
9
- using ::Leftovers::YAMLSymbolizeNames if defined?(::Leftovers::YAMLSymbolizeNames)
9
+ using ::Leftovers::SetCaseEq if defined?(::Leftovers::SetCaseEq)
10
10
  # :nocov:
11
11
 
12
12
  attr_reader :name
13
13
 
14
- def initialize(
15
- name,
16
- path: ::File.join(__dir__, '..', 'config', "#{name}.yml"),
17
- content: (::File.exist?(path) ? ::File.read(path) : '')
18
- )
14
+ def initialize(name, path: nil, content: nil)
19
15
  @name = name.to_sym
20
16
  @path = path
21
17
  @content = content
@@ -39,15 +35,46 @@ module Leftovers
39
35
 
40
36
  def rules
41
37
  @rules ||= Rule.wrap(yaml[:rules])
38
+ rescue Leftovers::ConfigError => e
39
+ warn "\e[31mConfig Error: (#{path}): #{e.message}\e[0m"
40
+ Leftovers.exit 1
42
41
  end
43
42
 
44
43
  private
45
44
 
46
- def yaml
47
- @yaml ||= YAML.safe_load(@content, symbolize_names: true) || {}
48
- rescue Psych::SyntaxError => e
49
- warn "\e[31mError with config #{path}: #{e.message}\e[0m"
50
- exit 1
45
+ def content
46
+ @content ||= ::File.exist?(path) ? ::File.read(path) : ''
51
47
  end
48
+
49
+ def path
50
+ @path ||= ::File.expand_path("../config/#{name}.yml", __dir__)
51
+ end
52
+
53
+ def yaml # rubocop:disable Metrics/MethodLength
54
+ # :nocov:
55
+ @yaml ||= if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6')
56
+ Psych.safe_load(content, symbolize_names: true, filename: path) || {}
57
+ else
58
+ symbolize_names!(Psych.safe_load(content, [], [], false, path)) || {}
59
+ end
60
+ # :nocov:
61
+ rescue ::Psych::SyntaxError => e
62
+ warn "\e[31mConfig SyntaxError: #{e.message}\e[0m"
63
+ Leftovers.exit 1
64
+ end
65
+
66
+ # :nocov:
67
+ def symbolize_names!(obj) # rubocop:disable Metrics/MethodLength
68
+ case obj
69
+ when Hash
70
+ obj.keys.each do |key| # rubocop:disable Style/HashEachMethods # each_key never finishes.
71
+ obj[key.to_sym] = symbolize_names!(obj.delete(key))
72
+ end
73
+ when Array
74
+ obj.map! { |ea| symbolize_names!(ea) }
75
+ end
76
+ obj
77
+ end
78
+ # :nocov:
52
79
  end
53
80
  end
@@ -3,11 +3,13 @@
3
3
  require 'set'
4
4
 
5
5
  class Array
6
- def leftovers_append(other)
7
- case other
8
- when Array, Set then concat(other)
9
- when nil then self
10
- else self.<< other
6
+ def leftovers_append(other) # rubocop:disable Metrics/MethodLength
7
+ return self if other.nil?
8
+
9
+ if other.respond_to?(:to_a)
10
+ concat(other.to_a)
11
+ else
12
+ self << other
11
13
  end
12
14
  end
13
15
  end
@@ -4,13 +4,10 @@ module Leftovers
4
4
  class Definition
5
5
  attr_reader :name
6
6
  alias_method :names, :name
7
- alias_method :full_name, :name
8
- attr_reader :name_s
9
- alias_method :to_s, :name_s
10
7
  attr_reader :test
11
8
  alias_method :test?, :test
12
9
 
13
- def initialize( # rubocop:disable Metrics/MethodLength
10
+ def initialize(
14
11
  name,
15
12
  method_node: nil,
16
13
  location: method_node.loc.expression,
@@ -18,7 +15,6 @@ module Leftovers
18
15
  test: method_node.test?
19
16
  )
20
17
  @name = name
21
- @name_s = name.to_s.freeze
22
18
 
23
19
  @location = location
24
20
  @file = file
@@ -37,6 +33,10 @@ module Leftovers
37
33
  @file.relative_path
38
34
  end
39
35
 
36
+ def to_s
37
+ @name.to_s
38
+ end
39
+
40
40
  def line
41
41
  @location.line
42
42
  end
@@ -46,7 +46,7 @@ module Leftovers
46
46
  end
47
47
 
48
48
  def full_location
49
- "#{path}:#{@location.line}:#{@location.column}"
49
+ "#{path}:#{line}:#{column}"
50
50
  end
51
51
 
52
52
  def highlighted_source(highlight = "\e[31m", normal = "\e[0m") # rubocop:disable Metrics/AbcSize
@@ -64,7 +64,7 @@ module Leftovers
64
64
  end
65
65
 
66
66
  def skipped?
67
- Leftovers.config.skip_rules.any? { |r| r.match?(@name, @name_s, path) }
67
+ Leftovers.config.skip_rules.any? { |r| r.match?(@name, path) }
68
68
  end
69
69
  end
70
70
  end
@@ -21,10 +21,6 @@ module Leftovers
21
21
  freeze
22
22
  end
23
23
 
24
- def full_name
25
- names.join(', ')
26
- end
27
-
28
24
  def names
29
25
  @definitions.map(&:names)
30
26
  end
@@ -19,7 +19,7 @@ module Leftovers
19
19
  def ruby # rubocop:disable Metrics/MethodLength
20
20
  case extname
21
21
  when '.haml'
22
- Leftovers::Haml.precompile(read)
22
+ Leftovers::Haml.precompile(read, self)
23
23
  when '.rhtml', '.rjs', '.erb'
24
24
  Leftovers::ERB.precompile(read)
25
25
  else
@@ -52,11 +52,9 @@ module Leftovers
52
52
  def process_comments(comments) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
53
53
  comments.each do |comment|
54
54
  @allow_lines << comment.loc.line if comment.text.match?(LEFTOVERS_ALLOW_RE)
55
-
56
55
  @test_lines << comment.loc.line if comment.text.match?(LEFTOVERS_TEST_RE)
57
56
 
58
57
  next unless (match = comment.text.match(LEFTOVERS_CALL_RE))
59
- next unless match[1]
60
58
 
61
59
  match[1].scan(NAME_RE).each { |s| add_call(s.to_sym) }
62
60
  end
@@ -64,13 +62,15 @@ module Leftovers
64
62
 
65
63
  # grab method definitions
66
64
  def on_def(node)
67
- add_definition(node.children.first, node.loc.name)
65
+ add_definition(node.name, node.loc.name)
68
66
 
69
67
  super
70
68
  end
71
69
 
72
70
  def on_ivasgn(node)
73
- add_definition(node.children.first, node.loc.name)
71
+ add_definition(node.name, node.loc.name)
72
+
73
+ collect_rules(node)
74
74
 
75
75
  super
76
76
  end
@@ -78,7 +78,7 @@ module Leftovers
78
78
  alias_method :on_cvasgn, :on_ivasgn
79
79
 
80
80
  def on_ivar(node)
81
- add_call(node.children.first)
81
+ add_call(node.name)
82
82
 
83
83
  super
84
84
  end
@@ -107,8 +107,7 @@ module Leftovers
107
107
  def on_send(node)
108
108
  super
109
109
 
110
- add_call(node.children[1])
111
-
110
+ add_call(node.name)
112
111
  collect_rules(node)
113
112
  end
114
113
  alias_method :on_csend, :on_send
@@ -116,14 +115,14 @@ module Leftovers
116
115
  def on_const(node)
117
116
  super
118
117
 
119
- add_call(node.children[1])
118
+ add_call(node.name)
120
119
  end
121
120
 
122
121
  # grab e.g. :to_s in each(&:to_s)
123
122
  def on_block_pass(node)
124
123
  super
125
124
 
126
- add_call(node.children.first.to_sym) if node.children.first&.string_or_symbol?
125
+ add_call(node.children.first.to_sym) if node.children.first.string_or_symbol?
127
126
  end
128
127
 
129
128
  # grab class Constant or module Constant
@@ -134,7 +133,7 @@ module Leftovers
134
133
 
135
134
  node = node.children.first
136
135
 
137
- add_definition(node.children[1], node.loc.name)
136
+ add_definition(node.name, node.loc.name)
138
137
  end
139
138
  alias_method :on_module, :on_class
140
139
 
@@ -142,7 +141,7 @@ module Leftovers
142
141
  def on_casgn(node)
143
142
  super
144
143
 
145
- add_definition(node.children[1], node.loc.name)
144
+ add_definition(node.name, node.loc.name)
146
145
 
147
146
  collect_rules(node)
148
147
  end
@@ -177,22 +176,20 @@ module Leftovers
177
176
  def collect_var_op_asgn(node)
178
177
  name = node.children.first
179
178
 
180
- return unless name
181
-
182
179
  add_call(name)
183
180
  end
184
181
 
185
182
  def collect_send_op_asgn(node)
186
183
  name = node.children[1]
187
184
 
188
- return unless name
189
-
190
185
  add_call(:"#{name}=")
191
186
  end
192
187
 
193
188
  def collect_op_asgn(node)
194
189
  node = node.children.first
190
+ # :nocov: # don't need else, it's exhaustive for callers
195
191
  case node.type
192
+ # :nocov:
196
193
  when :send then collect_send_op_asgn(node)
197
194
  when :ivasgn, :gvasgn, :cvasgn then collect_var_op_asgn(node)
198
195
  end
@@ -200,7 +197,7 @@ module Leftovers
200
197
 
201
198
  def collect_rules(node) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
202
199
  Leftovers.config.rules.each do |rule|
203
- next unless rule.match?(node.name, node.name_s, filename)
200
+ next unless rule.match?(node.name, filename)
204
201
 
205
202
  next if rule.skip?
206
203
 
@@ -4,24 +4,19 @@ require 'fast_ignore'
4
4
  require_relative 'file'
5
5
 
6
6
  module Leftovers
7
- class FileList
8
- include Enumerable
7
+ class FileList < ::FastIgnore
8
+ def initialize
9
+ super(
10
+ ignore_rules: Leftovers.config.exclude_paths,
11
+ include_rules: Leftovers.config.include_paths,
12
+ root: Leftovers.pwd
13
+ )
14
+ end
9
15
 
10
16
  def each
11
- fast_ignore.each do |file|
17
+ super do |file|
12
18
  yield(Leftovers::File.new(file))
13
19
  end
14
20
  end
15
-
16
- def to_a
17
- enum_for(:each).to_a
18
- end
19
-
20
- def fast_ignore
21
- FastIgnore.new(
22
- ignore_rules: Leftovers.config.exclude_paths,
23
- include_rules: ['#!:ruby'] + Leftovers.config.include_paths
24
- )
25
- end
26
21
  end
27
22
  end
@@ -4,19 +4,16 @@ module Leftovers
4
4
  module Haml
5
5
  module_function
6
6
 
7
- def precompile(file) # rubocop:disable Metrics/MethodLength
8
- Leftovers.try_require('haml', message: <<~MESSAGE)
9
- Skipped parsing a haml file, because the haml gem was not available
10
- `gem install Haml`
7
+ def precompile(file, name) # rubocop:disable Metrics/MethodLength
8
+ return '' unless Leftovers.try_require('haml', message: <<~MESSAGE) # rubocop:disable Layout/EmptyLineAfterGuardClause
9
+ Skipped parsing #{name.relative_path}, because the haml gem was not available
10
+ `gem install haml`
11
11
  MESSAGE
12
- if defined?(::Haml)
13
- begin
14
- ::Haml::Engine.new(file).precompiled
15
- rescue ::Haml::SyntaxError => e
16
- Leftovers.warn "#{e.class}: #{e.message} #{filename}:#{e.line}"
17
- ''
18
- end
19
- else
12
+
13
+ begin
14
+ ::Haml::Engine.new(file).precompiled
15
+ rescue ::Haml::SyntaxError => e
16
+ Leftovers.warn "#{e.class}: #{e.message} #{name.relative_path}:#{e.line}"
20
17
  ''
21
18
  end
22
19
  end