pippi 0.0.4 → 0.0.5

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