Extend UnreachableCode rule: handle control flow (#83)

This commit is contained in:
V. Elenhaupt 2018-11-22 10:38:32 +02:00 committed by GitHub
parent eca0f3f350
commit 0fd5890738
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 880 additions and 101 deletions

View file

@ -5,42 +5,37 @@ module Ameba::AST
describe "#initialize" do
it "creates a new flow expression" do
node = as_node("return 22")
parent_node = as_node("def foo; return 22; end")
flow_expression = FlowExpression.new node, parent_node
flow_expression = FlowExpression.new node, false
flow_expression.node.should_not be_nil
flow_expression.parent_node.should_not be_nil
flow_expression.in_loop?.should eq false
end
describe "#delegation" do
it "delegates to_s to @node" do
node = as_node("return 22")
parent_node = as_node("def foo; return 22; end")
flow_expression = FlowExpression.new node, parent_node
flow_expression = FlowExpression.new node, false
flow_expression.to_s.should eq node.to_s
end
it "delegates location to @node" do
node = as_node %(break if true)
parent_node = as_node("def foo; return 22 if true; end")
flow_expression = FlowExpression.new node, parent_node
flow_expression = FlowExpression.new node, false
flow_expression.location.should eq node.location
end
end
describe "#find_unreachable_node" do
it "returns first unreachable node" do
describe "#unreachable_nodes" do
it "returns unreachable nodes" do
nodes = as_nodes %(
def foobar
return
a = 1
a + 1
a = 2
end
)
node = nodes.control_expression_nodes.first
assign_node = nodes.assign_nodes.first
def_node = nodes.def_nodes.first
flow_expression = FlowExpression.new node, def_node
flow_expression.find_unreachable_node.should eq assign_node
node = nodes.expressions_nodes.first
flow_expression = FlowExpression.new node, false
flow_expression.unreachable_nodes.should eq nodes.assign_nodes
end
it "returns nil if there is no unreachable node" do
@ -50,10 +45,9 @@ module Ameba::AST
return a
end
)
node = nodes.control_expression_nodes.first
def_node = nodes.def_nodes.first
flow_expression = FlowExpression.new node, def_node
flow_expression.find_unreachable_node.should eq nil
node = nodes.expressions_nodes.first
flow_expression = FlowExpression.new node, false
flow_expression.unreachable_nodes.empty?.should eq true
end
end
end

View file

