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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +108 -39
  4. data/bin/pippi +2 -2
  5. data/doc/docs.md +38 -20
  6. data/lib/pippi.rb +2 -1
  7. data/lib/pippi/auto_runner.rb +3 -7
  8. data/lib/pippi/check_loader.rb +2 -5
  9. data/lib/pippi/check_set_mapper.rb +2 -3
  10. data/lib/pippi/checks/assert_with_nil.rb +5 -6
  11. data/lib/pippi/checks/check.rb +10 -8
  12. data/lib/pippi/checks/debug_check.rb +1 -4
  13. data/lib/pippi/checks/map_followed_by_flatten.rb +7 -9
  14. data/lib/pippi/checks/reverse_followed_by_each.rb +6 -8
  15. data/lib/pippi/checks/select_followed_by_empty.rb +55 -0
  16. data/lib/pippi/checks/select_followed_by_first.rb +7 -9
  17. data/lib/pippi/checks/select_followed_by_size.rb +6 -12
  18. data/lib/pippi/context.rb +3 -7
  19. data/lib/pippi/exec_runner.rb +12 -7
  20. data/lib/pippi/problem.rb +0 -1
  21. data/lib/pippi/report.rb +2 -6
  22. data/lib/pippi/tasks.rb +11 -13
  23. data/lib/pippi/version.rb +2 -2
  24. metadata +4 -37
  25. data/.gitignore +0 -6
  26. data/.ruby-version +0 -1
  27. data/Gemfile +0 -3
  28. data/Gemfile.lock +0 -24
  29. data/Rakefile +0 -11
  30. data/pippi.gemspec +0 -23
  31. data/sample/map_followed_by_flatten.rb +0 -6
  32. data/test/check_test.rb +0 -41
  33. data/test/rails_core_extensions.rb +0 -13
  34. data/test/test_helper.rb +0 -7
  35. data/test/unit/assert_with_nil_test.rb +0 -50
  36. data/test/unit/check_set_mapper_test.rb +0 -17
  37. data/test/unit/map_followed_by_flatten_test.rb +0 -38
  38. data/test/unit/problem_test.rb +0 -23
  39. data/test/unit/report_test.rb +0 -25
  40. data/test/unit/reverse_followed_by_each_test.rb +0 -29
  41. data/test/unit/select_followed_by_first_test.rb +0 -33
  42. data/test/unit/select_followed_by_size_test.rb +0 -47
  43. data/vendor/cache/byebug-2.7.0.gem +0 -0
  44. data/vendor/cache/columnize-0.8.9.gem +0 -0
  45. data/vendor/cache/debugger-linecache-1.2.0.gem +0 -0
  46. data/vendor/cache/minitest-5.4.2.gem +0 -0
  47. 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(",").map do |specifier|
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
- self.ancestors.detect {|x| x == ActiveSupport::TestCase }._pippi_other_check_assert_with_nil
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
- "x = nil ; assert_equal(nil, x)"
33
+ 'x = nil ; assert_equal(nil, x)'
35
34
  end
35
+
36
36
  def instead_use
37
- "x = nil ; assert_nil(x)"
37
+ 'x = nil ; assert_nil(x)'
38
38
  end
39
39
  end
40
40
  end
41
-
42
41
  end
@@ -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.detect {|c| c.to_s !~ /byebug|lib\/pippi\/checks/ }
17
- ctx.report.add(Pippi::Problem.new(:line_number => problem_location.lineno, :file_path => problem_location.path, :check_class => self.class))
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
- Proc.new do |*args, &blk|
22
- problem_location = caller_locations.detect {|c| c.to_s !~ /byebug|lib\/pippi\/checks/ }
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
- Proc.new do |*args, &blk|
35
+ proc do |*args, &blk|
34
36
  begin
35
- singleton_class.ancestors.detect {|x| x == clazz }.instance_eval { remove_method method_name }
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
- def self._pippi_check_map_followed_by_flatten
38
- @_pippi_check_map_followed_by_flatten
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
- "[1,2,3].map {|x| [x,x+1] }.flatten"
48
+ '[1,2,3].map {|x| [x,x+1] }.flatten'
50
49
  end
50
+
51
51
  def instead_use
52
- "[1,2,3].flat_map {|x| [x, x+1]}"
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
- def self._pippi_check_reverse_followed_by_each
32
- @_pippi_check_reverse_followed_by_each
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
- "[1,2,3].reverse.each {|x| x+1 }"
42
+ '[1,2,3].reverse.each {|x| x+1 }'
44
43
  end
44
+
45
45
  def instead_use
46
- "[1,2,3].reverse_each {|x| x+1 }"
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
- def self._pippi_check_select_followed_by_first
37
- @_pippi_check_select_followed_by_first
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
- "[1,2,3].select {|x| x > 1 }.first"
47
+ '[1,2,3].select {|x| x > 1 }.first'
49
48
  end
49
+
50
50
  def instead_use
51
- "[1,2,3].detect {|x| x > 1 }"
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
- def self._pippi_check_select_followed_by_size
38
- @_pippi_check_select_followed_by_size
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
- "[1,2,3].select {|x| x > 1 }.size"
44
+ '[1,2,3].select {|x| x > 1 }.size'
50
45
  end
46
+
51
47
  def instead_use
52
- "[1,2,3].count {|x| x > 1 }"
48
+ '[1,2,3].count {|x| x > 1 }'
53
49
  end
54
50
  end
55
-
56
51
  end
57
-
58
52
  end
@@ -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("pippi_debug.log", "a") do |f|
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(str)
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
- Pippi::Context::DebugLogger.new
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
@@ -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 do |check|
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, "w") do |outfile|
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
- end
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
@@ -1,6 +1,5 @@
1
1
  module Pippi
2
2
  class Problem
3
-
4
3
  attr_accessor :file_path, :line_number, :check_class
5
4
 
6
5
  def initialize(opts)
@@ -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.detect {|existing| existing.eql?(candidate) }.nil?
20
+ !problems.find { |existing| existing.eql?(candidate) }.nil?
23
21
  end
24
-
25
22
  end
26
-
27
23
  end