mirror of
https://gitea.invidious.io/iv-org/shard-ameba.git
synced 2024-08-15 00:53:29 +00:00
Variable scope & useless assignments (#41)
* AST::Visitor -> AST::NodeVisitor * Scope & ScopeVisitor * Useless assignment rule * Instance vars and useless assignments * Multiple assigns one by one * Support outer scope * Variable used in the useless assignment * Support OpAssign & MultiAssign * Captured by block * Variable, Assignment, Reference & Refactoring * Variable has references, Assignment can be referenced * Branch entity * Handle useless assignments in branches * Handle assignments in a loop * Handle branch equality * Handle special var `$?` assignment * Improve captured by block stuff * Avoid assignments in property definitions (UselessAssign rule reports an warning) * Support MacroIf and MacroFor branches * Handle assignments with shadowed vars in inner scopes * Add method arguments as scope variables * Handle case if branch is blank * Top level scope * Handle case when branch is nop?
This commit is contained in:
parent
60c1b86890
commit
6475c2bb25
50 changed files with 2521 additions and 124 deletions
364
spec/ameba/ast/branch_spec.cr
Normal file
364
spec/ameba/ast/branch_spec.cr
Normal file
|
@ -0,0 +1,364 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
private def branch_of_assign_in_def(source)
|
||||
nodes = as_nodes source
|
||||
Ameba::AST::Branch.of(nodes.assign_nodes.first, nodes.def_nodes.first)
|
||||
end
|
||||
|
||||
module Ameba::AST
|
||||
describe Branch do
|
||||
describe ".of" do
|
||||
context "Crystal::If" do
|
||||
it "constructs a branch in If.cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method
|
||||
if a = get_something # --> Crystal::Assign
|
||||
puts a
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = get_something"
|
||||
end
|
||||
|
||||
it "constructs a branch in If.then" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method
|
||||
if true
|
||||
a = 2 # --> Crystal::Assign
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a branch in If.else" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method
|
||||
if true
|
||||
nil
|
||||
else
|
||||
a = 2 # --> Crystal::Assign
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a branch in inline If" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
a = 0 if a == 2 # --> Crystal::Assign
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 0"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::Unless" do
|
||||
it "constructs a branch in Unless.cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method
|
||||
unless a = get_something # --> Crystal::Assign
|
||||
puts a
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = get_something"
|
||||
end
|
||||
|
||||
it "constructs a branch in Unless.then" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method
|
||||
unless true
|
||||
a = 2 # --> Crystal::Assign
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a new branch in Unless.else" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method
|
||||
unless true
|
||||
nil
|
||||
else
|
||||
a = 2 # --> Crystal::Assign
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a branch in inline Unless" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
(a = 0; b = 3) unless a == 2 # --> Crystal::Expressions
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "(a = 0\nb = 3)"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::BinaryOp" do
|
||||
it "constructs a branch in left node" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
(a = 2) && do_something
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "(a = 2)"
|
||||
end
|
||||
|
||||
it "constructs a branch in right node" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
do_something || (a = 0)
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "(a = 0)"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::Case" do
|
||||
# FIXME:
|
||||
# https://github.com/crystal-lang/crystal/pull/6032
|
||||
# it "constructs a branch in cond" do
|
||||
# branch = branch_of_assign_in_def %(
|
||||
# def method(a)
|
||||
# case (a = 2)
|
||||
# when true then nil
|
||||
# end
|
||||
# end
|
||||
# )
|
||||
# branch.to_s.should eq "(a = 2)"
|
||||
# end
|
||||
|
||||
it "constructs a branch in when" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
case a
|
||||
when a = 3 then nil
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "when a = 3\n nil\n"
|
||||
end
|
||||
|
||||
it "constructs a branch in else" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
case a
|
||||
when true then nil
|
||||
else a = 4
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 4"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::While" do
|
||||
it "constructs a branch in cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
while a = 1
|
||||
nil
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in body" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
while true
|
||||
b = (a = 1)
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "b = (a = 1)"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::Until" do
|
||||
it "constructs a branch in cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
until a = 1
|
||||
nil
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in body" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
until false
|
||||
b = (a = 1)
|
||||
end
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "b = (a = 1)"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::ExceptionHandler" do
|
||||
it "constructs a branch in body" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
a = 1
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in a rescue" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
rescue
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in else" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
rescue
|
||||
else
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in ensure" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
rescue
|
||||
ensure
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::MacroIf" do
|
||||
it "constructs a branch in cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
def method(a)
|
||||
{% if a = 2 %}
|
||||
{% end %}
|
||||
end
|
||||
)
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a branch in then" do
|
||||
nodes = as_nodes %(
|
||||
def method(a)
|
||||
{% if true %}
|
||||
a = 2
|
||||
{% end %}
|
||||
end
|
||||
)
|
||||
branch = Branch.of(nodes.macro_literal_nodes.first, nodes.def_nodes.first)
|
||||
branch.to_s.strip.should eq "a = 2"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::MacroFor" do
|
||||
it "constructs a branch in body" do
|
||||
nodes = as_nodes %(
|
||||
def method(a)
|
||||
{% for x in [1, 2, 3] %}
|
||||
a = 2
|
||||
{% end %}
|
||||
end
|
||||
)
|
||||
branch = Branch.of(nodes.macro_literal_nodes.first, nodes.def_nodes.first)
|
||||
branch.to_s.strip.should eq "a = 2"
|
||||
end
|
||||
end
|
||||
|
||||
it "returns nil if branch does not exist" do
|
||||
nodes = as_nodes %(
|
||||
def method
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
branch = Branch.of(nodes.assign_nodes.first, nodes.def_nodes.first)
|
||||
branch.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#initialize" do
|
||||
it "creates new branch" do
|
||||
nodes = as_nodes %(
|
||||
if true
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
branchable = Branchable.new nodes.if_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.node.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "delegation" do
|
||||
it "delegates to_s to node" do
|
||||
nodes = as_nodes %(
|
||||
if true
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
branchable = Branchable.new nodes.if_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.to_s.should eq branch.node.to_s
|
||||
end
|
||||
|
||||
it "delegates location to node" do
|
||||
nodes = as_nodes %(
|
||||
if true
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
branchable = Branchable.new nodes.if_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.location.should eq branch.node.location
|
||||
end
|
||||
end
|
||||
|
||||
describe "#in_loop?" do
|
||||
it "returns true if branch is in a loop" do
|
||||
nodes = as_nodes %(
|
||||
while true
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
branchable = Branchable.new nodes.while_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.in_loop?.should be_true
|
||||
end
|
||||
|
||||
it "returns false if branch is not in a loop" do
|
||||
nodes = as_nodes %(
|
||||
if a > 2
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
branchable = Branchable.new nodes.if_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.in_loop?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
48
spec/ameba/ast/branchable_spec.cr
Normal file
48
spec/ameba/ast/branchable_spec.cr
Normal file
|
@ -0,0 +1,48 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
module Ameba::AST
|
||||
describe Branchable do
|
||||
describe "#initialize" do
|
||||
it "creates a new branchable" do
|
||||
branchable = Branchable.new as_node %(a = 2 if true)
|
||||
branchable.node.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "delegation" do
|
||||
it "delegates to_s to @node" do
|
||||
node = as_node %(a = 2 if true)
|
||||
branchable = Branchable.new node
|
||||
branchable.to_s.should eq node.to_s
|
||||
end
|
||||
|
||||
it "delegates location to @node" do
|
||||
node = as_node %(a = 2 if true)
|
||||
branchable = Branchable.new node
|
||||
branchable.location.should eq node.location
|
||||
end
|
||||
end
|
||||
|
||||
describe "#loop?" do
|
||||
it "returns true if it is a while loop" do
|
||||
branchable = Branchable.new as_node %(while true; a = 2; end)
|
||||
branchable.loop?.should be_true
|
||||
end
|
||||
|
||||
it "returns true if it is the until loop" do
|
||||
branchable = Branchable.new as_node %(until false; a = 2; end)
|
||||
branchable.loop?.should be_true
|
||||
end
|
||||
|
||||
it "returns true if it is loop" do
|
||||
branchable = Branchable.new as_node %(loop {})
|
||||
branchable.loop?.should be_true
|
||||
end
|
||||
|
||||
it "returns false otherwise" do
|
||||
branchable = Branchable.new as_node %(a = 2 if true)
|
||||
branchable.loop?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
72
spec/ameba/ast/scope_spec.cr
Normal file
72
spec/ameba/ast/scope_spec.cr
Normal file
|
@ -0,0 +1,72 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
module Ameba::AST
|
||||
describe Scope do
|
||||
describe "#initialize" do
|
||||
source = "a = 2"
|
||||
|
||||
it "assigns outer scope" do
|
||||
root = Scope.new as_node(source)
|
||||
child = Scope.new as_node(source), root
|
||||
child.outer_scope.should_not be_nil
|
||||
end
|
||||
|
||||
it "assigns node" do
|
||||
scope = Scope.new as_node(source)
|
||||
scope.node.should_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#add_variable" do
|
||||
it "adds a new variable to the scope" do
|
||||
scope = Scope.new as_node("")
|
||||
scope.add_variable(Crystal::Var.new "foo")
|
||||
scope.variables.any?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "#find_variable" do
|
||||
it "returns the variable in the scope by name" do
|
||||
scope = Scope.new as_node("foo = 1")
|
||||
scope.add_variable Crystal::Var.new "foo"
|
||||
scope.find_variable("foo").should_not be_nil
|
||||
end
|
||||
|
||||
it "returns nil if variable not exist in this scope" do
|
||||
scope = Scope.new as_node("foo = 1")
|
||||
scope.find_variable("bar").should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#assign_variable" do
|
||||
it "creates a new assignment" do
|
||||
scope = Scope.new as_node("foo = 1")
|
||||
scope.add_variable Crystal::Var.new "foo"
|
||||
scope.assign_variable(Crystal::Var.new "foo")
|
||||
scope.find_variable("foo").not_nil!.assignments.size.should eq 1
|
||||
end
|
||||
|
||||
it "does not create the assignment if variable is wrong" do
|
||||
scope = Scope.new as_node("foo = 1")
|
||||
scope.add_variable Crystal::Var.new "foo"
|
||||
scope.assign_variable(Crystal::Var.new "bar")
|
||||
scope.find_variable("foo").not_nil!.assignments.size.should eq 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "#block?" do
|
||||
it "returns true if Crystal::Block" do
|
||||
nodes = as_nodes %(
|
||||
3.times {}
|
||||
)
|
||||
scope = Scope.new nodes.block_nodes.first
|
||||
scope.block?.should be_true
|
||||
end
|
||||
|
||||
it "returns false otherwise" do
|
||||
scope = Scope.new as_node "a = 1"
|
||||
scope.block?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
90
spec/ameba/ast/variabling/assignment_spec.cr
Normal file
90
spec/ameba/ast/variabling/assignment_spec.cr
Normal file
|
@ -0,0 +1,90 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::AST
|
||||
describe Assignment do
|
||||
node = Crystal::NilLiteral.new
|
||||
scope = Scope.new as_node "foo = 1"
|
||||
variable = Variable.new(Crystal::Var.new("foo"), scope)
|
||||
|
||||
describe "#initialize" do
|
||||
it "creates a new assignment with node and var" do
|
||||
assignment = Assignment.new(node, variable)
|
||||
assignment.node.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#reference=" do
|
||||
it "creates a new reference" do
|
||||
assignment = Assignment.new(node, variable)
|
||||
assignment.referenced = true
|
||||
assignment.referenced?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "delegation" do
|
||||
it "delegates location" do
|
||||
assignment = Assignment.new(node, variable)
|
||||
assignment.location.should eq node.location
|
||||
end
|
||||
|
||||
it "delegates to_s" do
|
||||
assignment = Assignment.new(node, variable)
|
||||
assignment.to_s.should eq node.to_s
|
||||
end
|
||||
|
||||
it "delegates scope" do
|
||||
assignment = Assignment.new(node, variable)
|
||||
assignment.scope.should eq variable.scope
|
||||
end
|
||||
end
|
||||
|
||||
describe "#branch" do
|
||||
it "returns the branch of the assignment" do
|
||||
nodes = as_nodes %(
|
||||
def method(a)
|
||||
if a
|
||||
a = 3 # --> Crystal::Expressions
|
||||
puts a
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable)
|
||||
assignment.branch.should_not be_nil
|
||||
assignment.branch.not_nil!.node.class.should eq Crystal::Expressions
|
||||
end
|
||||
|
||||
it "returns inner branch" do
|
||||
nodes = as_nodes %(
|
||||
def method(a, b)
|
||||
if a
|
||||
if b
|
||||
a = 3 # --> Crystal::Assign
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable)
|
||||
assignment.branch.should_not be_nil
|
||||
assignment.branch.not_nil!.node.class.should eq Crystal::Assign
|
||||
end
|
||||
|
||||
it "returns nil if assignment does not have a branch" do
|
||||
nodes = as_nodes %(
|
||||
def method(a)
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable)
|
||||
assignment.branch.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
spec/ameba/ast/variabling/reference_spec.cr
Normal file
10
spec/ameba/ast/variabling/reference_spec.cr
Normal file
|
@ -0,0 +1,10 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::AST
|
||||
describe Reference do
|
||||
it "is derived from a Variable" do
|
||||
node = Crystal::Var.new "foo"
|
||||
Reference.new(node, Scope.new as_node "foo = 1").is_a?(Variable).should be_true
|
||||
end
|
||||
end
|
||||
end
|
154
spec/ameba/ast/variabling/variable_spec.cr
Normal file
154
spec/ameba/ast/variabling/variable_spec.cr
Normal file
|
@ -0,0 +1,154 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::AST
|
||||
describe Variable do
|
||||
var_node = Crystal::Var.new("foo")
|
||||
scope = Scope.new as_node "foo = 1"
|
||||
|
||||
describe "#initialize" do
|
||||
it "creates a new variable" do
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.node.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "delegation" do
|
||||
it "delegates location" do
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.location.should eq var_node.location
|
||||
end
|
||||
|
||||
it "delegates name" do
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.name.should eq var_node.name
|
||||
end
|
||||
|
||||
it "delegates to_s" do
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.to_s.should eq var_node.to_s
|
||||
end
|
||||
end
|
||||
|
||||
describe "#special?" do
|
||||
it "returns truthy if it is a special `$?` var" do
|
||||
variable = Variable.new Crystal::Var.new("$?"), scope
|
||||
variable.special?.should be_truthy
|
||||
end
|
||||
|
||||
it "returns falsey otherwise" do
|
||||
variable = Variable.new Crystal::Var.new("a"), scope
|
||||
variable.special?.should be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe "#assign" do
|
||||
assign_node = as_node("foo=1")
|
||||
|
||||
it "assigns the variable (creates a new assignment)" do
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.assign(assign_node)
|
||||
variable.assignments.any?.should be_true
|
||||
end
|
||||
|
||||
it "can create multiple assignments" do
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.assign(assign_node)
|
||||
variable.assign(assign_node)
|
||||
variable.assignments.size.should eq 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "#reference" do
|
||||
it "references the existed assignment" do
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.assign(as_node "foo=1")
|
||||
variable.reference(var_node, scope)
|
||||
variable.references.any?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "#captured_by_block?" do
|
||||
it "returns truthy if the variable is captured by block" do
|
||||
nodes = as_nodes %(
|
||||
def method
|
||||
a = 2
|
||||
3.times { |i| a = a + i }
|
||||
end
|
||||
)
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
var_node = nodes.var_nodes.first
|
||||
scope.add_variable var_node
|
||||
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
|
||||
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.reference nodes.var_nodes.last, scope.inner_scopes.last
|
||||
variable.captured_by_block?.should be_truthy
|
||||
end
|
||||
|
||||
it "returns falsey if the variable is not captured by the block" do
|
||||
scope = Scope.new as_node %(
|
||||
def method
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
scope.add_variable Crystal::Var.new "a"
|
||||
variable = scope.variables.first
|
||||
variable.captured_by_block?.should be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe "#target_of?" do
|
||||
it "returns true if the variable is a target of Crystal::Assign node" do
|
||||
assign_node = as_nodes("foo=1").assign_nodes.last
|
||||
variable = Variable.new assign_node.target.as(Crystal::Var), scope
|
||||
variable.target_of?(assign_node).should be_true
|
||||
end
|
||||
|
||||
it "returns true if the variable is a target of Crystal::OpAssign node" do
|
||||
assign_node = as_nodes("foo=1;foo+=1").op_assign_nodes.last
|
||||
variable = Variable.new assign_node.target.as(Crystal::Var), scope
|
||||
variable.target_of?(assign_node).should be_true
|
||||
end
|
||||
|
||||
it "returns true if the variable is a target of Crystal::MultiAssign node" do
|
||||
assign_node = as_nodes("a,b,c={1,2,3}").multi_assign_nodes.last
|
||||
assign_node.targets.size.should_not eq 0
|
||||
assign_node.targets.each do |target|
|
||||
variable = Variable.new target.as(Crystal::Var), scope
|
||||
variable.target_of?(assign_node).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
it "returns false if the node is not assign" do
|
||||
variable = Variable.new(Crystal::Var.new("v"), scope)
|
||||
variable.target_of?(as_node "nil").should be_false
|
||||
end
|
||||
|
||||
it "returns false if the variable is not a target of the assign" do
|
||||
variable = Variable.new(Crystal::Var.new("foo"), scope)
|
||||
variable.target_of?(as_node("bar = 1")).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#eql?" do
|
||||
var = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 1, 2))
|
||||
variable = Variable.new var, scope
|
||||
|
||||
it "is false if node is not a Crystal::Var" do
|
||||
variable.eql?(as_node("nil")).should be_false
|
||||
end
|
||||
|
||||
it "is false if node name is different" do
|
||||
variable.eql?(Crystal::Var.new "bar").should be_false
|
||||
end
|
||||
|
||||
it "is false if node has a different location" do
|
||||
variable.eql?(Crystal::Var.new "foo").should be_false
|
||||
end
|
||||
|
||||
it "is true otherwise" do
|
||||
variable.eql?(variable.node).should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +1,14 @@
|
|||
require "../../spec_helper"
|
||||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::AST
|
||||
rule = DummyRule.new
|
||||
source = Source.new ""
|
||||
|
||||
describe "Traverse" do
|
||||
describe NodeVisitor do
|
||||
{% for name in NODES %}
|
||||
describe "{{name}}" do
|
||||
it "allow to visit {{name}} node" do
|
||||
visitor = Visitor.new rule, source
|
||||
visitor = NodeVisitor.new rule, source
|
||||
nodes = Crystal::Parser.new("").parse
|
||||
nodes.accept visitor
|
||||
end
|
58
spec/ameba/ast/visitors/scope_visitor_spec.cr
Normal file
58
spec/ameba/ast/visitors/scope_visitor_spec.cr
Normal file
|
@ -0,0 +1,58 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::AST
|
||||
describe ScopeVisitor do
|
||||
it "creates a scope for the def" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
def method
|
||||
end
|
||||
)
|
||||
rule.scopes.size.should eq 1
|
||||
end
|
||||
|
||||
it "creates a scope for the proc" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
-> {}
|
||||
)
|
||||
rule.scopes.size.should eq 1
|
||||
end
|
||||
|
||||
it "creates a scope for the block" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
3.times {}
|
||||
)
|
||||
rule.scopes.size.should eq 2
|
||||
end
|
||||
|
||||
context "inner scopes" do
|
||||
it "creates scope for block inside def" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
def method
|
||||
3.times {}
|
||||
end
|
||||
)
|
||||
rule.scopes.size.should eq 2
|
||||
rule.scopes.last.outer_scope.should_not be_nil
|
||||
rule.scopes.first.outer_scope.should eq rule.scopes.last
|
||||
end
|
||||
|
||||
it "creates scope for block inside block" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
3.times do
|
||||
2.times {}
|
||||
end
|
||||
)
|
||||
rule.scopes.size.should eq 3
|
||||
inner_block = rule.scopes.first
|
||||
outer_block = rule.scopes.last
|
||||
inner_block.outer_scope.should_not eq outer_block
|
||||
outer_block.outer_scope.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -35,7 +35,7 @@ module Ameba::Rule
|
|||
subject.catch(s).should be_valid
|
||||
end
|
||||
|
||||
it "failes if there is unneeded directive" do
|
||||
it "fails if there is unneeded directive" do
|
||||
s = Source.new %Q(
|
||||
# ameba:disable #{NamedRule.name}
|
||||
a = 1
|
||||
|
|
813
spec/ameba/rule/useless_assign_spec.cr
Normal file
813
spec/ameba/rule/useless_assign_spec.cr
Normal file
|
@ -0,0 +1,813 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
module Ameba::Rule
|
||||
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 "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
|
||||
|
||||
error = s.errors.first
|
||||
error.rule.should_not be_nil
|
||||
error.location.to_s.should eq "source.cr:3:11"
|
||||
error.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.errors.first.location.to_s.should eq ":3:11"
|
||||
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
|
||||
|
||||
it "does not report if global var" do
|
||||
s = Source.new %(
|
||||
def method
|
||||
$? = 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
|
||||
|
||||
error = s.errors.last
|
||||
error.rule.should_not be_nil
|
||||
error.location.to_s.should eq "source.cr:5:13"
|
||||
error.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
|
||||
error = s.errors.first
|
||||
error.location.to_s.should eq ":3:16"
|
||||
error.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
|
||||
|
||||
error = s.errors.first
|
||||
error.location.to_s.should eq ":3:13"
|
||||
error.message.should eq "Useless assignment to variable `a`"
|
||||
|
||||
error = s.errors.last
|
||||
error.location.to_s.should eq ":3:16"
|
||||
error.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.errors.size.should eq 2
|
||||
s.errors.first.location.to_s.should eq ":2:11"
|
||||
s.errors.last.location.to_s.should eq ":3:11"
|
||||
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
|
||||
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.errors.size.should eq 1
|
||||
s.errors.first.location.to_s.should eq ":5:17"
|
||||
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.errors.size.should eq 1
|
||||
s.errors.first.location.to_s.should eq ":5:17"
|
||||
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.errors.size.should eq 2
|
||||
s.errors.first.location.to_s.should eq ":5:17"
|
||||
s.errors.last.location.to_s.should eq ":7:17"
|
||||
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.errors.size.should eq 1
|
||||
s.errors.first.location.to_s.should eq ":3:27"
|
||||
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.errors.size.should eq 1
|
||||
s.errors.first.location.to_s.should eq ":4:17"
|
||||
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.errors.size.should eq 1
|
||||
s.errors.first.location.to_s.should eq ":4:17"
|
||||
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.errors.size.should eq 1
|
||||
s.errors.first.location.to_s.should eq ":4:15"
|
||||
end
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue