shard-ameba/spec/ameba/rule/lint/useless_assign_spec.cr
Vitalii Elenhaupt 51b0a07e81
Avoid exponential recursion while finding variable references in scopes (#203)
* Avoid exponential recursion while finding variable references in scopes

* Adjust source example in test
2021-01-31 16:40:44 +02:00

1079 lines
26 KiB
Crystal

require "../../../spec_helper"
module Ameba::Rule::Lint
describe UselessAssign do
subject = UselessAssign.new
it "does not report used assigments" do
s = Source.new %(
def method
a = 2
a
end
)
subject.catch(s).should be_valid
end
it "reports a useless assignment in a method" do
s = Source.new %(
def method
a = 2
end
)
subject.catch(s).should_not be_valid
end
it "reports a useless assignment in a proc" do
s = Source.new %(
->() {
a = 2
}
)
subject.catch(s).should_not be_valid
end
it "reports a useless assignment in a block" do
s = Source.new %(
def method
3.times do
a = 1
end
end
)
subject.catch(s).should_not be_valid
end
it "reports a useless assignment in a proc inside def" do
s = Source.new %(
def method
->() {
a = 2
}
end
)
subject.catch(s).should_not be_valid
end
it "does not report ignored assigments" do
s = Source.new %(
payload, _header = decode
puts payload
)
subject.catch(s).should be_valid
end
it "reports a useless assignment in a proc inside a block" do
s = Source.new %(
def method
3.times do
->() {
a = 2
}
end
end
)
subject.catch(s).should_not be_valid
end
it "reports rule, position and a message" do
s = Source.new %(
def method
a = 2
end
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:3"
issue.message.should eq "Useless assignment to variable `a`"
end
it "does not report useless assignment of instance var" do
s = Source.new %(
class Cls
def initialize(@name)
end
end
)
subject.catch(s).should be_valid
end
it "does not report if assignment used in the inner block scope" do
s = Source.new %(
def method
var = true
3.times { var = false }
end
)
subject.catch(s).should be_valid
end
it "reports if assigned is not referenced in the inner block scope" do
s = Source.new %(
def method
var = true
3.times {}
end
)
subject.catch(s).should_not be_valid
end
it "doesn't report if assignment in referenced in inner block" do
s = Source.new %(
def method
two = true
3.times do
mutex.synchronize do
two = 2
end
end
two.should be_true
end
)
subject.catch(s).should be_valid
end
it "reports if first assignment is useless" do
s = Source.new %(
def method
var = true
var = false
var
end
)
subject.catch(s).should_not be_valid
s.issues.first.location.to_s.should eq ":2:3"
end
it "reports if variable reassigned and not used" do
s = Source.new %(
def method
var = true
var = false
end
)
subject.catch(s).should_not be_valid
end
it "does not report if variable used in a condition" do
s = Source.new %(
def method
a = 1
if a
nil
end
end
)
subject.catch(s).should be_valid
end
it "reports second assignment as useless" do
s = Source.new %(
def method
a = 1
a = a + 1
end
)
subject.catch(s).should_not be_valid
end
it "does not report if variable is referenced in other assignment" do
s = Source.new %(
def method
if f = get_something
@f = f
end
end
)
subject.catch(s).should be_valid
end
it "does not report if variable is referenced in a setter" do
s = Source.new %(
def method
foo = 2
table[foo] ||= "bar"
end
)
subject.catch(s).should be_valid
end
it "does not report if variable is reassigned but not referenced" do
s = Source.new %(
def method
foo = 1
puts foo
foo = 2
end
)
subject.catch(s).should_not be_valid
end
it "does not report if variable is referenced in a call" do
s = Source.new %(
def method
if f = FORMATTER
@formatter = f.new
end
end
)
subject.catch(s).should be_valid
end
it "does not report if a setter is invoked with operator assignment" do
s = Source.new %(
def method
obj = {} of Symbol => Int32
obj[:name] = 3
end
)
subject.catch(s).should be_valid
end
context "when transformed" do
it "does not report if the first arg is transformed and not used" do
s = Source.new %(
collection.each do |(a, b)|
puts b
end
)
subject.catch(s).should be_valid
end
it "does not report if the second arg is transformed and not used" do
s = Source.new %(
collection.each do |(a, b)|
puts a
end
)
subject.catch(s).should be_valid
end
it "does not report if all transformed args are not used in a block" do
s = Source.new %(
collection.each do |(foo, bar), (baz, _qux), index, object|
end
)
subject.catch(s).should be_valid
end
end
it "does not report if global var" do
s = Source.new %(
def method
$1 = 3
end
)
subject.catch(s).should be_valid
end
it "does not report if assignment is referenced in a proc" do
s = Source.new %(
def method
called = false
->() { called = true }
called
end
)
subject.catch(s).should be_valid
end
it "reports if variable is shadowed in inner scope" do
s = Source.new %(
def method
i = 1
3.times do |i|
i + 1
end
end
)
subject.catch(s).should_not be_valid
end
it "does not report if parameter is referenced after the branch" do
s = Source.new %(
def method(param)
3.times do
param = 3
end
param
end
)
subject.catch(s).should be_valid
end
context "op assigns" do
it "does not report if variable is referenced below the op assign" do
s = Source.new %(
def method
a = 1
a += 1
a
end
)
subject.catch(s).should be_valid
end
it "does not report if variable is referenced in op assign few times" do
s = Source.new %(
def method
a = 1
a += 1
a += 1
a = a + 1
a
end
)
subject.catch(s).should be_valid
end
it "reports if variable is not referenced below the op assign" do
s = Source.new %(
def method
a = 1
a += 1
end
)
subject.catch(s).should_not be_valid
end
it "reports rule, location and a message" do
s = Source.new %(
def method
b = 2
a = 3
a += 1
end
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.last
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:4:3"
issue.message.should eq "Useless assignment to variable `a`"
end
end
context "multi assigns" do
it "does not report if all assigns are referenced" do
s = Source.new %(
def method
a, b = {1, 2}
a + b
end
)
subject.catch(s).should be_valid
end
it "reports if one assign is not referenced" do
s = Source.new %(
def method
a, b = {1, 2}
a
end
)
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.location.to_s.should eq ":2:6"
issue.message.should eq "Useless assignment to variable `b`"
end
it "reports if both assigns are reassigned and useless" do
s = Source.new %(
def method
a, b = {1, 2}
a, b = {3, 4}
end
)
subject.catch(s).should_not be_valid
end
it "reports if both assigns are not referenced" do
s = Source.new %(
def method
a, b = {1, 2}
end
)
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.location.to_s.should eq ":2:3"
issue.message.should eq "Useless assignment to variable `a`"
issue = s.issues.last
issue.location.to_s.should eq ":2:6"
issue.message.should eq "Useless assignment to variable `b`"
end
end
context "top level" do
it "reports if assignment is not referenced" do
s = Source.new %(
a = 1
a = 2
)
subject.catch(s).should_not be_valid
s.issues.size.should eq 2
s.issues.first.location.to_s.should eq ":1:1"
s.issues.last.location.to_s.should eq ":2:1"
end
it "doesn't report if assignments are referenced" do
s = Source.new %(
a = 1
a += 1
a
b, c = {1, 2}
b
c
)
subject.catch(s).should be_valid
end
it "doesn't report if assignment is captured by block" do
s = Source.new %(
a = 1
3.times do
a = 2
end
)
subject.catch(s).should be_valid
end
it "doesn't report if assignment initialized and captured by block" do
s = Source.new %(
a : String? = nil
1.times do
a = "Fotis"
end
)
subject.catch(s).should be_valid
end
it "doesn't report if this is a record declaration" do
s = Source.new %(
record Foo, foo = "foo"
)
subject.catch(s).should be_valid
end
it "does not report if assignment is referenced after the record declaration" do
s = Source.new %(
foo = 2
record Bar, foo = 3 # foo = 3 is not parsed as assignment
puts foo
)
subject.catch(s).should be_valid
end
it "reports if assignment is not referenced after the record declaration" do
s = Source.new %(
foo = 2
record Bar, foo = 3
), "source.cr"
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
issue = s.issues.first
issue.location.to_s.should eq "source.cr:1:1"
issue.message.should eq "Useless assignment to variable `foo`"
end
end
context "branching" do
context "if-then-else" do
it "doesn't report if assignment is consumed by branches" do
s = Source.new %(
def method
a = 0
if something
a = 1
else
a = 2
end
a
end
)
subject.catch(s).should be_valid
end
it "doesn't report if assignment is in one branch" do
s = Source.new %(
def method
a = 0
if something
a = 1
else
nil
end
a
end
)
subject.catch(s).should be_valid
end
it "doesn't report if assignment is in one line branch" do
s = Source.new %(
def method
a = 0
a = 1 if something
a
end
)
subject.catch(s).should be_valid
end
it "reports if assignment is useless in the branch" do
s = Source.new %(
def method(a)
if a
a = 2
end
end
)
subject.catch(s).should_not be_valid
end
it "reports if only last assignment is referenced in a branch" do
s = Source.new %(
def method(a)
a = 1
if a
a = 2
a = 3
end
a
end
)
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
s.issues.first.location.to_s.should eq ":4:5"
end
it "does not report of assignments are referenced in all branches" do
s = Source.new %(
def method
if matches
matches = owner.lookup_matches signature
else
matches = owner.lookup_matches signature
end
matches
end
)
subject.catch(s).should be_valid
end
it "does not report referenced assignments in inner branches" do
s = Source.new %(
def method
has_newline = false
if something
do_something unless false
has_newline = false
else
do_something if true
has_newline = true
end
has_newline
end
)
subject.catch(s).should be_valid
end
end
context "unless-then-else" do
it "doesn't report if assignment is consumed by branches" do
s = Source.new %(
def method
a = 0
unless something
a = 1
else
a = 2
end
a
end
)
subject.catch(s).should be_valid
end
it "reports if there is a useless assignment in a branch" do
s = Source.new %(
def method
a = 0
unless something
a = 1
a = 2
else
a = 2
end
a
end
)
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
s.issues.first.location.to_s.should eq ":4:5"
end
end
context "case" do
it "does not report if assignment is referenced" do
s = Source.new %(
def method(a)
case a
when /foo/
a = 1
when /bar/
a = 2
end
puts a
end
)
subject.catch(s).should be_valid
end
it "reports if assignment is useless" do
s = Source.new %(
def method(a)
case a
when /foo/
a = 1
when /bar/
a = 2
end
end
)
subject.catch(s).should_not be_valid
s.issues.size.should eq 2
s.issues.first.location.to_s.should eq ":4:5"
s.issues.last.location.to_s.should eq ":6:5"
end
it "doesn't report if assignment is referenced in cond" do
s = Source.new %(
def method
a = 2
case a
when /foo/
end
end
)
subject.catch(s).should be_valid
end
end
context "binary operator" do
it "does not report if assignment is referenced" do
s = Source.new %(
def method(a)
(a = 1) && (b = 1)
a + b
end
)
subject.catch(s).should be_valid
end
it "reports if assignment is useless" do
s = Source.new %(
def method(a)
(a = 1) || (b = 1)
a
end
)
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
s.issues.first.location.to_s.should eq ":2:15"
end
end
context "while" do
it "does not report if assignment is referenced" do
s = Source.new %(
def method(a)
while a < 10
a = a + 1
end
a
end
)
subject.catch(s).should be_valid
end
it "reports if assignment is useless" do
s = Source.new %(
def method(a)
while a < 10
b = a
end
end
)
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
s.issues.first.location.to_s.should eq ":3:5"
end
it "does not report if assignment is referenced in a loop" do
s = Source.new %(
def method
a = 3
result = 0
while result < 10
result += a
a = a + 1
end
result
end
)
subject.catch(s).should be_valid
end
it "does not report if assignment is referenced as param in a loop" do
s = Source.new %(
def method(a)
result = 0
while result < 10
result += a
a = a + 1
end
result
end
)
subject.catch(s).should be_valid
end
it "does not report if assignment is referenced in loop and inner branch" do
s = Source.new %(
def method(a)
result = 0
while result < 10
result += a
if result > 0
a = a + 1
else
a = 3
end
end
result
end
)
subject.catch(s).should be_valid
end
it "works properly if there is branch with blank node" do
s = Source.new %(
def visit
count = 0
while true
break if count == 1
case something
when :any
else
:anything_else
end
count += 1
end
end
)
subject.catch(s).should be_valid
end
end
context "until" do
it "does not report if assignment is referenced" do
s = Source.new %(
def method(a)
until a > 10
a = a + 1
end
a
end
)
subject.catch(s).should be_valid
end
it "reports if assignment is useless" do
s = Source.new %(
def method(a)
until a > 10
b = a + 1
end
end
)
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
s.issues.first.location.to_s.should eq ":3:5"
end
end
context "exception handler" do
it "does not report if assignment is referenced in body" do
s = Source.new %(
def method(a)
a = 2
rescue
a
end
)
subject.catch(s).should be_valid
end
it "doesn't report if assignment is referenced in ensure" do
s = Source.new %(
def method(a)
a = 2
ensure
a
end
)
subject.catch(s).should be_valid
end
it "doesn't report if assignment is referenced in else" do
s = Source.new %(
def method(a)
a = 2
rescue
else
a
end
)
subject.catch(s).should be_valid
end
it "reports if assignment is useless" do
s = Source.new %(
def method(a)
rescue
a = 2
end
)
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
s.issues.first.location.to_s.should eq ":3:3"
end
end
end
context "typeof" do
it "reports useless assigments in typeof" do
s = Source.new %(
typeof(begin
foo = 1
bar = 2
end)
)
subject.catch(s).should_not be_valid
s.issues.size.should eq 2
s.issues.first.location.to_s.should eq ":2:3"
s.issues.first.message.should eq "Useless assignment to variable `foo`"
s.issues.last.location.to_s.should eq ":3:3"
s.issues.last.message.should eq "Useless assignment to variable `bar`"
end
end
context "macro" do
it "doesn't report if assignment is referenced in macro" do
s = Source.new %(
def method
a = 2
{% if flag?(:bits64) %}
a.to_s
{% else %}
a
{% end %}
end
)
subject.catch(s).should be_valid
end
it "doesn't report referenced assignments in macro literal" do
s = Source.new %(
def method
a = 2
{% if flag?(:bits64) %}
a = 3
{% else %}
a = 4
{% end %}
puts a
end
)
subject.catch(s).should be_valid
end
it "doesn't report if assignment is referenced in macro def" do
s = Source.new %(
macro macro_call
puts x
end
def foo
x = 1
macro_call
end
)
subject.catch(s).should be_valid
end
it "doesn't report if assignment is referenced in a macro below" do
s = Source.new %(
class Foo
def foo
a = 1
macro_call
end
macro macro_call
puts a
end
end
)
subject.catch(s).should be_valid
end
it "doesn't report if assignment is referenced in a macro expression as string" do
s = Source.new %(
foo = 1
puts {{ "foo".id }}
)
subject.catch(s).should be_valid
end
it "doesn't report if assignement is referenced in for macro in exp" do
s = Source.new %(
foo = 22
{% for x in %w(foo) %}
add({{x.id}})
{% end %}
)
subject.catch(s).should be_valid
end
it "doesn't report if assignement is referenced in for macro in body" do
s = Source.new %(
foo = 22
{% for x in %w(bar) %}
puts {{ "foo".id }}
{% end %}
)
subject.catch(s).should be_valid
end
it "doesn't report if assignement is referenced in if macro in cond" do
s = Source.new %(
foo = 22
{% if "foo".id %}
{% end %}
)
subject.catch(s).should be_valid
end
it "doesn't report if assignement is referenced in if macro in then" do
s = Source.new %(
foo = 22
{% if true %}
puts {{ "foo".id }}
{% end %}
)
subject.catch(s).should be_valid
end
it "doesn't report if assignement is referenced in if macro in else" do
s = Source.new %(
foo = 22
{% if true %}
{% else %}
puts {{ "foo".id }}
{% end %}
)
subject.catch(s).should be_valid
end
end
it "does not report if variable is referenced and there is a deep level scope" do
s = Source.new %(
response = JSON.build do |json|
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
json.object do
anything
end
end
end
end
end
end
end
end
end
end
end
end
end
end
end
end
response = JSON.parse(response)
response
)
subject.catch(s).should be_valid
end
context "uninitialized" do
it "reports if uninitialized assignment is not referenced at a top level" do
s = Source.new %(
a = uninitialized U
)
subject.catch(s).should_not be_valid
end
it "reports if uninitialized assignment is not referenced in a method" do
s = Source.new %(
def foo
a = uninitialized U
end
)
subject.catch(s).should_not be_valid
end
it "doesn't report if uninitialized assignment is referenced" do
s = Source.new %(
def foo
a = uninitialized U
a
end
)
subject.catch(s).should be_valid
end
end
end
end