nitpick 1.0.0
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.
- data/LICENSE +22 -0
- data/README +56 -0
- data/VERSION.yml +4 -0
- data/bin/nitpick +92 -0
- data/lib/nitpick.rb +32 -0
- data/lib/nitpick/argument_nitpicker.rb +21 -0
- data/lib/nitpick/block_nitpicker.rb +21 -0
- data/lib/nitpick/branch_nitpicker.rb +17 -0
- data/lib/nitpick/local_variable_counter.rb +81 -0
- data/lib/nitpick/local_variable_nitpicker.rb +26 -0
- data/lib/nitpick/method_nitpicker.rb +14 -0
- data/lib/nitpick/nitpicker.rb +46 -0
- data/lib/nitpick/rescue_nitpicker.rb +15 -0
- data/lib/nitpick/sexp_extension.rb +7 -0
- data/lib/nitpick/warnings/assignment_as_condition.rb +22 -0
- data/lib/nitpick/warnings/empty_method.rb +19 -0
- data/lib/nitpick/warnings/identical_branch.rb +18 -0
- data/lib/nitpick/warnings/rescue_everything.rb +20 -0
- data/lib/nitpick/warnings/rescue_value.rb +22 -0
- data/lib/nitpick/warnings/shadowed_variable.rb +51 -0
- data/lib/nitpick/warnings/simple_warning.rb +28 -0
- data/lib/nitpick/warnings/unprotected_block.rb +24 -0
- data/lib/nitpick/warnings/unused_argument.rb +19 -0
- data/lib/nitpick/warnings/unused_variable.rb +19 -0
- data/lib/nitpick/warnings/useless_branch.rb +20 -0
- data/spec/argument_nitpicker_spec.rb +79 -0
- data/spec/assignment_as_condition_spec.rb +41 -0
- data/spec/block_nitpicker_spec.rb +31 -0
- data/spec/branch_nitpicker_spec.rb +24 -0
- data/spec/fixtures/block_badness.rb +23 -0
- data/spec/fixtures/branch_badness.rb +27 -0
- data/spec/fixtures/local_variable_badness.rb +113 -0
- data/spec/fixtures/method_badness.rb +10 -0
- data/spec/fixtures/rescue_badness.rb +15 -0
- data/spec/local_variable_nitpicker_spec.rb +90 -0
- data/spec/method_nitpicker_spec.rb +18 -0
- data/spec/nitpicker_spec.rb +20 -0
- data/spec/rescue_nitpicker_spec.rb +25 -0
- data/spec/rescue_value_spec.rb +27 -0
- data/spec/shadowed_variable_spec.rb +60 -0
- data/spec/simple_warning_spec.rb +22 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/useless_branch_spec.rb +11 -0
- metadata +109 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2008 Kevin Clark
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
data/README
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
Ruby lint. Sorta.
|
2
|
+
|
3
|
+
http://github.com/kevinclark/nitpick/wikis
|
4
|
+
|
5
|
+
|
6
|
+
Clio:nitpick kev$ ./bin/nitpick spec/fixtures/block_badness.rb \
|
7
|
+
> spec/fixtures/branch_badness.rb \
|
8
|
+
> spec/fixtures/method_badness.rb \
|
9
|
+
> spec/fixtures/rescue_badness.rb \
|
10
|
+
> spec/fixtures/local_variable_badness.rb
|
11
|
+
./spec/fixtures/branch_badness.rb:20: warning: found = in conditional, should be ==
|
12
|
+
Fixtures::BlockBadness
|
13
|
+
no_conditional_for_block_given
|
14
|
+
- A block is being yielded to without a check for block_given?
|
15
|
+
simple_conditional_without_check
|
16
|
+
- A block is being yielded to without a check for block_given?
|
17
|
+
|
18
|
+
Fixtures::BranchBadness
|
19
|
+
branch_returning_identical_things
|
20
|
+
- The branches of 'if (true)' are identical.
|
21
|
+
branch_returning_true_or_false
|
22
|
+
- No need for an if. Just return '(1 == 2)' as a boolean.
|
23
|
+
branch_with_assignment_as_condition
|
24
|
+
- An assigment is being used as a condition: (a = 1)
|
25
|
+
- The variable :a is unused.
|
26
|
+
|
27
|
+
Fixtures::LocalVariableBadness
|
28
|
+
anonymous_args
|
29
|
+
- The method :anonymous_args is empty.
|
30
|
+
block_arg_unused
|
31
|
+
- The argument :block is unused.
|
32
|
+
lvar_shadowed
|
33
|
+
- One or more variables are being shadowed (x)
|
34
|
+
lvar_shadowed_many_block_vars
|
35
|
+
- One or more variables are being shadowed (x)
|
36
|
+
rescue_to_variable
|
37
|
+
- The variable :e is unused.
|
38
|
+
simple_unused_arg
|
39
|
+
- The argument :other is unused.
|
40
|
+
unused_arg
|
41
|
+
- The argument :arg is unused.
|
42
|
+
unused_lasgn
|
43
|
+
- The variable :bar is unused.
|
44
|
+
|
45
|
+
Fixtures::MethodBadness
|
46
|
+
empty_method
|
47
|
+
- The method :empty_method is empty.
|
48
|
+
|
49
|
+
Fixtures::RescueBadness
|
50
|
+
rescue_exception
|
51
|
+
- A rescue is capturing Object or Exception, which may hide errors.
|
52
|
+
rescue_nil
|
53
|
+
- A rescue is returning "nil" directly and may not handle an error.
|
54
|
+
rescue_object
|
55
|
+
- A rescue is capturing Object or Exception, which may hide errors.
|
56
|
+
|
data/VERSION.yml
ADDED
data/bin/nitpick
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
6
|
+
require 'nitpick'
|
7
|
+
|
8
|
+
# {Constant => {Method => [Warnings]}}
|
9
|
+
$warnings = Hash.new {|h,k| h[k] = Hash.new {|_h,_k| _h[_k] = [] }}
|
10
|
+
|
11
|
+
require 'trollop'
|
12
|
+
opts = Trollop.options do
|
13
|
+
banner <<-BANNER
|
14
|
+
Nitpick is a lint-like static code analyzer for Ruby.
|
15
|
+
|
16
|
+
Usage: `nitpick [(--only str | --except str)*|-h] file1.rb file2.rb`
|
17
|
+
|
18
|
+
By default, nitpick will analyze *all* loaded code. To reduce the noise:
|
19
|
+
|
20
|
+
BANNER
|
21
|
+
opt :only, "Nitpick only classes/modules that match this string.", :type => :string, :multi => true
|
22
|
+
opt :except, "Don't nitpick classes/modules that match this string.", :type => :string, :multi => true
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO(kevinclark) 2009-03-19: Figure out how to make this print -h
|
26
|
+
Trollop.die "I need something to nitpick. Gimmie a filename or two. Or ten" if ARGV.size == 0
|
27
|
+
|
28
|
+
NitpickOptions = { :only => opts[:only] || [], :except => opts[:except] || []}
|
29
|
+
Nitpickers = [
|
30
|
+
Nitpick::ArgumentNitpicker, Nitpick::BlockNitpicker, Nitpick::BranchNitpicker,
|
31
|
+
Nitpick::LocalVariableNitpicker, Nitpick::MethodNitpicker, Nitpick::RescueNitpicker
|
32
|
+
]
|
33
|
+
|
34
|
+
class Module
|
35
|
+
# So we don't set off -w
|
36
|
+
alias_method :original_method_added, :method_added
|
37
|
+
|
38
|
+
def method_added(name)
|
39
|
+
original_method_added(name)
|
40
|
+
|
41
|
+
# Except means don't match this
|
42
|
+
return if NitpickOptions[:except].any? {|namespace| /#{namespace}/ =~ self.to_s }
|
43
|
+
# Only means it must match one
|
44
|
+
return unless NitpickOptions[:only].find? {|namespace| /#{namespace}/ =~ self.to_s } or
|
45
|
+
NitpickOptions[:only].empty?
|
46
|
+
|
47
|
+
warnings = Nitpickers.map do |nitpicker_class|
|
48
|
+
nitpicker = nitpicker_class.new(self, name)
|
49
|
+
nitpicker.nitpick!
|
50
|
+
nitpicker.warnings
|
51
|
+
end.flatten
|
52
|
+
|
53
|
+
$warnings[self.name][name.to_s] = warnings
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
at_exit do
|
58
|
+
# Prune empty classes and methods
|
59
|
+
$warnings.each do |klass, methods|
|
60
|
+
methods.each do |meth, warnings|
|
61
|
+
$warnings[klass].delete(meth) if warnings.empty?
|
62
|
+
end
|
63
|
+
$warnings.delete(klass) if $warnings[klass].empty?
|
64
|
+
end
|
65
|
+
|
66
|
+
puts "Nothing to report boss! He's clean!" if $warnings.empty?
|
67
|
+
|
68
|
+
class_names_in_order = $warnings.keys.sort
|
69
|
+
|
70
|
+
class_names_in_order.each do |klass|
|
71
|
+
puts "#{klass}"
|
72
|
+
|
73
|
+
methods_in_order = $warnings[klass].keys.sort
|
74
|
+
|
75
|
+
methods_in_order.each do |meth|
|
76
|
+
puts " #{meth}"
|
77
|
+
|
78
|
+
$warnings[klass][meth].each do |w|
|
79
|
+
puts " - #{w.message}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
puts
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
ARGV.each do |file|
|
87
|
+
begin
|
88
|
+
load file
|
89
|
+
rescue Exception => e # grab *everything*
|
90
|
+
$stderr.puts "*** Nitpick had trouble loading #{file.inspect}:\n\t#{e.class} #{e.message}"
|
91
|
+
end
|
92
|
+
end
|
data/lib/nitpick.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'parse_tree'
|
5
|
+
require 'ruby2ruby'
|
6
|
+
|
7
|
+
require 'nitpick/warnings/simple_warning'
|
8
|
+
require 'nitpick/warnings/unused_variable'
|
9
|
+
require 'nitpick/warnings/unprotected_block'
|
10
|
+
require 'nitpick/warnings/useless_branch'
|
11
|
+
require 'nitpick/warnings/identical_branch'
|
12
|
+
require 'nitpick/warnings/rescue_value'
|
13
|
+
require 'nitpick/warnings/rescue_everything'
|
14
|
+
require 'nitpick/warnings/unused_argument'
|
15
|
+
require 'nitpick/warnings/assignment_as_condition'
|
16
|
+
require 'nitpick/warnings/shadowed_variable'
|
17
|
+
require 'nitpick/warnings/empty_method'
|
18
|
+
|
19
|
+
require 'nitpick/nitpicker'
|
20
|
+
require 'nitpick/local_variable_counter'
|
21
|
+
require 'nitpick/local_variable_nitpicker'
|
22
|
+
require 'nitpick/argument_nitpicker'
|
23
|
+
require 'nitpick/block_nitpicker'
|
24
|
+
require 'nitpick/branch_nitpicker'
|
25
|
+
require 'nitpick/rescue_nitpicker'
|
26
|
+
require 'nitpick/method_nitpicker'
|
27
|
+
|
28
|
+
|
29
|
+
require 'nitpick/sexp_extension'
|
30
|
+
class Sexp
|
31
|
+
include Nitpick::SexpExtension
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Nitpick
|
2
|
+
class ArgumentNitpicker < LocalVariableCounter
|
3
|
+
def nitpick!
|
4
|
+
@silence_warnings = false
|
5
|
+
super
|
6
|
+
return if @silence_warnings
|
7
|
+
|
8
|
+
@lvars.each do |name, details|
|
9
|
+
next if details[:calls] > 0
|
10
|
+
next if details[:uses] >= 2
|
11
|
+
next if !@args.include?(name)
|
12
|
+
warn Warnings::UnusedArgument.new(name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_zsuper(exp)
|
17
|
+
@silence_warnings = true
|
18
|
+
s(:zsuper)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Nitpick
|
2
|
+
class BlockNitpicker < Nitpicker
|
3
|
+
def process_if(exp)
|
4
|
+
cond = Sexp.from_array(exp.shift)
|
5
|
+
yes_branch = Sexp.from_array(exp.shift)
|
6
|
+
no_branch = Sexp.from_array(exp.shift)
|
7
|
+
|
8
|
+
scan_for [Warnings::UnprotectedBlock],
|
9
|
+
:with => [cond, yes_branch, no_branch]
|
10
|
+
|
11
|
+
s(:if, cond, yes_branch, no_branch)
|
12
|
+
end
|
13
|
+
|
14
|
+
def process_yield(exp)
|
15
|
+
exp.clear
|
16
|
+
warn Warnings::UnprotectedBlock.new
|
17
|
+
|
18
|
+
s(:yield)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Nitpick
|
2
|
+
class BranchNitpicker < Nitpicker
|
3
|
+
def process_if(exp)
|
4
|
+
cond = process(exp.shift)
|
5
|
+
yes_branch = process(exp.shift)
|
6
|
+
no_branch = process(exp.shift)
|
7
|
+
|
8
|
+
scan_for [Warnings::UselessBranch, Warnings::IdenticalBranch],
|
9
|
+
:with => [cond, yes_branch, no_branch]
|
10
|
+
|
11
|
+
scan_for [Warnings::AssignmentAsCondition],
|
12
|
+
:with => [cond]
|
13
|
+
|
14
|
+
s(:if, cond, yes_branch, no_branch)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Nitpick
|
2
|
+
class LocalVariableCounter < Nitpicker
|
3
|
+
def initialize(klass, meth)
|
4
|
+
super
|
5
|
+
# uses.succ each time a local variable is referenced or assigned to
|
6
|
+
# calls.succ each time a message is sent to a lvar (foo.bar)
|
7
|
+
@lvars = Hash.new {|h,k| h[k] = {:uses => 0, :calls => 0}}
|
8
|
+
@args = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def uses(name)
|
12
|
+
@lvars[name][:uses]
|
13
|
+
end
|
14
|
+
|
15
|
+
def use(name)
|
16
|
+
@lvars[name][:uses] += 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(name)
|
20
|
+
@lvars[name][:calls] += 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def process_lasgn(exp)
|
24
|
+
name = exp.shift
|
25
|
+
value = exp.shift
|
26
|
+
|
27
|
+
use name
|
28
|
+
process(value)
|
29
|
+
|
30
|
+
s(:lasgn, name, value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_iasgn(exp)
|
34
|
+
name = exp.shift
|
35
|
+
value = exp.shift
|
36
|
+
|
37
|
+
process(value)
|
38
|
+
|
39
|
+
s(:iasgn, name, value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def process_call(exp)
|
43
|
+
recv = process(exp.shift)
|
44
|
+
meth = exp.shift
|
45
|
+
args = process(exp.shift)
|
46
|
+
|
47
|
+
call recv.last if recv.first == :lvar
|
48
|
+
|
49
|
+
s(:call, recv, meth, args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_lvar(exp)
|
53
|
+
name = exp.shift
|
54
|
+
use name
|
55
|
+
s(:lvar, name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_args(exp)
|
59
|
+
exp.each do |arg|
|
60
|
+
next unless arg.is_a? Symbol
|
61
|
+
|
62
|
+
name = arg.to_s.gsub(/^\*/, '')
|
63
|
+
next if name == ""
|
64
|
+
|
65
|
+
@args << name.to_sym
|
66
|
+
use name.to_sym
|
67
|
+
end
|
68
|
+
|
69
|
+
exp.clear
|
70
|
+
|
71
|
+
s(:args, *@args)
|
72
|
+
end
|
73
|
+
|
74
|
+
def process_block_arg(exp)
|
75
|
+
name = exp.shift
|
76
|
+
@args << name
|
77
|
+
use name
|
78
|
+
s(:block_arg, name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Nitpick
|
2
|
+
class LocalVariableNitpicker < LocalVariableCounter
|
3
|
+
def nitpick!
|
4
|
+
super
|
5
|
+
|
6
|
+
@lvars.each do |name, details|
|
7
|
+
next if @args.include? name
|
8
|
+
next if details[:calls] > 0
|
9
|
+
# The first assignment is a use
|
10
|
+
next if details[:uses] > 1
|
11
|
+
warn Warnings::UnusedVariable.new(name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def process_iter(exp)
|
16
|
+
call = process(exp.shift)
|
17
|
+
assignments = process(exp.shift)
|
18
|
+
block = process(exp.shift)
|
19
|
+
|
20
|
+
scan_for [Warnings::ShadowedVariable],
|
21
|
+
:with => [assignments]
|
22
|
+
|
23
|
+
s(:iter, call, assignments, block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Nitpick
|
2
|
+
class MethodNitpicker < Nitpicker
|
3
|
+
def process_defn(exp)
|
4
|
+
# def foo(x); 1 end =>
|
5
|
+
# [:defn, :foo, [:scope, [:block, [:args, :x], [:lit, 1]]]]
|
6
|
+
name, (_scope, (_block, (_argsym, *args), body)) = exp
|
7
|
+
|
8
|
+
scan_for [Warnings::EmptyMethod],
|
9
|
+
:with => [name, args, body]
|
10
|
+
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Nitpick
|
2
|
+
class Nitpicker < SexpProcessor
|
3
|
+
attr_accessor :warnings
|
4
|
+
|
5
|
+
def initialize(klass, meth)
|
6
|
+
super()
|
7
|
+
self.auto_shift_type = true
|
8
|
+
self.strict = false
|
9
|
+
self.expected = Sexp
|
10
|
+
|
11
|
+
@unsupported.delete(:cfunc)
|
12
|
+
@class = klass
|
13
|
+
@method = meth
|
14
|
+
@warnings = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def nitpick!
|
18
|
+
process(ParseTree.translate(@class, @method))
|
19
|
+
end
|
20
|
+
|
21
|
+
def scan_for(warning_classes, options = {})
|
22
|
+
matched_warnings = warning_classes.map do |warning_class|
|
23
|
+
warning_class.discover(options[:with])
|
24
|
+
end
|
25
|
+
|
26
|
+
warn(*matched_warnings)
|
27
|
+
end
|
28
|
+
|
29
|
+
def warn(*warnings)
|
30
|
+
@warnings += warnings.compact
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_defn(exp)
|
34
|
+
method = exp.shift
|
35
|
+
result = s(:defn, method)
|
36
|
+
|
37
|
+
until exp.empty?
|
38
|
+
result << process(exp.shift)
|
39
|
+
end
|
40
|
+
|
41
|
+
result
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_cfunc(exp); exp.clear; s(); end
|
45
|
+
end
|
46
|
+
end
|