mirror of
				https://gitea.invidious.io/iv-org/shard-ameba.git
				synced 2024-08-15 00:53:29 +00:00 
			
		
		
		
	Unneeded disable directive
This commit is contained in:
		
							parent
							
								
									2382657e15
								
							
						
					
					
						commit
						8075c39aa9
					
				
					 5 changed files with 197 additions and 14 deletions
				
			
		| 
						 | 
					@ -83,5 +83,22 @@ module Ameba
 | 
				
			||||||
      s.error(NamedRule.new, 3, 12, "")
 | 
					      s.error(NamedRule.new, 3, 12, "")
 | 
				
			||||||
      s.should_not be_valid
 | 
					      s.should_not be_valid
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "does not disable if that is a commented out directive" do
 | 
				
			||||||
 | 
					      s = Source.new %Q(
 | 
				
			||||||
 | 
					        # # ameba:disable #{NamedRule.name}
 | 
				
			||||||
 | 
					        Time.epoch(1483859302)
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      s.error(NamedRule.new, 3, 12, "")
 | 
				
			||||||
 | 
					      s.should_not be_valid
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "does not disable if that is an inline commented out directive" do
 | 
				
			||||||
 | 
					      s = Source.new %Q(
 | 
				
			||||||
 | 
					        a = 1 # Disable it: # ameba:disable #{NamedRule.name}
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      s.error(NamedRule.new, 2, 12, "")
 | 
				
			||||||
 | 
					      s.should_not be_valid
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										80
									
								
								spec/ameba/rule/unneded_disable_directive_spec.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								spec/ameba/rule/unneded_disable_directive_spec.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,80 @@
 | 
				
			||||||
 | 
					require "../../spec_helper"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module Ameba::Rule
 | 
				
			||||||
 | 
					  describe UnneededDisableDirective do
 | 
				
			||||||
 | 
					    subject = UnneededDisableDirective.new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "passes if there are no comments" do
 | 
				
			||||||
 | 
					      s = Source.new %(
 | 
				
			||||||
 | 
					        a = 1
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      subject.catch(s).should be_valid
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "passes if there is disable directive" do
 | 
				
			||||||
 | 
					      s = Source.new %(
 | 
				
			||||||
 | 
					        a = 1 # my super var
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      subject.catch(s).should be_valid
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "passes if there is disable directive and it is needed" do
 | 
				
			||||||
 | 
					      s = Source.new %Q(
 | 
				
			||||||
 | 
					        a = 1 # ameba:disable #{NamedRule.name}
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      s.error NamedRule.new, 2, 1, "Alarm!", :disabled
 | 
				
			||||||
 | 
					      subject.catch(s).should be_valid
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "ignores commented out disable directive" do
 | 
				
			||||||
 | 
					      s = Source.new %Q(
 | 
				
			||||||
 | 
					        # # ameba:disable #{NamedRule.name}
 | 
				
			||||||
 | 
					        a = 1
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      s.error NamedRule.new, 3, 1, "Alarm!", :disabled
 | 
				
			||||||
 | 
					      subject.catch(s).should be_valid
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "failes if there is unneeded directive" do
 | 
				
			||||||
 | 
					      s = Source.new %Q(
 | 
				
			||||||
 | 
					        # ameba:disable #{NamedRule.name}
 | 
				
			||||||
 | 
					        a = 1
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      subject.catch(s).should_not be_valid
 | 
				
			||||||
 | 
					      s.errors.first.message.should eq(
 | 
				
			||||||
 | 
					        "Unnecessary disabling of #{NamedRule.name}"
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "fails if there is inline unneeded directive" do
 | 
				
			||||||
 | 
					      s = Source.new %Q(a = 1 # ameba:disable #{NamedRule.name})
 | 
				
			||||||
 | 
					      subject.catch(s).should_not be_valid
 | 
				
			||||||
 | 
					      s.errors.first.message.should eq(
 | 
				
			||||||
 | 
					        "Unnecessary disabling of #{NamedRule.name}"
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "detects mixed inline directives" do
 | 
				
			||||||
 | 
					      s = Source.new %Q(
 | 
				
			||||||
 | 
					        # ameba:disable Rule1, Rule2
 | 
				
			||||||
 | 
					        a = 1 # ameba:disable Rule3
 | 
				
			||||||
 | 
					      ), "source.cr"
 | 
				
			||||||
 | 
					      subject.catch(s).should_not be_valid
 | 
				
			||||||
 | 
					      s.errors.size.should eq 2
 | 
				
			||||||
 | 
					      s.errors.first.message.should contain "Rule1, Rule2"
 | 
				
			||||||
 | 
					      s.errors.last.message.should contain "Rule3"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "reports error, location and message" do
 | 
				
			||||||
 | 
					      s = Source.new %Q(
 | 
				
			||||||
 | 
					        # ameba:disable Rule1, Rule2
 | 
				
			||||||
 | 
					        a = 1
 | 
				
			||||||
 | 
					      ), "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:2:9"
 | 
				
			||||||
 | 
					      error.message.should eq "Unnecessary disabling of Rule1, Rule2"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
module Ameba
 | 
					module Ameba
 | 
				
			||||||
  # A module that represents inline comments parsing and processing logic.
 | 
					  # A module that utilizes inline comments parsing and processing logic.
 | 
				
			||||||
  module InlineComments
 | 
					  module InlineComments
 | 
				
			||||||
    COMMENT_DIRECTIVE_REGEX = Regex.new "# ameba : (\\w+) ([\\w, ]+)".gsub(" ", "\\s*")
 | 
					    COMMENT_DIRECTIVE_REGEX = Regex.new "# ameba : (\\w+) ([\\w, ]+)".gsub(" ", "\\s*")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,22 +42,48 @@ module Ameba
 | 
				
			||||||
          line_disabled?(prev_line, rule))
 | 
					          line_disabled?(prev_line, rule))
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private def comment?(line)
 | 
					    # Parses inline comment directive. Returns a tuple that consists of
 | 
				
			||||||
      line.lstrip.starts_with? '#'
 | 
					    # an action and parsed rules if directive found, nil otherwise.
 | 
				
			||||||
    end
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
    private def line_disabled?(line, rule)
 | 
					    # line = "# ameba:disable Rule1, Rule2"
 | 
				
			||||||
      return false unless inline_comment = parse_inline_comment(line)
 | 
					    # directive = parse_inline_directive(line)
 | 
				
			||||||
      inline_comment[:action] == "disable" && inline_comment[:rules].includes?(rule)
 | 
					    # directive[:action] # => "disable"
 | 
				
			||||||
    end
 | 
					    # directive[:rules]  # => ["Rule1", "Rule2"]
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
    private def parse_inline_comment(line)
 | 
					    #
 | 
				
			||||||
      if comment = COMMENT_DIRECTIVE_REGEX.match(line)
 | 
					    # It ignores the directive if it is commented out.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # line = "# # ameba:disable Rule1, Rule2"
 | 
				
			||||||
 | 
					    # parse_inline_directive(line) # => nil
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    def parse_inline_directive(line)
 | 
				
			||||||
 | 
					      if directive = COMMENT_DIRECTIVE_REGEX.match(line)
 | 
				
			||||||
 | 
					        return if commented_out?(line.gsub directive[0], "")
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          action: comment[1],
 | 
					          action: directive[1],
 | 
				
			||||||
          rules:  comment[2].split(/[\s,]/, remove_empty: true),
 | 
					          rules:  directive[2].split(/[\s,]/, remove_empty: true),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def comment?(line)
 | 
				
			||||||
 | 
					      return true if line.lstrip.starts_with? '#'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def line_disabled?(line, rule)
 | 
				
			||||||
 | 
					      return false unless directive = parse_inline_directive(line)
 | 
				
			||||||
 | 
					      directive[:action] == "disable" && directive[:rules].includes?(rule)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def commented_out?(line)
 | 
				
			||||||
 | 
					      commented? = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lexer = Crystal::Lexer.new(line).tap { |l| l.comments_enabled = true }
 | 
				
			||||||
 | 
					      Tokenizer.new(lexer).run { |t| commented? = true if t.type == :COMMENT }
 | 
				
			||||||
 | 
					      commented?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/ameba/rule/unneeded_disable_directive.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/ameba/rule/unneeded_disable_directive.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					module Ameba::Rule
 | 
				
			||||||
 | 
					  # A rule that reports unneeded disable directives.
 | 
				
			||||||
 | 
					  # For example, this is considered invalid:
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  # # ameba:disable PredicateName
 | 
				
			||||||
 | 
					  # def comment?
 | 
				
			||||||
 | 
					  #   do_something
 | 
				
			||||||
 | 
					  # end
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # as the predicate name is correct and comment directive does not
 | 
				
			||||||
 | 
					  # have any effect, the snippet should be written as the following:
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  # def comment?
 | 
				
			||||||
 | 
					  #   do_something
 | 
				
			||||||
 | 
					  # end
 | 
				
			||||||
 | 
					  # ```
 | 
				
			||||||
 | 
					  #
 | 
				
			||||||
 | 
					  struct UnneededDisableDirective < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Reports unneeded disable directives in comments"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test(source)
 | 
				
			||||||
 | 
					      Tokenizer.new(source).run do |token|
 | 
				
			||||||
 | 
					        next unless token.type == :COMMENT
 | 
				
			||||||
 | 
					        next unless directive = source.parse_inline_directive(token.value.to_s)
 | 
				
			||||||
 | 
					        next unless names = unneeded_disables(source, directive, token.location)
 | 
				
			||||||
 | 
					        next unless names.any?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        source.error self, token.location,
 | 
				
			||||||
 | 
					          "Unnecessary disabling of #{names.join(", ")}"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def unneeded_disables(source, directive, location)
 | 
				
			||||||
 | 
					      return unless directive[:action] == "disable"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      directive[:rules].reject do |rule_name|
 | 
				
			||||||
 | 
					        any = source.errors.any? do |error|
 | 
				
			||||||
 | 
					          error.rule.name == rule_name &&
 | 
				
			||||||
 | 
					            error.disabled? &&
 | 
				
			||||||
 | 
					            error.location.try(&.line_number) == location.line_number
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,16 @@ module Ameba
 | 
				
			||||||
      @lexer.filename = source.path
 | 
					      @lexer.filename = source.path
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Instantiates Tokenizer using a `lexer`.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    # lexer = Crystal::Lexer.new(code)
 | 
				
			||||||
 | 
					    # Ameba::Tokenizer.new(lexer)
 | 
				
			||||||
 | 
					    # ```
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    def initialize(@lexer : Crystal::Lexer)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Runs the tokenizer and yields each token as a block argument.
 | 
					    # Runs the tokenizer and yields each token as a block argument.
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
    # ```
 | 
					    # ```
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue