pippi 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +108 -39
- data/bin/pippi +2 -2
- data/doc/docs.md +38 -20
- data/lib/pippi.rb +2 -1
- data/lib/pippi/auto_runner.rb +3 -7
- data/lib/pippi/check_loader.rb +2 -5
- data/lib/pippi/check_set_mapper.rb +2 -3
- data/lib/pippi/checks/assert_with_nil.rb +5 -6
- data/lib/pippi/checks/check.rb +10 -8
- data/lib/pippi/checks/debug_check.rb +1 -4
- data/lib/pippi/checks/map_followed_by_flatten.rb +7 -9
- data/lib/pippi/checks/reverse_followed_by_each.rb +6 -8
- data/lib/pippi/checks/select_followed_by_empty.rb +55 -0
- data/lib/pippi/checks/select_followed_by_first.rb +7 -9
- data/lib/pippi/checks/select_followed_by_size.rb +6 -12
- data/lib/pippi/context.rb +3 -7
- data/lib/pippi/exec_runner.rb +12 -7
- data/lib/pippi/problem.rb +0 -1
- data/lib/pippi/report.rb +2 -6
- data/lib/pippi/tasks.rb +11 -13
- data/lib/pippi/version.rb +2 -2
- metadata +4 -37
- data/.gitignore +0 -6
- data/.ruby-version +0 -1
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -24
- data/Rakefile +0 -11
- data/pippi.gemspec +0 -23
- data/sample/map_followed_by_flatten.rb +0 -6
- data/test/check_test.rb +0 -41
- data/test/rails_core_extensions.rb +0 -13
- data/test/test_helper.rb +0 -7
- data/test/unit/assert_with_nil_test.rb +0 -50
- data/test/unit/check_set_mapper_test.rb +0 -17
- data/test/unit/map_followed_by_flatten_test.rb +0 -38
- data/test/unit/problem_test.rb +0 -23
- data/test/unit/report_test.rb +0 -25
- data/test/unit/reverse_followed_by_each_test.rb +0 -29
- data/test/unit/select_followed_by_first_test.rb +0 -33
- data/test/unit/select_followed_by_size_test.rb +0 -47
- data/vendor/cache/byebug-2.7.0.gem +0 -0
- data/vendor/cache/columnize-0.8.9.gem +0 -0
- data/vendor/cache/debugger-linecache-1.2.0.gem +0 -0
- data/vendor/cache/minitest-5.4.2.gem +0 -0
- data/vendor/cache/rake-10.1.0.gem +0 -0
data/lib/pippi/tasks.rb
CHANGED
@@ -3,16 +3,13 @@ require 'pippi'
|
|
3
3
|
module Pippi
|
4
4
|
class Documentation
|
5
5
|
def generate
|
6
|
-
str =
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
obj = clz.new
|
14
|
-
str << %Q{
|
15
|
-
### #{clz.name.to_s.split('::')[2]}
|
6
|
+
str = ''
|
7
|
+
Pippi::CheckSetMapper.new("").predefined_sets.sort.select {|k,v| v.any? }.each do |checkset_name, checks|
|
8
|
+
str << "### #{checkset_name}\n"
|
9
|
+
checks.sort.each do |check|
|
10
|
+
obj = Object.const_get("Pippi::Checks::#{check}::Documentation").new
|
11
|
+
str << %(
|
12
|
+
#### #{check}
|
16
13
|
|
17
14
|
#{obj.description}
|
18
15
|
|
@@ -27,15 +24,16 @@ Instead, consider doing this:
|
|
27
24
|
\`\`\`ruby
|
28
25
|
#{obj.instead_use}
|
29
26
|
\`\`\`
|
30
|
-
|
27
|
+
)
|
28
|
+
end
|
31
29
|
end
|
32
|
-
File.open(
|
30
|
+
File.open('doc/docs.md', 'w') { |f| f.syswrite(str) }
|
33
31
|
end
|
34
32
|
end
|
35
33
|
end
|
36
34
|
|
37
35
|
namespace :pippi do
|
38
|
-
desc
|
36
|
+
desc 'Generate check documentation'
|
39
37
|
task :generate_docs do
|
40
38
|
Pippi::Documentation.new.generate
|
41
39
|
end
|
data/lib/pippi/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Pippi
|
2
|
-
VERSION =
|
3
|
-
end
|
2
|
+
VERSION = '0.0.5'
|
3
|
+
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.5
|
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-
|
11
|
+
date: 2014-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -60,13 +60,8 @@ executables:
|
|
60
60
|
extensions: []
|
61
61
|
extra_rdoc_files: []
|
62
62
|
files:
|
63
|
-
- ".gitignore"
|
64
|
-
- ".ruby-version"
|
65
63
|
- CHANGELOG.md
|
66
|
-
- Gemfile
|
67
|
-
- Gemfile.lock
|
68
64
|
- README.md
|
69
|
-
- Rakefile
|
70
65
|
- bin/pippi
|
71
66
|
- doc/README
|
72
67
|
- doc/docs.md
|
@@ -79,6 +74,7 @@ files:
|
|
79
74
|
- lib/pippi/checks/debug_check.rb
|
80
75
|
- lib/pippi/checks/map_followed_by_flatten.rb
|
81
76
|
- lib/pippi/checks/reverse_followed_by_each.rb
|
77
|
+
- lib/pippi/checks/select_followed_by_empty.rb
|
82
78
|
- lib/pippi/checks/select_followed_by_first.rb
|
83
79
|
- lib/pippi/checks/select_followed_by_size.rb
|
84
80
|
- lib/pippi/context.rb
|
@@ -87,24 +83,6 @@ files:
|
|
87
83
|
- lib/pippi/report.rb
|
88
84
|
- lib/pippi/tasks.rb
|
89
85
|
- lib/pippi/version.rb
|
90
|
-
- pippi.gemspec
|
91
|
-
- sample/map_followed_by_flatten.rb
|
92
|
-
- test/check_test.rb
|
93
|
-
- test/rails_core_extensions.rb
|
94
|
-
- test/test_helper.rb
|
95
|
-
- test/unit/assert_with_nil_test.rb
|
96
|
-
- test/unit/check_set_mapper_test.rb
|
97
|
-
- test/unit/map_followed_by_flatten_test.rb
|
98
|
-
- test/unit/problem_test.rb
|
99
|
-
- test/unit/report_test.rb
|
100
|
-
- test/unit/reverse_followed_by_each_test.rb
|
101
|
-
- test/unit/select_followed_by_first_test.rb
|
102
|
-
- test/unit/select_followed_by_size_test.rb
|
103
|
-
- vendor/cache/byebug-2.7.0.gem
|
104
|
-
- vendor/cache/columnize-0.8.9.gem
|
105
|
-
- vendor/cache/debugger-linecache-1.2.0.gem
|
106
|
-
- vendor/cache/minitest-5.4.2.gem
|
107
|
-
- vendor/cache/rake-10.1.0.gem
|
108
86
|
homepage: https://github.com/tcopeland/pippi
|
109
87
|
licenses:
|
110
88
|
- MIT
|
@@ -129,15 +107,4 @@ rubygems_version: 2.2.2
|
|
129
107
|
signing_key:
|
130
108
|
specification_version: 4
|
131
109
|
summary: A Ruby runtime code analyzer
|
132
|
-
test_files:
|
133
|
-
- test/check_test.rb
|
134
|
-
- test/rails_core_extensions.rb
|
135
|
-
- test/test_helper.rb
|
136
|
-
- test/unit/assert_with_nil_test.rb
|
137
|
-
- test/unit/check_set_mapper_test.rb
|
138
|
-
- test/unit/map_followed_by_flatten_test.rb
|
139
|
-
- test/unit/problem_test.rb
|
140
|
-
- test/unit/report_test.rb
|
141
|
-
- test/unit/reverse_followed_by_each_test.rb
|
142
|
-
- test/unit/select_followed_by_first_test.rb
|
143
|
-
- test/unit/select_followed_by_size_test.rb
|
110
|
+
test_files: []
|
data/.gitignore
DELETED
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
ruby-2.1.2
|
data/Gemfile
DELETED
data/Gemfile.lock
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
pippi (0.0.4)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
byebug (2.7.0)
|
10
|
-
columnize (~> 0.3)
|
11
|
-
debugger-linecache (~> 1.2)
|
12
|
-
columnize (0.8.9)
|
13
|
-
debugger-linecache (1.2.0)
|
14
|
-
minitest (5.4.2)
|
15
|
-
rake (10.1.0)
|
16
|
-
|
17
|
-
PLATFORMS
|
18
|
-
ruby
|
19
|
-
|
20
|
-
DEPENDENCIES
|
21
|
-
byebug (~> 2.7)
|
22
|
-
minitest (~> 5.0)
|
23
|
-
pippi!
|
24
|
-
rake (~> 10.1)
|
data/Rakefile
DELETED
data/pippi.gemspec
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
$:.push File.expand_path("../lib", __FILE__)
|
2
|
-
|
3
|
-
require 'pippi/version'
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = 'pippi'
|
7
|
-
s.version = Pippi::VERSION
|
8
|
-
s.authors = ["Tom Copeland"]
|
9
|
-
s.email = ["tom@thomasleecopeland.com"]
|
10
|
-
s.homepage = "https://github.com/tcopeland/pippi"
|
11
|
-
s.summary = "A Ruby runtime code analyzer"
|
12
|
-
s.description = "Pippi is a utility for locating suboptimal Ruby class API usage."
|
13
|
-
s.license = "MIT"
|
14
|
-
s.rubyforge_project = "none"
|
15
|
-
s.files = `git ls-files`.split("\n")
|
16
|
-
s.test_files = `git ls-files test/*`.split("\n")
|
17
|
-
s.executables = "pippi"
|
18
|
-
s.require_paths = ["lib"]
|
19
|
-
s.add_development_dependency 'rake', '~> 10.1'
|
20
|
-
s.add_development_dependency 'minitest', '~> 5.0'
|
21
|
-
s.add_development_dependency 'byebug', '~> 2.7'
|
22
|
-
s.required_ruby_version = '>= 2.0.0'
|
23
|
-
end
|
data/test/check_test.rb
DELETED
@@ -1,41 +0,0 @@
|
|
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, opts={})
|
8
|
-
assert execute_pippi_on(foo_bar_code_sample(str, opts[:subclass] || ""), opts).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[:subclass] || ""), 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, subclass="")
|
24
|
-
CodeSample.new.tap {|c| c.code_text = "class Foo #{subclass.size > 0 ? "< #{subclass}" : ""}; 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
|
data/test/test_helper.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class AssertWithNilTest < CheckTest
|
4
|
-
|
5
|
-
def test_canonical_case_is_found
|
6
|
-
assert_problems "x = 42 ; assert_equal(nil, x)", :include_rails_core_extensions => true, :subclass => "ActiveSupport::TestCase"
|
7
|
-
end
|
8
|
-
|
9
|
-
def test_non_nil_first_arg_doesnt_flag
|
10
|
-
assert_no_problems "x = 42 ; assert_equal(42, x)", :include_rails_core_extensions => true, :subclass => "ActiveSupport::TestCase"
|
11
|
-
end
|
12
|
-
|
13
|
-
# Seems like there's some way to do this, but maybe not... anyhow, moving this rule to "buggy" for now
|
14
|
-
# pippi> $ echo "foo(nil)" | ruby --dump insns
|
15
|
-
# == disasm: <RubyVM::InstructionSequence:<main>@->=======================
|
16
|
-
# 0000 trace 1 ( 1)
|
17
|
-
# 0002 putself
|
18
|
-
# 0003 putnil
|
19
|
-
# 0004 opt_send_simple <callinfo!mid:foo, argc:1, FCALL|ARGS_SKIP>
|
20
|
-
# 0006 leave
|
21
|
-
# pippi> $ echo "foo(x)" | ruby --dump insns
|
22
|
-
# == disasm: <RubyVM::InstructionSequence:<main>@->=======================
|
23
|
-
# 0000 trace 1 ( 1)
|
24
|
-
# 0002 putself
|
25
|
-
# 0003 putself
|
26
|
-
# 0004 opt_send_simple <callinfo!mid:x, argc:0, FCALL|VCALL|ARGS_SKIP>
|
27
|
-
# 0006 opt_send_simple <callinfo!mid:foo, argc:1, FCALL|ARGS_SKIP>
|
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
|
42
|
-
def test_nil_reference_first_arg_doesnt_flag
|
43
|
-
assert_no_problems "x = 42 ; y = nil ; assert_equal(nil, x)", :include_rails_core_extensions => true, :subclass => "ActiveSupport::TestCase"
|
44
|
-
end
|
45
|
-
|
46
|
-
def test_three_arg_is_flagged
|
47
|
-
assert_problems "x = 42 ; assert_equal(nil, x, 'whatevs')", :include_rails_core_extensions => true, :subclass => "ActiveSupport::TestCase"
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
@@ -1,17 +0,0 @@
|
|
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
|
@@ -1,38 +0,0 @@
|
|
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
|
data/test/unit/problem_test.rb
DELETED
@@ -1,23 +0,0 @@
|
|
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
|
data/test/unit/report_test.rb
DELETED
@@ -1,25 +0,0 @@
|
|
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
|
@@ -1,29 +0,0 @@
|
|
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
|