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.
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