@ -71,5 +71,242 @@ module Ameba::AST
])
end
end
describe "#flow_command?" do
it "returns true if this is return" do
node = as_node("return 22")
subject.flow_command?(node, false).should eq true
end
it "returns true if this is a break in a loop" do
node = as_node("break")
subject.flow_command?(node, true).should eq true
end
it "returns false if this is a break out of loop" do
node = as_node("break")
subject.flow_command?(node, false).should eq false
end
it "returns true if this is a next in a loop" do
node = as_node("next")
subject.flow_command?(node, true).should eq true
end
it "returns false if this is a next out of loop" do
node = as_node("next")
subject.flow_command?(node, false).should eq false
end
it "returns true if this is raise" do
node = as_node("raise e")
subject.flow_command?(node, false).should eq true
end
it "returns true if this is exit" do
node = as_node("exit")
subject.flow_command?(node, false).should eq true
end
it "returns true if this is abort" do
node = as_node("abort")
subject.flow_command?(node, false).should eq true
end
it "returns false otherwise" do
node = as_node("foobar")
subject.flow_command?(node, false).should eq false
end
end
describe "#flow_expression?" do
it "returns true if this is a flow command" do
node = as_node("return")
subject.flow_expression?(node, true).should eq true
end
it "returns true if this is if-else consumed by flow expressions" do
node = as_node %(
if foo
return :foo
else
return :bar
end
)
subject.flow_expression?(node, false).should eq true
end
it "returns true if this is unless-else consumed by flow expressions" do
node = as_node %(
unless foo
return :foo
else
return :bar
end
)
subject.flow_expression?(node).should eq true
end
it "returns true if this is case consumed by flow expressions" do
node = as_node %(
case
when 1
return 1
when 2
return 2
else
return 3
end
)
subject.flow_expression?(node).should eq true
end
it "returns true if this is exception handler consumed by flow expressions" do
node = as_node %(
begin
raise "exp"
rescue e
return e
end
)
subject.flow_expression?(node).should eq true
end
it "returns true if this while consumed by flow expressions" do
node = as_node %(
while true
return
end
)
subject.flow_expression?(node).should eq true
end
it "returns false if this while with break" do
node = as_node %(
while true
break
end
)
subject.flow_expression?(node).should eq false
end
it "returns true if this until consumed by flow expressions" do
node = as_node %(
until false
return
end
)
subject.flow_expression?(node).should eq true
end
it "returns false if this until with break" do
node = as_node %(
until false
break
end
)
subject.flow_expression?(node).should eq false
end
it "returns true if this expressions consumed by flow expressions" do
node = as_node %(
exp1
exp2
return
)
subject.flow_expression?(node).should eq true
end
it "returns false otherwise" do
node = as_node %(
exp1
exp2
)
subject.flow_expression?(node).should eq false
end
end
describe "#raise?" do
it "returns true if this is a raise method call" do
node = as_node "raise e"
subject.raise?(node).should eq true
end
it "returns false if it has a receiver" do
node = as_node "obj.raise e"
subject.raise?(node).should eq false
end
it "returns false if size of the arguments doesn't match" do
node = as_node "raise"
subject.raise?(node).should eq false
end
end
describe "#exit?" do
it "returns true if this is a exit method call" do
node = as_node "exit"
subject.exit?(node).should eq true
end
it "returns true if this is a exit method call with one argument" do
node = as_node "exit 1"
subject.exit?(node).should eq true
end
it "returns false if it has a receiver" do
node = as_node "obj.exit"
subject.exit?(node).should eq false
end
it "returns false if size of the arguments doesn't match" do
node = as_node "exit 1, 1"
subject.exit?(node).should eq false
end
end
describe "#abort?" do
it "returns true if this is an abort method call" do
node = as_node "abort"
subject.abort?(node).should eq true
end
it "returns true if this is an abort method call with one argument" do
node = as_node "abort \"message\""
subject.abort?(node).should eq true
end
it "returns true if this is an abort method call with two arguments" do
node = as_node "abort \"message\", 1"
subject.abort?(node).should eq true
end
it "returns false if it has a receiver" do
node = as_node "obj.abort"
subject.abort?(node).should eq false
end
it "returns false if size of the arguments doesn't match" do
node = as_node "abort 1, 1, 1"
subject.abort?(node).should eq false
end
end
describe "#loop?" do
it "returns true if this is a loop method call" do
node = as_node "loop"
subject.loop?(node).should eq true
end
it "returns false if it has a receiver" do
node = as_node "obj.loop"
subject.loop?(node).should eq false
end
it "returns false if size of the arguments doesn't match" do
node = as_node "loop 1"
subject.loop?(node).should eq false
end
end
end
end

View file

@ -25,7 +25,7 @@ module Ameba::AST
end
end
)
rule.expressions.size.should eq 2
rule.expressions.size.should eq 3
end
it "properly creates nested flow expressions" do
@ -40,7 +40,7 @@ module Ameba::AST
)
end
)
rule.expressions.size.should eq 3
rule.expressions.size.should eq 4
end
it "creates an expression for break" do

View file

@ -5,14 +5,12 @@ module Ameba::AST
source = Source.new ""
describe NodeVisitor do
{% for name in NODES %}
describe "{{name}}" do
it "allow to visit {{name}} node" do
visitor = NodeVisitor.new rule, source
nodes = Crystal::Parser.new("").parse
nodes.accept visitor
end
describe "visit" do
it "allow to visit ASTNode" do
visitor = NodeVisitor.new rule, source
nodes = Crystal::Parser.new("").parse
nodes.accept visitor
end
{% end %}
end
end
end