nitpick 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -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,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
|