mirror of
				https://gitea.invidious.io/iv-org/shard-ameba.git
				synced 2024-08-15 00:53:29 +00:00 
			
		
		
		
	Make config loading more flexible
This commit is contained in:
		
							parent
							
								
									b5c9f4dff6
								
							
						
					
					
						commit
						f4f401d56f
					
				
					 33 changed files with 243 additions and 92 deletions
				
			
		
							
								
								
									
										31
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										31
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
					@ -108,35 +108,32 @@ Each rule is enabled by default, even if you remove it from the config file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Writing a new Rule
 | 
					## Writing a new Rule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Adding a new rule is as simple as inheriting from `Rule::Base` struct and implementing
 | 
					Adding a new rule is as simple as inheriting from `Ameba::Rule::Base` struct and implementing
 | 
				
			||||||
your logic to detect a problem:
 | 
					a logic to detect a problem in the source file:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```crystal
 | 
					```crystal
 | 
				
			||||||
struct DebuggerStatement < Rule::Base
 | 
					struct MySuperRule < Ameba::Rule::Base
 | 
				
			||||||
  # This is a required method to be implemented by the rule.
 | 
					  # This is a required method to be implemented by the rule.
 | 
				
			||||||
  # Source will be passed here. If rule finds an issue in this source,
 | 
					  # Source will be passed here. If rule detects an issue in the source,
 | 
				
			||||||
  # it reports an error:
 | 
					  # it reports an error:
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  #   source.error rule, line_number, message
 | 
					  #   source.error rule, location, message
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  def test(source)
 | 
					  def test(source)
 | 
				
			||||||
    # This line deletegates verification to a particular callback in the AST visitor.
 | 
					    # TODO: test source
 | 
				
			||||||
    AST::Visitor.new self, source
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # This method is called once the visitor finds a needed node.
 | 
					 | 
				
			||||||
  def test(source, node : Crystal::Call)
 | 
					 | 
				
			||||||
    # It reports an error, if there is `debugger` method call
 | 
					 | 
				
			||||||
    # without arguments and a receiver. That's it, somebody forgot
 | 
					 | 
				
			||||||
    # to remove a debugger statement.
 | 
					 | 
				
			||||||
    return unless node.name == "debugger" && node.args.empty? && node.obj.nil?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    source.error self, node.location, "Possible forgotten debugger statement detected"
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					As soon as a custom rule is defined, it becomes available in a full set of rules
 | 
				
			||||||
 | 
					executed by default and also can be configured via config file:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					MySuperRule:
 | 
				
			||||||
 | 
					  Enabled: false
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Credits & inspirations
 | 
					## Credits & inspirations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [Crystal Language](crystal-lang.org)
 | 
					- [Crystal Language](crystal-lang.org)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
