mirror of
https://gitea.invidious.io/iv-org/shard-ameba.git
synced 2024-08-15 00:53:29 +00:00
Performance improvements (#15)
* Performance improvements Two main changes: 1. Cache parsed AST in a Source. So Parser will parse content only once. 2. Use one unified visitor with multiple callbacks instead of multiple visitors to traverse AST. This improves performance significantly, for example running it on Crystal repository takes ~1 second, which 6 times faster than before. * Change readme example
This commit is contained in:
parent
f27e32cbea
commit
adfe654733
16 changed files with 34 additions and 42 deletions
15
README.md
15
README.md
|
@ -67,22 +67,21 @@ your logic to detect a problem:
|
|||
```crystal
|
||||
struct DebuggerStatement < Rule
|
||||
# This is a required method to be implemented by the rule.
|
||||
# Source will pass here. If rule finds an issue in this source,
|
||||
# it adds an error:
|
||||
# Source will be passed here. If rule finds an issue in this source,
|
||||
# it reports an error:
|
||||
#
|
||||
# source.error rule, line_number, message
|
||||
#
|
||||
def test(source)
|
||||
# This line deletegates verification to a particular AST visitor.
|
||||
AST::CallVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
# This method is called once our visitor finds a required node.
|
||||
#
|
||||
# It reports an error, once there is `debugger` method call
|
||||
# without arguments and a receiver. That's it, somebody forgot
|
||||
# to remove debugger statement.
|
||||
# This method is called once the visitor finds a required 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.try &.line_number,
|
||||
|
|
|
@ -8,7 +8,7 @@ module Ameba::AST
|
|||
{% for name in NODE_VISITORS %}
|
||||
describe "{{name}}" do
|
||||
it "allow to visit {{name}} node" do
|
||||
visitor = {{name}}Visitor.new rule, source
|
||||
visitor = Visitor.new rule, source
|
||||
nodes = Crystal::Parser.new("").parse
|
||||
nodes.accept visitor
|
||||
end
|
||||
|
|
|
@ -19,26 +19,23 @@ module Ameba::AST
|
|||
Var,
|
||||
]
|
||||
|
||||
abstract class Visitor < Crystal::Visitor
|
||||
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
|
||||
@source.ast.accept self
|
||||
end
|
||||
|
||||
def visit(node : Crystal::ASTNode)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
{% for name in NODE_VISITORS %}
|
||||
class {{name}}Visitor < Visitor
|
||||
{% for name in NODE_VISITORS %}
|
||||
def visit(node : Crystal::{{name}})
|
||||
@rule.test @source, node
|
||||
true
|
||||
end
|
||||
end
|
||||
{% end %}
|
||||
{% end %}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ module Ameba
|
|||
abstract def test(source : Source)
|
||||
|
||||
def test(source : Source, node : Crystal::ASTNode)
|
||||
raise "Unimplemented"
|
||||
# can't be abstract
|
||||
end
|
||||
|
||||
def catch(source : Source)
|
||||
|
|
|
@ -14,7 +14,7 @@ module Ameba::Rules
|
|||
#
|
||||
struct ComparisonToBoolean < Rule
|
||||
def test(source)
|
||||
AST::CallVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
def test(source, node : Crystal::Call)
|
||||
|
|
|
@ -17,7 +17,7 @@ module Ameba::Rules
|
|||
#
|
||||
struct ConstantNames < Rule
|
||||
def test(source)
|
||||
AST::AssignVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
def test(source, node : Crystal::Assign)
|
||||
|
|
|
@ -6,7 +6,7 @@ module Ameba::Rules
|
|||
#
|
||||
struct DebuggerStatement < Rule
|
||||
def test(source)
|
||||
AST::CallVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
def test(source, node : Crystal::Call)
|
||||
|
|
|
@ -17,9 +17,7 @@ module Ameba::Rules
|
|||
include AST::Util
|
||||
|
||||
def test(source)
|
||||
AST::IfVisitor.new self, source
|
||||
AST::UnlessVisitor.new self, source
|
||||
AST::CaseVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
def check_node(source, node)
|
||||
|
|
|
@ -13,7 +13,7 @@ module Ameba::Rules
|
|||
include AST::Util
|
||||
|
||||
def test(source)
|
||||
AST::StringInterpolationVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
def test(source, node : Crystal::StringInterpolation)
|
||||
|
|
|
@ -32,7 +32,7 @@ module Ameba::Rules
|
|||
# ```
|
||||
struct MethodNames < Rule
|
||||
def test(source)
|
||||
AST::DefVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
def test(source, node : Crystal::Def)
|
||||
|
|
|
@ -22,7 +22,7 @@ module Ameba::Rules
|
|||
#
|
||||
struct NegatedConditionsInUnless < Rule
|
||||
def test(source)
|
||||
AST::UnlessVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
def test(source, node : Crystal::Unless)
|
||||
|
|
|
@ -24,7 +24,7 @@ module Ameba::Rules
|
|||
#
|
||||
struct PredicateName < Rule
|
||||
def test(source)
|
||||
AST::DefVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
def test(source, node : Crystal::Def)
|
||||
|
|
|
@ -47,13 +47,7 @@ module Ameba::Rules
|
|||
#
|
||||
struct TypeNames < Rule
|
||||
def test(source)
|
||||
[
|
||||
AST::ClassDefVisitor,
|
||||
AST::EnumDefVisitor,
|
||||
AST::ModuleDefVisitor,
|
||||
AST::AliasVisitor,
|
||||
AST::LibDefVisitor,
|
||||
].each(&.new self, source)
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
private def check_node(source, node)
|
||||
|
|
|
@ -38,7 +38,7 @@ module Ameba::Rules
|
|||
#
|
||||
struct UnlessElse < Rule
|
||||
def test(source)
|
||||
AST::UnlessVisitor.new self, source
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
def test(source, node : Crystal::Unless)
|
||||
|
|
|
@ -26,11 +26,7 @@ module Ameba::Rules
|
|||
#
|
||||
struct VariableNames < Rule
|
||||
def test(source)
|
||||
[
|
||||
AST::VarVisitor,
|
||||
AST::InstanceVarVisitor,
|
||||
AST::ClassVarVisitor,
|
||||
].each &.new(self, source)
|
||||
AST::Visitor.new self, source
|
||||
end
|
||||
|
||||
private def check_node(source, node)
|
||||
|
|
|
@ -15,6 +15,7 @@ module Ameba
|
|||
getter errors = [] of Error
|
||||
getter path : String?
|
||||
getter content : String
|
||||
getter ast : Crystal::ASTNode?
|
||||
|
||||
def initialize(@content : String, @path = nil)
|
||||
end
|
||||
|
@ -30,5 +31,12 @@ module Ameba
|
|||
def lines
|
||||
@lines ||= @content.split("\n")
|
||||
end
|
||||
|
||||
def ast
|
||||
@ast ||=
|
||||
Crystal::Parser.new(content)
|
||||
.tap { |parser| parser.filename = @path }
|
||||
.parse
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue