2017-10-26 16:46:58 +00:00
|
|
|
require "spec"
|
|
|
|
require "../src/ameba"
|
2021-10-22 17:54:39 +00:00
|
|
|
require "../src/ameba/spec/support"
|
2017-10-30 20:00:01 +00:00
|
|
|
|
2017-11-01 20:05:41 +00:00
|
|
|
module Ameba
|
2018-12-08 20:52:32 +00:00
|
|
|
# Dummy Rule which does nothing.
|
2021-01-18 15:45:35 +00:00
|
|
|
class DummyRule < Rule::Base
|
2017-11-22 06:44:29 +00:00
|
|
|
properties do
|
|
|
|
description : String = "Dummy rule that does nothing."
|
|
|
|
end
|
|
|
|
|
2017-11-01 20:05:41 +00:00
|
|
|
def test(source)
|
|
|
|
end
|
2017-10-30 20:00:01 +00:00
|
|
|
end
|
2017-11-30 21:50:07 +00:00
|
|
|
|
2021-01-18 15:45:35 +00:00
|
|
|
class NamedRule < Rule::Base
|
2018-01-29 22:25:36 +00:00
|
|
|
properties do
|
2020-06-15 11:19:23 +00:00
|
|
|
description "A rule with a custom name."
|
2018-01-29 22:25:36 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.name
|
|
|
|
"BreakingRule"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-01-18 15:45:35 +00:00
|
|
|
class ErrorRule < Rule::Base
|
2018-12-27 21:34:10 +00:00
|
|
|
properties do
|
2020-06-15 11:19:23 +00:00
|
|
|
description "Always adds an error at 1:1"
|
2018-12-27 21:34:10 +00:00
|
|
|
end
|
|
|
|
|
2017-11-30 21:50:07 +00:00
|
|
|
def test(source)
|
2018-06-10 21:15:12 +00:00
|
|
|
issue_for({1, 1}, "This rule always adds an error")
|
2017-11-30 21:50:07 +00:00
|
|
|
end
|
|
|
|
end
|
2017-11-01 20:05:41 +00:00
|
|
|
|
2021-01-18 15:45:35 +00:00
|
|
|
class ScopeRule < Rule::Base
|
2020-06-15 11:19:23 +00:00
|
|
|
@[YAML::Field(ignore: true)]
|
2018-05-03 15:57:47 +00:00
|
|
|
getter scopes = [] of AST::Scope
|
|
|
|
|
2020-06-15 11:19:23 +00:00
|
|
|
properties do
|
|
|
|
description "Internal rule to test scopes"
|
2018-05-03 15:57:47 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node : Crystal::ASTNode, scope : AST::Scope)
|
|
|
|
@scopes << scope
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-01-18 15:45:35 +00:00
|
|
|
class FlowExpressionRule < Rule::Base
|
2020-06-15 11:19:23 +00:00
|
|
|
@[YAML::Field(ignore: true)]
|
2018-11-05 18:56:54 +00:00
|
|
|
getter expressions = [] of AST::FlowExpression
|
|
|
|
|
2020-06-15 11:19:23 +00:00
|
|
|
properties do
|
|
|
|
description "Internal rule to test flow expressions"
|
2018-11-05 18:56:54 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node, flow_expression : AST::FlowExpression)
|
|
|
|
@expressions << flow_expression
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-01-18 15:45:35 +00:00
|
|
|
class RedundantControlExpressionRule < Rule::Base
|
2020-06-15 11:19:23 +00:00
|
|
|
@[YAML::Field(ignore: true)]
|
2020-03-24 16:01:23 +00:00
|
|
|
getter nodes = [] of Crystal::ASTNode
|
|
|
|
|
2020-06-15 11:19:23 +00:00
|
|
|
properties do
|
|
|
|
description "Internal rule to test redundant control expressions"
|
2020-03-24 16:01:23 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node, visitor : AST::RedundantControlExpressionVisitor)
|
|
|
|
nodes << node
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-11-09 17:31:41 +00:00
|
|
|
# A rule that always raises an error
|
2021-01-18 15:45:35 +00:00
|
|
|
class RaiseRule < Rule::Base
|
2019-11-09 17:31:41 +00:00
|
|
|
property should_raise = false
|
|
|
|
|
2020-06-15 11:19:23 +00:00
|
|
|
properties do
|
|
|
|
description "Internal rule that always raises"
|
|
|
|
end
|
|
|
|
|
2019-11-09 17:31:41 +00:00
|
|
|
def test(source)
|
|
|
|
should_raise && raise "something went wrong"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-04-15 14:26:49 +00:00
|
|
|
class PerfRule < Rule::Performance::Base
|
|
|
|
properties do
|
|
|
|
description : String = "Sample performance rule"
|
|
|
|
end
|
|
|
|
|
|
|
|
def test(source)
|
|
|
|
issue_for({1, 1}, "Poor performance")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-10-26 20:05:22 +00:00
|
|
|
class AtoAA < Rule::Base
|
|
|
|
include AST::Util
|
|
|
|
|
|
|
|
properties do
|
|
|
|
description "This rule is only used to test infinite loop detection"
|
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)
|
|
|
|
return unless (name = node_source(node.name, source.lines))
|
|
|
|
return unless name.includes?("A")
|
|
|
|
|
|
|
|
issue_for(node.name, message: "A to AA") do |corrector|
|
|
|
|
corrector.replace(node.name, name.sub("A", "AA"))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class AtoB < Rule::Base
|
|
|
|
include AST::Util
|
|
|
|
|
|
|
|
properties do
|
|
|
|
description "This rule is only used to test infinite loop detection"
|
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)
|
|
|
|
return unless (name = node_source(node.name, source.lines))
|
|
|
|
return unless name.includes?("A")
|
|
|
|
|
|
|
|
issue_for(node.name, message: "A to B") do |corrector|
|
|
|
|
corrector.replace(node.name, name.tr("A", "B"))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class BtoA < Rule::Base
|
|
|
|
include AST::Util
|
|
|
|
|
|
|
|
properties do
|
|
|
|
description "This rule is only used to test infinite loop detection"
|
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)
|
|
|
|
return unless (name = node_source(node.name, source.lines))
|
|
|
|
return unless name.includes?("B")
|
|
|
|
|
|
|
|
issue_for(node.name, message: "B to A") do |corrector|
|
|
|
|
corrector.replace(node.name, name.tr("B", "A"))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class BtoC < Rule::Base
|
|
|
|
include AST::Util
|
|
|
|
|
|
|
|
properties do
|
|
|
|
description "This rule is only used to test infinite loop detection"
|
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)
|
|
|
|
return unless (name = node_source(node.name, source.lines))
|
|
|
|
return unless name.includes?("B")
|
|
|
|
|
|
|
|
issue_for(node.name, message: "B to C") do |corrector|
|
|
|
|
corrector.replace(node.name, name.tr("B", "C"))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class CtoA < Rule::Base
|
|
|
|
include AST::Util
|
|
|
|
|
|
|
|
properties do
|
|
|
|
description "This rule is only used to test infinite loop detection"
|
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)
|
|
|
|
return unless (name = node_source(node.name, source.lines))
|
|
|
|
return unless name.includes?("C")
|
|
|
|
|
|
|
|
issue_for(node.name, message: "C to A") do |corrector|
|
|
|
|
corrector.replace(node.name, name.tr("C", "A"))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class ClassToModule < Ameba::Rule::Base
|
|
|
|
include Ameba::AST::Util
|
|
|
|
|
|
|
|
properties do
|
|
|
|
description "This rule is only used to test infinite loop detection"
|
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node : Crystal::ClassDef)
|
|
|
|
return unless (location = node.location)
|
|
|
|
|
|
|
|
end_location = Crystal::Location.new(
|
|
|
|
location.filename,
|
|
|
|
location.line_number,
|
|
|
|
location.column_number + "class".size - 1
|
|
|
|
)
|
|
|
|
issue_for(location, end_location, message: "class to module") do |corrector|
|
|
|
|
corrector.replace(location, end_location, "module")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class ModuleToClass < Ameba::Rule::Base
|
|
|
|
include Ameba::AST::Util
|
|
|
|
|
|
|
|
properties do
|
|
|
|
description "This rule is only used to test infinite loop detection"
|
|
|
|
end
|
|
|
|
|
|
|
|
def test(source, node : Crystal::ModuleDef)
|
|
|
|
return unless (location = node.location)
|
|
|
|
|
|
|
|
end_location = Crystal::Location.new(
|
|
|
|
location.filename,
|
|
|
|
location.line_number,
|
|
|
|
location.column_number + "module".size - 1
|
|
|
|
)
|
|
|
|
issue_for(location, end_location, message: "module to class") do |corrector|
|
|
|
|
corrector.replace(location, end_location, "class")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-14 07:24:36 +00:00
|
|
|
class DummyFormatter < Formatter::BaseFormatter
|
|
|
|
property started_sources : Array(Source)?
|
|
|
|
property finished_sources : Array(Source)?
|
|
|
|
property started_source : Source?
|
|
|
|
property finished_source : Source?
|
|
|
|
|
|
|
|
def started(sources)
|
|
|
|
@started_sources = sources
|
|
|
|
end
|
|
|
|
|
|
|
|
def source_finished(source : Source)
|
|
|
|
@started_source = source
|
|
|
|
end
|
|
|
|
|
|
|
|
def source_started(source : Source)
|
|
|
|
@finished_source = source
|
|
|
|
end
|
|
|
|
|
|
|
|
def finished(sources)
|
|
|
|
@finished_sources = sources
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-03 15:57:47 +00:00
|
|
|
class TestNodeVisitor < Crystal::Visitor
|
|
|
|
NODES = [
|
2020-10-23 07:41:17 +00:00
|
|
|
Crystal::NilLiteral,
|
2018-05-03 15:57:47 +00:00
|
|
|
Crystal::Var,
|
|
|
|
Crystal::Assign,
|
|
|
|
Crystal::OpAssign,
|
|
|
|
Crystal::MultiAssign,
|
|
|
|
Crystal::Block,
|
2020-02-15 18:49:47 +00:00
|
|
|
Crystal::Macro,
|
2018-05-03 15:57:47 +00:00
|
|
|
Crystal::Def,
|
|
|
|
Crystal::If,
|
|
|
|
Crystal::While,
|
|
|
|
Crystal::MacroLiteral,
|
2018-11-22 08:38:32 +00:00
|
|
|
Crystal::Expressions,
|
2020-03-24 16:01:23 +00:00
|
|
|
Crystal::ControlExpression,
|
2018-05-03 15:57:47 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
def initialize(node)
|
|
|
|
node.accept self
|
|
|
|
end
|
|
|
|
|
|
|
|
def visit(node : Crystal::ASTNode)
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
{% for node in NODES %}
|
|
|
|
{{getter_name = node.stringify.split("::").last.underscore + "_nodes"}}
|
|
|
|
|
|
|
|
getter {{getter_name.id}} = [] of {{node}}
|
|
|
|
|
|
|
|
def visit(node : {{node}})
|
|
|
|
{{getter_name.id}} << node
|
|
|
|
true
|
|
|
|
end
|
|
|
|
{% end %}
|
|
|
|
end
|
2017-11-01 20:05:41 +00:00
|
|
|
end
|
|
|
|
|
2018-05-03 15:57:47 +00:00
|
|
|
def as_node(source)
|
|
|
|
Crystal::Parser.new(source).parse
|
|
|
|
end
|
|
|
|
|
|
|
|
def as_nodes(source)
|
|
|
|
Ameba::TestNodeVisitor.new(as_node source)
|
|
|
|
end
|