pippi 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/CHANGELOG.md +3 -0
- data/Gemfile.lock +1 -1
- data/README.md +72 -28
- data/lib/pippi/check_set_mapper.rb +4 -1
- data/lib/pippi/checks/assert_with_nil.rb +1 -2
- data/lib/pippi/checks/check.rb +12 -7
- data/lib/pippi/checks/map_followed_by_flatten.rb +5 -2
- data/lib/pippi/checks/reverse_followed_by_each.rb +1 -2
- data/lib/pippi/checks/select_followed_by_first.rb +4 -5
- data/lib/pippi/checks/select_followed_by_size.rb +2 -3
- data/lib/pippi/version.rb +1 -1
- data/test/unit/assert_with_nil_test.rb +13 -0
- data/test/unit/check_set_mapper_test.rb +17 -0
- data/test/unit/select_followed_by_size_test.rb +15 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95752be642c66d479b293d0887592472cbb7a6d4
|
4
|
+
data.tar.gz: 80f13c0e7329c6909e2729161f09f3489d6eb8e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3544e1ec0e442f38f8cfb52fe05a46eb6f504af6e9ff4f25665cf1930d1f618660a8adbd5e6bb0c19c750f46b542d2afa39ff42973d67441372a1aa0cc233b50
|
7
|
+
data.tar.gz: a56988007f42d5ee982887c5be19a8635fea55c0813193710dec41317f0125801658f87fb09a4a27e319c6f751844219362d8bc100f47d86c909b44ab76cfbc1
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,48 @@
|
|
1
|
+
# Pippi
|
2
|
+
|
1
3
|
Pippi is a utility for finding suboptimal Ruby class API usage.
|
2
4
|
|
3
|
-
|
5
|
+
[Here's a project overview](http://thomasleecopeland.com/2014/10/22/finding-suboptimal-api-usage.html).
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
### Rails with test-unit
|
10
|
+
|
11
|
+
* Add `gem 'pippi'` to the `test` group in your project's `Gemfile`
|
12
|
+
* Add this to `test_helper.rb` just before the `require 'rails/test_help'` line
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
if ENV['USE_PIPPI'].present?
|
16
|
+
Pippi::AutoRunner.new(:checkset => ENV['PIPPI_CHECKSET'] || "basic")
|
17
|
+
end
|
18
|
+
```
|
19
|
+
* Run it:
|
20
|
+
|
21
|
+
```text
|
22
|
+
USE_PIPPI=true bundle exec rake test:units && cat log/pippi.log
|
23
|
+
```
|
24
|
+
|
25
|
+
Here's a [demo Rails application](https://github.com/tcopeland/pippi_demo#pippi-demo).
|
26
|
+
|
27
|
+
### Rails with rspec
|
28
|
+
|
29
|
+
TODO and FIXME
|
30
|
+
|
31
|
+
### From the command line:
|
32
|
+
|
33
|
+
Assuming you're using bundler:
|
34
|
+
|
35
|
+
```bash
|
36
|
+
# Add this to your project's Gemfile:
|
37
|
+
gem 'pippi'
|
38
|
+
# Run 'bundle', see some output
|
39
|
+
# To run a particular check:
|
40
|
+
bundle exec pippi tmp/tmpfile.rb MapFollowedByFlatten Foo.new.bar out.txt
|
41
|
+
# Or to run all the basic Pippi checks on your code and exercise it with MyClass.new.exercise_some_code:
|
42
|
+
bundle exec ruby -rpippi/auto_runner -e "MyClass.new.exercise_some_code"
|
43
|
+
```
|
44
|
+
|
45
|
+
|
4
46
|
|
5
47
|
## Checksets
|
6
48
|
|
@@ -35,6 +77,7 @@ Instead, consider doing this:
|
|
35
77
|
|
36
78
|
```ruby
|
37
79
|
x = nil ; assert_nil(x)
|
80
|
+
```
|
38
81
|
|
39
82
|
### MapFollowedByFlatten
|
40
83
|
|
@@ -100,26 +143,6 @@ Instead, consider doing this:
|
|
100
143
|
[1,2,3].count {|x| x > 1 }
|
101
144
|
```
|
102
145
|
|
103
|
-
## Usage
|
104
|
-
|
105
|
-
### Inside Rails tests
|
106
|
-
|
107
|
-
See https://github.com/tcopeland/pippi_demo#pippi-demo
|
108
|
-
|
109
|
-
### From the command line:
|
110
|
-
|
111
|
-
Assuming you're using bundler:
|
112
|
-
|
113
|
-
```bash
|
114
|
-
# Add this to your project's Gemfile:
|
115
|
-
gem 'pippi'
|
116
|
-
# Run 'bundle', see some output
|
117
|
-
# To run a particular check:
|
118
|
-
bundle exec pippi tmp/tmpfile.rb MapFollowedByFlatten Foo.new.bar out.txt
|
119
|
-
# Or to run all the basic Pippi checks on your code and exercise it with MyClass.new.exercise_some_code:
|
120
|
-
bundle exec ruby -rpippi/auto_runner -e "MyClass.new.exercise_some_code"
|
121
|
-
```
|
122
|
-
|
123
146
|
## Ideas for other problems to detect:
|
124
147
|
|
125
148
|
```ruby
|
@@ -158,20 +181,29 @@ product_path(@product)
|
|
158
181
|
````
|
159
182
|
|
160
183
|
## Here are some things that Pippi is not well suited for
|
161
|
-
|
162
|
-
|
184
|
+
|
185
|
+
Use self.new vs MyClass.new. This is not a good fit for Pippi because it
|
186
|
+
involves a receiver usage that can be detected with static analysis.
|
187
|
+
|
188
|
+
**wrong**:
|
189
|
+
|
190
|
+
```
|
163
191
|
class Foo
|
164
192
|
def self.bar
|
165
193
|
Foo.new
|
166
194
|
end
|
167
195
|
end
|
168
|
-
|
196
|
+
```
|
197
|
+
|
198
|
+
**right**:
|
199
|
+
|
200
|
+
```
|
169
201
|
class Foo
|
170
202
|
def self.bar
|
171
203
|
self.new
|
172
204
|
end
|
173
205
|
end
|
174
|
-
|
206
|
+
```
|
175
207
|
|
176
208
|
## TODO
|
177
209
|
|
@@ -197,8 +229,20 @@ rm -rf pippi_debug.log pippi.log .bundle/gems/pippi-0.0.1/ .bundle/cache/pippi-0
|
|
197
229
|
|
198
230
|
```
|
199
231
|
|
232
|
+
## How to do a release
|
233
|
+
|
234
|
+
* Bump version number
|
235
|
+
* Move anything from 'training' to 'buggy' or elsewhere
|
236
|
+
* Tie off Changelog notes
|
237
|
+
* Regenerate docs with `pippi:generate_docs`, copy and paste that into README
|
238
|
+
* Commit, push
|
239
|
+
* `bundle exec gem build pippi.gemspec`
|
240
|
+
* `gem push pippi-x.gem`
|
241
|
+
|
200
242
|
## Credits
|
201
243
|
|
202
|
-
*
|
203
|
-
*
|
204
|
-
*
|
244
|
+
* Christopher Schramm bugfixes in fault proc clearing
|
245
|
+
* [Evan Phoenix](https://twitter.com/evanphx) for the idea of watching method invocations at runtime using metaprogramming rather than using `Tracepoint`.
|
246
|
+
* Igor Kapkov documentation fixes
|
247
|
+
* LivingSocial](https://www.livingsocial.com/) for letting me develop and open source this utility.
|
248
|
+
* [Michael Bernstein](https://twitter.com/mrb_bk) (of [CodeClimate](https://codeclimate.com/) fame) for an inspirational discussion of code anaysis in general.
|
@@ -3,9 +3,11 @@ module Pippi
|
|
3
3
|
class CheckSetMapper
|
4
4
|
|
5
5
|
attr_reader :raw_check_specifier
|
6
|
+
attr_accessor :predefined_sets
|
6
7
|
|
7
8
|
def initialize(raw_check_specifier)
|
8
9
|
@raw_check_specifier = raw_check_specifier
|
10
|
+
define_standard_sets
|
9
11
|
end
|
10
12
|
|
11
13
|
def check_names
|
@@ -16,7 +18,8 @@ module Pippi
|
|
16
18
|
|
17
19
|
private
|
18
20
|
|
19
|
-
def
|
21
|
+
def define_standard_sets
|
22
|
+
@predefined_sets =
|
20
23
|
{
|
21
24
|
"basic" => [
|
22
25
|
"SelectFollowedByFirst",
|
data/lib/pippi/checks/check.rb
CHANGED
@@ -17,10 +17,11 @@ module Pippi::Checks
|
|
17
17
|
ctx.report.add(Pippi::Problem.new(:line_number => problem_location.lineno, :file_path => problem_location.path, :check_class => self.class))
|
18
18
|
end
|
19
19
|
|
20
|
-
def clear_fault_proc
|
21
|
-
Proc.new do
|
20
|
+
def clear_fault_proc(clz)
|
21
|
+
Proc.new do |*args, &blk|
|
22
22
|
problem_location = caller_locations.detect {|c| c.to_s !~ /byebug|lib\/pippi\/checks/ }
|
23
|
-
|
23
|
+
clz.clear_fault(problem_location.lineno, problem_location.path)
|
24
|
+
super(*args, &blk)
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
@@ -29,11 +30,15 @@ module Pippi::Checks
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def its_ok_watcher_proc(clazz, method_name)
|
32
|
-
Proc.new do
|
33
|
-
|
34
|
-
|
33
|
+
Proc.new do |*args, &blk|
|
34
|
+
begin
|
35
|
+
singleton_class.ancestors.detect {|x| x == clazz }.instance_eval { remove_method method_name }
|
36
|
+
rescue NameError
|
37
|
+
return super(*args, &blk)
|
38
|
+
else
|
39
|
+
return super(*args, &blk)
|
40
|
+
end
|
35
41
|
end
|
36
42
|
end
|
37
|
-
|
38
43
|
end
|
39
44
|
end
|
@@ -4,11 +4,14 @@ module Pippi::Checks
|
|
4
4
|
|
5
5
|
module MyFlatten
|
6
6
|
def flatten(depth=nil)
|
7
|
-
result = super(depth)
|
8
7
|
if depth && depth == 1
|
9
8
|
self.class._pippi_check_map_followed_by_flatten.add_problem
|
10
9
|
end
|
11
|
-
|
10
|
+
if depth
|
11
|
+
super(depth)
|
12
|
+
else
|
13
|
+
super()
|
14
|
+
end
|
12
15
|
end
|
13
16
|
end
|
14
17
|
|
@@ -4,15 +4,14 @@ module Pippi::Checks
|
|
4
4
|
|
5
5
|
module MyFirst
|
6
6
|
def first(elements=nil)
|
7
|
-
|
7
|
+
unless elements
|
8
|
+
self.class._pippi_check_select_followed_by_first.add_problem
|
9
|
+
end
|
10
|
+
if elements
|
8
11
|
super(elements)
|
9
12
|
else
|
10
13
|
super()
|
11
14
|
end
|
12
|
-
unless elements
|
13
|
-
self.class._pippi_check_select_followed_by_first.add_problem
|
14
|
-
end
|
15
|
-
result
|
16
15
|
end
|
17
16
|
end
|
18
17
|
|
@@ -4,12 +4,11 @@ module Pippi::Checks
|
|
4
4
|
|
5
5
|
module MySize
|
6
6
|
def size
|
7
|
-
result = super()
|
8
7
|
self.class._pippi_check_select_followed_by_size.add_problem
|
9
8
|
self.class._pippi_check_select_followed_by_size.method_names_that_indicate_this_is_being_used_as_a_collection.each do |this_means_its_ok_sym|
|
10
|
-
define_singleton_method(this_means_its_ok_sym, self.class._pippi_check_select_followed_by_size.clear_fault_proc)
|
9
|
+
define_singleton_method(this_means_its_ok_sym, self.class._pippi_check_select_followed_by_size.clear_fault_proc(self.class._pippi_check_select_followed_by_size))
|
11
10
|
end
|
12
|
-
|
11
|
+
super()
|
13
12
|
end
|
14
13
|
end
|
15
14
|
|
data/lib/pippi/version.rb
CHANGED
@@ -26,6 +26,19 @@ class AssertWithNilTest < CheckTest
|
|
26
26
|
# 0004 opt_send_simple <callinfo!mid:x, argc:0, FCALL|VCALL|ARGS_SKIP>
|
27
27
|
# 0006 opt_send_simple <callinfo!mid:foo, argc:1, FCALL|ARGS_SKIP>
|
28
28
|
# 0008 leave
|
29
|
+
# also consider
|
30
|
+
=begin
|
31
|
+
class Bar
|
32
|
+
def buz(x)
|
33
|
+
end
|
34
|
+
def foo
|
35
|
+
y = nil
|
36
|
+
buz(y)
|
37
|
+
RubyVM::InstructionSequence.of(method(__method__)).disasm.split("\n").each {|x| puts x }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
Bar.new.foo
|
41
|
+
=end
|
29
42
|
def test_nil_reference_first_arg_doesnt_flag
|
30
43
|
assert_no_problems "x = 42 ; y = nil ; assert_equal(nil, x)", :include_rails_core_extensions => true, :subclass => "ActiveSupport::TestCase"
|
31
44
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class CheckSetMapperTest < Minitest::Test
|
4
|
+
|
5
|
+
def test_should_find_predefined_sets
|
6
|
+
csm = Pippi::CheckSetMapper.new("basic")
|
7
|
+
assert csm.check_names.include?("SelectFollowedByFirst")
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_should_allow_comma_separated_checkset_names
|
11
|
+
csm = Pippi::CheckSetMapper.new("a,b")
|
12
|
+
csm.predefined_sets = {"a" => ["foo"], "b" => ["bar"]}
|
13
|
+
assert csm.check_names.include?("foo")
|
14
|
+
assert csm.check_names.include?("bar")
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -26,8 +26,22 @@ class SelectFollowedBySizeTest < CheckTest
|
|
26
26
|
assert_no_problems "tmp = [1,2,3].select {|x| x > 1 } ; tmp.reject! {|x| x } ; tmp.size"
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
29
|
+
def test_will_not_flag_if_method_subsequently_invoked
|
30
30
|
assert_no_problems "tmp = [1,2,3].select {|x| x > 1 } ; tmp.size ; y = tmp.sort!"
|
31
31
|
end
|
32
32
|
|
33
|
+
def test_clear_fault_proc_should_not_cause_errors_by_failing_to_return_result_of_method_invocations
|
34
|
+
str = <<-EOS
|
35
|
+
tmp = [1,2,3].select {|x| x > 4 }
|
36
|
+
tmp.size
|
37
|
+
tmp = tmp.reject{|l| l.nil? }
|
38
|
+
tmp.map {|x| 1 }
|
39
|
+
EOS
|
40
|
+
assert_problems str
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_clear_fault_proc_doesnt_try_to_remove_singleton_method_twice
|
44
|
+
assert_no_problems "tmp = [1,2,3].select {|x| x > 1 } ; y = tmp.sort! ; y = tmp.sort!"
|
45
|
+
end
|
46
|
+
|
33
47
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pippi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Copeland
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
11
|
+
date: 2014-10-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -93,13 +93,13 @@ files:
|
|
93
93
|
- test/rails_core_extensions.rb
|
94
94
|
- test/test_helper.rb
|
95
95
|
- test/unit/assert_with_nil_test.rb
|
96
|
+
- test/unit/check_set_mapper_test.rb
|
96
97
|
- test/unit/map_followed_by_flatten_test.rb
|
97
98
|
- test/unit/problem_test.rb
|
98
99
|
- test/unit/report_test.rb
|
99
100
|
- test/unit/reverse_followed_by_each_test.rb
|
100
101
|
- test/unit/select_followed_by_first_test.rb
|
101
102
|
- test/unit/select_followed_by_size_test.rb
|
102
|
-
- tmp/.gitignore
|
103
103
|
- vendor/cache/byebug-2.7.0.gem
|
104
104
|
- vendor/cache/columnize-0.8.9.gem
|
105
105
|
- vendor/cache/debugger-linecache-1.2.0.gem
|
@@ -134,6 +134,7 @@ test_files:
|
|
134
134
|
- test/rails_core_extensions.rb
|
135
135
|
- test/test_helper.rb
|
136
136
|
- test/unit/assert_with_nil_test.rb
|
137
|
+
- test/unit/check_set_mapper_test.rb
|
137
138
|
- test/unit/map_followed_by_flatten_test.rb
|
138
139
|
- test/unit/problem_test.rb
|
139
140
|
- test/unit/report_test.rb
|