New rule: type names

This commit is contained in:
Vitalii Elenhaupt 2017-11-04 18:53:21 +02:00
parent e383ec17c2
commit 20fd53682f
No known key found for this signature in database
GPG Key ID: 7558EF3A4056C706
3 changed files with 169 additions and 14 deletions

View File

@ -0,0 +1,60 @@
require "../../spec_helper"
module Ameba
subject = Rules::TypeNames.new
private def it_reports_name(content, expected)
it "reports type name #{expected}" do
s = Source.new content
Rules::TypeNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected
end
end
describe Rules::TypeNames do
it "passes if type names are camelcased" do
s = Source.new %(
class ParseError < Exception
end
module HTTP
class RequestHandler
end
end
alias NumericValue = Float32 | Float64 | Int32 | Int64
lib LibYAML
end
struct TagDirective
end
enum Time::DayOfWeek
end
)
subject.catch(s).should be_valid
end
it_reports_name "class My_class; end", "MyClass"
it_reports_name "module HTT_p; end", "HTTP"
it_reports_name "alias Numeric_value = Int32", "NumericValue"
it_reports_name "lib Lib_YAML; end", "LibYAML"
it_reports_name "struct Tag_directive; end", "TagDirective"
it_reports_name "enum Time_enum::Day_of_week; end", "TimeEnum::DayOfWeek"
it "reports rule, pos and message" do
s = Source.new %(
class My_class
end
)
subject.catch(s).should_not be_valid
error = s.errors.first
error.rule.should_not be_nil
error.pos.should eq 2
error.message.should eq(
"Type name should be camelcased: MyClass, but it was My_class"
)
end
end
end

View File

@ -2,29 +2,36 @@ require "compiler/crystal/syntax/*"
module Ameba::AST
NODE_VISITORS = [
Alias,
Call,
Case,
ClassDef,
Def,
EnumDef,
If,
LibDef,
ModuleDef,
StringInterpolation,
Unless,
]
abstract class Visitor < Crystal::Visitor
@rule : Rule
@source : Source
def initialize(@rule, @source)
parser = Crystal::Parser.new(@source.content)
parser.filename = @source.path
parser.parse.accept self
end
def visit(node : Crystal::ASTNode)
true
end
end
{% for name in NODE_VISITORS %}
class {{name}}Visitor < Crystal::Visitor
@rule : Rule
@source : Source
def initialize(@rule, @source)
parser = Crystal::Parser.new(@source.content)
parser.filename = @source.path
parser.parse.accept self
end
def visit(node : Crystal::ASTNode)
true
end
class {{name}}Visitor < Visitor
def visit(node : Crystal::{{name}})
@rule.test @source, node
end

View File

@ -0,0 +1,88 @@
module Ameba::Rules
# A rule that enforces type names in camelcase manner.
#
# For example, these are considered valid:
#
# ```
# class ParseError < Exception
# end
#
# module HTTP
# class RequestHandler
# end
# end
#
# alias NumericValue = Float32 | Float64 | Int32 | Int64
#
# lib LibYAML
# end
#
# struct TagDirective
# end
#
# enum Time::DayOfWeek
# end
# ```
#
# And these are invalid type names
#
# ```
# class My_class
# end
#
# module HTT_p
# end
#
# alias Numeric_value = Int32
#
# lib Lib_YAML
# end
#
# struct Tag_directive
# end
#
# enum Time_enum::Day_of_week
# end
# ```
#
struct TypeNames < Rule
def test(source)
[
AST::ClassDefVisitor,
AST::EnumDefVisitor,
AST::ModuleDefVisitor,
AST::AliasVisitor,
AST::LibDefVisitor,
].each(&.new self, source)
end
private def check_node(source, node)
name = node.name.to_s
expected = name.camelcase
return if expected == name
source.error self, node.location.try &.line_number,
"Type name should be camelcased: #{expected}, but it was #{name}"
end
def test(source, node : Crystal::ClassDef)
check_node(source, node)
end
def test(source, node : Crystal::Alias)
check_node(source, node)
end
def test(source, node : Crystal::LibDef)
check_node(source, node)
end
def test(source, node : Crystal::EnumDef)
check_node(source, node)
end
def test(source, node : Crystal::ModuleDef)
check_node(source, node)
end
end
end