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
|
```crystal
|
||||||
struct DebuggerStatement < Rule
|
struct DebuggerStatement < Rule
|
||||||
# This is a required method to be implemented by the rule.
|
# This is a required method to be implemented by the rule.
|
||||||
# Source will pass here. If rule finds an issue in this source,
|
# Source will be passed here. If rule finds an issue in this source,
|
||||||
# it adds an error:
|
# it reports an error:
|
||||||
#
|
#
|
||||||
# source.error rule, line_number, message
|
# source.error rule, line_number, message
|
||||||
#
|
#
|
||||||
def test(source)
|
def test(source)
|
||||||
# This line deletegates verification to a particular AST visitor.
|
# This line deletegates verification to a particular AST visitor.
|
||||||
AST::CallVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
end
|
end
|
||||||
|
|
||||||
# This method is called once our visitor finds a required node.
|
# This method is called once the 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.
|
|
||||||
def test(source, node : Crystal::Call)
|
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?
|
return unless node.name == "debugger" && node.args.empty? && node.obj.nil?
|
||||||
|
|
||||||
source.error self, node.location.try &.line_number,
|
source.error self, node.location.try &.line_number,
|
||||||
|
|
|
@ -8,7 +8,7 @@ module Ameba::AST
|
||||||
{% for name in NODE_VISITORS %}
|
{% for name in NODE_VISITORS %}
|
||||||
describe "{{name}}" do
|
describe "{{name}}" do
|
||||||
it "allow to visit {{name}} node" 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 = Crystal::Parser.new("").parse
|
||||||
nodes.accept visitor
|
nodes.accept visitor
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,26 +19,23 @@ module Ameba::AST
|
||||||
Var,
|
Var,
|
||||||
]
|
]
|
||||||
|
|
||||||
abstract class Visitor < Crystal::Visitor
|
class Visitor < Crystal::Visitor
|
||||||
@rule : Rule
|
@rule : Rule
|
||||||
@source : Source
|
@source : Source
|
||||||
|
|
||||||
def initialize(@rule, @source)
|
def initialize(@rule, @source)
|
||||||
parser = Crystal::Parser.new(@source.content)
|
@source.ast.accept self
|
||||||
parser.filename = @source.path
|
|
||||||
parser.parse.accept self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit(node : Crystal::ASTNode)
|
def visit(node : Crystal::ASTNode)
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
{% for name in NODE_VISITORS %}
|
{% for name in NODE_VISITORS %}
|
||||||
class {{name}}Visitor < Visitor
|
|
||||||
def visit(node : Crystal::{{name}})
|
def visit(node : Crystal::{{name}})
|
||||||
@rule.test @source, node
|
@rule.test @source, node
|
||||||
|
true
|
||||||
end
|
end
|
||||||
end
|
{% end %}
|
||||||
{% end %}
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@ module Ameba
|
||||||
abstract def test(source : Source)
|
abstract def test(source : Source)
|
||||||
|
|
||||||
def test(source : Source, node : Crystal::ASTNode)
|
def test(source : Source, node : Crystal::ASTNode)
|
||||||
raise "Unimplemented"
|
# can't be abstract
|
||||||
end
|
end
|
||||||
|
|
||||||
def catch(source : Source)
|
def catch(source : Source)
|
||||||
|
|
|
@ -14,7 +14,7 @@ module Ameba::Rules
|
||||||
#
|
#
|
||||||
struct ComparisonToBoolean < Rule
|
struct ComparisonToBoolean < Rule
|
||||||
def test(source)
|
def test(source)
|
||||||
AST::CallVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
end
|
end
|
||||||
|
|
||||||
def test(source, node : Crystal::Call)
|
def test(source, node : Crystal::Call)
|
||||||
|
|
|
@ -17,7 +17,7 @@ module Ameba::Rules
|
||||||
#
|
#
|
||||||
struct ConstantNames < Rule
|
struct ConstantNames < Rule
|
||||||
def test(source)
|
def test(source)
|
||||||
AST::AssignVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
end
|
end
|
||||||
|
|
||||||
def test(source, node : Crystal::Assign)
|
def test(source, node : Crystal::Assign)
|
||||||
|
|
|
@ -6,7 +6,7 @@ module Ameba::Rules
|
||||||
#
|
#
|
||||||
struct DebuggerStatement < Rule
|
struct DebuggerStatement < Rule
|
||||||
def test(source)
|
def test(source)
|
||||||
AST::CallVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
end
|
end
|
||||||
|
|
||||||
def test(source, node : Crystal::Call)
|
def test(source, node : Crystal::Call)
|
||||||
|
|
|
@ -17,9 +17,7 @@ module Ameba::Rules
|
||||||
include AST::Util
|
include AST::Util
|
||||||
|
|
||||||
def test(source)
|
def test(source)
|
||||||
AST::IfVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
AST::UnlessVisitor.new self, source
|
|
||||||
AST::CaseVisitor.new self, source
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_node(source, node)
|
def check_node(source, node)
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Ameba::Rules
|
||||||
include AST::Util
|
include AST::Util
|
||||||
|
|
||||||
def test(source)
|
def test(source)
|
||||||
AST::StringInterpolationVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
end
|
end
|
||||||
|
|
||||||
def test(source, node : Crystal::StringInterpolation)
|
def test(source, node : Crystal::StringInterpolation)
|
||||||
|
|
|
@ -32,7 +32,7 @@ module Ameba::Rules
|
||||||
# ```
|
# ```
|
||||||
struct MethodNames < Rule
|
struct MethodNames < Rule
|
||||||
def test(source)
|
def test(source)
|
||||||
AST::DefVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
end
|
end
|
||||||
|
|
||||||
def test(source, node : Crystal::Def)
|
def test(source, node : Crystal::Def)
|
||||||
|
|
|
@ -22,7 +22,7 @@ module Ameba::Rules
|
||||||
#
|
#
|
||||||
struct NegatedConditionsInUnless < Rule
|
struct NegatedConditionsInUnless < Rule
|
||||||
def test(source)
|
def test(source)
|
||||||
AST::UnlessVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
end
|
end
|
||||||
|
|
||||||
def test(source, node : Crystal::Unless)
|
def test(source, node : Crystal::Unless)
|
||||||
|
|
|
@ -24,7 +24,7 @@ module Ameba::Rules
|
||||||
#
|
#
|
||||||
struct PredicateName < Rule
|
struct PredicateName < Rule
|
||||||
def test(source)
|
def test(source)
|
||||||
AST::DefVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
end
|
end
|
||||||
|
|
||||||
def test(source, node : Crystal::Def)
|
def test(source, node : Crystal::Def)
|
||||||
|
|
|
@ -47,13 +47,7 @@ module Ameba::Rules
|
||||||
#
|
#
|
||||||
struct TypeNames < Rule
|
struct TypeNames < Rule
|
||||||
def test(source)
|
def test(source)
|
||||||
[
|
AST::Visitor.new self, source
|
||||||
AST::ClassDefVisitor,
|
|
||||||
AST::EnumDefVisitor,
|
|
||||||
AST::ModuleDefVisitor,
|
|
||||||
AST::AliasVisitor,
|
|
||||||
AST::LibDefVisitor,
|
|
||||||
].each(&.new self, source)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private def check_node(source, node)
|
private def check_node(source, node)
|
||||||
|
|
|
@ -38,7 +38,7 @@ module Ameba::Rules
|
||||||
#
|
#
|
||||||
struct UnlessElse < Rule
|
struct UnlessElse < Rule
|
||||||
def test(source)
|
def test(source)
|
||||||
AST::UnlessVisitor.new self, source
|
AST::Visitor.new self, source
|
||||||
end
|
end
|
||||||
|
|
||||||
def test(source, node : Crystal::Unless)
|
def test(source, node : Crystal::Unless)
|
||||||
|
|
|
@ -26,11 +26,7 @@ module Ameba::Rules
|
||||||
#
|
#
|
||||||
struct VariableNames < Rule
|
struct VariableNames < Rule
|
||||||
def test(source)
|
def test(source)
|
||||||
[
|
AST::Visitor.new self, source
|
||||||
AST::VarVisitor,
|
|
||||||
AST::InstanceVarVisitor,
|
|
||||||
AST::ClassVarVisitor,
|
|
||||||
].each &.new(self, source)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private def check_node(source, node)
|
private def check_node(source, node)
|
||||||
|
|
|
@ -15,6 +15,7 @@ module Ameba
|
||||||
getter errors = [] of Error
|
getter errors = [] of Error
|
||||||
getter path : String?
|
getter path : String?
|
||||||
getter content : String
|
getter content : String
|
||||||
|
getter ast : Crystal::ASTNode?
|
||||||
|
|
||||||
def initialize(@content : String, @path = nil)
|
def initialize(@content : String, @path = nil)
|
||||||
end
|
end
|
||||||
|
@ -30,5 +31,12 @@ module Ameba
|
||||||
def lines
|
def lines
|
||||||
@lines ||= @content.split("\n")
|
@lines ||= @content.split("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ast
|
||||||
|
@ast ||=
|
||||||
|
Crystal::Parser.new(content)
|
||||||
|
.tap { |parser| parser.filename = @path }
|
||||||
|
.parse
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue