Ameba::Rule -> Ameba::Rule::Base

This commit is contained in:
Vitalii Elenhaupt 2017-11-07 23:50:25 +02:00
parent b6c0d3e8ad
commit 80e2ab4f55
No known key found for this signature in database
GPG key ID: 7558EF3A4056C706
39 changed files with 79 additions and 73 deletions

View file

@ -65,11 +65,11 @@ Finished in 10.53 milliseconds
## Write a new Rule ## Write a new Rule
Adding a new rule is as simple as inheriting from `Rule` struct and implementing Adding a new rule is as simple as inheriting from `Rule::Base` struct and implementing
your logic to detect a problem: your logic to detect a problem:
```crystal ```crystal
struct DebuggerStatement < Rule struct DebuggerStatement < 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 finds an issue in this source,
# it reports an error: # it reports an error:

View file

@ -1,7 +1,7 @@
require "../../spec/spec_helper" require "../../spec/spec_helper"
module Ameba module Ameba
describe Rule do describe Rule::Base do
describe "#catch" do describe "#catch" do
it "accepts and returns source" do it "accepts and returns source" do
s = Source.new "", "" s = Source.new "", ""
@ -14,7 +14,9 @@ module Ameba
DummyRule.new.name.should eq "Ameba::DummyRule" DummyRule.new.name.should eq "Ameba::DummyRule"
end end
end end
end
describe Rule do
describe ".rules" do describe ".rules" do
it "returns a list of all defined rules" do it "returns a list of all defined rules" do
Rule.rules.includes?(DummyRule).should be_true Rule.rules.includes?(DummyRule).should be_true

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = ComparisonToBoolean.new subject = ComparisonToBoolean.new
describe ComparisonToBoolean do describe ComparisonToBoolean do

View file

@ -1,17 +1,17 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba module Ameba
subject = Rules::ConstantNames.new subject = Rule::ConstantNames.new
private def it_reports_constant(code, expected) private def it_reports_constant(code, expected)
it "reports constant name #{expected}" do it "reports constant name #{expected}" do
s = Source.new code s = Source.new code
Rules::ConstantNames.new.catch(s).should_not be_valid Rule::ConstantNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.errors.first.message.should contain expected
end end
end end
describe Rules::ConstantNames do describe Rule::ConstantNames do
it "passes if type names are screaming-cased" do it "passes if type names are screaming-cased" do
s = Source.new %( s = Source.new %(
LUCKY_NUMBERS = [3, 7, 11] LUCKY_NUMBERS = [3, 7, 11]

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = DebuggerStatement.new subject = DebuggerStatement.new
describe DebuggerStatement do describe DebuggerStatement do

View file

@ -1,17 +1,17 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba module Ameba
subject = Rules::EmptyExpression.new subject = Rule::EmptyExpression.new
def it_detects_empty_expression(code) def it_detects_empty_expression(code)
it "detects empty expression" do it "detects empty expression" do
s = Source.new code s = Source.new code
rule = Rules::EmptyExpression.new rule = Rule::EmptyExpression.new
rule.catch(s).should_not be_valid rule.catch(s).should_not be_valid
end end
end end
describe Rules::EmptyExpression do describe Rule::EmptyExpression do
it "passes if there is no empty expression" do it "passes if there is no empty expression" do
s = Source.new %( s = Source.new %(
def method() def method()

View file

@ -1,17 +1,17 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba module Ameba
subject = Rules::LargeNumbers.new subject = Rule::LargeNumbers.new
private def it_transforms(number, expected) private def it_transforms(number, expected)
it "transforms large number #{number}" do it "transforms large number #{number}" do
s = Source.new number s = Source.new number
Rules::LargeNumbers.new.catch(s).should_not be_valid Rule::LargeNumbers.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.errors.first.message.should contain expected
end end
end end
describe Rules::LargeNumbers do describe Rule::LargeNumbers do
it "passes if large number does not require underscore" do it "passes if large number does not require underscore" do
s = Source.new %q( s = Source.new %q(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = LineLength.new subject = LineLength.new
long_line = "*" * 81 long_line = "*" * 81

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = LiteralInCondition.new subject = LiteralInCondition.new
describe LiteralInCondition do describe LiteralInCondition do

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = LiteralInInterpolation.new subject = LiteralInInterpolation.new
describe LiteralInInterpolation do describe LiteralInInterpolation do

View file

@ -1,17 +1,17 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba module Ameba
subject = Rules::MethodNames.new subject = Rule::MethodNames.new
private def it_reports_method_name(code, expected) private def it_reports_method_name(code, expected)
it "reports method name #{expected}" do it "reports method name #{expected}" do
s = Source.new code s = Source.new code
Rules::MethodNames.new.catch(s).should_not be_valid Rule::MethodNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.errors.first.message.should contain expected
end end
end end
describe Rules::MethodNames do describe Rule::MethodNames do
it "passes if method names are underscore-cased" do it "passes if method names are underscore-cased" do
s = Source.new %( s = Source.new %(
class Person class Person

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = NegatedConditionsInUnless.new subject = NegatedConditionsInUnless.new
describe NegatedConditionsInUnless do describe NegatedConditionsInUnless do

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = PredicateName.new subject = PredicateName.new
describe PredicateName do describe PredicateName do

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = TrailingBlankLines.new subject = TrailingBlankLines.new
describe TrailingBlankLines do describe TrailingBlankLines do

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = TrailingWhitespace.new subject = TrailingWhitespace.new
describe TrailingWhitespace do describe TrailingWhitespace do

View file

@ -1,17 +1,17 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba module Ameba
subject = Rules::TypeNames.new subject = Rule::TypeNames.new
private def it_reports_name(code, expected) private def it_reports_name(code, expected)
it "reports type name #{expected}" do it "reports type name #{expected}" do
s = Source.new code s = Source.new code
Rules::TypeNames.new.catch(s).should_not be_valid Rule::TypeNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.errors.first.message.should contain expected
end end
end end
describe Rules::TypeNames do describe Rule::TypeNames do
it "passes if type names are camelcased" do it "passes if type names are camelcased" do
s = Source.new %( s = Source.new %(
class ParseError < Exception class ParseError < Exception

View file

@ -1,6 +1,6 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba::Rules module Ameba::Rule
subject = UnlessElse.new subject = UnlessElse.new
describe UnlessElse do describe UnlessElse do

View file

@ -1,17 +1,17 @@
require "../../spec_helper" require "../../spec_helper"
module Ameba module Ameba
subject = Rules::VariableNames.new subject = Rule::VariableNames.new
private def it_reports_var_name(code, expected) private def it_reports_var_name(code, expected)
it "reports method name #{expected}" do it "reports method name #{expected}" do
s = Source.new code s = Source.new code
Rules::VariableNames.new.catch(s).should_not be_valid Rule::VariableNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.errors.first.message.should contain expected
end end
end end
describe Rules::VariableNames do describe Rule::VariableNames do
it "passes if var names are underscore-cased" do it "passes if var names are underscore-cased" do
s = Source.new %( s = Source.new %(
class Greeting class Greeting

View file

@ -2,7 +2,7 @@ require "spec"
require "../src/ameba" require "../src/ameba"
module Ameba module Ameba
struct DummyRule < Ameba::Rule struct DummyRule < Rule::Base
def test(source) def test(source)
end end
end end

View file

@ -1,6 +1,6 @@
require "./ameba/*" require "./ameba/*"
require "./ameba/ast/*" require "./ameba/ast/*"
require "./ameba/rules/*" require "./ameba/rule/*"
require "./ameba/formatter/*" require "./ameba/formatter/*"
module Ameba module Ameba

View file

@ -21,7 +21,7 @@ module Ameba::AST
] ]
class Visitor < Crystal::Visitor class Visitor < Crystal::Visitor
@rule : Rule @rule : Rule::Base
@source : Source @source : Source
def initialize(@rule, @source) def initialize(@rule, @source)

View file

@ -1,5 +1,5 @@
module Ameba module Ameba::Rule
abstract struct Rule abstract struct Base
abstract def test(source : Source) abstract def test(source : Source)
def test(source : Source, node : Crystal::ASTNode) def test(source : Source, node : Crystal::ASTNode)
@ -11,11 +11,15 @@ module Ameba
end end
def name def name
self.class.name.gsub("Ameba::Rules::", "") self.class.name.gsub("Ameba::Rule::", "")
end end
def self.rules protected def self.subclasses
{{ @type.subclasses }} {{ @type.subclasses }}
end end
end end
def self.rules
Base.subclasses
end
end end

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows comparison to booleans. # A rule that disallows comparison to booleans.
# #
# For example, these are considered invalid: # For example, these are considered invalid:
@ -12,7 +12,7 @@ module Ameba::Rules
# could get the same result by using either the variable directly, # could get the same result by using either the variable directly,
# or negating the variable. # or negating the variable.
# #
struct ComparisonToBoolean < Rule struct ComparisonToBoolean < Base
def test(source) def test(source)
AST::Visitor.new self, source AST::Visitor.new self, source
end end

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that enforces constant names to be in screaming case. # A rule that enforces constant names to be in screaming case.
# #
# For example, these constant names are considered valid: # For example, these constant names are considered valid:
@ -15,7 +15,7 @@ module Ameba::Rules
# Wrong_NAME = 2 # Wrong_NAME = 2
# ``` # ```
# #
struct ConstantNames < Rule struct ConstantNames < Base
def test(source) def test(source)
AST::Visitor.new self, source AST::Visitor.new self, source
end end

View file

@ -1,10 +1,10 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows calls to debugger. # A rule that disallows calls to debugger.
# #
# This is because we don't want debugger breakpoints accidentally being # This is because we don't want debugger breakpoints accidentally being
# committed into our codebase. # committed into our codebase.
# #
struct DebuggerStatement < Rule struct DebuggerStatement < Base
def test(source) def test(source)
AST::Visitor.new self, source AST::Visitor.new self, source
end end

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows empty expressions. # A rule that disallows empty expressions.
# #
# This is considered invalid: # This is considered invalid:
@ -21,7 +21,7 @@ module Ameba::Rules
# end # end
# ``` # ```
# #
struct EmptyExpression < Rule struct EmptyExpression < Base
include AST::Util include AST::Util
def test(source) def test(source)

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows usage of large numbers without underscore. # A rule that disallows usage of large numbers without underscore.
# These do not affect the value of the number, but can help read # These do not affect the value of the number, but can help read
# large numbers more easily. # large numbers more easily.
@ -19,7 +19,7 @@ module Ameba::Rules
# 5.123_45 # 5.123_45
# ``` # ```
# #
struct LargeNumbers < Rule struct LargeNumbers < Base
def test(source) def test(source)
Tokenizer.new(source).run do |token| Tokenizer.new(source).run do |token|
next unless token.type == :NUMBER && decimal?(token.raw) next unless token.type == :NUMBER && decimal?(token.raw)

View file

@ -1,7 +1,7 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows lines longer than 80 symbols. # A rule that disallows lines longer than 80 symbols.
# #
struct LineLength < Rule struct LineLength < Base
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.size > 80 next unless line.size > 80

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows useless conditional statements that contain a literal # A rule that disallows useless conditional statements that contain a literal
# in place of a variable or predicate function. # in place of a variable or predicate function.
# #
@ -13,7 +13,7 @@ module Ameba::Rules
# end # end
# ``` # ```
# #
struct LiteralInCondition < Rule struct LiteralInCondition < Base
include AST::Util include AST::Util
def test(source) def test(source)

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows useless string interpolations # A rule that disallows useless string interpolations
# that contain a literal value instead of a variable or function. # that contain a literal value instead of a variable or function.
# #
@ -9,7 +9,7 @@ module Ameba::Rules
# "There are #{4} cats" # "There are #{4} cats"
# ``` # ```
# #
struct LiteralInInterpolation < Rule struct LiteralInInterpolation < Base
include AST::Util include AST::Util
def test(source) def test(source)

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that enforces method names to be in underscored case. # A rule that enforces method names to be in underscored case.
# #
# For example, these are considered valid: # For example, these are considered valid:
@ -30,7 +30,7 @@ module Ameba::Rules
# end # end
# end # end
# ``` # ```
struct MethodNames < Rule struct MethodNames < Base
def test(source) def test(source)
AST::Visitor.new self, source AST::Visitor.new self, source
end end

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows negated conditions in unless. # A rule that disallows negated conditions in unless.
# #
# For example, this is considered invalid: # For example, this is considered invalid:
@ -20,7 +20,7 @@ module Ameba::Rules
# It is pretty difficult to wrap your head around a block of code # It is pretty difficult to wrap your head around a block of code
# that is executed if a negated condition is NOT met. # that is executed if a negated condition is NOT met.
# #
struct NegatedConditionsInUnless < Rule struct NegatedConditionsInUnless < Base
def test(source) def test(source)
AST::Visitor.new self, source AST::Visitor.new self, source
end end

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows tautological predicate names, meaning those that # A rule that disallows tautological predicate names, meaning those that
# start with the prefix `has_` or the prefix `is_`. # start with the prefix `has_` or the prefix `is_`.
# #
@ -22,7 +22,7 @@ module Ameba::Rules
# end # end
# ``` # ```
# #
struct PredicateName < Rule struct PredicateName < Base
def test(source) def test(source)
AST::Visitor.new self, source AST::Visitor.new self, source
end end

View file

@ -1,7 +1,7 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows trailing blank lines at the end of the source file. # A rule that disallows trailing blank lines at the end of the source file.
# #
struct TrailingBlankLines < Rule struct TrailingBlankLines < Base
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),

View file

@ -1,7 +1,7 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows trailing whitespaces. # A rule that disallows trailing whitespaces.
# #
struct TrailingWhitespace < Rule struct TrailingWhitespace < Base
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$/

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that enforces type names in camelcase manner. # A rule that enforces type names in camelcase manner.
# #
# For example, these are considered valid: # For example, these are considered valid:
@ -45,7 +45,7 @@ module Ameba::Rules
# end # end
# ``` # ```
# #
struct TypeNames < Rule struct TypeNames < Base
def test(source) def test(source)
AST::Visitor.new self, source AST::Visitor.new self, source
end end

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that disallows the use of an `else` block with the `unless`. # A rule that disallows the use of an `else` block with the `unless`.
# #
# For example, the rule considers these valid: # For example, the rule considers these valid:
@ -36,7 +36,7 @@ module Ameba::Rules
# end # end
# ``` # ```
# #
struct UnlessElse < Rule struct UnlessElse < Base
def test(source) def test(source)
AST::Visitor.new self, source AST::Visitor.new self, source
end end

View file

@ -1,4 +1,4 @@
module Ameba::Rules module Ameba::Rule
# A rule that enforces variable names to be in underscored case. # A rule that enforces variable names to be in underscored case.
# #
# For example, these variable names are considered valid: # For example, these variable names are considered valid:
@ -24,7 +24,7 @@ module Ameba::Rules
# wrong_Name = 2 # wrong_Name = 2
# ``` # ```
# #
struct VariableNames < Rule struct VariableNames < Base
def test(source) def test(source)
AST::Visitor.new self, source AST::Visitor.new self, source
end end

View file

@ -5,9 +5,9 @@ module Ameba
# Represents an error caught by Ameba. # Represents an error caught by Ameba.
# #
# Each error has the rule that created this error, # Each error has the rule that created this error,
# position of the error and a message. # location of the issue and a message.
record Error, record Error,
rule : Rule, rule : Rule::Base,
location : Crystal::Location?, location : Crystal::Location?,
message : String message : String
@ -20,7 +20,7 @@ module Ameba
def initialize(@code : String, @path = nil) def initialize(@code : String, @path = nil)
end end
def error(rule : Rule, location, message : String) def error(rule : Rule::Base, location, message : String)
errors << Error.new rule, location, message errors << Error.new rule, location, message
end end