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
 | 
					module Ameba::AST
 | 
				
			||||||
  rule = DummyRule.new
 | 
					  rule = DummyRule.new
 | 
				
			||||||
  source = Source.new ""
 | 
					  source = Source.new ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe "Traverse" do
 | 
					  describe NodeVisitor do
 | 
				
			||||||
    {% for name in NODES %}
 | 
					    {% for name in NODES %}
 | 
				
			||||||
      describe "{{name}}" do
 | 
					      describe "{{name}}" do
 | 
				
			||||||
        it "allow to visit {{name}} node" do
 | 
					        it "allow to visit {{name}} node" do
 | 
				
			||||||
          visitor = Visitor.new rule, source
 | 
					          visitor = NodeVisitor.new rule, source
 | 
				
			||||||
          nodes = Crystal::Parser.new("").parse
 | 
					          nodes = Crystal::Parser.new("").parse
 | 
				
			||||||
          nodes.accept visitor
 | 
					          nodes.accept visitor
 | 
				
			||||||
        end
 | 
					        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
 | 
					      subject.catch(s).should be_valid
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it "failes if there is unneeded directive" do
 | 
					    it "fails if there is unneeded directive" do
 | 
				
			||||||
      s = Source.new %Q(
 | 
					      s = Source.new %Q(
 | 
				
			||||||
        # ameba:disable #{NamedRule.name}
 | 
					        # ameba:disable #{NamedRule.name}
 | 
				
			||||||
        a = 1
 | 
					        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
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,17 @@ module Ameba
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct ScopeRule < Rule::Base
 | 
				
			||||||
 | 
					    getter scopes = [] of AST::Scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test(source)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test(source, node : Crystal::ASTNode, scope : AST::Scope)
 | 
				
			||||||
 | 
					      @scopes << scope
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  class DummyFormatter < Formatter::BaseFormatter
 | 
					  class DummyFormatter < Formatter::BaseFormatter
 | 
				
			||||||
    property started_sources : Array(Source)?
 | 
					    property started_sources : Array(Source)?
 | 
				
			||||||
    property finished_sources : Array(Source)?
 | 
					    property finished_sources : Array(Source)?
 | 
				
			||||||
| 
						 | 
					@ -71,8 +82,49 @@ module Ameba
 | 
				
			||||||
      "Source expected to be invalid, but it is valid."
 | 
					      "Source expected to be invalid, but it is valid."
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  class TestNodeVisitor < Crystal::Visitor
 | 
				
			||||||
 | 
					    NODES = [
 | 
				
			||||||
 | 
					      Crystal::Var,
 | 
				
			||||||
 | 
					      Crystal::Assign,
 | 
				
			||||||
 | 
					      Crystal::OpAssign,
 | 
				
			||||||
 | 
					      Crystal::MultiAssign,
 | 
				
			||||||
 | 
					      Crystal::Block,
 | 
				
			||||||
 | 
					      Crystal::Def,
 | 
				
			||||||
 | 
					      Crystal::If,
 | 
				
			||||||
 | 
					      Crystal::While,
 | 
				
			||||||
 | 
					      Crystal::MacroLiteral,
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def initialize(node)
 | 
				
			||||||
 | 
					      node.accept self
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def visit(node : Crystal::ASTNode)
 | 
				
			||||||
 | 
					      true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {% for node in NODES %}
 | 
				
			||||||
 | 
					      {{getter_name = node.stringify.split("::").last.underscore + "_nodes"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      getter {{getter_name.id}} = [] of {{node}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : {{node}})
 | 
				
			||||||
 | 
					        {{getter_name.id}} << node
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    {% end %}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def be_valid
 | 
					def be_valid
 | 
				
			||||||
  Ameba::BeValidExpectation.new
 | 
					  Ameba::BeValidExpectation.new
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def as_node(source)
 | 
				
			||||||
 | 
					  Crystal::Parser.new(source).parse
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def as_nodes(source)
 | 
				
			||||||
 | 
					  Ameba::TestNodeVisitor.new(as_node source)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
require "./ameba/*"
 | 
					require "./ameba/*"
 | 
				
			||||||
require "./ameba/ast/*"
 | 
					require "./ameba/ast/**"
 | 
				
			||||||
require "./ameba/rule/*"
 | 
					require "./ameba/rule/*"
 | 
				
			||||||
require "./ameba/formatter/*"
 | 
					require "./ameba/formatter/*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										193
									
								
								src/ameba/ast/branch.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								src/ameba/ast/branch.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,193 @@
 | 
				
			||||||
 | 
					module Ameba::AST
 | 
				
			||||||
 | 
					  # Represents the branch in Crystal code.
 | 
				
			||||||
 | 
					  # Branch is a part of a branchable statement.
 | 
				
			||||||
 | 
					  # For example, the branchable if statement contains 3 branches:
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  # if a = something # --> Branch A
 | 
				
			||||||
 | 
					  #   a = 1          # --> Branch B
 | 
				
			||||||
 | 
					  #   put a if out   # --> Branch C
 | 
				
			||||||
 | 
					  # else
 | 
				
			||||||
 | 
					  #   do_something a # --> Branch D
 | 
				
			||||||
 | 
					  # end
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  class Branch
 | 
				
			||||||
 | 
					    # The actual branch node.
 | 
				
			||||||
 | 
					    getter node : Crystal::ASTNode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The parent branchable.
 | 
				
			||||||
 | 
					    getter parent : Branchable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delegate to_s, to: @node
 | 
				
			||||||
 | 
					    delegate location, to: @node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def_equals_and_hash node, location
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Creates a new branch.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # Branch.new(if_node)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def initialize(@node, @parent)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if current branch is in a loop, false - otherwise.
 | 
				
			||||||
 | 
					    # For example, this branch is in a loop:
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # while true
 | 
				
			||||||
 | 
					    #   handle_input # this branch is in a loop
 | 
				
			||||||
 | 
					    #   if wrong_input
 | 
				
			||||||
 | 
					    #     show_message # this branch is also in a loop.
 | 
				
			||||||
 | 
					    #   end
 | 
				
			||||||
 | 
					    # end
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    def in_loop?
 | 
				
			||||||
 | 
					      @parent.loop?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Constructs a new branch based on the node in scope.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # Branch.of(assign_node, scope)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def self.of(node : Crystal::ASTNode, scope : Scope)
 | 
				
			||||||
 | 
					      of(node, scope.node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Constructs a new branch based on the node some parent scope.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # Branch.of(assign_node, def_node)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def self.of(node : Crystal::ASTNode, parent_node : Crystal::ASTNode)
 | 
				
			||||||
 | 
					      BranchVisitor.new(node).tap(&.accept parent_node).branch
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    private class BranchVisitor < Crystal::Visitor
 | 
				
			||||||
 | 
					      @current_branch : Crystal::ASTNode?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      property branchable : Branchable?
 | 
				
			||||||
 | 
					      property branch : Branch?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def initialize(@node : Crystal::ASTNode)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      private def on_branchable_start(node, *branches)
 | 
				
			||||||
 | 
					        on_branchable_start(node, branches)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      private def on_branchable_start(node, branches : Array | Tuple)
 | 
				
			||||||
 | 
					        @branchable = Branchable.new(node, @branchable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        branches.each do |node|
 | 
				
			||||||
 | 
					          break if branch # branch found
 | 
				
			||||||
 | 
					          @current_branch = node if node && !node.nop?
 | 
				
			||||||
 | 
					          node.try &.accept(self)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        false
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      private def on_branchable_end(node)
 | 
				
			||||||
 | 
					        @branchable = @branchable.try &.parent
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::ASTNode)
 | 
				
			||||||
 | 
					        return false if branch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if node.class == @node.class &&
 | 
				
			||||||
 | 
					           node.location == @node.location &&
 | 
				
			||||||
 | 
					           (branchable = @branchable) &&
 | 
				
			||||||
 | 
					           (branch = @current_branch)
 | 
				
			||||||
 | 
					          @branch = Branch.new(branch, branchable)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::If)
 | 
				
			||||||
 | 
					        on_branchable_start node, node.cond, node.then, node.else
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::If)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::Unless)
 | 
				
			||||||
 | 
					        on_branchable_start node, node.cond, node.then, node.else
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::Unless)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::BinaryOp)
 | 
				
			||||||
 | 
					        on_branchable_start node, node.left, node.right
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::BinaryOp)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::Case)
 | 
				
			||||||
 | 
					        on_branchable_start node, [node.cond, node.whens, node.else].flatten
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::Case)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::While)
 | 
				
			||||||
 | 
					        on_branchable_start node, node.cond, node.body
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::While)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::Until)
 | 
				
			||||||
 | 
					        on_branchable_start node, node.cond, node.body
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::Until)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::ExceptionHandler)
 | 
				
			||||||
 | 
					        on_branchable_start node, [node.body, node.rescues, node.else, node.ensure].flatten
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::ExceptionHandler)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::Rescue)
 | 
				
			||||||
 | 
					        on_branchable_start node, node.body
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::Rescue)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::MacroIf)
 | 
				
			||||||
 | 
					        on_branchable_start node, node.cond, node.then, node.else
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::MacroIf)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def visit(node : Crystal::MacroFor)
 | 
				
			||||||
 | 
					        on_branchable_start node, node.exp, node.body
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def end_visit(node : Crystal::MacroFor)
 | 
				
			||||||
 | 
					        on_branchable_end node
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										42
									
								
								src/ameba/ast/branchable.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/ameba/ast/branchable.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					module Ameba::AST
 | 
				
			||||||
 | 
					  # A generic entity to represent a branchable Crystal node.
 | 
				
			||||||
 | 
					  # For example, `Crystal::If`, `Crystal::Unless`, `Crystal::While`
 | 
				
			||||||
 | 
					  # are branchable.
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  # white a > 100 # Branchable A
 | 
				
			||||||
 | 
					  #   if b > 2    # Branchable B
 | 
				
			||||||
 | 
					  #     a += 1
 | 
				
			||||||
 | 
					  #   end
 | 
				
			||||||
 | 
					  # end
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  class Branchable
 | 
				
			||||||
 | 
					    getter branches = [] of Crystal::ASTNode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The actual Crystal node.
 | 
				
			||||||
 | 
					    getter node : Crystal::ASTNode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Parent branchable (if any)
 | 
				
			||||||
 | 
					    getter parent : Branchable?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delegate to_s, to: @node
 | 
				
			||||||
 | 
					    delegate location, to: @node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Creates a new branchable
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # Branchable.new(node, parent_branchable)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def initialize(@node, @parent = nil)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if this node or one of the parent branchables is a loop, false otherwise.
 | 
				
			||||||
 | 
					    def loop?
 | 
				
			||||||
 | 
					      return true if node.is_a? Crystal::While ||
 | 
				
			||||||
 | 
					                     node.is_a? Crystal::Until ||
 | 
				
			||||||
 | 
					                     ((n = node) && n.is_a?(Crystal::Call) && n.name == "loop")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      parent.try(&.loop?) || false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										110
									
								
								src/ameba/ast/scope.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/ameba/ast/scope.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,110 @@
 | 
				
			||||||
 | 
					require "./variabling/*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module Ameba::AST
 | 
				
			||||||
 | 
					  # Represents a context of the local variable visibility.
 | 
				
			||||||
 | 
					  # This is where the local variables belong to.
 | 
				
			||||||
 | 
					  class Scope
 | 
				
			||||||
 | 
					    # Link to local variables
 | 
				
			||||||
 | 
					    getter variables = [] of Variable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Link to the outer scope
 | 
				
			||||||
 | 
					    getter outer_scope : Scope?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # List of inner scopes
 | 
				
			||||||
 | 
					    getter inner_scopes = [] of Scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The actual AST node that represents a current scope.
 | 
				
			||||||
 | 
					    getter node : Crystal::ASTNode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delegate to_s, to: node
 | 
				
			||||||
 | 
					    delegate location, to: node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def_equals_and_hash node, location
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Creates a new scope. Accepts the AST node and the outer scope.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # scope = Scope.new(class_node, nil)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def initialize(@node, @outer_scope = nil)
 | 
				
			||||||
 | 
					      @outer_scope.try &.inner_scopes.<<(self)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Creates a new variable in the current scope.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # scope = Scope.new(class_node, nil)
 | 
				
			||||||
 | 
					    # scope.add_variable(var_node)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def add_variable(node)
 | 
				
			||||||
 | 
					      variables << Variable.new(node, self)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns variable by its name or nil if it does not exist.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # scope = Scope.new(class_node, nil)
 | 
				
			||||||
 | 
					    # scope.find_variable("foo")
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def find_variable(name : String)
 | 
				
			||||||
 | 
					      variables.find { |v| v.name == name } || outer_scope.try &.find_variable(name)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Creates a new assignment for the variable.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # scope = Scope.new(class_node, nil)
 | 
				
			||||||
 | 
					    # scope.assign_variable(var_node)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def assign_variable(node)
 | 
				
			||||||
 | 
					      node.is_a?(Crystal::Var) && find_variable(node.name).try &.assign(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if current scope represents a block (or proc),
 | 
				
			||||||
 | 
					    # false if not.
 | 
				
			||||||
 | 
					    def block?
 | 
				
			||||||
 | 
					      node.is_a?(Crystal::Block) || node.is_a?(Crystal::ProcLiteral)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if and only if current scope represents some
 | 
				
			||||||
 | 
					    # type definition, for example a class.
 | 
				
			||||||
 | 
					    def type_definition?
 | 
				
			||||||
 | 
					      node.is_a?(Crystal::ClassDef) ||
 | 
				
			||||||
 | 
					        node.is_a?(Crystal::ModuleDef) ||
 | 
				
			||||||
 | 
					        node.is_a?(Crystal::LibDef) ||
 | 
				
			||||||
 | 
					        node.is_a?(Crystal::FunDef) ||
 | 
				
			||||||
 | 
					        node.is_a?(Crystal::TypeDef) ||
 | 
				
			||||||
 | 
					        node.is_a?(Crystal::CStructOrUnionDef)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if current scope references variable, false if not.
 | 
				
			||||||
 | 
					    def references?(variable : Variable)
 | 
				
			||||||
 | 
					      variable.references.any? { |reference| reference.scope == self }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns arguments of this scope (if any).
 | 
				
			||||||
 | 
					    def args
 | 
				
			||||||
 | 
					      case current_node = node
 | 
				
			||||||
 | 
					      when Crystal::Block, Crystal::Def then current_node.args
 | 
				
			||||||
 | 
					      when Crystal::ProcLiteral         then current_node.def.args
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        [] of Crystal::Var
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if variable is an argument in current scope, false if not.
 | 
				
			||||||
 | 
					    def arg?(var : Crystal::Var)
 | 
				
			||||||
 | 
					      args.any? do |arg|
 | 
				
			||||||
 | 
					        arg.is_a?(Crystal::Var) &&
 | 
				
			||||||
 | 
					          arg.name == var.name &&
 | 
				
			||||||
 | 
					          arg.location == var.location
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if the `node` represents exactly
 | 
				
			||||||
 | 
					    # the same Crystal node as `@node`.
 | 
				
			||||||
 | 
					    def eql?(node)
 | 
				
			||||||
 | 
					      node == @node && node.location == @node.location
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -1,68 +0,0 @@
 | 
				
			||||||
require "compiler/crystal/syntax/*"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# A module that helps to traverse Crystal AST using `Crystal::Visitor`.
 | 
					 | 
				
			||||||
module Ameba::AST
 | 
					 | 
				
			||||||
  # List of nodes to be visited by Ameba's rules.
 | 
					 | 
				
			||||||
  NODES = [
 | 
					 | 
				
			||||||
    Alias,
 | 
					 | 
				
			||||||
    Assign,
 | 
					 | 
				
			||||||
    Call,
 | 
					 | 
				
			||||||
    Case,
 | 
					 | 
				
			||||||
    ClassDef,
 | 
					 | 
				
			||||||
    ClassVar,
 | 
					 | 
				
			||||||
    Def,
 | 
					 | 
				
			||||||
    EnumDef,
 | 
					 | 
				
			||||||
    ExceptionHandler,
 | 
					 | 
				
			||||||
    Expressions,
 | 
					 | 
				
			||||||
    HashLiteral,
 | 
					 | 
				
			||||||
    If,
 | 
					 | 
				
			||||||
    InstanceVar,
 | 
					 | 
				
			||||||
    LibDef,
 | 
					 | 
				
			||||||
    ModuleDef,
 | 
					 | 
				
			||||||
    NilLiteral,
 | 
					 | 
				
			||||||
    StringInterpolation,
 | 
					 | 
				
			||||||
    Unless,
 | 
					 | 
				
			||||||
    Var,
 | 
					 | 
				
			||||||
    When,
 | 
					 | 
				
			||||||
    While,
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # An AST Visitor used by rules.
 | 
					 | 
				
			||||||
  #
 | 
					 | 
				
			||||||
  # ```
 | 
					 | 
				
			||||||
  # visitor = Ameba::AST::Visitor.new(rule, source)
 | 
					 | 
				
			||||||
  # ```
 | 
					 | 
				
			||||||
  #
 | 
					 | 
				
			||||||
  class Visitor < Crystal::Visitor
 | 
					 | 
				
			||||||
    # A corresponding rule that uses this visitor.
 | 
					 | 
				
			||||||
    @rule : Rule::Base
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # A source that needs to be traversed.
 | 
					 | 
				
			||||||
    @source : Source
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Creates instance of this visitor.
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # ```
 | 
					 | 
				
			||||||
    # visitor = Ameba::AST::Visitor.new(rule, source)
 | 
					 | 
				
			||||||
    # ```
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    def initialize(@rule, @source)
 | 
					 | 
				
			||||||
      @source.ast.accept self
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # A main visit method that accepts `Crystal::ASTNode`.
 | 
					 | 
				
			||||||
    # Returns true meaning all child nodes will be traversed.
 | 
					 | 
				
			||||||
    def visit(node : Crystal::ASTNode)
 | 
					 | 
				
			||||||
      true
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    {% for name in NODES %}
 | 
					 | 
				
			||||||
      # A visit callback for `Crystal::{{name}}` node.
 | 
					 | 
				
			||||||
      # Returns true meaning that child nodes will be traversed as well.
 | 
					 | 
				
			||||||
      def visit(node : Crystal::{{name}})
 | 
					 | 
				
			||||||
        @rule.test @source, node
 | 
					 | 
				
			||||||
        true
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    {% end %}
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
							
								
								
									
										42
									
								
								src/ameba/ast/variabling/assignment.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/ameba/ast/variabling/assignment.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					require "./reference"
 | 
				
			||||||
 | 
					require "./variable"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module Ameba::AST
 | 
				
			||||||
 | 
					  # Represents the assignment to the variable.
 | 
				
			||||||
 | 
					  # Holds the assign node and the variable.
 | 
				
			||||||
 | 
					  class Assignment
 | 
				
			||||||
 | 
					    property? referenced = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The actual assignment node.
 | 
				
			||||||
 | 
					    getter node : Crystal::ASTNode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Variable of this assignment.
 | 
				
			||||||
 | 
					    getter variable : Variable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Branch of this assignment.
 | 
				
			||||||
 | 
					    getter branch : Branch?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delegate location, to: @node
 | 
				
			||||||
 | 
					    delegate to_s, to: @node
 | 
				
			||||||
 | 
					    delegate scope, to: @variable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Creates a new assignment.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # Assignment.new(node, variable)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    def initialize(@node, @variable)
 | 
				
			||||||
 | 
					      if scope = @variable.scope
 | 
				
			||||||
 | 
					        @branch = Branch.of(@node, scope)
 | 
				
			||||||
 | 
					        @referenced = true if @variable.special? ||
 | 
				
			||||||
 | 
					                              @variable.scope.type_definition? ||
 | 
				
			||||||
 | 
					                              referenced_in_loop?
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def referenced_in_loop?
 | 
				
			||||||
 | 
					      @variable.referenced? && @branch.try &.in_loop?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/ameba/ast/variabling/reference.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/ameba/ast/variabling/reference.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					require "./variable"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module Ameba::AST
 | 
				
			||||||
 | 
					  # Represents a reference to the variable.
 | 
				
			||||||
 | 
					  # It behaves like a variable is used to distinguish a
 | 
				
			||||||
 | 
					  # the variable from its reference.
 | 
				
			||||||
 | 
					  class Reference < Variable
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										138
									
								
								src/ameba/ast/variabling/variable.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/ameba/ast/variabling/variable.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,138 @@
 | 
				
			||||||
 | 
					module Ameba::AST
 | 
				
			||||||
 | 
					  # Represents the existence of the local variable.
 | 
				
			||||||
 | 
					  # Holds the var node and variable assigments.
 | 
				
			||||||
 | 
					  class Variable
 | 
				
			||||||
 | 
					    # List of the assigments of this variable.
 | 
				
			||||||
 | 
					    getter assignments = [] of Assignment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # List of the references of this variable.
 | 
				
			||||||
 | 
					    getter references = [] of Reference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The actual var node.
 | 
				
			||||||
 | 
					    getter node : Crystal::Var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Scope of this variable.
 | 
				
			||||||
 | 
					    getter scope : Scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delegate location, to: @node
 | 
				
			||||||
 | 
					    delegate name, to: @node
 | 
				
			||||||
 | 
					    delegate to_s, to: @node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Creates a new variable(in the scope).
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # Variable.new(node, scope)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    def initialize(@node, @scope)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if it is a special variable, i.e `$?`.
 | 
				
			||||||
 | 
					    def special?
 | 
				
			||||||
 | 
					      name =~ /^\$/
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Assigns the variable (creates a new assignment).
 | 
				
			||||||
 | 
					    # Variable may have multiple assignments.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # variable = Variable.new(node, scope)
 | 
				
			||||||
 | 
					    # variable.assign(node1)
 | 
				
			||||||
 | 
					    # variable.assign(node2)
 | 
				
			||||||
 | 
					    # variable.assignment.size # => 2
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    def assign(node)
 | 
				
			||||||
 | 
					      assignments << Assignment.new(node, self)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if variable has any reference.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # variable = Variable.new(node, scope)
 | 
				
			||||||
 | 
					    # variable.reference(var_node)
 | 
				
			||||||
 | 
					    # variable.referenced? # => true
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def referenced?
 | 
				
			||||||
 | 
					      references.any?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Creates a reference to this variable in some scope.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # variable = Variable.new(node, scope)
 | 
				
			||||||
 | 
					    # variable.reference(var_node, some_scope)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    def reference(node : Crystal::Var, scope : Scope)
 | 
				
			||||||
 | 
					      Reference.new(node, scope).tap do |reference|
 | 
				
			||||||
 | 
					        references << reference
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Reference variable's assignments.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # variable = Variable.new(node, scope)
 | 
				
			||||||
 | 
					    # variable.assign(assign_node)
 | 
				
			||||||
 | 
					    # variable.reference_assignments!
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def reference_assignments!
 | 
				
			||||||
 | 
					      consumed_branches = Set(Branch).new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      assignments.reverse_each do |assignment|
 | 
				
			||||||
 | 
					        next if consumed_branches.includes?(assignment.branch)
 | 
				
			||||||
 | 
					        assignment.referenced = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        break unless assignment.branch
 | 
				
			||||||
 | 
					        consumed_branches << assignment.branch.not_nil!
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if the current assignment is referenced in
 | 
				
			||||||
 | 
					    # in the block. For example this variable is captured
 | 
				
			||||||
 | 
					    # by block:
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # a = 1
 | 
				
			||||||
 | 
					    # 3.times { |i| a = a + i }
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # And this variable is not captured by block.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # i = 1
 | 
				
			||||||
 | 
					    # 3.times { |i| i + 1 }
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    def captured_by_block?(scope = @scope)
 | 
				
			||||||
 | 
					      return false unless scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      scope.inner_scopes.each do |inner_scope|
 | 
				
			||||||
 | 
					        return true if inner_scope.block? && inner_scope.references?(self)
 | 
				
			||||||
 | 
					        return true if captured_by_block?(inner_scope)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if the variable is a target (on the left) of the assignment,
 | 
				
			||||||
 | 
					    # false otherwise.
 | 
				
			||||||
 | 
					    def target_of?(assign)
 | 
				
			||||||
 | 
					      case assign
 | 
				
			||||||
 | 
					      when Crystal::Assign      then eql?(assign.target)
 | 
				
			||||||
 | 
					      when Crystal::OpAssign    then eql?(assign.target)
 | 
				
			||||||
 | 
					      when Crystal::MultiAssign then assign.targets.any? { |t| eql?(t) }
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        false
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Returns true if the `node` represents exactly
 | 
				
			||||||
 | 
					    # the same Crystal node as `@node`.
 | 
				
			||||||
 | 
					    def eql?(node)
 | 
				
			||||||
 | 
					      node.is_a?(Crystal::Var) &&
 | 
				
			||||||
 | 
					        node.name == @node.name &&
 | 
				
			||||||
 | 
					        node.location == @node.location
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/ameba/ast/visitors/base_visitor.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/ameba/ast/visitors/base_visitor.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					require "compiler/crystal/syntax/*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# A module that helps to traverse Crystal AST using `Crystal::Visitor`.
 | 
				
			||||||
 | 
					module Ameba::AST
 | 
				
			||||||
 | 
					  # An abstract base visitor that utilizes general logic for all visitors.
 | 
				
			||||||
 | 
					  abstract class BaseVisitor < Crystal::Visitor
 | 
				
			||||||
 | 
					    # A corresponding rule that uses this visitor.
 | 
				
			||||||
 | 
					    @rule : Rule::Base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # A source that needs to be traversed.
 | 
				
			||||||
 | 
					    @source : Source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Creates instance of this visitor.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # visitor = Ameba::AST::NodeVisitor.new(rule, source)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    def initialize(@rule, @source)
 | 
				
			||||||
 | 
					      @source.ast.accept self
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # A main visit method that accepts `Crystal::ASTNode`.
 | 
				
			||||||
 | 
					    # Returns true meaning all child nodes will be traversed.
 | 
				
			||||||
 | 
					    def visit(node : Crystal::ASTNode)
 | 
				
			||||||
 | 
					      true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/ameba/ast/visitors/node_visitor.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/ameba/ast/visitors/node_visitor.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					require "./base_visitor"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module Ameba::AST
 | 
				
			||||||
 | 
					  # List of nodes to be visited by Ameba's rules.
 | 
				
			||||||
 | 
					  NODES = [
 | 
				
			||||||
 | 
					    Alias,
 | 
				
			||||||
 | 
					    Assign,
 | 
				
			||||||
 | 
					    Call,
 | 
				
			||||||
 | 
					    Case,
 | 
				
			||||||
 | 
					    ClassDef,
 | 
				
			||||||
 | 
					    ClassVar,
 | 
				
			||||||
 | 
					    Def,
 | 
				
			||||||
 | 
					    EnumDef,
 | 
				
			||||||
 | 
					    ExceptionHandler,
 | 
				
			||||||
 | 
					    Expressions,
 | 
				
			||||||
 | 
					    HashLiteral,
 | 
				
			||||||
 | 
					    If,
 | 
				
			||||||
 | 
					    InstanceVar,
 | 
				
			||||||
 | 
					    LibDef,
 | 
				
			||||||
 | 
					    ModuleDef,
 | 
				
			||||||
 | 
					    NilLiteral,
 | 
				
			||||||
 | 
					    StringInterpolation,
 | 
				
			||||||
 | 
					    Unless,
 | 
				
			||||||
 | 
					    Var,
 | 
				
			||||||
 | 
					    When,
 | 
				
			||||||
 | 
					    While,
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # An AST Visitor that traverses the source and allows all nodes
 | 
				
			||||||
 | 
					  # to be inspected by rules.
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  # visitor = Ameba::AST::NodeVisitor.new(rule, source)
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  class NodeVisitor < BaseVisitor
 | 
				
			||||||
 | 
					    {% for name in NODES %}
 | 
				
			||||||
 | 
					      # A visit callback for `Crystal::{{name}}` node.
 | 
				
			||||||
 | 
					      # Returns true meaning that child nodes will be traversed as well.
 | 
				
			||||||
 | 
					      def visit(node : Crystal::{{name}})
 | 
				
			||||||
 | 
					        @rule.test @source, node
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    {% end %}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										140
									
								
								src/ameba/ast/visitors/scope_visitor.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/ameba/ast/visitors/scope_visitor.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,140 @@
 | 
				
			||||||
 | 
					require "./base_visitor"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module Ameba::AST
 | 
				
			||||||
 | 
					  # AST Visitor that traverses the source and constructs scopes.
 | 
				
			||||||
 | 
					  class ScopeVisitor < BaseVisitor
 | 
				
			||||||
 | 
					    @current_scope : Scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def initialize(@rule, @source)
 | 
				
			||||||
 | 
					      @current_scope = Scope.new(@source.ast) # top level scope
 | 
				
			||||||
 | 
					      @source.ast.accept self
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def on_scope_enter(node)
 | 
				
			||||||
 | 
					      @current_scope = Scope.new(node, @current_scope)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def on_scope_end(node)
 | 
				
			||||||
 | 
					      @rule.test @source, node, @current_scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # go up, if this is not a top level scope
 | 
				
			||||||
 | 
					      if outer_scope = @current_scope.outer_scope
 | 
				
			||||||
 | 
					        @current_scope = outer_scope
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def end_visit(node : Crystal::ASTNode)
 | 
				
			||||||
 | 
					      on_scope_end(node) if @current_scope.eql?(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::ClassDef)
 | 
				
			||||||
 | 
					      on_scope_enter(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::ModuleDef)
 | 
				
			||||||
 | 
					      on_scope_enter(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::LibDef)
 | 
				
			||||||
 | 
					      on_scope_enter(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::FunDef)
 | 
				
			||||||
 | 
					      on_scope_enter(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::TypeDef)
 | 
				
			||||||
 | 
					      on_scope_enter(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::CStructOrUnionDef)
 | 
				
			||||||
 | 
					      on_scope_enter(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::Def)
 | 
				
			||||||
 | 
					      node.name == "->" || on_scope_enter(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::ProcLiteral)
 | 
				
			||||||
 | 
					      on_scope_enter(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::Block)
 | 
				
			||||||
 | 
					      on_scope_enter(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @current_assign : Crystal::ASTNode?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::Assign | Crystal::OpAssign | Crystal::MultiAssign)
 | 
				
			||||||
 | 
					      @current_assign = node
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def end_visit(node : Crystal::Assign | Crystal::OpAssign)
 | 
				
			||||||
 | 
					      @current_scope.assign_variable(node.target)
 | 
				
			||||||
 | 
					      @current_assign = nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def end_visit(node : Crystal::MultiAssign)
 | 
				
			||||||
 | 
					      node.targets.each { |target| @current_scope.assign_variable(target) }
 | 
				
			||||||
 | 
					      @current_assign = nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::Arg)
 | 
				
			||||||
 | 
					      @current_scope.add_variable Crystal::Var.new(node.name).at(node.location)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::Var)
 | 
				
			||||||
 | 
					      if !@current_scope.arg?(node) && (variable = @current_scope.find_variable node.name)
 | 
				
			||||||
 | 
					        reference = variable.reference node, @current_scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if @current_assign.is_a?(Crystal::OpAssign) || !reference.target_of?(@current_assign)
 | 
				
			||||||
 | 
					          variable.reference_assignments!
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        @current_scope.add_variable(node)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # :nodoc:
 | 
				
			||||||
 | 
					    def visit(node : Crystal::MacroLiteral)
 | 
				
			||||||
 | 
					      MacroLiteralVarVisitor.new(node).vars.each { |var| visit(var) }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private class MacroLiteralVarVisitor < Crystal::Visitor
 | 
				
			||||||
 | 
					    getter vars = [] of Crystal::Var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def initialize(literal)
 | 
				
			||||||
 | 
					      Crystal::Parser.new(literal.value).parse.accept self
 | 
				
			||||||
 | 
					    rescue
 | 
				
			||||||
 | 
					      nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def visit(node : Crystal::ASTNode)
 | 
				
			||||||
 | 
					      true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def visit(node : Crystal::Var)
 | 
				
			||||||
 | 
					      vars << node
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def visit(node : Crystal::Call)
 | 
				
			||||||
 | 
					      vars << Crystal::Var.new(node.name).at(node.location)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -126,12 +126,16 @@ class Ameba::Config
 | 
				
			||||||
      {% definitions = [] of NamedTuple %}
 | 
					      {% definitions = [] of NamedTuple %}
 | 
				
			||||||
      {% if block.body.is_a? Assign %}
 | 
					      {% if block.body.is_a? Assign %}
 | 
				
			||||||
        {% definitions << {var: block.body.target, value: block.body.value} %}
 | 
					        {% definitions << {var: block.body.target, value: block.body.value} %}
 | 
				
			||||||
 | 
					      {% elsif block.body.is_a? Call %}
 | 
				
			||||||
 | 
					          {% definitions << {var: block.body.name, value: block.body.args.first} %}
 | 
				
			||||||
      {% elsif block.body.is_a? TypeDeclaration %}
 | 
					      {% elsif block.body.is_a? TypeDeclaration %}
 | 
				
			||||||
        {% definitions << {var: block.body.var, value: block.body.value, type: block.body.type} %}
 | 
					        {% definitions << {var: block.body.var, value: block.body.value, type: block.body.type} %}
 | 
				
			||||||
      {% elsif block.body.is_a? Expressions %}
 | 
					      {% elsif block.body.is_a? Expressions %}
 | 
				
			||||||
        {% for prop in block.body.expressions %}
 | 
					        {% for prop in block.body.expressions %}
 | 
				
			||||||
          {% if prop.is_a? Assign %}
 | 
					          {% if prop.is_a? Assign %}
 | 
				
			||||||
            {% definitions << {var: prop.target, value: prop.value} %}
 | 
					            {% definitions << {var: prop.target, value: prop.value} %}
 | 
				
			||||||
 | 
					          {% elsif prop.is_a? Call %}
 | 
				
			||||||
 | 
					            {% definitions << {var: prop.name, value: prop.args.first} %}
 | 
				
			||||||
          {% elsif prop.is_a? TypeDeclaration %}
 | 
					          {% elsif prop.is_a? TypeDeclaration %}
 | 
				
			||||||
            {% definitions << {var: prop.var, value: prop.value, type: prop.type} %}
 | 
					            {% definitions << {var: prop.var, value: prop.value, type: prop.type} %}
 | 
				
			||||||
          {% end %}
 | 
					          {% end %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@ module Ameba::Rule
 | 
				
			||||||
    # that are tested by this rule, it should add an error.
 | 
					    # that are tested by this rule, it should add an error.
 | 
				
			||||||
    abstract def test(source : Source)
 | 
					    abstract def test(source : Source)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source : Source, node : Crystal::ASTNode)
 | 
					    def test(source : Source, node : Crystal::ASTNode, *opts)
 | 
				
			||||||
      # can't be abstract
 | 
					      # can't be abstract
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,14 +22,14 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct ComparisonToBoolean < Base
 | 
					  struct ComparisonToBoolean < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      enabled = false
 | 
					      enabled false
 | 
				
			||||||
      description = "Disallows comparison to booleans"
 | 
					      description "Disallows comparison to booleans"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Comparison to a boolean is pointless"
 | 
					    MSG = "Comparison to a boolean is pointless"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::Call)
 | 
					    def test(source, node : Crystal::Call)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,13 +24,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct ConstantNames < Base
 | 
					  struct ConstantNames < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Enforces constant names to be in screaming case"
 | 
					      description "Enforces constant names to be in screaming case"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Constant name should be screaming-cased: %s, not %s"
 | 
					    MSG = "Constant name should be screaming-cased: %s, not %s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::Assign)
 | 
					    def test(source, node : Crystal::Assign)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,13 +13,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct DebuggerStatement < Base
 | 
					  struct DebuggerStatement < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows calls to debugger"
 | 
					      description "Disallows calls to debugger"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Possible forgotten debugger statement detected"
 | 
					    MSG = "Possible forgotten debugger statement detected"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::Call)
 | 
					    def test(source, node : Crystal::Call)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,13 +34,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct EmptyEnsure < Base
 | 
					  struct EmptyEnsure < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows empty ensure statement"
 | 
					      description "Disallows empty ensure statement"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Empty `ensure` block detected"
 | 
					    MSG = "Empty `ensure` block detected"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::ExceptionHandler)
 | 
					    def test(source, node : Crystal::ExceptionHandler)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,14 +32,14 @@ module Ameba::Rule
 | 
				
			||||||
    include AST::Util
 | 
					    include AST::Util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows empty expressions"
 | 
					      description "Disallows empty expressions"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG      = "Avoid empty expression %s"
 | 
					    MSG      = "Avoid empty expression %s"
 | 
				
			||||||
    MSG_EXRS = "Avoid empty expressions"
 | 
					    MSG_EXRS = "Avoid empty expressions"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::NilLiteral)
 | 
					    def test(source, node : Crystal::NilLiteral)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,13 +22,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct HashDuplicatedKey < Base
 | 
					  struct HashDuplicatedKey < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows duplicated keys in hash literals"
 | 
					      description "Disallows duplicated keys in hash literals"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Duplicated keys in hash literal: %s"
 | 
					    MSG = "Duplicated keys in hash literal: %s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::HashLiteral)
 | 
					    def test(source, node : Crystal::HashLiteral)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,8 +29,8 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct LargeNumbers < Base
 | 
					  struct LargeNumbers < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows usage of large numbers without underscore"
 | 
					      description "Disallows usage of large numbers without underscore"
 | 
				
			||||||
      int_min_digits = 5
 | 
					      int_min_digits 5
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Large numbers should be written with underscores: %s"
 | 
					    MSG = "Large numbers should be written with underscores: %s"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,9 +11,9 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct LineLength < Base
 | 
					  struct LineLength < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      enabled = false
 | 
					      enabled false
 | 
				
			||||||
      description = "Disallows lines longer than `MaxLength` number of symbols"
 | 
					      description "Disallows lines longer than `MaxLength` number of symbols"
 | 
				
			||||||
      max_length = 80
 | 
					      max_length 80
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Line too long"
 | 
					    MSG = "Line too long"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,14 +24,14 @@ module Ameba::Rule
 | 
				
			||||||
    include AST::Util
 | 
					    include AST::Util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows useless conditional statements that contain \
 | 
					      description "Disallows useless conditional statements that contain \
 | 
				
			||||||
        a literal in place of a variable or predicate function"
 | 
					        a literal in place of a variable or predicate function"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Literal value found in conditional"
 | 
					    MSG = "Literal value found in conditional"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def check_node(source, node)
 | 
					    def check_node(source, node)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,13 +20,13 @@ module Ameba::Rule
 | 
				
			||||||
    include AST::Util
 | 
					    include AST::Util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows useless string interpolations"
 | 
					      description "Disallows useless string interpolations"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Literal value found in interpolation"
 | 
					    MSG = "Literal value found in interpolation"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::StringInterpolation)
 | 
					    def test(source, node : Crystal::StringInterpolation)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,13 +40,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct MethodNames < Base
 | 
					  struct MethodNames < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Enforces method names to be in underscored case"
 | 
					      description "Enforces method names to be in underscored case"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Method name should be underscore-cased: %s, not %s"
 | 
					    MSG = "Method name should be underscore-cased: %s, not %s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::Def)
 | 
					    def test(source, node : Crystal::Def)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,13 +29,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct NegatedConditionsInUnless < Base
 | 
					  struct NegatedConditionsInUnless < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows negated conditions in unless"
 | 
					      description "Disallows negated conditions in unless"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Avoid negated conditions in unless blocks"
 | 
					    MSG = "Avoid negated conditions in unless blocks"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::Unless)
 | 
					    def test(source, node : Crystal::Unless)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,9 +26,9 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct PercentArrays < Base
 | 
					  struct PercentArrays < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows some unwanted symbols in percent array literals"
 | 
					      description "Disallows some unwanted symbols in percent array literals"
 | 
				
			||||||
      string_array_unwanted_symbols = ",\""
 | 
					      string_array_unwanted_symbols ",\""
 | 
				
			||||||
      symbol_array_unwanted_symbols = ",:"
 | 
					      symbol_array_unwanted_symbols ",:"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Symbols `%s` may be unwanted in %s array literals"
 | 
					    MSG = "Symbols `%s` may be unwanted in %s array literals"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,13 +31,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct PredicateName < Base
 | 
					  struct PredicateName < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows tautological predicate names"
 | 
					      description "Disallows tautological predicate names"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Favour method name '%s?' over '%s'"
 | 
					    MSG = "Favour method name '%s?' over '%s'"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::Def)
 | 
					    def test(source, node : Crystal::Def)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,13 +25,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct RandZero < Base
 | 
					  struct RandZero < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows rand zero calls"
 | 
					      description "Disallows rand zero calls"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "%s always returns 0"
 | 
					    MSG = "%s always returns 0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::Call)
 | 
					    def test(source, node : Crystal::Call)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,13 +59,13 @@ module Ameba::Rule
 | 
				
			||||||
  struct RedundantBegin < Base
 | 
					  struct RedundantBegin < Base
 | 
				
			||||||
    include AST::Util
 | 
					    include AST::Util
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows redundant begin blocks"
 | 
					      description "Disallows redundant begin blocks"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Redundant `begin` block detected"
 | 
					    MSG = "Redundant `begin` block detected"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::Def)
 | 
					    def test(source, node : Crystal::Def)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,13 +36,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct ShadowedException < Base
 | 
					  struct ShadowedException < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows rescued exception that get shadowed"
 | 
					      description "Disallows rescued exception that get shadowed"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Exception handler has shadowed exceptions: %s"
 | 
					    MSG = "Exception handler has shadowed exceptions: %s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::ExceptionHandler)
 | 
					    def test(source, node : Crystal::ExceptionHandler)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct TrailingBlankLines < Base
 | 
					  struct TrailingBlankLines < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows trailing blank lines"
 | 
					      description "Disallows trailing blank lines"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Blank lines detected at the end of the file"
 | 
					    MSG = "Blank lines detected at the end of the file"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct TrailingWhitespace < Base
 | 
					  struct TrailingWhitespace < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows trailing whitespaces"
 | 
					      description "Disallows trailing whitespaces"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Trailing whitespace detected"
 | 
					    MSG = "Trailing whitespace detected"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,13 +54,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct TypeNames < Base
 | 
					  struct TypeNames < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Enforces type names in camelcase manner"
 | 
					      description "Enforces type names in camelcase manner"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Type name should be camelcased: %s, but it was %s"
 | 
					    MSG = "Type name should be camelcased: %s, but it was %s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private def check_node(source, node)
 | 
					    private def check_node(source, node)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,13 +45,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct UnlessElse < Base
 | 
					  struct UnlessElse < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows the use of an `else` block with the `unless`"
 | 
					      description "Disallows the use of an `else` block with the `unless`"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Favour if over unless with else"
 | 
					    MSG = "Favour if over unless with else"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::Unless)
 | 
					    def test(source, node : Crystal::Unless)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,7 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct UnneededDisableDirective < Base
 | 
					  struct UnneededDisableDirective < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Reports unneeded disable directives in comments"
 | 
					      description "Reports unneeded disable directives in comments"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Unnecessary disabling of %s"
 | 
					    MSG = "Unnecessary disabling of %s"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/ameba/rule/useless_assign.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/ameba/rule/useless_assign.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,51 @@
 | 
				
			||||||
 | 
					module Ameba::Rule
 | 
				
			||||||
 | 
					  # A rule that disallows useless assignments.
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # For example, this is considered invalid:
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  # def method
 | 
				
			||||||
 | 
					  #   var = 1
 | 
				
			||||||
 | 
					  #   do_something
 | 
				
			||||||
 | 
					  # end
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # And has to be written as the following:
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  # def method
 | 
				
			||||||
 | 
					  #   var = 1
 | 
				
			||||||
 | 
					  #   do_something(var)
 | 
				
			||||||
 | 
					  # end
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # YAML configuration example:
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  # UselessAssign:
 | 
				
			||||||
 | 
					  #   Enabled: true
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  struct UselessAssign < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description "Disallows useless variable assignments"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MSG = "Useless assignment to variable `%s`"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test(source)
 | 
				
			||||||
 | 
					      AST::ScopeVisitor.new self, source
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test(source, node, scope : AST::Scope)
 | 
				
			||||||
 | 
					      scope.variables.each do |var|
 | 
				
			||||||
 | 
					        next if var.captured_by_block?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var.assignments.each do |assign|
 | 
				
			||||||
 | 
					          next if assign.referenced?
 | 
				
			||||||
 | 
					          source.error self, assign.location, MSG % var.name
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,7 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct UselessConditionInWhen < Base
 | 
					  struct UselessConditionInWhen < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows useless conditions in when"
 | 
					      description "Disallows useless conditions in when"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Useless condition in when detected"
 | 
					    MSG = "Useless condition in when detected"
 | 
				
			||||||
| 
						 | 
					@ -52,7 +52,7 @@ module Ameba::Rule
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::When)
 | 
					    def test(source, node : Crystal::When)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,13 +33,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct VariableNames < Base
 | 
					  struct VariableNames < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Enforces variable names to be in underscored case"
 | 
					      description "Enforces variable names to be in underscored case"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "Var name should be underscore-cased: %s, not %s"
 | 
					    MSG = "Var name should be underscore-cased: %s, not %s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private def check_node(source, node)
 | 
					    private def check_node(source, node)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,13 +28,13 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct WhileTrue < Base
 | 
					  struct WhileTrue < Base
 | 
				
			||||||
    properties do
 | 
					    properties do
 | 
				
			||||||
      description = "Disallows while statements with a true literal as condition"
 | 
					      description "Disallows while statements with a true literal as condition"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MSG = "While statement using true literal as condition"
 | 
					    MSG = "While statement using true literal as condition"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::NodeVisitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source, node : Crystal::While)
 | 
					    def test(source, node : Crystal::While)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue