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