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 locations" do variable = Variable.new(var_node, scope) variable.location.should eq var_node.location variable.end_location.should eq var_node.end_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, scope) variable.assignments.empty?.should be_false end it "can create multiple assignments" do variable = Variable.new(var_node, scope) variable.assign(assign_node, scope) variable.assign(assign_node, scope) 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"), scope) variable.reference(var_node, scope) variable.references.empty?.should be_false end it "adds a reference to the scope" do scope = Scope.new as_node "foo = 1" variable = Variable.new(var_node, scope) variable.assign(as_node("foo=1"), scope) variable.reference(var_node, scope) scope.references.size.should eq 1 scope.references.first.node.to_s.should eq "foo" end end describe "#captured_by_block?" do it "returns truthy if the variable is captured by block" do nodes = as_nodes <<-CRYSTAL def method a = 2 3.times { |i| a = a + i } end CRYSTAL var_node = nodes.var_nodes.first scope = Scope.new(nodes.def_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 falsy if the variable is not captured by the block" do scope = Scope.new as_node <<-CRYSTAL def method a = 1 end CRYSTAL 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 "#ignored?" do it "is true if variable is ignored" do variable = Variable.new(Crystal::Var.new("_var"), scope) variable.ignored?.should be_true end it "is false if variable is not ignored" do variable = Variable.new(Crystal::Var.new("v_ar"), scope) variable.ignored?.should be_false end it "is true if variable is a black hole" do variable = Variable.new(Crystal::Var.new("_"), scope) variable.ignored?.should be_true 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 describe "#declared_before?" do it "is falsey if variable doesn't have location" do var1 = Crystal::Var.new("foo") var2 = Crystal::Var.new("bar").at(Crystal::Location.new(nil, 1, 2)) Variable.new(var1, scope).declared_before?(var2).should be_falsey end it "is falsey if node doesn't have location" do var1 = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 1, 2)) var2 = Crystal::Var.new("bar") Variable.new(var1, scope).declared_before?(var2).should be_falsey end it "is true if var's line_number below the node" do var1 = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 1, 2)) var2 = Crystal::Var.new("bar").at(Crystal::Location.new(nil, 2, 2)) Variable.new(var1, scope).declared_before?(var2).should be_true end it "is true if var's column_number is after the node" do var1 = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 1, 2)) var2 = Crystal::Var.new("bar").at(Crystal::Location.new(nil, 1, 3)) Variable.new(var1, scope).declared_before?(var2).should be_true end it "is false if var's location is before the node" do var1 = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 2, 2)) var2 = Crystal::Var.new("bar").at(Crystal::Location.new(nil, 1, 3)) Variable.new(var1, scope).declared_before?(var2).should be_false end it "is false is the node is the same var" do var = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 2, 2)) Variable.new(var, scope).declared_before?(var).should be_false end end end end