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,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'parser'
|
4
|
+
|
5
|
+
module Parser
|
6
|
+
module AST
|
7
|
+
class Node # rubocop:disable Metrics/ClassLength
|
8
|
+
def initialize(type, children = [], properties = {})
|
9
|
+
# ::AST::Node#initialize freezes itself.
|
10
|
+
# so can't use normal memoizations
|
11
|
+
@memo = {}
|
12
|
+
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def first
|
17
|
+
children.first
|
18
|
+
end
|
19
|
+
|
20
|
+
def file
|
21
|
+
@memo[:file]
|
22
|
+
end
|
23
|
+
|
24
|
+
def file=(value)
|
25
|
+
@memo[:file] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def test
|
29
|
+
@memo[:test]
|
30
|
+
end
|
31
|
+
alias_method :test?, :test
|
32
|
+
|
33
|
+
def test=(value)
|
34
|
+
@memo[:test] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_scalar_value # rubocop:disable Metrics/MethodLength
|
38
|
+
@memo[:scalar_value] ||= case type
|
39
|
+
when :sym
|
40
|
+
first
|
41
|
+
when :str
|
42
|
+
first.to_s.freeze
|
43
|
+
when :true
|
44
|
+
true
|
45
|
+
when :false
|
46
|
+
false
|
47
|
+
when :nil
|
48
|
+
nil
|
49
|
+
else
|
50
|
+
raise "Not scalar node, (#{type})"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s # rubocop:disable Metrics/MethodLength
|
55
|
+
@memo[:to_s] ||= if scalar?
|
56
|
+
to_scalar_value
|
57
|
+
elsif named?
|
58
|
+
name
|
59
|
+
else
|
60
|
+
raise "No to_s, (#{type})"
|
61
|
+
end.to_s.freeze
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_sym
|
65
|
+
case type
|
66
|
+
when :str, :sym then first
|
67
|
+
when :nil, :true, :false then type
|
68
|
+
else to_s.to_sym
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
SCALAR_TYPES = %i{sym str true false nil}.freeze
|
73
|
+
def scalar?
|
74
|
+
SCALAR_TYPES.include?(type)
|
75
|
+
end
|
76
|
+
|
77
|
+
def string_or_symbol?
|
78
|
+
type == :str || type == :sym
|
79
|
+
end
|
80
|
+
|
81
|
+
def send?
|
82
|
+
type == :send || type == :csend
|
83
|
+
end
|
84
|
+
|
85
|
+
def named?
|
86
|
+
send? || type == :casgn
|
87
|
+
end
|
88
|
+
|
89
|
+
def arguments
|
90
|
+
@memo[:arguments] ||= case type
|
91
|
+
when :send, :csend then children.drop(2)
|
92
|
+
when :casgn then [children[2]]
|
93
|
+
else raise "Not argument node (#{type})"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def positional_arguments
|
98
|
+
@memo[:positional_arguments] ||= kwargs ? arguments[0...-1] : arguments
|
99
|
+
end
|
100
|
+
|
101
|
+
def unwrap_freeze
|
102
|
+
return self unless type == :send && name == :freeze
|
103
|
+
|
104
|
+
first
|
105
|
+
end
|
106
|
+
|
107
|
+
def kwargs
|
108
|
+
@memo.fetch(:kwargs) do
|
109
|
+
last_arg = arguments[-1]&.unwrap_freeze
|
110
|
+
@memo[:kwargs] = (last_arg if last_arg&.type == :hash)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def keys
|
115
|
+
each_pair.map { |k, _| k }
|
116
|
+
end
|
117
|
+
|
118
|
+
def key?(key)
|
119
|
+
each_pair.find do |k, _v|
|
120
|
+
next unless k.string_or_symbol?
|
121
|
+
|
122
|
+
k.to_sym == key
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def values
|
127
|
+
@memo[:kwargs] ||= case type
|
128
|
+
when :hash then each_pair.map { |_, v| v }
|
129
|
+
when :array then children
|
130
|
+
else []
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def values_at_match(matcher)
|
135
|
+
each_pair.with_object([]) do |(key, value), values|
|
136
|
+
values << value if matcher.match?(key.to_sym, key.to_s)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def positional_arguments_at(positions)
|
141
|
+
positional_arguments.values_at(*positions).compact
|
142
|
+
end
|
143
|
+
|
144
|
+
def each_pair
|
145
|
+
raise "not hash node (#{type})" unless type == :hash
|
146
|
+
|
147
|
+
return enum_for(:each_pair) unless block_given?
|
148
|
+
|
149
|
+
children.each do |pair|
|
150
|
+
yield(*pair.children) if pair.type == :pair
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def name
|
155
|
+
return "Not named node (#{type})" unless named?
|
156
|
+
|
157
|
+
@memo[:name] ||= children[1]
|
158
|
+
end
|
159
|
+
|
160
|
+
def name_s
|
161
|
+
@memo[:name_s] ||= name.to_s.freeze
|
162
|
+
end
|
163
|
+
|
164
|
+
def [](index) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
165
|
+
case type
|
166
|
+
when :send, :csend
|
167
|
+
index.is_a?(Integer) ? arguments[index] : kwargs && kwargs[index]
|
168
|
+
when :array
|
169
|
+
children[index]
|
170
|
+
when :hash
|
171
|
+
each_pair do |key, value|
|
172
|
+
next unless key.string_or_symbol?
|
173
|
+
|
174
|
+
return value if key.to_sym == index
|
175
|
+
end
|
176
|
+
|
177
|
+
nil
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'shellwords'
|
5
|
+
require_relative 'cli'
|
6
|
+
|
7
|
+
module Leftovers
|
8
|
+
class RakeTask
|
9
|
+
include ::Rake::DSL
|
10
|
+
|
11
|
+
def self.generate_task(name = :leftovers, *default_argv) # rubocop:disable
|
12
|
+
new(name, default_argv)
|
13
|
+
|
14
|
+
name
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(name, default_argv)
|
18
|
+
@name = name
|
19
|
+
@default_argv = default_argv
|
20
|
+
|
21
|
+
describe_task
|
22
|
+
define_task
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def escaped_argv(argv = @default_argv)
|
28
|
+
return if argv.empty?
|
29
|
+
|
30
|
+
Shellwords.shelljoin(argv)
|
31
|
+
end
|
32
|
+
|
33
|
+
def describe_task
|
34
|
+
return desc('Run leftovers') if @default_argv.empty?
|
35
|
+
|
36
|
+
desc("Run leftovers (default args: #{escaped_argv})")
|
37
|
+
end
|
38
|
+
|
39
|
+
def define_task
|
40
|
+
task(@name, :'*args') do |_, task_argv|
|
41
|
+
argv = argv_or_default(task_argv)
|
42
|
+
write_cli_cmd(argv)
|
43
|
+
run(argv)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def write_cli_cmd(argv)
|
48
|
+
$stdout.puts("\e[2mleftovers #{escaped_argv(argv)}\e[0m")
|
49
|
+
end
|
50
|
+
|
51
|
+
def run(argv)
|
52
|
+
Leftovers::CLI.new(argv: argv, stdout: $stdout, stderr: $stderr)
|
53
|
+
rescue SystemExit => e
|
54
|
+
raise unless e.status == 0
|
55
|
+
end
|
56
|
+
|
57
|
+
def argv_or_default(task_argv)
|
58
|
+
task_argv = task_argv.to_a.compact
|
59
|
+
task_argv.empty? ? @default_argv : task_argv
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Leftovers
|
4
|
+
class Reporter
|
5
|
+
def call(definition)
|
6
|
+
Leftovers.puts(
|
7
|
+
"\e[36m#{definition.full_location}\e[0m #{definition.full_name} \e[2m#{definition.highlighted_source("\e[33m", "\e[0;2m")}\e[0m" # rubocop:disable Layout/LineLength
|
8
|
+
)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'name_rule'
|
4
|
+
require_relative 'argument_rule'
|
5
|
+
require 'fast_ignore'
|
6
|
+
|
7
|
+
module Leftovers
|
8
|
+
class Rule
|
9
|
+
def self.wrap(rules)
|
10
|
+
case rules
|
11
|
+
when Array then rules.flat_map { |r| wrap(r) }
|
12
|
+
when nil then [].freeze
|
13
|
+
else new(**rules)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :skip
|
18
|
+
alias_method :skip?, :skip
|
19
|
+
|
20
|
+
def initialize( # rubocop:disable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
21
|
+
name: nil,
|
22
|
+
names: nil,
|
23
|
+
calls: nil,
|
24
|
+
call: nil,
|
25
|
+
skip: false,
|
26
|
+
defines: nil,
|
27
|
+
define: nil,
|
28
|
+
path: nil,
|
29
|
+
paths: nil
|
30
|
+
)
|
31
|
+
raise ArgumentError, 'Only use one of name/names' if name && names
|
32
|
+
raise ArgumentError, 'Only use one of path/paths' if path && paths
|
33
|
+
raise ArgumentError, 'Only use one of call/calls' if call && calls
|
34
|
+
raise ArgumentError, 'Only use one of define/defines' if define && defines
|
35
|
+
if skip && (defines || calls)
|
36
|
+
raise ArgumentError, "skip can't exist with defines or calls for #{name || names}"
|
37
|
+
end
|
38
|
+
|
39
|
+
@name_matcher = NameRule.new(name || names)
|
40
|
+
@path = FastIgnore.new(include_rules: path || paths, gitignore: false) if path || paths
|
41
|
+
@skip = skip
|
42
|
+
|
43
|
+
begin
|
44
|
+
@calls = ArgumentRule.wrap(calls)
|
45
|
+
rescue ArgumentError => e
|
46
|
+
raise e, "#{e.message} for calls for #{name}", e.backtrace
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
@defines = ArgumentRule.wrap(defines, definer: true)
|
51
|
+
rescue ArgumentError => e
|
52
|
+
raise e, "#{e.message} for defines for #{name}", e.backtrace
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def filename?(file)
|
57
|
+
return true unless @path
|
58
|
+
|
59
|
+
@path.allowed?(file)
|
60
|
+
end
|
61
|
+
|
62
|
+
def match?(name, name_s, file)
|
63
|
+
@name_matcher.match?(name, name_s) && filename?(file)
|
64
|
+
end
|
65
|
+
|
66
|
+
def calls(node)
|
67
|
+
@calls.flat_map { |m| m.matches(node) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def definitions(node)
|
71
|
+
@defines.flat_map { |m| m.matches(node) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Leftovers
|
4
|
+
class TransformRule # rubocop:disable Metrics/ClassLength
|
5
|
+
# :nocov:
|
6
|
+
using ::Leftovers::StringDeletePrefixSuffix if defined?(::Leftovers::StringDeletePrefixSuffix)
|
7
|
+
# :nocov:
|
8
|
+
|
9
|
+
RUBY_STRING_METHODS = %i{
|
10
|
+
downcase
|
11
|
+
upcase
|
12
|
+
capitalize
|
13
|
+
swapcase
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
ACTIVESUPPORT_STRING_METHODS = %i{
|
17
|
+
pluralize
|
18
|
+
singularize
|
19
|
+
camelize
|
20
|
+
camelcase
|
21
|
+
underscore
|
22
|
+
titleize
|
23
|
+
titlecase
|
24
|
+
demodulize
|
25
|
+
deconstantize
|
26
|
+
parameterize
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
CUSTOM_TRANSFORMS = %i{
|
30
|
+
original
|
31
|
+
delete_before
|
32
|
+
delete_after
|
33
|
+
add_prefix
|
34
|
+
add_suffix
|
35
|
+
delete_prefix
|
36
|
+
delete_suffix
|
37
|
+
replace_with
|
38
|
+
}.freeze
|
39
|
+
|
40
|
+
VALID_TRANSFORMS = RUBY_STRING_METHODS + ACTIVESUPPORT_STRING_METHODS + CUSTOM_TRANSFORMS
|
41
|
+
# more possible transformations
|
42
|
+
# gsub sub tr tr_s
|
43
|
+
def initialize(transforms)
|
44
|
+
@transforms = prepare_transforms(transforms)
|
45
|
+
|
46
|
+
freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
def transform(original_string, method_node)
|
50
|
+
string = original_string
|
51
|
+
@transforms.each { |proc| string = proc.call(string, method_node) }
|
52
|
+
|
53
|
+
string.to_sym
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def prepare_transforms(transforms) # rubocop:disable Metrics/MethodLength
|
59
|
+
transforms.map do |key, value|
|
60
|
+
unless VALID_TRANSFORMS.include?(key)
|
61
|
+
raise ArgumentError, <<~MESSAGE
|
62
|
+
invalid transform key: (#{key}: #{value}).
|
63
|
+
Valid transform keys are #{ALL_VALID_TRANSFORMS}"
|
64
|
+
MESSAGE
|
65
|
+
end
|
66
|
+
|
67
|
+
key, value = prepare_hash_value(key, value) if value.is_a?(Hash)
|
68
|
+
|
69
|
+
instance_variable_set(:"@#{key}", value.freeze)
|
70
|
+
method(key)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
HASH_VALUE_TRANSFORMS = %i{add_prefix add_suffix}.freeze
|
75
|
+
HASH_VALUE_KEYS = %i{from_argument joiner}.freeze
|
76
|
+
def prepare_hash_value(method, hash) # rubocop:disable Metrics/MethodLength
|
77
|
+
raise ArgumentError, <<~MESSAGE unless HASH_VALUE_TRANSFORMS.include?(method)
|
78
|
+
invalid transform value (#{key}: #{value}).
|
79
|
+
Hash values are only valid for #{HASH_VALUE_TRANSFORMS}
|
80
|
+
MESSAGE
|
81
|
+
|
82
|
+
hash = hash.map do |k, v|
|
83
|
+
raise ArgumentError, <<~MESSAGE unless HASH_VALUE_KEYS.include?(k)
|
84
|
+
invalid transform value argument (#{method}: { #{k}: #{v} }).
|
85
|
+
Hash values are only valid for #{HASH_VALUE_TRANSFORMS}
|
86
|
+
MESSAGE
|
87
|
+
|
88
|
+
[k, v.to_sym]
|
89
|
+
end.to_h
|
90
|
+
|
91
|
+
["#{method}_dynamic", hash]
|
92
|
+
end
|
93
|
+
|
94
|
+
RUBY_STRING_METHODS.each do |method|
|
95
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
96
|
+
def #{method}(string, _method_node)
|
97
|
+
string.#{method}
|
98
|
+
end
|
99
|
+
RUBY
|
100
|
+
end
|
101
|
+
|
102
|
+
# leftovers:call activesupport_available?
|
103
|
+
ACTIVESUPPORT_STRING_METHODS.each do |method|
|
104
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
105
|
+
def #{method}(string, _method_node)
|
106
|
+
return string unless activesupport_available?(:#{method})
|
107
|
+
|
108
|
+
string.#{method}
|
109
|
+
end
|
110
|
+
RUBY
|
111
|
+
end
|
112
|
+
|
113
|
+
def original(string, _method_node)
|
114
|
+
string
|
115
|
+
end
|
116
|
+
|
117
|
+
def delete_prefix(string, _method_node)
|
118
|
+
string.delete_prefix(@delete_prefix)
|
119
|
+
end
|
120
|
+
|
121
|
+
def delete_suffix(string, _method_node)
|
122
|
+
string.delete_suffix(@delete_suffix)
|
123
|
+
end
|
124
|
+
|
125
|
+
def delete_before(string, _method_node)
|
126
|
+
string.split(@delete_before, 2)[1] || string
|
127
|
+
end
|
128
|
+
|
129
|
+
def delete_after(string, _method_node)
|
130
|
+
string.split(@delete_after, 2).first
|
131
|
+
end
|
132
|
+
|
133
|
+
def replace_with(_string, _method_node)
|
134
|
+
@replace_with
|
135
|
+
end
|
136
|
+
|
137
|
+
def add_prefix(string, _method_node)
|
138
|
+
"#{@add_prefix}#{string}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_suffix(string, _method_node)
|
142
|
+
"#{string}#{@add_suffix}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def add_prefix_dynamic(string, method_node)
|
146
|
+
"#{dynamic_value(@add_prefix_dynamic, method_node)}#{@add_prefix_dynamic[:joiner]}#{string}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def add_suffix_dynamic(string, method_node)
|
150
|
+
"#{string}#{@add_suffix_dynamic[:joiner]}#{dynamic_value(@add_suffix_dynamic, method_node)}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def dynamic_value(value, method_node)
|
154
|
+
method_node.kwargs[value[:from_argument]].to_s if value[:from_argument]
|
155
|
+
end
|
156
|
+
|
157
|
+
def activesupport_available?(method) # rubocop:disable Metrics/MethodLength
|
158
|
+
Leftovers.try_require(
|
159
|
+
'active_support/core_ext/string', 'active_support/inflections',
|
160
|
+
message: <<~MESSAGE
|
161
|
+
Tried transforming a string using an activesupport method (#{method}), but the activesupport gem was not available
|
162
|
+
`gem install activesupport`
|
163
|
+
MESSAGE
|
164
|
+
)
|
165
|
+
|
166
|
+
Leftovers.try_require(::File.join(Leftovers.pwd, 'config', 'initializers', 'inflections.rb'))
|
167
|
+
|
168
|
+
defined?(ActiveSupport)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Leftovers
|
4
|
+
class ValueRule
|
5
|
+
def initialize(values) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
|
6
|
+
literal_values = Set.new
|
7
|
+
value_types = Set.new
|
8
|
+
|
9
|
+
Array.each_or_self(values) do |value|
|
10
|
+
case value
|
11
|
+
when Hash
|
12
|
+
raise ArgumentError, "invalid value #{value.inspect}" unless value[:type]
|
13
|
+
|
14
|
+
value_types.merge(
|
15
|
+
Array(value[:type]).map do |v|
|
16
|
+
case v
|
17
|
+
when 'String', :String then :str
|
18
|
+
when 'Symbol', :Symbol then :sym
|
19
|
+
else v.to_s.downcase.to_sym
|
20
|
+
end
|
21
|
+
end
|
22
|
+
)
|
23
|
+
else
|
24
|
+
(literal_values << value) if value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if literal_values.length <= 1
|
29
|
+
@literal_value = literal_values.first
|
30
|
+
@literal_values = nil
|
31
|
+
else
|
32
|
+
@literal_value = nil
|
33
|
+
@literal_values = literal_values
|
34
|
+
end
|
35
|
+
|
36
|
+
if value_types.length <= 1
|
37
|
+
@value_type = value_types.first
|
38
|
+
@value_types = nil
|
39
|
+
else
|
40
|
+
@value_type = nil
|
41
|
+
@value_types = value_types
|
42
|
+
end
|
43
|
+
|
44
|
+
freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
def match?(value_node) # rubocop:disable Metrics/CyclomaticComplexity
|
48
|
+
return true if @value_type&.== value_node.type
|
49
|
+
return true if @value_types&.include?(value_node.type)
|
50
|
+
return false unless (@literal_value || @literal_values) && value_node.scalar?
|
51
|
+
return true if @literal_value&.== value_node.to_scalar_value
|
52
|
+
|
53
|
+
@literal_values&.include?(value_node.to_scalar_value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/leftovers.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './leftovers/core_ext'
|
4
|
+
require_relative './leftovers/backports'
|
5
|
+
require_relative './leftovers/collector'
|
6
|
+
require_relative './leftovers/merged_config'
|
7
|
+
require_relative './leftovers/reporter'
|
8
|
+
|
9
|
+
module Leftovers
|
10
|
+
module_function
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :parallel
|
14
|
+
alias_method :parallel?, :parallel
|
15
|
+
|
16
|
+
attr_accessor :quiet
|
17
|
+
alias_method :quiet?, :quiet
|
18
|
+
|
19
|
+
attr_accessor :progress
|
20
|
+
alias_method :progress?, :progress
|
21
|
+
end
|
22
|
+
|
23
|
+
def stdout
|
24
|
+
@stdout ||= StringIO.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def stderr
|
28
|
+
@stderr ||= StringIO.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def config
|
32
|
+
@config ||= Leftovers::MergedConfig.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def collector
|
36
|
+
@collector ||= Leftovers::Collector.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def reporter
|
40
|
+
@reporter ||= Leftovers::Reporter.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def leftovers # rubocop:disable Metrics/MethodLength
|
44
|
+
@leftovers ||= begin
|
45
|
+
collector.collect
|
46
|
+
collector.definitions.reject do |definition|
|
47
|
+
definition.skipped? || definition.in_collection?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def run(stdout: StringIO.new, stderr: StringIO.new) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
53
|
+
reset
|
54
|
+
@stdout = stdout
|
55
|
+
@stderr = stderr
|
56
|
+
return 0 if leftovers.empty?
|
57
|
+
|
58
|
+
only_test = []
|
59
|
+
none = []
|
60
|
+
leftovers.sort.each do |definition|
|
61
|
+
if !definition.test? && definition.in_test_collection?
|
62
|
+
only_test << definition
|
63
|
+
else
|
64
|
+
none << definition
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
unless only_test.empty?
|
69
|
+
puts "\e[31mOnly directly called in tests:\e[0m"
|
70
|
+
only_test.each { |definition| reporter.call(definition) }
|
71
|
+
end
|
72
|
+
|
73
|
+
unless none.empty?
|
74
|
+
puts "\e[31mNot directly called at all:\e[0m"
|
75
|
+
none.each { |definition| reporter.call(definition) }
|
76
|
+
end
|
77
|
+
|
78
|
+
1
|
79
|
+
end
|
80
|
+
|
81
|
+
def reset # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
82
|
+
remove_instance_variable(:@config) if defined?(@config)
|
83
|
+
remove_instance_variable(:@collector) if defined?(@collector)
|
84
|
+
remove_instance_variable(:@reporter) if defined?(@reporter)
|
85
|
+
remove_instance_variable(:@leftovers) if defined?(@leftovers)
|
86
|
+
remove_instance_variable(:@try_require) if defined?(@try_require)
|
87
|
+
remove_instance_variable(:@stdout) if defined?(@stdout)
|
88
|
+
remove_instance_variable(:@stderr) if defined?(@stderr)
|
89
|
+
remove_instance_variable(:@parallel) if defined?(@parallel)
|
90
|
+
remove_instance_variable(:@quiet) if defined?(@quiet)
|
91
|
+
remove_instance_variable(:@pwd) if defined?(@pwd)
|
92
|
+
end
|
93
|
+
|
94
|
+
def warn(message)
|
95
|
+
stderr.puts("\e[2K#{message}") unless quiet?
|
96
|
+
end
|
97
|
+
|
98
|
+
def puts(message)
|
99
|
+
stdout.puts("\e[2K#{message}") unless quiet?
|
100
|
+
end
|
101
|
+
|
102
|
+
def print(message)
|
103
|
+
stdout.print(message) unless quiet?
|
104
|
+
end
|
105
|
+
|
106
|
+
def newline
|
107
|
+
stdout.puts('')
|
108
|
+
end
|
109
|
+
|
110
|
+
def pwd
|
111
|
+
@pwd ||= Pathname.new(Dir.pwd + '/')
|
112
|
+
end
|
113
|
+
|
114
|
+
def try_require(*requirables, message: nil) # rubocop:disable Metrics/MethodLength
|
115
|
+
@try_require ||= {}
|
116
|
+
requirables.each do |requirable|
|
117
|
+
begin
|
118
|
+
return @try_require[requirable] if @try_require.key?(requirable)
|
119
|
+
|
120
|
+
@try_require[requirable] = require requirable
|
121
|
+
rescue LoadError
|
122
|
+
warn message if message
|
123
|
+
@try_require[requirable] = false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|