mirror of
				https://gitea.invidious.io/iv-org/shard-ameba.git
				synced 2024-08-15 00:53:29 +00:00 
			
		
		
		
	Handle raise, exit, abort in unreachable code
This commit is contained in:
		
							parent
							
								
									67d76116f7
								
							
						
					
					
						commit
						eca0f3f350
					
				
					 2 changed files with 160 additions and 7 deletions
				
			
		| 
						 | 
					@ -33,13 +33,46 @@ module Ameba::Rule::Lint
 | 
				
			||||||
      it "doesn't report if there are returns in if-then-else" do
 | 
					      it "doesn't report if there are returns in if-then-else" do
 | 
				
			||||||
        s = Source.new %(
 | 
					        s = Source.new %(
 | 
				
			||||||
          if a > 0
 | 
					          if a > 0
 | 
				
			||||||
            return :positivie
 | 
					            return :positive
 | 
				
			||||||
          else
 | 
					          else
 | 
				
			||||||
            return :negative
 | 
					            return :negative
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        subject.catch(s).should be_valid
 | 
					        subject.catch(s).should be_valid
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "doesn't report if return is used in a block" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          def foo
 | 
				
			||||||
 | 
					            bar = obj.try do
 | 
				
			||||||
 | 
					              if something
 | 
				
			||||||
 | 
					                a = 1
 | 
				
			||||||
 | 
					              end
 | 
				
			||||||
 | 
					              return nil
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bar
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should be_valid
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      pending "reports if there is unreachable code after if-then-else" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          def foo
 | 
				
			||||||
 | 
					            if a > 0
 | 
				
			||||||
 | 
					              return :positive
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              return :negative
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            :unreachable
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should_not be_valid
 | 
				
			||||||
 | 
					        issue = s.issues.first
 | 
				
			||||||
 | 
					        issue.location.to_s.should eq ":8:4"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context "break" do
 | 
					    context "break" do
 | 
				
			||||||
| 
						 | 
					@ -98,5 +131,98 @@ module Ameba::Rule::Lint
 | 
				
			||||||
        subject.catch(s).should be_valid
 | 
					        subject.catch(s).should be_valid
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "raise" do
 | 
				
			||||||
 | 
					      it "reports if there is unreachable code after raise" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          a = 1
 | 
				
			||||||
 | 
					          raise "exception"
 | 
				
			||||||
 | 
					          b = 2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should_not be_valid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        issue = s.issues.first
 | 
				
			||||||
 | 
					        issue.location.to_s.should eq ":3:1"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "doesn't report if raise is in a condition" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          a = 1
 | 
				
			||||||
 | 
					          raise "exception" if a > 0
 | 
				
			||||||
 | 
					          b = 2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should be_valid
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "exit" do
 | 
				
			||||||
 | 
					      it "reports if there is unreachable code after exit without args" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          a = 1
 | 
				
			||||||
 | 
					          exit
 | 
				
			||||||
 | 
					          b = 2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should_not be_valid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        issue = s.issues.first
 | 
				
			||||||
 | 
					        issue.location.to_s.should eq ":3:1"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "reports if there is unreachable code after exit with exit code" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          a = 1
 | 
				
			||||||
 | 
					          exit 1
 | 
				
			||||||
 | 
					          b = 2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should_not be_valid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        issue = s.issues.first
 | 
				
			||||||
 | 
					        issue.location.to_s.should eq ":3:1"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "doesn't report if exit is in a condition" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          a = 1
 | 
				
			||||||
 | 
					          exit if a > 0
 | 
				
			||||||
 | 
					          b = 2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should be_valid
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "abort" do
 | 
				
			||||||
 | 
					      it "reports if there is unreachable code after abort with one argument" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          a = 1
 | 
				
			||||||
 | 
					          abort "abort"
 | 
				
			||||||
 | 
					          b = 2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should_not be_valid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        issue = s.issues.first
 | 
				
			||||||
 | 
					        issue.location.to_s.should eq ":3:1"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "reports if there is unreachable code after abort with two args" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          a = 1
 | 
				
			||||||
 | 
					          abort "abort", 1
 | 
				
			||||||
 | 
					          b = 2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should_not be_valid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        issue = s.issues.first
 | 
				
			||||||
 | 
					        issue.location.to_s.should eq ":3:1"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "doesn't report if abort is in a condition" do
 | 
				
			||||||
 | 
					        s = Source.new %(
 | 
				
			||||||
 | 
					          a = 1
 | 
				
			||||||
 | 
					          abort "abort" if a > 0
 | 
				
			||||||
 | 
					          b = 2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        subject.catch(s).should be_valid
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ module Ameba::AST
 | 
				
			||||||
  # AST Visitor that traverses all the flow expressions.
 | 
					  # AST Visitor that traverses all the flow expressions.
 | 
				
			||||||
  class FlowExpressionVisitor < BaseVisitor
 | 
					  class FlowExpressionVisitor < BaseVisitor
 | 
				
			||||||
    @node_stack = Array(Crystal::ASTNode).new
 | 
					    @node_stack = Array(Crystal::ASTNode).new
 | 
				
			||||||
 | 
					    @flow_expression : FlowExpression?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def initialize(@rule, @source)
 | 
					    def initialize(@rule, @source)
 | 
				
			||||||
      @source.ast.accept self
 | 
					      @source.ast.accept self
 | 
				
			||||||
| 
						 | 
					@ -14,20 +15,46 @@ module Ameba::AST
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def end_visit(node)
 | 
					    def end_visit(node)
 | 
				
			||||||
      @node_stack.pop
 | 
					      if @flow_expression.nil?
 | 
				
			||||||
 | 
					        @node_stack.pop unless @node_stack.empty?
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        @flow_expression = nil
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def visit(node : Crystal::ControlExpression)
 | 
					    def visit(node : Crystal::ControlExpression)
 | 
				
			||||||
      if parent_node = @node_stack.last?
 | 
					      on_flow_expression_start(node)
 | 
				
			||||||
        flow_expression = FlowExpression.new(node, parent_node)
 | 
					
 | 
				
			||||||
        @rule.test @source, node, flow_expression
 | 
					      true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def visit(node : Crystal::Call)
 | 
				
			||||||
 | 
					      if raise?(node) || exit?(node) || abort?(node)
 | 
				
			||||||
 | 
					        on_flow_expression_start(node)
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        @node_stack.push node
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      true
 | 
					      true
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def end_visit(node : Crystal::ControlExpression)
 | 
					    private def on_flow_expression_start(node)
 | 
				
			||||||
      #
 | 
					      if parent_node = @node_stack.last?
 | 
				
			||||||
 | 
					        @flow_expression = FlowExpression.new(node, parent_node)
 | 
				
			||||||
 | 
					        @rule.test @source, node, @flow_expression
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def raise?(node)
 | 
				
			||||||
 | 
					      node.name == "raise" && node.args.size == 1 && node.obj.nil?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def exit?(node)
 | 
				
			||||||
 | 
					      node.name == "exit" && node.args.size <= 1 && node.obj.nil?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def abort?(node)
 | 
				
			||||||
 | 
					      node.name == "abort" && node.args.size <= 2 && node.obj.nil?
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue