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
@@ -0,0 +1,15 @@
1
+ module Nitpick
2
+ class RescueNitpicker < Nitpicker
3
+ def process_resbody(exp)
4
+ exceptions = exp.shift
5
+ rescue_value = exp.shift
6
+
7
+ scan_for [Warnings::RescueValue, Warnings::RescueEverything],
8
+ :with => [exceptions, rescue_value]
9
+
10
+ exp.clear
11
+
12
+ s(:resbody, exceptions, rescue_value)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Nitpick
2
+ module SexpExtension
3
+ define_method :"!~" do |pattern|
4
+ !(self =~ pattern)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class AssignmentAsCondition < SimpleWarning
4
+ ASSIGNMENT_NODES = [:lasgn, :op_asgn_or, :op_asgn_and, :iasgn,
5
+ :op_asgn1, :gasgn, :dasgn_curr]
6
+
7
+ attr_reader :condition
8
+
9
+ def initialize(*args)
10
+ @condition = args.shift
11
+ end
12
+
13
+ def matches?
14
+ ASSIGNMENT_NODES.include? @condition.first
15
+ end
16
+
17
+ def message
18
+ "An assigment is being used as a condition: (#{sexp_to_ruby(@condition)})"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class EmptyMethod < SimpleWarning
4
+ attr_reader :body
5
+
6
+ def initialize(*args)
7
+ @name, @args, @body = args
8
+ end
9
+
10
+ def matches?
11
+ @body == [:nil]
12
+ end
13
+
14
+ def message
15
+ "The method #{@name.inspect} is empty."
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class IdenticalBranch < SimpleWarning
4
+ attr_reader :yes_branch, :no_branch
5
+ def initialize(*args)
6
+ @cond, @yes_branch, @no_branch = args
7
+ end
8
+
9
+ def matches?
10
+ yes_branch == no_branch
11
+ end
12
+
13
+ def message
14
+ "The branches of 'if (#{sexp_to_ruby(@cond)})' are identical."
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class RescueEverything < SimpleWarning
4
+ def initialize(*args)
5
+ @exceptions, @rescue_value = args
6
+ end
7
+
8
+ def matches?
9
+ return false unless @exceptions
10
+ [:Object, :Exception].find do |const|
11
+ @exceptions.include? [:const, const]
12
+ end
13
+ end
14
+
15
+ def message
16
+ "A rescue is capturing Object or Exception, which may hide errors."
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class RescueValue < SimpleWarning
4
+ def initialize(*args)
5
+ @exceptions, *@rescue_value = args
6
+ end
7
+
8
+ def matches?
9
+ return false if @rescue_value.nil?
10
+ return false if @rescue_value.size > 1
11
+ return false if @rescue_value.empty?
12
+ val = @rescue_value.first
13
+ return false if val.nil? or val.empty?
14
+ [:lit, :nil].include? val.first # the value's type
15
+ end
16
+
17
+ def message
18
+ "A rescue is returning #{sexp_to_ruby(@rescue_value.first).inspect} directly and may not handle an error."
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,51 @@
1
+ require 'set'
2
+
3
+ module Nitpick
4
+ module Warnings
5
+ class ShadowedVariable < SimpleWarning
6
+ attr_reader :vars
7
+
8
+ # ShadowedVariable.new takes one or more block variable assignment sexps
9
+ def initialize(*args)
10
+ @block_vars = args.shift
11
+ @vars = Set.new
12
+ end
13
+
14
+ def matches?
15
+ case @block_vars && @block_vars.first
16
+ when :masgn
17
+ matched = false
18
+ # label, args, splats, ..
19
+ # :masgns look like [:masgn, [:array, [:dasgn_curr, :x]], [:lasgn, :a], nil]
20
+ # or [:masgn, nil, [:lasgn, :a], nil]
21
+ # or [:masgn, [:array, [:dasgn_curr, :x], [:lasgn, :a]], nil, nil]
22
+
23
+ to_check = []
24
+
25
+ assigns = @block_vars[1].deep_clone
26
+ if assigns
27
+ assigns.shift # bump the array
28
+ to_check += assigns
29
+ end
30
+
31
+ to_check += s(@block_vars[2].deep_clone) if @block_vars[2]
32
+
33
+ to_check.each do |sexp|
34
+ next unless sexp.first == :lasgn
35
+ vars << sexp[1]
36
+ matched = true
37
+ end
38
+
39
+ matched
40
+ when :lasgn
41
+ vars << @block_vars[1]
42
+ true
43
+ end
44
+ end
45
+
46
+ def message
47
+ "One or more variables are being shadowed (#{@vars.to_a.join(',')})"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class SimpleWarning
4
+ def self.discover(args = [])
5
+ warning = new(*args)
6
+ warning if warning.matches?
7
+ end
8
+
9
+ def matches?
10
+ false
11
+ end
12
+
13
+ def ==(other)
14
+ other.is_a? self.class
15
+ end
16
+
17
+ def message
18
+ raise NotImplementedError
19
+ end
20
+
21
+ private
22
+
23
+ def sexp_to_ruby(sexp)
24
+ Ruby2Ruby.new.process(Unifier.new.process(sexp))
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class UnprotectedBlock < SimpleWarning
4
+ attr_reader :condition, :yes_branch, :no_branch
5
+
6
+ def initialize(*args)
7
+ @condition, @yes_branch, @no_branch = args
8
+ end
9
+
10
+ def matches?
11
+ # raise "YES: #{yes_branch.inspect}"
12
+ yes_branch =~ s(:yield) && condition !~ s(:fcall, :block_given?)
13
+ end
14
+
15
+ def ==(other)
16
+ yes_branch == other.yes_branch && no_branch == other.no_branch
17
+ end
18
+
19
+ def message
20
+ "A block is being yielded to without a check for block_given?"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class UnusedArgument < SimpleWarning
4
+ attr_reader :argument
5
+
6
+ def initialize(arg)
7
+ @argument = arg
8
+ end
9
+
10
+ def ==(other)
11
+ @argument == other.argument
12
+ end
13
+
14
+ def message
15
+ "The argument #{@argument.inspect} is unused."
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class UnusedVariable < SimpleWarning
4
+ attr_reader :variable
5
+
6
+ def initialize(variable)
7
+ @variable = variable
8
+ end
9
+
10
+ def ==(other)
11
+ other.is_a?(self.class) && @variable == other.variable
12
+ end
13
+
14
+ def message
15
+ "The variable #{@variable.inspect} is unused."
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Nitpick
2
+ module Warnings
3
+ class UselessBranch < SimpleWarning
4
+ attr_reader :yes_branch, :no_branch
5
+
6
+ def initialize(*args)
7
+ @cond, @yes_branch, @no_branch = args
8
+ end
9
+
10
+ def matches?
11
+ (yes_branch == s(:true) and no_branch == s(:false)) or
12
+ (yes_branch == s(:false) and no_branch == s(:true))
13
+ end
14
+
15
+ def message
16
+ "No need for an if. Just return '#{sexp_to_ruby(@cond)}' as a boolean."
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,79 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'fixtures/local_variable_badness'
3
+ require 'fixtures/block_badness'
4
+
5
+ include Fixtures
6
+
7
+ describe Nitpick::ArgumentNitpicker do
8
+ it "should create warnings for an unreferenced argument" do
9
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :unused_arg)
10
+ nitpicker.nitpick!
11
+ nitpicker.warnings.should == [Nitpick::Warnings::UnusedArgument.new(:arg)]
12
+ end
13
+
14
+ it "should not create warnings for a referenced argument" do
15
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :used_arg)
16
+ nitpicker.nitpick!
17
+ nitpicker.warnings.should == []
18
+ end
19
+
20
+ it "should not create warnings for splat args" do
21
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :used_splat_arg)
22
+ nitpicker.nitpick!
23
+ nitpicker.warnings.should == []
24
+ end
25
+
26
+ it "should not create warnings when calling super with no explicit arguments" do
27
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :super_with_implicit_args)
28
+ nitpicker.nitpick!
29
+ nitpicker.warnings.should == []
30
+ end
31
+
32
+ it "should create warnings when calling super with explicit arguments" do
33
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :super_with_explicit_args)
34
+ nitpicker.nitpick!
35
+ nitpicker.warnings.should == [Nitpick::Warnings::UnusedArgument.new(:arg)]
36
+ end
37
+
38
+ it "should create warnings when calling super with explicit arguments but without referencing the passed arguments" do
39
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :super_with_explicit_args)
40
+ nitpicker.nitpick!
41
+ nitpicker.warnings.should == [Nitpick::Warnings::UnusedArgument.new(:arg)]
42
+ end
43
+
44
+ it "should not create warnings for args used with splats" do
45
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :args_used_with_splats)
46
+ nitpicker.nitpick!
47
+ nitpicker.warnings.should == []
48
+ end
49
+
50
+ it "should ignore [:block] arguments in args" do
51
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :default_args)
52
+ nitpicker.nitpick!
53
+ nitpicker.warnings.should == []
54
+ end
55
+
56
+ it "should handle &block in args when called" do
57
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :block_arg_called)
58
+ nitpicker.nitpick!
59
+ nitpicker.warnings.should == []
60
+ end
61
+
62
+ it "should handle &block in args when curried" do
63
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :block_arg_curried)
64
+ nitpicker.nitpick!
65
+ nitpicker.warnings.should == []
66
+ end
67
+
68
+ it "should warn when &block in args is unused" do
69
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :block_arg_unused)
70
+ nitpicker.nitpick!
71
+ nitpicker.warnings.should == [Nitpick::Warnings::UnusedArgument.new(:block)]
72
+ end
73
+
74
+ it "shouldn't warn on non-argument variables" do
75
+ nitpicker = Nitpick::ArgumentNitpicker.new(LocalVariableBadness, :rescue_to_variable)
76
+ nitpicker.nitpick!
77
+ nitpicker.warnings.should == []
78
+ end
79
+ end
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'fixtures/branch_badness'
3
+
4
+ include Fixtures
5
+
6
+ describe Nitpick::Warnings::AssignmentAsCondition do
7
+ it "should match against an assignment" do
8
+ # TODO(kevinclark):
9
+ # This should really include :dasgn, :cvasgn, and :op_asgn2,
10
+ # but I can't figure out how they show up in the AST.
11
+
12
+ matches = {
13
+ "a = 1" => s(:lasgn, :a, s(:lit, 1)),
14
+ "a ||= 2" => s(:op_asgn_or, s(:lvar, :a), s(:lasgn, :a, s(:lit, 2))),
15
+ "a &&= 3" => s(:op_asgn_and, s(:lvar, :a), s(:lasgn, :a, s(:lit, 2))),
16
+ "@a = 4" => s(:iasgn, :@a, s(:lit, 4)),
17
+ "a[0] ||= 5" => s(:op_asgn1, s(:vcall, :a), s(:array, s(:lit, 0)), :"||", s(:lit, 1)),
18
+ "$a = 6" => s(:gasgn, :$a, s(:lit, 1)),
19
+ "a = 7 # in a block" => s(:dasgn_curr, :a, s(:lit, 1))
20
+ }
21
+
22
+ matches.each do |_, assignment|
23
+ warning = Nitpick::Warnings::AssignmentAsCondition.new(assignment)
24
+ warning.matches?.should be_true
25
+ end
26
+ end
27
+
28
+ it "shouldn't match against nodes that aren't an assignment" do
29
+ non_matches = {
30
+ "a == 1" => s(:call, s(:vcall, :a), :==, s(:array, s(:lit, 1))),
31
+ "a.nil?" => s(:call, s(:vcall, :a), :nil?),
32
+ "a" => s(:vcall, :a),
33
+ "a(b,c,d)" => s(:fcall, :a, s(:array, s(:vcall, :b), s(:vcall, :c), s(:vcall, :d)))
34
+ }
35
+
36
+ non_matches.each do |_, condition|
37
+ warning = Nitpick::Warnings::AssignmentAsCondition.new(condition)
38
+ warning.matches?.should be_false
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'fixtures/block_badness'
3
+
4
+ include Fixtures
5
+
6
+ describe Nitpick::BlockNitpicker do
7
+ it "should warn when a yield is present without a conditional" do
8
+ nitpicker = Nitpick::BlockNitpicker.new(BlockBadness, :no_conditional_for_block_given)
9
+ nitpicker.nitpick!
10
+ nitpicker.warnings.should == [Nitpick::Warnings::UnprotectedBlock.new]
11
+ end
12
+
13
+ it "should not warn when there is a check for block_given? before yield" do
14
+ nitpicker = Nitpick::BlockNitpicker.new(BlockBadness, :simple_check_for_block_given)
15
+ nitpicker.nitpick!
16
+ nitpicker.warnings.should == []
17
+ end
18
+
19
+ it "should warn when there is a yield in a conditional without a block_given? check" do
20
+ nitpicker = Nitpick::BlockNitpicker.new(BlockBadness, :simple_conditional_without_check)
21
+ nitpicker.nitpick!
22
+ nitpicker.warnings.size.should == 1
23
+ nitpicker.warnings.first.should be_a_kind_of(Nitpick::Warnings::UnprotectedBlock)
24
+ end
25
+
26
+ it "should not warn when there is a check for block_given? in a complex conditional before yield" do
27
+ nitpicker = Nitpick::BlockNitpicker.new(BlockBadness, :complex_conditional_with_check)
28
+ nitpicker.nitpick!
29
+ nitpicker.warnings.should == []
30
+ end
31
+ end