ComparisonToBoolean:
 | 
					ComparisonToBoolean:
 | 
				
			||||||
  # Disallows comparison to booleans in conditions.
 | 
					  # Disallows comparison to booleans in conditions.
 | 
				
			||||||
  Enabled: true
 | 
					  Enabled: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ConstantNames:
 | 
					ConstantNames:
 | 
				
			||||||
  # Enforces constant names to be in a screaming case.
 | 
					  # Enforces constant names to be in a screaming case.
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,7 @@ LargeNumbers:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LineLength:
 | 
					LineLength:
 | 
				
			||||||
  # Disallows lines longer that MaxLength number of symbols.
 | 
					  # Disallows lines longer that MaxLength number of symbols.
 | 
				
			||||||
  Enabled: true
 | 
					  Enabled: false
 | 
				
			||||||
  MaxLength: 80
 | 
					  MaxLength: 80
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LiteralInCondition:
 | 
					LiteralInCondition:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										28
									
								
								spec/ameba/base_spec.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								spec/ameba/base_spec.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					require "../spec_helper"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module Ameba::Rule
 | 
				
			||||||
 | 
					  struct NoProperties < Rule::Base
 | 
				
			||||||
 | 
					    def test(source)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe Base do
 | 
				
			||||||
 | 
					    context "properties" do
 | 
				
			||||||
 | 
					      subject = DummyRule.new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "is enabled by default" do
 | 
				
			||||||
 | 
					        subject.enabled.should be_true
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "has a description property" do
 | 
				
			||||||
 | 
					        subject.description.should_not be_nil
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe "when a rule does not have defined properties" do
 | 
				
			||||||
 | 
					      it "is enabled by default" do
 | 
				
			||||||
 | 
					        NoProperties.new.enabled.should be_true
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -112,7 +112,7 @@ module Ameba::Rule
 | 
				
			||||||
      error = s.errors.first
 | 
					      error = s.errors.first
 | 
				
			||||||
      error.rule.should_not be_nil
 | 
					      error.rule.should_not be_nil
 | 
				
			||||||
      error.location.to_s.should eq "source.cr:2:9"
 | 
					      error.location.to_s.should eq "source.cr:2:9"
 | 
				
			||||||
      error.message.should eq "Duplicated when conditions in case."
 | 
					      error.message.should eq "Duplicated when conditions in case"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,5 +116,14 @@ module Ameba
 | 
				
			||||||
      error.location.to_s.should eq "source.cr:2:10"
 | 
					      error.location.to_s.should eq "source.cr:2:10"
 | 
				
			||||||
      error.message.should match /1_200_000/
 | 
					      error.message.should match /1_200_000/
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "properties" do
 | 
				
			||||||
 | 
					      it "allows to configure integer min digits" do
 | 
				
			||||||
 | 
					        s = Source.new %q(1200000)
 | 
				
			||||||
 | 
					        rule = Rule::LargeNumbers.new
 | 
				
			||||||
 | 
					        rule.int_min_digits = 10
 | 
				
			||||||
 | 
					        rule.catch(s).should be_valid
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,5 +29,14 @@ module Ameba::Rule
 | 
				
			||||||
      error.location.to_s.should eq "source.cr:1:81"
 | 
					      error.location.to_s.should eq "source.cr:1:81"
 | 
				
			||||||
      error.message.should eq "Line too long"
 | 
					      error.message.should eq "Line too long"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "properties" do
 | 
				
			||||||
 | 
					      it "allows to configure max length of the line" do
 | 
				
			||||||
 | 
					        source = Source.new long_line
 | 
				
			||||||
 | 
					        rule = LineLength.new
 | 
				
			||||||
 | 
					        rule.max_length = long_line.size
 | 
				
			||||||
 | 
					        rule.catch(source).should be_valid
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,5 +65,21 @@ module Ameba::Rule
 | 
				
			||||||
        "Symbols `,\"` may be unwanted in %w array literals"
 | 
					        "Symbols `,\"` may be unwanted in %w array literals"
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "properties" do
 | 
				
			||||||
 | 
					      it "allows to configure string_array_unwanted_symbols" do
 | 
				
			||||||
 | 
					        rule = PercentArrays.new
 | 
				
			||||||
 | 
					        rule.string_array_unwanted_symbols = ","
 | 
				
			||||||
 | 
					        s = Source.new %( %w("one") )
 | 
				
			||||||
 | 
					        rule.catch(s).should be_valid
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "allows to configure symbol_array_unwanted_symbols" do
 | 
				
			||||||
 | 
					        rule = PercentArrays.new
 | 
				
			||||||
 | 
					        rule.symbol_array_unwanted_symbols = ","
 | 
				
			||||||
 | 
					        s = Source.new %( %i(:one) )
 | 
				
			||||||
 | 
					        rule.catch(s).should be_valid
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -207,7 +207,7 @@ module Ameba::Rule
 | 
				
			||||||
      error = s.errors.first
 | 
					      error = s.errors.first
 | 
				
			||||||
      error.rule.should_not be_nil
 | 
					      error.rule.should_not be_nil
 | 
				
			||||||
      error.location.to_s.should eq "source.cr:2:9"
 | 
					      error.location.to_s.should eq "source.cr:2:9"
 | 
				
			||||||
      error.message.should eq "Redundant `begin` block detected."
 | 
					      error.message.should eq "Redundant `begin` block detected"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,10 @@ require "../src/ameba"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module Ameba
 | 
					module Ameba
 | 
				
			||||||
  struct DummyRule < Rule::Base
 | 
					  struct DummyRule < Rule::Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description : String = "Dummy rule that does nothing."
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,66 +80,71 @@ class Ameba::Config
 | 
				
			||||||
    Formatter::DotFormatter.new
 | 
					    Formatter::DotFormatter.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # An entity that represents a corresponding configuration for a specific Rule.
 | 
					  # :no_doc:
 | 
				
			||||||
  module Rule
 | 
					  module Rule
 | 
				
			||||||
    # Represents a configuration of a specific Rule.
 | 
					    macro properties(&block)
 | 
				
			||||||
    getter config : YAML::Any?
 | 
					      {% definitions = [] of NamedTuple %}
 | 
				
			||||||
 | 
					      {% if block.body.is_a? Assign %}
 | 
				
			||||||
    # A macro that defines a dsl to define configurable properties.
 | 
					        {% definitions << {var: block.body.target, value: block.body.value} %}
 | 
				
			||||||
    #
 | 
					      {% elsif block.body.is_a? TypeDeclaration %}
 | 
				
			||||||
    # ```
 | 
					        {% definitions << {var: block.body.var, value: block.body.value, type: block.body.type} %}
 | 
				
			||||||
    # class Configurable
 | 
					      {% elsif block.body.is_a? Expressions %}
 | 
				
			||||||
    #   include Ameba::Config::Rule
 | 
					        {% for prop in block.body.expressions %}
 | 
				
			||||||
    #
 | 
					          {% if prop.is_a? Assign %}
 | 
				
			||||||
    #   prop enabled? = false
 | 
					            {% definitions << {var: prop.target, value: prop.value} %}
 | 
				
			||||||
    #   prop max_length = 80
 | 
					          {% elsif prop.is_a? TypeDeclaration %}
 | 
				
			||||||
    #   prop wildcard = "*"
 | 
					            {% definitions << {var: prop.var, value: prop.value, type: prop.type} %}
 | 
				
			||||||
    # end
 | 
					          {% end %}
 | 
				
			||||||
    # ```
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    macro prop(assign)
 | 
					 | 
				
			||||||
      # Rule configuration property.
 | 
					 | 
				
			||||||
      def {{assign.target}}
 | 
					 | 
				
			||||||
        {% prop_name = assign.target.id.camelcase.gsub(/\?/, "") %}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        {% if assign.value.is_a? NumberLiteral %}
 | 
					 | 
				
			||||||
          int_prop "{{prop_name}}", {{assign.value}}
 | 
					 | 
				
			||||||
        {% elsif assign.value.is_a? BoolLiteral %}
 | 
					 | 
				
			||||||
          bool_prop "{{prop_name}}", {{assign.value}}
 | 
					 | 
				
			||||||
        {% elsif assign.value.is_a? StringLiteral %}
 | 
					 | 
				
			||||||
          str_prop "{{prop_name}}", {{assign.value}}
 | 
					 | 
				
			||||||
        {% end %}
 | 
					        {% end %}
 | 
				
			||||||
 | 
					      {% end %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% properties = {} of MacroId => NamedTuple %}
 | 
				
			||||||
 | 
					      {% for df in definitions %}
 | 
				
			||||||
 | 
					        {% name = df[:var].id %}
 | 
				
			||||||
 | 
					        {% key = name.camelcase.stringify %}
 | 
				
			||||||
 | 
					        {% value = df[:value] %}
 | 
				
			||||||
 | 
					        {% type = df[:type] %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {% if type == nil %}
 | 
				
			||||||
 | 
					          {% if value.is_a? BoolLiteral %}
 | 
				
			||||||
 | 
					            {% type = Bool %}
 | 
				
			||||||
 | 
					          {% elsif value.is_a? StringLiteral %}
 | 
				
			||||||
 | 
					            {% type = String %}
 | 
				
			||||||
 | 
					          {% elsif value.is_a? NumberLiteral %}
 | 
				
			||||||
 | 
					            {% if value.kind == :i32 %}
 | 
				
			||||||
 | 
					              {% type = Int32 %}
 | 
				
			||||||
 | 
					            {% elsif value.kind == :i64 %}
 | 
				
			||||||
 | 
					              {% type = Int64 %}
 | 
				
			||||||
 | 
					            {% elsif value.kind == :f32 %}
 | 
				
			||||||
 | 
					              {% type = Float32 %}
 | 
				
			||||||
 | 
					            {% elsif value.kind == :f64 %}
 | 
				
			||||||
 | 
					              {% type = Float64 %}
 | 
				
			||||||
 | 
					            {% end %}
 | 
				
			||||||
 | 
					          {% end %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          {% type = Nil if type == nil %}
 | 
				
			||||||
 | 
					        {% end %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {% properties[name] = {key: key, default: value, type: type} %}
 | 
				
			||||||
 | 
					      {% end %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% if properties["enabled".id] == nil %}
 | 
				
			||||||
 | 
					        {% properties["enabled".id] = {key: "Enabled", default: true, type: Bool} %}
 | 
				
			||||||
 | 
					      {% end %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      YAML.mapping({{properties}})
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    macro included
 | 
				
			||||||
 | 
					      macro inherited
 | 
				
			||||||
 | 
					        # allow creating rules without properties
 | 
				
			||||||
 | 
					        properties {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def self.new(config : Ameba::Config? = nil)
 | 
				
			||||||
 | 
					          yaml = config.try &.subconfig(class_name).try &.to_yaml || "{}"
 | 
				
			||||||
 | 
					          from_yaml yaml
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Creates an instance of a Rule configuration.
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # ```
 | 
					 | 
				
			||||||
    # class Configurable
 | 
					 | 
				
			||||||
    #   include Ameba::Config::Rule
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    #   prop enabled? = false
 | 
					 | 
				
			||||||
    #   prop max_length = 80
 | 
					 | 
				
			||||||
    #   prop wildcard = "*"
 | 
					 | 
				
			||||||
    # end
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # Configurable.new config
 | 
					 | 
				
			||||||
    # ```
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    def initialize(config = nil)
 | 
					 | 
				
			||||||
      @config = config.try &.subconfig(name)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected def int_prop(name, default : Number)
 | 
					 | 
				
			||||||
      str_prop(name, default).to_i
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected def bool_prop(name, default : Bool)
 | 
					 | 
				
			||||||
      str_prop(name, default.to_s) == "true"
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected def str_prop(name, default)
 | 
					 | 
				
			||||||
      config.try &.[name]?.try &.as_s || default
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,10 +27,6 @@ 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Enabled property indicates whether this rule enabled or not.
 | 
					 | 
				
			||||||
    # Only enabled rules will be included into the inspection.
 | 
					 | 
				
			||||||
    prop enabled? = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test(source : Source, node : Crystal::ASTNode)
 | 
					    def test(source : Source, node : Crystal::ASTNode)
 | 
				
			||||||
      # can't be abstract
 | 
					      # can't be abstract
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -59,7 +55,11 @@ module Ameba::Rule
 | 
				
			||||||
    # ```
 | 
					    # ```
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
    def name
 | 
					    def name
 | 
				
			||||||
      self.class.name.gsub("Ameba::Rule::", "")
 | 
					      {{@type}}.class_name
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected def self.class_name
 | 
				
			||||||
 | 
					      name.gsub("Ameba::Rule::", "")
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected def self.subclasses
 | 
					    protected def self.subclasses
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,11 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct ComparisonToBoolean < Base
 | 
					  struct ComparisonToBoolean < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      enabled = false
 | 
				
			||||||
 | 
					      description = "Disallows comparison to booleans"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct ConstantNames < Base
 | 
					  struct ConstantNames < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Enforces constant names to be in screaming case"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct DebuggerStatement < Base
 | 
					  struct DebuggerStatement < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows calls to debugger"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct DuplicatedWhen < Base
 | 
					  struct DuplicatedWhen < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows duplicated when conditions in case"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -38,7 +42,7 @@ module Ameba::Rule
 | 
				
			||||||
    def test(source, node : Crystal::Case)
 | 
					    def test(source, node : Crystal::Case)
 | 
				
			||||||
      return unless duplicated_whens?(node.whens)
 | 
					      return unless duplicated_whens?(node.whens)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      source.error self, node.location, "Duplicated when conditions in case."
 | 
					      source.error self, node.location, "Duplicated when conditions in case"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private def duplicated_whens?(whens)
 | 
					    private def duplicated_whens?(whens)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,6 +33,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct EmptyEnsure < Base
 | 
					  struct EmptyEnsure < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows empty ensure statement"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,10 @@ module Ameba::Rule
 | 
				
			||||||
  struct EmptyExpression < Base
 | 
					  struct EmptyExpression < Base
 | 
				
			||||||
    include AST::Util
 | 
					    include AST::Util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows empty expressions"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct HashDuplicatedKey < Base
 | 
					  struct HashDuplicatedKey < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows duplicated keys in hash literals"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct LargeNumbers < Base
 | 
					  struct LargeNumbers < Base
 | 
				
			||||||
    prop int_min_digits = 5
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows usage of large numbers without underscore"
 | 
				
			||||||
 | 
					      int_min_digits = 5
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      Tokenizer.new(source).run do |token|
 | 
					      Tokenizer.new(source).run do |token|
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,11 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct LineLength < Base
 | 
					  struct LineLength < Base
 | 
				
			||||||
    prop max_length = 80
 | 
					    properties do
 | 
				
			||||||
 | 
					      enabled = false
 | 
				
			||||||
 | 
					      description = "Disallows lines longer than `MaxLength` number of symbols"
 | 
				
			||||||
 | 
					      max_length = 80
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      source.lines.each_with_index do |line, index|
 | 
					      source.lines.each_with_index do |line, index|
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,11 @@ module Ameba::Rule
 | 
				
			||||||
  struct LiteralInCondition < Base
 | 
					  struct LiteralInCondition < Base
 | 
				
			||||||
    include AST::Util
 | 
					    include AST::Util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows useless conditional statements that contain\
 | 
				
			||||||
 | 
					        a literal in place of a variable or predicate function"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,10 @@ module Ameba::Rule
 | 
				
			||||||
  struct LiteralInInterpolation < Base
 | 
					  struct LiteralInInterpolation < Base
 | 
				
			||||||
    include AST::Util
 | 
					    include AST::Util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows useless string interpolations"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,6 +39,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct MethodNames < Base
 | 
					  struct MethodNames < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Enforces method names to be in underscored case"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct NegatedConditionsInUnless < Base
 | 
					  struct NegatedConditionsInUnless < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows negated conditions in unless"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,8 +25,11 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct PercentArrays < Base
 | 
					  struct PercentArrays < Base
 | 
				
			||||||
    prop string_array_unwanted_symbols = ",\""
 | 
					    properties do
 | 
				
			||||||
    prop symbol_array_unwanted_symbols = ",:"
 | 
					      description = "Disallows some unwanted symbols in percent array literals"
 | 
				
			||||||
 | 
					      string_array_unwanted_symbols = ",\""
 | 
				
			||||||
 | 
					      symbol_array_unwanted_symbols = ",:"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      error = start_token = nil
 | 
					      error = start_token = nil
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct PredicateName < Base
 | 
					  struct PredicateName < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows tautological predicate names"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,6 +57,9 @@ module Ameba::Rule
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct RedundantBegin < Base
 | 
					  struct RedundantBegin < Base
 | 
				
			||||||
    include AST::Util
 | 
					    include AST::Util
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows redundant begin blocks"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
| 
						 | 
					@ -65,7 +68,7 @@ module Ameba::Rule
 | 
				
			||||||
    def test(source, node : Crystal::Def)
 | 
					    def test(source, node : Crystal::Def)
 | 
				
			||||||
      return unless redundant_begin?(source, node)
 | 
					      return unless redundant_begin?(source, node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      source.error self, node.location, "Redundant `begin` block detected."
 | 
					      source.error self, node.location, "Redundant `begin` block detected"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private def redundant_begin?(source, node)
 | 
					    private def redundant_begin?(source, node)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct TrailingBlankLines < Base
 | 
					  struct TrailingBlankLines < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows trailing blank lines"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      if source.lines.size > 1 && source.lines[-2, 2].join.strip.empty?
 | 
					      if source.lines.size > 1 && source.lines[-2, 2].join.strip.empty?
 | 
				
			||||||
        source.error self, source.location(source.lines.size, 1),
 | 
					        source.error self, source.location(source.lines.size, 1),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct TrailingWhitespace < Base
 | 
					  struct TrailingWhitespace < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows trailing whitespaces"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      source.lines.each_with_index do |line, index|
 | 
					      source.lines.each_with_index do |line, index|
 | 
				
			||||||
        next unless line =~ /\s$/
 | 
					        next unless line =~ /\s$/
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,6 +53,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct TypeNames < Base
 | 
					  struct TypeNames < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Enforces type names in camelcase manner"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,6 +44,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct UnlessElse < Base
 | 
					  struct UnlessElse < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Disallows the use of an `else` block with the `unless`"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,6 +32,10 @@ module Ameba::Rule
 | 
				
			||||||
  # ```
 | 
					  # ```
 | 
				
			||||||
  #
 | 
					  #
 | 
				
			||||||
  struct VariableNames < Base
 | 
					  struct VariableNames < Base
 | 
				
			||||||
 | 
					    properties do
 | 
				
			||||||
 | 
					      description = "Enforces variable names to be in underscored case"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(source)
 | 
					    def test(source)
 | 
				
			||||||
      AST::Visitor.new self, source
 | 
					      AST::Visitor.new self, source
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,7 +93,7 @@ module Ameba
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private def load_rules(config)
 | 
					    private def load_rules(config)
 | 
				
			||||||
      Rule.rules.map { |r| r.new config }.select &.enabled?
 | 
					      Rule.rules.map { |r| r.new config }.select &.enabled
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue