pippi 0.0.1

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +24 -0
  7. data/README.md +177 -0
  8. data/Rakefile +11 -0
  9. data/bin/pippi +7 -0
  10. data/doc/README +1 -0
  11. data/doc/docs.md +64 -0
  12. data/lib/pippi.rb +15 -0
  13. data/lib/pippi/auto_runner.rb +24 -0
  14. data/lib/pippi/check_loader.rb +23 -0
  15. data/lib/pippi/check_set_mapper.rb +35 -0
  16. data/lib/pippi/checks/check.rb +39 -0
  17. data/lib/pippi/checks/debug_check.rb +14 -0
  18. data/lib/pippi/checks/map_followed_by_flatten.rb +55 -0
  19. data/lib/pippi/checks/reverse_followed_by_each.rb +53 -0
  20. data/lib/pippi/checks/select_followed_by_first.rb +58 -0
  21. data/lib/pippi/checks/select_followed_by_size.rb +59 -0
  22. data/lib/pippi/context.rb +31 -0
  23. data/lib/pippi/exec_runner.rb +34 -0
  24. data/lib/pippi/problem.rb +24 -0
  25. data/lib/pippi/report.rb +27 -0
  26. data/lib/pippi/tasks.rb +41 -0
  27. data/lib/pippi/version.rb +3 -0
  28. data/pippi.gemspec +23 -0
  29. data/sample/map_followed_by_flatten.rb +6 -0
  30. data/test/check_test.rb +41 -0
  31. data/test/rails_core_extensions.rb +5 -0
  32. data/test/test_helper.rb +7 -0
  33. data/test/unit/map_followed_by_flatten_test.rb +38 -0
  34. data/test/unit/problem_test.rb +23 -0
  35. data/test/unit/report_test.rb +25 -0
  36. data/test/unit/reverse_followed_by_each_test.rb +29 -0
  37. data/test/unit/select_followed_by_first_test.rb +33 -0
  38. data/test/unit/select_followed_by_size_test.rb +33 -0
  39. data/vendor/cache/byebug-2.7.0.gem +0 -0
  40. data/vendor/cache/columnize-0.8.9.gem +0 -0
  41. data/vendor/cache/debugger-linecache-1.2.0.gem +0 -0
  42. data/vendor/cache/minitest-5.4.2.gem +0 -0
  43. data/vendor/cache/rake-10.1.0.gem +0 -0
  44. metadata +139 -0
@@ -0,0 +1,41 @@
1
+ require 'tempfile'
2
+
3
+ class CheckTest < MiniTest::Test
4
+
5
+ CodeSample = Struct.new(:code_text, :eval_to_execute)
6
+
7
+ def assert_no_problems(str)
8
+ assert execute_pippi_on(foo_bar_code_sample(str)).empty?
9
+ end
10
+
11
+ def assert_problems(str, opts={})
12
+ assert_equal opts[:count] || 1, execute_pippi_on(foo_bar_code_sample(str), opts).size
13
+ end
14
+
15
+ def output_file_name
16
+ @output_file_name ||= Tempfile.new("pippi_output").path
17
+ end
18
+
19
+ def tmp_code_sample_file_name
20
+ @tmp_code_sample_file_name ||= Tempfile.new("pippi_codesample").path
21
+ end
22
+
23
+ def foo_bar_code_sample(code)
24
+ CodeSample.new.tap {|c| c.code_text = "class Foo ; def bar ; #{code} ; end ; end" ; c.eval_to_execute = "Foo.new.bar" }
25
+ end
26
+
27
+ def execute_pippi_on(code, opts={})
28
+ File.open(tmp_code_sample_file_name, "w") {|f| f.syswrite(code.code_text) }
29
+ maybe_extensions = opts[:include_rails_core_extensions].nil? ? "" : "-r#{Dir.pwd}/test/rails_core_extensions.rb"
30
+ cmd = "bundle exec ruby #{maybe_extensions} bin/pippi #{tmp_code_sample_file_name} #{check_for_test} #{code.eval_to_execute} #{output_file_name}"
31
+ IO.popen(cmd).close
32
+ report = File.read(output_file_name).split
33
+ FileUtils.rm_f(output_file_name) if File.exists?(output_file_name)
34
+ report
35
+ end
36
+
37
+ def check_for_test
38
+ self.class.name.sub(/Test/, "")
39
+ end
40
+
41
+ end
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def present?
3
+ self.nil? || self.size == 0
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'minitest/autorun'
4
+ require 'check_test'
5
+ require 'byebug'
6
+
7
+ require 'pippi'
@@ -0,0 +1,38 @@
1
+ require 'test_helper'
2
+
3
+ class MapFollowedByFlattenTest < CheckTest
4
+
5
+ def test_canonical_case_is_found
6
+ assert_problems "[1,2,3].map {|x| [x,x+1] }.flatten(1)"
7
+ end
8
+
9
+ def test_requires_first_call_be_to_map
10
+ assert_no_problems "[1,2,3].select {|x| [x,x+1] }.flatten(1)"
11
+ end
12
+
13
+ def test_requires_last_call_be_to_flatten
14
+ assert_no_problems "[1,2,3].map {|x| [x] }.first"
15
+ end
16
+
17
+ def test_works_across_statements
18
+ assert_problems "tmp = [1,2,3].map {|x| [x] } ; tmp.flatten(1)"
19
+ end
20
+
21
+ def test_requires_arg_to_flatten_to_be_one
22
+ assert_no_problems "[1,2,3].map {|x| [x] }.flatten"
23
+ end
24
+
25
+ def test_will_not_flag_if_theres_an_intervening_method
26
+ assert_no_problems "[1,2,3].map {|x| [x] }.select {|x| x.to_s > '1' }.flatten(1)"
27
+ end
28
+
29
+ def test_will_not_flag_if_mutator_invoked
30
+ assert_no_problems "t = [1,2,3].map {|x| [x] } ; t.select! {|x| rand > 0.5 } ; t.flatten(1)"
31
+ end
32
+
33
+ =begin
34
+ def test_dfa_issue
35
+ assert_no_problems "t = [1,2,3].map {|x| [x] } ; if (rand > 0.5) ; t.sort! ; end ; t.flatten(1)"
36
+ end
37
+ =end
38
+ end
@@ -0,0 +1,23 @@
1
+ require "test_helper"
2
+
3
+ class ProblemTest < MiniTest::Test
4
+
5
+ def test_eql_should_say_equal_things_are_equal
6
+ p1 = Pippi::Problem.new(:file_path => "foo", :line_number => 42, :check_class => "String")
7
+ p2 = Pippi::Problem.new(:file_path => "foo", :line_number => 42, :check_class => "String")
8
+ assert p1.eql?(p2)
9
+ assert p2.eql?(p1)
10
+ end
11
+
12
+ def test_eql_should_say_unequal_things_are_unequal
13
+ p1 = Pippi::Problem.new(:file_path => "foo", :line_number => 42, :check_class => "String")
14
+ p2 = Pippi::Problem.new(:file_path => "foo", :line_number => 43, :check_class => "String")
15
+ assert !p1.eql?(p2)
16
+ assert !p2.eql?(p1)
17
+ p3 = Pippi::Problem.new(:file_path => "foo2", :line_number => 42, :check_class => "String")
18
+ assert !p1.eql?(p3)
19
+ p4 = Pippi::Problem.new(:file_path => "foo", :line_number => 42, :check_class => "Array")
20
+ assert !p1.eql?(p4)
21
+ end
22
+
23
+ end
@@ -0,0 +1,25 @@
1
+ require "test_helper"
2
+
3
+ class ReportTest < MiniTest::Test
4
+
5
+ def test_can_add_a_problem
6
+ report = Pippi::Report.new
7
+ report.add(Pippi::Problem.new(:file_path => "foo", :line_number => 42, :check_class => Pippi::Checks::SelectFollowedByFirst))
8
+ assert_equal 1, report.problems.size
9
+ end
10
+
11
+ def test_filters_duplicates
12
+ report = Pippi::Report.new
13
+ report.add(Pippi::Problem.new(:file_path => "foo", :line_number => 42, :check_class => Pippi::Checks::SelectFollowedByFirst))
14
+ report.add(Pippi::Problem.new(:file_path => "foo", :line_number => 42, :check_class => Pippi::Checks::SelectFollowedByFirst))
15
+ assert_equal 1, report.problems.size
16
+ end
17
+
18
+ def test_can_remove_problem
19
+ report = Pippi::Report.new
20
+ report.add(Pippi::Problem.new(:file_path => "foo", :line_number => 42, :check_class => Pippi::Checks::SelectFollowedByFirst))
21
+ report.remove 42, "foo", Pippi::Checks::SelectFollowedByFirst
22
+ assert report.problems.empty?
23
+ end
24
+
25
+ end
@@ -0,0 +1,29 @@
1
+ require "test_helper"
2
+
3
+ class ReverseFollowedByEachTest < CheckTest
4
+
5
+ def test_reverse_still_works
6
+ assert_no_problems "raise 'bang' unless [1,2,3].reverse == [3,2,1]"
7
+ end
8
+
9
+ def test_canonical_case_is_found
10
+ assert_problems "[1,2,3].reverse.each {|x| x }"
11
+ end
12
+
13
+ def test_requires_first_call_be_to_reverse
14
+ assert_no_problems "[1,2,3].sort.each{|x| [x,x+1] }"
15
+ end
16
+
17
+ def test_requires_last_call_be_to_each
18
+ assert_no_problems "[1,2,3].reverse.select {|x| [x] }"
19
+ end
20
+
21
+ def test_works_across_statements
22
+ assert_problems "tmp = [1,2,3].reverse ; tmp.each {|x| [x] }"
23
+ end
24
+
25
+ def test_will_not_flag_if_mutator_invoked
26
+ assert_no_problems "t = [1,2,3].reverse ; t.sort! ; t.each {|x| x }"
27
+ end
28
+
29
+ end
@@ -0,0 +1,33 @@
1
+ require "test_helper"
2
+
3
+ class SelectFollowedByFirstTest < CheckTest
4
+
5
+ def test_canonical_case_is_found
6
+ assert_problems "[1,2,3].select {|x| x > 1 }.first"
7
+ end
8
+
9
+ def test_requires_first_call_be_to_select
10
+ assert_no_problems "[1,2,3].map {|x| x > 1 }.first"
11
+ end
12
+
13
+ def test_requires_last_call_be_to_first
14
+ assert_no_problems "[1,2,3].select {|x| x > 1 }.sort"
15
+ end
16
+
17
+ def test_works_across_statements
18
+ assert_problems "tmp = [1,2,3].select {|x| x > 1 } ; tmp.first"
19
+ end
20
+
21
+ def test_will_not_flag_if_theres_an_intervening_method
22
+ assert_no_problems "[1,2,3].select {|x| x > 1 }.map {|x| x+1 }.first"
23
+ end
24
+
25
+ def test_will_not_flag_if_other_method_invoked_on_select_result
26
+ assert_no_problems "tmp = [1,2,3].select {|x| x > 1 } ; tmp.reject! {|x| x } ; tmp.first"
27
+ end
28
+
29
+ def test_will_not_flag_if_arg_passed
30
+ assert_no_problems "[1,2,3].select {|x| x > 1 }.first(1)"
31
+ end
32
+
33
+ end
@@ -0,0 +1,33 @@
1
+ require "test_helper"
2
+
3
+ class SelectFollowedBySizeTest < CheckTest
4
+
5
+ def test_canonical_case_is_found
6
+ assert_problems "[1,2,3].select {|x| x > 1 }.size"
7
+ end
8
+
9
+ def test_requires_first_call_be_to_select
10
+ assert_no_problems "[1,2,3].map {|x| x > 1 }.size"
11
+ end
12
+
13
+ def test_requires_last_call_be_to_size
14
+ assert_no_problems "[1,2,3].select {|x| x > 1 }.sort"
15
+ end
16
+
17
+ def test_works_across_statements
18
+ assert_problems "tmp = [1,2,3].select {|x| x > 1 } ; tmp.size"
19
+ end
20
+
21
+ def test_will_not_flag_if_theres_an_intervening_method
22
+ assert_no_problems "[1,2,3].select {|x| x > 1 }.map {|x| x+1 }.size"
23
+ end
24
+
25
+ def test_will_not_flag_if_other_method_invoked_on_select_result
26
+ assert_no_problems "tmp = [1,2,3].select {|x| x > 1 } ; tmp.reject! {|x| x } ; tmp.size"
27
+ end
28
+
29
+ def test_will_not_flag_if_method_subsequently_invokedzz
30
+ assert_no_problems "tmp = [1,2,3].select {|x| x > 1 } ; tmp.size ; y = tmp.sort!"
31
+ end
32
+
33
+ end
Binary file
Binary file
Binary file
Binary file
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pippi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tom Copeland
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.7'
55
+ description: Pippi is a utility for locating suboptimal Ruby class API usage.
56
+ email:
57
+ - tom@thomasleecopeland.com
58
+ executables:
59
+ - pippi
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".ruby-version"
65
+ - CHANGELOG.md
66
+ - Gemfile
67
+ - Gemfile.lock
68
+ - README.md
69
+ - Rakefile
70
+ - bin/pippi
71
+ - doc/README
72
+ - doc/docs.md
73
+ - lib/pippi.rb
74
+ - lib/pippi/auto_runner.rb
75
+ - lib/pippi/check_loader.rb
76
+ - lib/pippi/check_set_mapper.rb
77
+ - lib/pippi/checks/check.rb
78
+ - lib/pippi/checks/debug_check.rb
79
+ - lib/pippi/checks/map_followed_by_flatten.rb
80
+ - lib/pippi/checks/reverse_followed_by_each.rb
81
+ - lib/pippi/checks/select_followed_by_first.rb
82
+ - lib/pippi/checks/select_followed_by_size.rb
83
+ - lib/pippi/context.rb
84
+ - lib/pippi/exec_runner.rb
85
+ - lib/pippi/problem.rb
86
+ - lib/pippi/report.rb
87
+ - lib/pippi/tasks.rb
88
+ - lib/pippi/version.rb
89
+ - pippi.gemspec
90
+ - sample/map_followed_by_flatten.rb
91
+ - test/check_test.rb
92
+ - test/rails_core_extensions.rb
93
+ - test/test_helper.rb
94
+ - test/unit/map_followed_by_flatten_test.rb
95
+ - test/unit/problem_test.rb
96
+ - test/unit/report_test.rb
97
+ - test/unit/reverse_followed_by_each_test.rb
98
+ - test/unit/select_followed_by_first_test.rb
99
+ - test/unit/select_followed_by_size_test.rb
100
+ - tmp/.gitignore
101
+ - vendor/cache/byebug-2.7.0.gem
102
+ - vendor/cache/columnize-0.8.9.gem
103
+ - vendor/cache/debugger-linecache-1.2.0.gem
104
+ - vendor/cache/minitest-5.4.2.gem
105
+ - vendor/cache/rake-10.1.0.gem
106
+ homepage: https://github.com/tcopeland/pippi
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 2.0.0
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project: none
126
+ rubygems_version: 2.2.2
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: A Ruby runtime code analyzer
130
+ test_files:
131
+ - test/check_test.rb
132
+ - test/rails_core_extensions.rb
133
+ - test/test_helper.rb
134
+ - test/unit/map_followed_by_flatten_test.rb
135
+ - test/unit/problem_test.rb
136
+ - test/unit/report_test.rb
137
+ - test/unit/reverse_followed_by_each_test.rb
138
+ - test/unit/select_followed_by_first_test.rb
139
+ - test/unit/select_followed_by_size_test.rb