nitpick 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/LICENSE +22 -0
  2. data/README +56 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/nitpick +92 -0
  5. data/lib/nitpick.rb +32 -0
  6. data/lib/nitpick/argument_nitpicker.rb +21 -0
  7. data/lib/nitpick/block_nitpicker.rb +21 -0
  8. data/lib/nitpick/branch_nitpicker.rb +17 -0
  9. data/lib/nitpick/local_variable_counter.rb +81 -0
  10. data/lib/nitpick/local_variable_nitpicker.rb +26 -0
  11. data/lib/nitpick/method_nitpicker.rb +14 -0
  12. data/lib/nitpick/nitpicker.rb +46 -0
  13. data/lib/nitpick/rescue_nitpicker.rb +15 -0
  14. data/lib/nitpick/sexp_extension.rb +7 -0
  15. data/lib/nitpick/warnings/assignment_as_condition.rb +22 -0
  16. data/lib/nitpick/warnings/empty_method.rb +19 -0
  17. data/lib/nitpick/warnings/identical_branch.rb +18 -0
  18. data/lib/nitpick/warnings/rescue_everything.rb +20 -0
  19. data/lib/nitpick/warnings/rescue_value.rb +22 -0
  20. data/lib/nitpick/warnings/shadowed_variable.rb +51 -0
  21. data/lib/nitpick/warnings/simple_warning.rb +28 -0
  22. data/lib/nitpick/warnings/unprotected_block.rb +24 -0
  23. data/lib/nitpick/warnings/unused_argument.rb +19 -0
  24. data/lib/nitpick/warnings/unused_variable.rb +19 -0
  25. data/lib/nitpick/warnings/useless_branch.rb +20 -0
  26. data/spec/argument_nitpicker_spec.rb +79 -0
  27. data/spec/assignment_as_condition_spec.rb +41 -0
  28. data/spec/block_nitpicker_spec.rb +31 -0
  29. data/spec/branch_nitpicker_spec.rb +24 -0
  30. data/spec/fixtures/block_badness.rb +23 -0
  31. data/spec/fixtures/branch_badness.rb +27 -0
  32. data/spec/fixtures/local_variable_badness.rb +113 -0
  33. data/spec/fixtures/method_badness.rb +10 -0
  34. data/spec/fixtures/rescue_badness.rb +15 -0
  35. data/spec/local_variable_nitpicker_spec.rb +90 -0
  36. data/spec/method_nitpicker_spec.rb +18 -0
  37. data/spec/nitpicker_spec.rb +20 -0
  38. data/spec/rescue_nitpicker_spec.rb +25 -0
  39. data/spec/rescue_value_spec.rb +27 -0
  40. data/spec/shadowed_variable_spec.rb +60 -0
  41. data/spec/simple_warning_spec.rb +22 -0
  42. data/spec/spec_helper.rb +10 -0
  43. data/spec/useless_branch_spec.rb +11 -0
  44. 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
+
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 0
3
+ :patch: 0
4
+ :major: 1
@@ -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
@@ -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