leftovers 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.leftovers.yml +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +216 -0
- data/.ruby-version +1 -0
- data/.spellr.yml +13 -0
- data/.spellr_wordlists/english.txt +21 -0
- data/.spellr_wordlists/lorem.txt +1 -0
- data/.spellr_wordlists/ruby.txt +67 -0
- data/.spellr_wordlists/shell.txt +3 -0
- data/.travis.yml +10 -0
- data/Configuration.md +427 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +172 -0
- data/Rakefile +16 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/leftovers +6 -0
- data/leftovers.gemspec +42 -0
- data/lib/config/attr_encrypted.yml +16 -0
- data/lib/config/builder.yml +3 -0
- data/lib/config/capistrano.yml +3 -0
- data/lib/config/datagrid.yml +9 -0
- data/lib/config/flipper.yml +3 -0
- data/lib/config/graphql.yml +20 -0
- data/lib/config/guard.yml +3 -0
- data/lib/config/haml.yml +2 -0
- data/lib/config/jbuilder.yml +2 -0
- data/lib/config/okcomputer.yml +3 -0
- data/lib/config/parser.yml +91 -0
- data/lib/config/pry.yml +3 -0
- data/lib/config/rack.yml +2 -0
- data/lib/config/rails.yml +387 -0
- data/lib/config/rake.yml +5 -0
- data/lib/config/redcarpet.yml +38 -0
- data/lib/config/rollbar.yml +3 -0
- data/lib/config/rspec.yml +56 -0
- data/lib/config/ruby.yml +77 -0
- data/lib/config/selenium.yml +21 -0
- data/lib/config/simplecov.yml +2 -0
- data/lib/config/will_paginate.yml +14 -0
- data/lib/leftovers/argument_rule.rb +216 -0
- data/lib/leftovers/backports.rb +56 -0
- data/lib/leftovers/cli.rb +50 -0
- data/lib/leftovers/collector.rb +67 -0
- data/lib/leftovers/config.rb +53 -0
- data/lib/leftovers/core_ext.rb +32 -0
- data/lib/leftovers/definition.rb +70 -0
- data/lib/leftovers/definition_set.rb +44 -0
- data/lib/leftovers/erb.rb +20 -0
- data/lib/leftovers/file.rb +30 -0
- data/lib/leftovers/file_collector.rb +219 -0
- data/lib/leftovers/file_list.rb +24 -0
- data/lib/leftovers/haml.rb +24 -0
- data/lib/leftovers/hash_rule.rb +40 -0
- data/lib/leftovers/merged_config.rb +71 -0
- data/lib/leftovers/name_rule.rb +53 -0
- data/lib/leftovers/node.rb +182 -0
- data/lib/leftovers/rake_task.rb +62 -0
- data/lib/leftovers/reporter.rb +11 -0
- data/lib/leftovers/rule.rb +74 -0
- data/lib/leftovers/transform_rule.rb +171 -0
- data/lib/leftovers/value_rule.rb +56 -0
- data/lib/leftovers/version.rb +5 -0
- data/lib/leftovers.rb +127 -0
- metadata +281 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Leftovers
|
4
|
+
class Definition
|
5
|
+
attr_reader :name
|
6
|
+
alias_method :names, :name
|
7
|
+
alias_method :full_name, :name
|
8
|
+
attr_reader :name_s
|
9
|
+
alias_method :to_s, :name_s
|
10
|
+
attr_reader :test
|
11
|
+
alias_method :test?, :test
|
12
|
+
|
13
|
+
def initialize( # rubocop:disable Metrics/MethodLength
|
14
|
+
name,
|
15
|
+
method_node: nil,
|
16
|
+
location: method_node.loc.expression,
|
17
|
+
file: method_node.file,
|
18
|
+
test: method_node.test?
|
19
|
+
)
|
20
|
+
@name = name
|
21
|
+
@name_s = name.to_s.freeze
|
22
|
+
|
23
|
+
@location = location
|
24
|
+
@file = file
|
25
|
+
@test = test
|
26
|
+
|
27
|
+
freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
def <=>(other)
|
31
|
+
(path <=> other.path).nonzero? ||
|
32
|
+
(line <=> other.line).nonzero? ||
|
33
|
+
(column <=> other.column)
|
34
|
+
end
|
35
|
+
|
36
|
+
def path
|
37
|
+
@file.relative_path
|
38
|
+
end
|
39
|
+
|
40
|
+
def line
|
41
|
+
@location.line
|
42
|
+
end
|
43
|
+
|
44
|
+
def column
|
45
|
+
@location.column
|
46
|
+
end
|
47
|
+
|
48
|
+
def full_location
|
49
|
+
"#{path}:#{@location.line}:#{@location.column}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def highlighted_source(highlight = "\e[31m", normal = "\e[0m") # rubocop:disable Metrics/AbcSize
|
53
|
+
@location.source_line.to_s[0...(@location.column_range.begin)].lstrip +
|
54
|
+
highlight + @location.source.to_s + normal +
|
55
|
+
@location.source_line.to_s[(@location.column_range.end)..-1].rstrip
|
56
|
+
end
|
57
|
+
|
58
|
+
def in_collection?
|
59
|
+
Leftovers.collector.calls.include?(@name) || (@test && in_test_collection?)
|
60
|
+
end
|
61
|
+
|
62
|
+
def in_test_collection?
|
63
|
+
Leftovers.collector.test_calls.include?(@name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def skipped?
|
67
|
+
Leftovers.config.skip_rules.any? { |r| r.match?(@name, @name_s, path) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'definition'
|
4
|
+
module Leftovers
|
5
|
+
class DefinitionSet < Leftovers::Definition
|
6
|
+
def initialize( # rubocop:disable Metrics/MethodLength
|
7
|
+
names,
|
8
|
+
method_node: nil,
|
9
|
+
location: method_node.loc.expression,
|
10
|
+
file: method_node.file,
|
11
|
+
test: method_node.test?
|
12
|
+
)
|
13
|
+
@definitions = names.map do |name|
|
14
|
+
Leftovers::Definition.new(name, test: test, location: location, file: file)
|
15
|
+
end
|
16
|
+
|
17
|
+
@test = test
|
18
|
+
@location = location
|
19
|
+
@file = file
|
20
|
+
|
21
|
+
freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def names
|
25
|
+
@definitions.map(&:names)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@definitions.map(&:to_s).join(', ')
|
30
|
+
end
|
31
|
+
|
32
|
+
def in_collection?
|
33
|
+
@definitions.any?(&:in_collection?)
|
34
|
+
end
|
35
|
+
|
36
|
+
def in_test_collection?
|
37
|
+
@definitions.any?(&:in_test_collection?)
|
38
|
+
end
|
39
|
+
|
40
|
+
def skipped?
|
41
|
+
@definitions.any?(&:skipped?)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module Leftovers
|
6
|
+
class ERB < ::ERB::Compiler
|
7
|
+
def self.precompile(erb)
|
8
|
+
@compiler ||= new('-')
|
9
|
+
@compiler.compile(erb).first
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_insert_cmd(out, content)
|
13
|
+
out.push("#{content}\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_put_cmd(out, _content)
|
17
|
+
out
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'erb'
|
4
|
+
require_relative 'haml'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
module Leftovers
|
8
|
+
class File < Pathname
|
9
|
+
def relative_path
|
10
|
+
@relative_path ||= relative_path_from(Leftovers.pwd)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test?
|
14
|
+
return @test if defined?(@test)
|
15
|
+
|
16
|
+
@test = Leftovers.config.test_paths.allowed?(relative_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ruby # rubocop:disable Metrics/MethodLength
|
20
|
+
case extname
|
21
|
+
when '.haml'
|
22
|
+
Leftovers::Haml.precompile(read)
|
23
|
+
when '.rhtml', '.rjs', '.erb'
|
24
|
+
Leftovers::ERB.precompile(read)
|
25
|
+
else
|
26
|
+
read
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fast_ignore'
|
4
|
+
require 'set'
|
5
|
+
require 'parser'
|
6
|
+
require 'parser/current'
|
7
|
+
require_relative 'node'
|
8
|
+
require_relative 'definition'
|
9
|
+
|
10
|
+
module Leftovers
|
11
|
+
class FileCollector < Parser::AST::Processor # rubocop:disable Metrics/ClassLength
|
12
|
+
attr_reader :calls
|
13
|
+
attr_reader :definitions
|
14
|
+
|
15
|
+
def initialize(ruby, file) # rubocop:disable Metrics/MethodLength
|
16
|
+
@calls = []
|
17
|
+
@definitions = []
|
18
|
+
@allow_lines = Set.new.compare_by_identity
|
19
|
+
@test_lines = Set.new.compare_by_identity
|
20
|
+
@ruby = ruby
|
21
|
+
@file = file
|
22
|
+
end
|
23
|
+
|
24
|
+
def filename
|
25
|
+
@filename ||= @file.relative_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_h
|
29
|
+
{
|
30
|
+
test?: @file.test?,
|
31
|
+
calls: calls,
|
32
|
+
definitions: definitions
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def collect
|
37
|
+
ast, comments = Parser::CurrentRuby.parse_with_comments(@ruby)
|
38
|
+
process_comments(comments)
|
39
|
+
process(ast)
|
40
|
+
rescue Parser::SyntaxError => e
|
41
|
+
Leftovers.warn "#{e.class}: #{e.message} #{filename}:#{e.diagnostic.location.line}:#{e.diagnostic.location.column}" # rubocop:disable Layout/LineLength
|
42
|
+
end
|
43
|
+
|
44
|
+
METHOD_NAME_RE = /[[:alpha:]_][[:alnum:]_]*\b[\?!=]?/.freeze
|
45
|
+
NON_ALNUM_METHOD_NAME_RE = Regexp.union(%w{
|
46
|
+
[]= [] ** ~ +@ -@ * / % + - >> << &
|
47
|
+
^ | <=> <= >= < > === == != =~ !~ !
|
48
|
+
}.map { |op| /#{Regexp.escape(op)}/ })
|
49
|
+
CONSTANT_NAME_RE = /[[:upper:]][[:alnum:]_]*\b/.freeze
|
50
|
+
NAME_RE = Regexp.union(METHOD_NAME_RE, NON_ALNUM_METHOD_NAME_RE, CONSTANT_NAME_RE)
|
51
|
+
LEFTOVERS_CALL_RE = /\bleftovers:call(?:s|ed|er|ers|) (#{NAME_RE}(?:[, :]+#{NAME_RE})*)/.freeze
|
52
|
+
LEFTOVERS_ALLOW_RE = /\bleftovers:(?:keeps?|skip(?:s|ped|)|allow(?:s|ed|))\b/.freeze
|
53
|
+
LEFTOVERS_TEST_RE = /\bleftovers:(?:for_tests?|tests?|testing)\b/.freeze
|
54
|
+
def process_comments(comments) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
55
|
+
comments.each do |comment|
|
56
|
+
@allow_lines << comment.loc.line if comment.text.match?(LEFTOVERS_ALLOW_RE)
|
57
|
+
|
58
|
+
@test_lines << comment.loc.line if comment.text.match?(LEFTOVERS_TEST_RE)
|
59
|
+
|
60
|
+
next unless (match = comment.text.match(LEFTOVERS_CALL_RE))
|
61
|
+
next unless match[1]
|
62
|
+
|
63
|
+
match[1].scan(NAME_RE).each { |s| add_call(s.to_sym) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# grab method definitions
|
68
|
+
def on_def(node)
|
69
|
+
add_definition(node.children.first, node.loc.name)
|
70
|
+
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
def on_ivasgn(node)
|
75
|
+
add_definition(node.children.first, node.loc.name)
|
76
|
+
|
77
|
+
super
|
78
|
+
end
|
79
|
+
alias_method :on_gvasgn, :on_ivasgn
|
80
|
+
alias_method :on_cvasgn, :on_ivasgn
|
81
|
+
|
82
|
+
def on_ivar(node)
|
83
|
+
add_call(node.children.first)
|
84
|
+
|
85
|
+
super
|
86
|
+
end
|
87
|
+
alias_method :on_gvar, :on_ivar
|
88
|
+
alias_method :on_cvar, :on_ivar
|
89
|
+
|
90
|
+
def on_op_asgn(node)
|
91
|
+
collect_op_asgn(node)
|
92
|
+
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_and_asgn(node)
|
97
|
+
collect_op_asgn(node)
|
98
|
+
|
99
|
+
super
|
100
|
+
end
|
101
|
+
|
102
|
+
def on_or_asgn(node)
|
103
|
+
collect_op_asgn(node)
|
104
|
+
|
105
|
+
super
|
106
|
+
end
|
107
|
+
|
108
|
+
# grab method calls
|
109
|
+
def on_send(node)
|
110
|
+
super
|
111
|
+
|
112
|
+
add_call(node.children[1])
|
113
|
+
|
114
|
+
collect_rules(node)
|
115
|
+
end
|
116
|
+
alias_method :on_csend, :on_send
|
117
|
+
|
118
|
+
def on_const(node)
|
119
|
+
super
|
120
|
+
|
121
|
+
add_call(node.children[1])
|
122
|
+
end
|
123
|
+
|
124
|
+
# grab e.g. :to_s in each(&:to_s)
|
125
|
+
def on_block_pass(node)
|
126
|
+
super
|
127
|
+
|
128
|
+
add_call(node.children.first.to_sym) if node.children.first&.string_or_symbol?
|
129
|
+
end
|
130
|
+
|
131
|
+
# grab class Constant or module Constant
|
132
|
+
def on_class(node)
|
133
|
+
# don't call super so we don't process the class name
|
134
|
+
# !!! (# wtf does this mean dana? what would happen instead?)
|
135
|
+
process_all(node.children.drop(1))
|
136
|
+
|
137
|
+
node = node.children.first
|
138
|
+
|
139
|
+
add_definition(node.children[1], node.loc.name)
|
140
|
+
end
|
141
|
+
alias_method :on_module, :on_class
|
142
|
+
|
143
|
+
# grab Constant = Class.new or CONSTANT = 'string'.freeze
|
144
|
+
def on_casgn(node)
|
145
|
+
super
|
146
|
+
|
147
|
+
add_definition(node.children[1], node.loc.name)
|
148
|
+
|
149
|
+
collect_rules(node)
|
150
|
+
end
|
151
|
+
|
152
|
+
# grab calls to `alias new_method original_method`
|
153
|
+
def on_alias(node)
|
154
|
+
super
|
155
|
+
|
156
|
+
new_method, original_method = node.children
|
157
|
+
|
158
|
+
add_definition(new_method.children.first, new_method.loc.expression)
|
159
|
+
add_call(original_method.children.first)
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def test?(loc)
|
165
|
+
@file.test? || @test_lines.include?(loc.line)
|
166
|
+
end
|
167
|
+
|
168
|
+
def add_definition(name, loc)
|
169
|
+
return if @allow_lines.include?(loc.line)
|
170
|
+
|
171
|
+
definitions << Leftovers::Definition.new(name, location: loc, file: @file, test: test?(loc))
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_call(name)
|
175
|
+
calls << name
|
176
|
+
end
|
177
|
+
|
178
|
+
# just collects the call, super will collect the definition
|
179
|
+
def collect_var_op_asgn(node)
|
180
|
+
name = node.children.first
|
181
|
+
|
182
|
+
return unless name
|
183
|
+
|
184
|
+
add_call(name)
|
185
|
+
end
|
186
|
+
|
187
|
+
def collect_send_op_asgn(node)
|
188
|
+
name = node.children[1]
|
189
|
+
|
190
|
+
return unless name
|
191
|
+
|
192
|
+
add_call(:"#{name}=")
|
193
|
+
end
|
194
|
+
|
195
|
+
def collect_op_asgn(node)
|
196
|
+
node = node.children.first
|
197
|
+
case node.type
|
198
|
+
when :send then collect_send_op_asgn(node)
|
199
|
+
when :ivasgn, :gvasgn, :cvasgn then collect_var_op_asgn(node)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def collect_rules(node) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
204
|
+
Leftovers.config.rules.each do |rule|
|
205
|
+
next unless rule.match?(node.name, node.name_s, filename)
|
206
|
+
|
207
|
+
next if rule.skip?
|
208
|
+
|
209
|
+
calls.concat(rule.calls(node))
|
210
|
+
|
211
|
+
next if @allow_lines.include?(node.loc.line)
|
212
|
+
|
213
|
+
node.file = @file
|
214
|
+
node.test = test?(node.loc)
|
215
|
+
definitions.concat(rule.definitions(node))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fast_ignore'
|
4
|
+
require_relative 'file'
|
5
|
+
|
6
|
+
module Leftovers
|
7
|
+
class FileList
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def each # rubocop:disable Metrics/MethodLength
|
11
|
+
FastIgnore.new(
|
12
|
+
ignore_rules: Leftovers.config.exclude_paths,
|
13
|
+
include_rules: Leftovers.config.include_paths,
|
14
|
+
include_shebangs: :ruby
|
15
|
+
).each do |file|
|
16
|
+
yield(Leftovers::File.new(file))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_a
|
21
|
+
enum_for(:each).to_a
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Leftovers
|
4
|
+
module Haml
|
5
|
+
module_function
|
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`
|
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
|
20
|
+
''
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require_relative 'value_rule'
|
5
|
+
require_relative 'name_rule'
|
6
|
+
|
7
|
+
module Leftovers
|
8
|
+
class HashRule
|
9
|
+
def initialize(patterns) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
|
10
|
+
keys = []
|
11
|
+
pairs = []
|
12
|
+
Array.each_or_self(patterns) do |pat|
|
13
|
+
if pat.is_a?(Hash) && pat[:value]
|
14
|
+
pairs << [
|
15
|
+
(NameRule.new(pat[:keyword]) if pat[:keyword]),
|
16
|
+
(ValueRule.new(pat[:value]) if pat[:value])
|
17
|
+
]
|
18
|
+
else
|
19
|
+
keys << NameRule.new(pat)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@keys = (NameRule.new(keys) if keys)
|
24
|
+
|
25
|
+
@pairs = (pairs unless pairs.empty?)
|
26
|
+
|
27
|
+
freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
def match_pair?(key_node, value_node)
|
31
|
+
return true if @keys&.match?(key_node.to_sym, key_node.to_s)
|
32
|
+
|
33
|
+
@pairs&.any? do |(key_rule, value_rule)|
|
34
|
+
next unless !key_rule || key_rule.match?(key_node.to_sym, key_node.to_s)
|
35
|
+
|
36
|
+
(!value_rule || value_rule.match?(value_node))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require_relative 'config'
|
5
|
+
require 'fast_ignore'
|
6
|
+
|
7
|
+
module Leftovers
|
8
|
+
class MergedConfig
|
9
|
+
def initialize
|
10
|
+
@configs = []
|
11
|
+
@loaded_configs = Set.new
|
12
|
+
self.<< Leftovers::Config.new(:ruby)
|
13
|
+
self.<< project_config
|
14
|
+
load_bundled_gem_config
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(config)
|
18
|
+
return if @loaded_configs.include?(config.name)
|
19
|
+
|
20
|
+
unmemoize
|
21
|
+
@configs << config
|
22
|
+
@loaded_configs << config.name
|
23
|
+
config.gems.each { |gem| self.<< Leftovers::Config.new(gem) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def project_config
|
27
|
+
Leftovers::Config.new(:'.leftovers.yml', path: Leftovers.pwd + '.leftovers.yml')
|
28
|
+
end
|
29
|
+
|
30
|
+
def unmemoize
|
31
|
+
remove_instance_variable(:@exclude_paths) if defined?(@exclude_paths)
|
32
|
+
remove_instance_variable(:@include_paths) if defined?(@include_paths)
|
33
|
+
remove_instance_variable(:@test_paths) if defined?(@test_paths)
|
34
|
+
remove_instance_variable(:@rules) if defined?(@rules)
|
35
|
+
end
|
36
|
+
|
37
|
+
def exclude_paths
|
38
|
+
@exclude_paths ||= @configs.flat_map(&:exclude_paths)
|
39
|
+
end
|
40
|
+
|
41
|
+
def include_paths
|
42
|
+
@include_paths ||= @configs.flat_map(&:include_paths)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_paths
|
46
|
+
@test_paths ||= FastIgnore.new(
|
47
|
+
include_rules: @configs.flat_map(&:test_paths),
|
48
|
+
gitignore: false
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def skip_rules
|
53
|
+
@skip_rules ||= rules.select(&:skip?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def rules
|
57
|
+
@rules ||= @configs.flat_map(&:rules)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def load_bundled_gem_config
|
63
|
+
Leftovers.try_require('bundler')
|
64
|
+
return unless defined?(Bundler)
|
65
|
+
|
66
|
+
Bundler.locked_gems.specs.each do |spec|
|
67
|
+
self.<< Leftovers::Config.new(spec.name.to_sym)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
module Leftovers
|
5
|
+
class NameRule
|
6
|
+
attr_reader :sym, :syms, :regexp
|
7
|
+
|
8
|
+
def initialize(patterns) # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
9
|
+
regexps = []
|
10
|
+
syms = Set.new
|
11
|
+
Array.each_or_self(patterns) do |pat|
|
12
|
+
case pat
|
13
|
+
when Leftovers::NameRule
|
14
|
+
syms.merge(pat.sym) if pat.sym
|
15
|
+
syms.merge(pat.syms) if pat.syms
|
16
|
+
regexps.concat(pat.regexp) if pat.regexp
|
17
|
+
when String
|
18
|
+
syms.merge(pat.split(/\s+/).map(&:to_sym))
|
19
|
+
when Hash
|
20
|
+
if pat[:match]
|
21
|
+
regexps << /\A#{pattern[:match]}\z/
|
22
|
+
elsif pat[:has_prefix] && pat[:has_suffix]
|
23
|
+
regexps << /\A#{Regexp.escape(pat[:has_prefix])}.*#{Regexp.escape(pat[:has_suffix])}\z/
|
24
|
+
elsif pat[:has_prefix]
|
25
|
+
regexps << /\A#{Regexp.escape(pat[:has_prefix])}/
|
26
|
+
elsif pat[:has_suffix]
|
27
|
+
regexps << /#{Regexp.escape(pat[:has_suffix])}\z/
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if syms.length <= 0
|
33
|
+
@sym = syms.first
|
34
|
+
@syms = nil
|
35
|
+
else
|
36
|
+
@sym = nil
|
37
|
+
@syms = syms
|
38
|
+
end
|
39
|
+
|
40
|
+
@regexp = if regexps.empty?
|
41
|
+
nil
|
42
|
+
else
|
43
|
+
Regexp.union(regexps)
|
44
|
+
end
|
45
|
+
|
46
|
+
freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
def match?(sym, string)
|
50
|
+
@sym&.==(sym) || @syms&.include?(sym) || @regexp&.match?(string)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|