pippi 0.0.4 → 0.0.5
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 +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +108 -39
- data/bin/pippi +2 -2
- data/doc/docs.md +38 -20
- data/lib/pippi.rb +2 -1
- data/lib/pippi/auto_runner.rb +3 -7
- data/lib/pippi/check_loader.rb +2 -5
- data/lib/pippi/check_set_mapper.rb +2 -3
- data/lib/pippi/checks/assert_with_nil.rb +5 -6
- data/lib/pippi/checks/check.rb +10 -8
- data/lib/pippi/checks/debug_check.rb +1 -4
- data/lib/pippi/checks/map_followed_by_flatten.rb +7 -9
- data/lib/pippi/checks/reverse_followed_by_each.rb +6 -8
- data/lib/pippi/checks/select_followed_by_empty.rb +55 -0
- data/lib/pippi/checks/select_followed_by_first.rb +7 -9
- data/lib/pippi/checks/select_followed_by_size.rb +6 -12
- data/lib/pippi/context.rb +3 -7
- data/lib/pippi/exec_runner.rb +12 -7
- data/lib/pippi/problem.rb +0 -1
- data/lib/pippi/report.rb +2 -6
- data/lib/pippi/tasks.rb +11 -13
- data/lib/pippi/version.rb +2 -2
- metadata +4 -37
- data/.gitignore +0 -6
- data/.ruby-version +0 -1
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -24
- data/Rakefile +0 -11
- data/pippi.gemspec +0 -23
- data/sample/map_followed_by_flatten.rb +0 -6
- data/test/check_test.rb +0 -41
- data/test/rails_core_extensions.rb +0 -13
- data/test/test_helper.rb +0 -7
- data/test/unit/assert_with_nil_test.rb +0 -50
- data/test/unit/check_set_mapper_test.rb +0 -17
- data/test/unit/map_followed_by_flatten_test.rb +0 -38
- data/test/unit/problem_test.rb +0 -23
- data/test/unit/report_test.rb +0 -25
- data/test/unit/reverse_followed_by_each_test.rb +0 -29
- data/test/unit/select_followed_by_first_test.rb +0 -33
- data/test/unit/select_followed_by_size_test.rb +0 -47
- data/vendor/cache/byebug-2.7.0.gem +0 -0
- data/vendor/cache/columnize-0.8.9.gem +0 -0
- data/vendor/cache/debugger-linecache-1.2.0.gem +0 -0
- data/vendor/cache/minitest-5.4.2.gem +0 -0
- data/vendor/cache/rake-10.1.0.gem +0 -0
@@ -1,7 +1,5 @@
|
|
1
1
|
module Pippi
|
2
|
-
|
3
2
|
class CheckSetMapper
|
4
|
-
|
5
3
|
attr_reader :raw_check_specifier
|
6
4
|
attr_accessor :predefined_sets
|
7
5
|
|
@@ -11,7 +9,7 @@ module Pippi
|
|
11
9
|
end
|
12
10
|
|
13
11
|
def check_names
|
14
|
-
raw_check_specifier.split(
|
12
|
+
raw_check_specifier.split(',').map do |specifier|
|
15
13
|
predefined_sets[specifier] || specifier
|
16
14
|
end.flatten
|
17
15
|
end
|
@@ -24,6 +22,7 @@ module Pippi
|
|
24
22
|
"basic" => [
|
25
23
|
"SelectFollowedByFirst",
|
26
24
|
"SelectFollowedBySize",
|
25
|
+
"SelectFollowedByEmpty",
|
27
26
|
"ReverseFollowedByEach",
|
28
27
|
],
|
29
28
|
"training" => [
|
@@ -1,7 +1,5 @@
|
|
1
1
|
module Pippi::Checks
|
2
|
-
|
3
2
|
class AssertWithNil < Check
|
4
|
-
|
5
3
|
module MyAssertEqual
|
6
4
|
def assert_equal(*args)
|
7
5
|
if args.size > 1 && args[0].object_id == 8
|
@@ -19,7 +17,7 @@ module Pippi::Checks
|
|
19
17
|
@_pippi_check_assert_with_nil
|
20
18
|
end
|
21
19
|
def self._pippi_check_assert_with_nil
|
22
|
-
|
20
|
+
ancestors.find { |x| x == ActiveSupport::TestCase }._pippi_other_check_assert_with_nil
|
23
21
|
end
|
24
22
|
end
|
25
23
|
ActiveSupport::TestCase.prepend Pippi::Checks::AssertWithNil::MyAssertEqual
|
@@ -30,13 +28,14 @@ module Pippi::Checks
|
|
30
28
|
def description
|
31
29
|
"Don't use assert_equal with nil as a first argument; use assert_nil instead"
|
32
30
|
end
|
31
|
+
|
33
32
|
def sample
|
34
|
-
|
33
|
+
'x = nil ; assert_equal(nil, x)'
|
35
34
|
end
|
35
|
+
|
36
36
|
def instead_use
|
37
|
-
|
37
|
+
'x = nil ; assert_nil(x)'
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
42
41
|
end
|
data/lib/pippi/checks/check.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module Pippi::Checks
|
2
|
-
|
3
2
|
class Check
|
4
|
-
|
5
3
|
attr_accessor :ctx
|
6
4
|
|
7
5
|
def initialize(ctx)
|
@@ -12,14 +10,18 @@ module Pippi::Checks
|
|
12
10
|
[:collect!, :compact!, :flatten!, :map!, :reject!, :reverse!, :rotate!, :select!, :shuffle!, :slice!, :sort!, :sort_by!, :uniq!]
|
13
11
|
end
|
14
12
|
|
13
|
+
def method_names_that_indicate_this_is_being_used_as_a_collection
|
14
|
+
[:collect!, :compact!, :flatten!, :map!, :reject!, :reverse!, :rotate!, :select!, :shuffle!, :slice!, :sort!, :sort_by!, :uniq!, :collect, :compact, :flatten, :map, :reject, :reverse, :rotate, :select, :shuffle, :slice, :sort, :sort_by, :uniq]
|
15
|
+
end
|
16
|
+
|
15
17
|
def add_problem
|
16
|
-
problem_location = caller_locations.
|
17
|
-
ctx.report.add(Pippi::Problem.new(:
|
18
|
+
problem_location = caller_locations.find { |c| c.to_s !~ /byebug|lib\/pippi\/checks/ }
|
19
|
+
ctx.report.add(Pippi::Problem.new(line_number: problem_location.lineno, file_path: problem_location.path, check_class: self.class))
|
18
20
|
end
|
19
21
|
|
20
22
|
def clear_fault_proc(clz)
|
21
|
-
|
22
|
-
problem_location = caller_locations.
|
23
|
+
proc do |*args, &blk|
|
24
|
+
problem_location = caller_locations.find { |c| c.to_s !~ /byebug|lib\/pippi\/checks/ }
|
23
25
|
clz.clear_fault(problem_location.lineno, problem_location.path)
|
24
26
|
super(*args, &blk)
|
25
27
|
end
|
@@ -30,9 +32,9 @@ module Pippi::Checks
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def its_ok_watcher_proc(clazz, method_name)
|
33
|
-
|
35
|
+
proc do |*args, &blk|
|
34
36
|
begin
|
35
|
-
singleton_class.ancestors.
|
37
|
+
singleton_class.ancestors.find { |x| x == clazz }.instance_eval { remove_method method_name }
|
36
38
|
rescue NameError
|
37
39
|
return super(*args, &blk)
|
38
40
|
else
|
@@ -1,14 +1,11 @@
|
|
1
1
|
module Pippi::Checks
|
2
|
-
|
3
2
|
class DebugCheck < Check
|
4
|
-
|
5
|
-
["line", "class", "end", "call", "return", "c_call", "c_return", "b_call", "b_return", "raise", "thread_begin", "thread_end"].each do |event_name|
|
3
|
+
%w(line class end call return c_call c_return b_call b_return raise thread_begin thread_end).each do |event_name|
|
6
4
|
define_method "#{event_name}_event" do |tp|
|
7
5
|
return if tp.path =~ %r{lib/pippi}
|
8
6
|
ctx.debug_logger.warn "#{event_name}_event in #{tp.defined_class}##{tp.method_id} at line #{tp.lineno} of #{tp.path}"
|
9
7
|
ctx.debug_logger.warn " return_value #{tp.return_value}" rescue nil
|
10
8
|
end
|
11
9
|
end
|
12
|
-
|
13
10
|
end
|
14
11
|
end
|
@@ -1,9 +1,7 @@
|
|
1
1
|
module Pippi::Checks
|
2
|
-
|
3
2
|
class MapFollowedByFlatten < Check
|
4
|
-
|
5
3
|
module MyFlatten
|
6
|
-
def flatten(depth=nil)
|
4
|
+
def flatten(depth = nil)
|
7
5
|
if depth && depth == 1
|
8
6
|
self.class._pippi_check_map_followed_by_flatten.add_problem
|
9
7
|
end
|
@@ -34,8 +32,8 @@ module Pippi::Checks
|
|
34
32
|
Array.class_exec(self) do |my_check|
|
35
33
|
# How to do this without a class instance variable?
|
36
34
|
@_pippi_check_map_followed_by_flatten = my_check
|
37
|
-
|
38
|
-
|
35
|
+
class << self
|
36
|
+
attr_reader :_pippi_check_map_followed_by_flatten
|
39
37
|
end
|
40
38
|
end
|
41
39
|
Array.prepend MyMap
|
@@ -45,14 +43,14 @@ module Pippi::Checks
|
|
45
43
|
def description
|
46
44
|
"Don't use map followed by flatten; use flat_map instead"
|
47
45
|
end
|
46
|
+
|
48
47
|
def sample
|
49
|
-
|
48
|
+
'[1,2,3].map {|x| [x,x+1] }.flatten'
|
50
49
|
end
|
50
|
+
|
51
51
|
def instead_use
|
52
|
-
|
52
|
+
'[1,2,3].flat_map {|x| [x, x+1]}'
|
53
53
|
end
|
54
54
|
end
|
55
|
-
|
56
55
|
end
|
57
|
-
|
58
56
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
module Pippi::Checks
|
2
|
-
|
3
2
|
class ReverseFollowedByEach < Check
|
4
|
-
|
5
3
|
module MyEach
|
6
4
|
def each
|
7
5
|
self.class._pippi_check_reverse_followed_by_each.add_problem
|
@@ -28,8 +26,8 @@ module Pippi::Checks
|
|
28
26
|
Array.class_exec(self) do |my_check|
|
29
27
|
# How to do this without a class instance variable?
|
30
28
|
@_pippi_check_reverse_followed_by_each = my_check
|
31
|
-
|
32
|
-
|
29
|
+
class << self
|
30
|
+
attr_reader :_pippi_check_reverse_followed_by_each
|
33
31
|
end
|
34
32
|
end
|
35
33
|
Array.prepend MyReverse
|
@@ -39,14 +37,14 @@ module Pippi::Checks
|
|
39
37
|
def description
|
40
38
|
"Don't use each followed by reverse; use reverse_each instead"
|
41
39
|
end
|
40
|
+
|
42
41
|
def sample
|
43
|
-
|
42
|
+
'[1,2,3].reverse.each {|x| x+1 }'
|
44
43
|
end
|
44
|
+
|
45
45
|
def instead_use
|
46
|
-
|
46
|
+
'[1,2,3].reverse_each {|x| x+1 }'
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
50
49
|
end
|
51
|
-
|
52
50
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Pippi::Checks
|
2
|
+
|
3
|
+
class SelectFollowedByEmpty < Check
|
4
|
+
|
5
|
+
module MyEmpty
|
6
|
+
def empty?(&blk)
|
7
|
+
self.class._pippi_check_select_followed_by_empty.add_problem
|
8
|
+
self.class._pippi_check_select_followed_by_empty.method_names_that_indicate_this_is_being_used_as_a_collection.each do |this_means_its_ok_sym|
|
9
|
+
define_singleton_method(this_means_its_ok_sym, self.class._pippi_check_select_followed_by_empty.clear_fault_proc(self.class._pippi_check_select_followed_by_empty))
|
10
|
+
end
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module MySelect
|
16
|
+
def select(&blk)
|
17
|
+
result = super
|
18
|
+
if self.class._pippi_check_select_followed_by_empty.nil?
|
19
|
+
# Ignore Array subclasses since select or empty may have difference meanings
|
20
|
+
# elsif defined?(ActiveRecord::Relation) && self.class.kind_of?(ActiveRecord::Relation) # maybe also this
|
21
|
+
else
|
22
|
+
result.extend MyEmpty
|
23
|
+
self.class._pippi_check_select_followed_by_empty.array_mutator_methods.each do |this_means_its_ok_sym|
|
24
|
+
result.define_singleton_method(this_means_its_ok_sym, self.class._pippi_check_select_followed_by_empty.its_ok_watcher_proc(MyEmpty, :empty?))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def decorate
|
32
|
+
Array.class_exec(self) do |my_check|
|
33
|
+
@_pippi_check_select_followed_by_empty = my_check
|
34
|
+
def self._pippi_check_select_followed_by_empty
|
35
|
+
@_pippi_check_select_followed_by_empty
|
36
|
+
end
|
37
|
+
end
|
38
|
+
Array.prepend MySelect
|
39
|
+
end
|
40
|
+
|
41
|
+
class Documentation
|
42
|
+
def description
|
43
|
+
"Don't use select followed by empty?; use none? instead"
|
44
|
+
end
|
45
|
+
def sample
|
46
|
+
"[1,2,3].select {|x| x > 1 }.empty?"
|
47
|
+
end
|
48
|
+
def instead_use
|
49
|
+
"[1,2,3].none? {|x| x > 1 }"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -1,9 +1,7 @@
|
|
1
1
|
module Pippi::Checks
|
2
|
-
|
3
2
|
class SelectFollowedByFirst < Check
|
4
|
-
|
5
3
|
module MyFirst
|
6
|
-
def first(elements=nil)
|
4
|
+
def first(elements = nil)
|
7
5
|
unless elements
|
8
6
|
self.class._pippi_check_select_followed_by_first.add_problem
|
9
7
|
end
|
@@ -33,8 +31,8 @@ module Pippi::Checks
|
|
33
31
|
def decorate
|
34
32
|
Array.class_exec(self) do |my_check|
|
35
33
|
@_pippi_check_select_followed_by_first = my_check
|
36
|
-
|
37
|
-
|
34
|
+
class << self
|
35
|
+
attr_reader :_pippi_check_select_followed_by_first
|
38
36
|
end
|
39
37
|
end
|
40
38
|
Array.prepend MySelect
|
@@ -44,14 +42,14 @@ module Pippi::Checks
|
|
44
42
|
def description
|
45
43
|
"Don't use select followed by first; use detect instead"
|
46
44
|
end
|
45
|
+
|
47
46
|
def sample
|
48
|
-
|
47
|
+
'[1,2,3].select {|x| x > 1 }.first'
|
49
48
|
end
|
49
|
+
|
50
50
|
def instead_use
|
51
|
-
|
51
|
+
'[1,2,3].detect {|x| x > 1 }'
|
52
52
|
end
|
53
53
|
end
|
54
|
-
|
55
54
|
end
|
56
|
-
|
57
55
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
module Pippi::Checks
|
2
|
-
|
3
2
|
class SelectFollowedBySize < Check
|
4
|
-
|
5
3
|
module MySize
|
6
4
|
def size
|
7
5
|
self.class._pippi_check_select_followed_by_size.add_problem
|
@@ -27,15 +25,11 @@ module Pippi::Checks
|
|
27
25
|
end
|
28
26
|
end
|
29
27
|
|
30
|
-
def method_names_that_indicate_this_is_being_used_as_a_collection
|
31
|
-
[:collect!, :compact!, :flatten!, :map!, :reject!, :reverse!, :rotate!, :select!, :shuffle!, :slice!, :sort!, :sort_by!, :uniq!, :collect, :compact, :flatten, :map, :reject, :reverse, :rotate, :select, :shuffle, :slice, :sort, :sort_by, :uniq]
|
32
|
-
end
|
33
|
-
|
34
28
|
def decorate
|
35
29
|
Array.class_exec(self) do |my_check|
|
36
30
|
@_pippi_check_select_followed_by_size = my_check
|
37
|
-
|
38
|
-
|
31
|
+
class << self
|
32
|
+
attr_reader :_pippi_check_select_followed_by_size
|
39
33
|
end
|
40
34
|
end
|
41
35
|
Array.prepend MySelect
|
@@ -45,14 +39,14 @@ module Pippi::Checks
|
|
45
39
|
def description
|
46
40
|
"Don't use select followed by size; use count instead"
|
47
41
|
end
|
42
|
+
|
48
43
|
def sample
|
49
|
-
|
44
|
+
'[1,2,3].select {|x| x > 1 }.size'
|
50
45
|
end
|
46
|
+
|
51
47
|
def instead_use
|
52
|
-
|
48
|
+
'[1,2,3].count {|x| x > 1 }'
|
53
49
|
end
|
54
50
|
end
|
55
|
-
|
56
51
|
end
|
57
|
-
|
58
52
|
end
|
data/lib/pippi/context.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
module Pippi
|
2
|
-
|
3
2
|
class Context
|
4
|
-
|
5
3
|
class DebugLogger
|
6
4
|
def warn(str)
|
7
|
-
File.open(
|
5
|
+
File.open('pippi_debug.log', 'a') do |f|
|
8
6
|
f.syswrite("#{str}\n")
|
9
7
|
end
|
10
8
|
end
|
11
9
|
end
|
12
10
|
|
13
11
|
class NullLogger
|
14
|
-
def warn(
|
12
|
+
def warn(_str)
|
15
13
|
end
|
16
14
|
end
|
17
15
|
|
@@ -20,12 +18,10 @@ module Pippi
|
|
20
18
|
def initialize
|
21
19
|
@report = Pippi::Report.new
|
22
20
|
@debug_logger = if ENV['PIPPI_DEBUG']
|
23
|
-
|
21
|
+
Pippi::Context::DebugLogger.new
|
24
22
|
else
|
25
23
|
Pippi::Context::NullLogger.new
|
26
24
|
end
|
27
25
|
end
|
28
|
-
|
29
26
|
end
|
30
|
-
|
31
27
|
end
|
data/lib/pippi/exec_runner.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
module Pippi
|
2
|
-
|
3
2
|
class ExecRunner
|
4
|
-
|
5
3
|
attr_accessor :codefile, :check_name, :code_to_eval, :output_file_name
|
6
4
|
|
7
5
|
def initialize(args)
|
6
|
+
check_args(args)
|
8
7
|
@codefile = args[0]
|
9
8
|
@check_name = args[1]
|
10
9
|
@code_to_eval = args[2]
|
@@ -13,22 +12,28 @@ module Pippi
|
|
13
12
|
|
14
13
|
def run
|
15
14
|
ctx = Pippi::Context.new
|
16
|
-
CheckLoader.new(ctx, check_name).checks.each
|
17
|
-
check.decorate
|
18
|
-
end
|
15
|
+
CheckLoader.new(ctx, check_name).checks.each(&:decorate)
|
19
16
|
load "#{codefile}"
|
20
17
|
eval code_to_eval
|
21
18
|
dump_report ctx
|
22
19
|
end
|
23
20
|
|
24
21
|
def dump_report(ctx)
|
25
|
-
File.open(output_file_name,
|
22
|
+
File.open(output_file_name, 'w') do |outfile|
|
26
23
|
ctx.report.problems.each do |problem|
|
27
24
|
outfile.syswrite("#{problem.to_text}\n")
|
28
25
|
end
|
29
26
|
end
|
30
27
|
end
|
31
28
|
|
32
|
-
|
29
|
+
private
|
33
30
|
|
31
|
+
def check_args(args)
|
32
|
+
fail ArgumentError if args.size != 4
|
33
|
+
rescue ArgumentError => e
|
34
|
+
puts 'ERROR: wrong number of arguments'
|
35
|
+
puts 'Use: pippi code_file check_name eval_string output_file'
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
end
|
34
39
|
end
|
data/lib/pippi/problem.rb
CHANGED
data/lib/pippi/report.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module Pippi
|
2
|
-
|
3
2
|
class Report
|
4
|
-
|
5
3
|
attr_reader :problems
|
6
4
|
|
7
5
|
def initialize
|
@@ -13,15 +11,13 @@ module Pippi
|
|
13
11
|
end
|
14
12
|
|
15
13
|
def remove(lineno, path, clazz)
|
16
|
-
@problems.reject! {|p| p.line_number == lineno && p.file_path == path && p.check_class == clazz }
|
14
|
+
@problems.reject! { |p| p.line_number == lineno && p.file_path == path && p.check_class == clazz }
|
17
15
|
end
|
18
16
|
|
19
17
|
private
|
20
18
|
|
21
19
|
def duplicate_report?(candidate)
|
22
|
-
!problems.
|
20
|
+
!problems.find { |existing| existing.eql?(candidate) }.nil?
|
23
21
|
end
|
24
|
-
|
25
22
|
end
|
26
|
-
|
27
23
|
end
|