pippi 0.0.3 → 0.0.4
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/.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
|