leftovers 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|