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