Raise error if infinite correction loop

This commit is contained in:
fn ⌃ ⌥ 2021-10-26 13:05:22 -07:00
parent 16608965f5
commit 437584f9db
3 changed files with 277 additions and 7 deletions

View file

@ -8,6 +8,13 @@ module Ameba
config.update_rule ErrorRule.rule_name, enabled: false
config.update_rule PerfRule.rule_name, enabled: false
config.update_rule AtoAA.rule_name, enabled: false
config.update_rule AtoB.rule_name, enabled: false
config.update_rule BtoA.rule_name, enabled: false
config.update_rule BtoC.rule_name, enabled: false
config.update_rule CtoA.rule_name, enabled: false
config.update_rule ClassToModule.rule_name, enabled: false
config.update_rule ModuleToClass.rule_name, enabled: false
Runner.new(config)
end
@ -54,6 +61,15 @@ module Ameba
Runner.new(all_rules, [source], formatter, default_severity).run.success?.should be_true
end
it "aborts because of an infinite loop" do
rules = [AtoAA.new] of Rule::Base
source = Source.new "class A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoAA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
context "exception in rule" do
it "raises an exception raised in fiber while running a rule" do
rule = RaiseRule.new
@ -180,5 +196,56 @@ module Ameba
.run.success?.should be_true
end
end
describe "#run with rules autocorrecting each other" do
context "with two conflicting rules" do
context "if there is an offense in an inspected file" do
it "aborts because of an infinite loop" do
rules = [AtoB.new, BtoA.new]
source = Source.new "class A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
end
context "if there are multiple offenses in an inspected file" do
it "aborts because of an infinite loop" do
rules = [AtoB.new, BtoA.new]
source = Source.new %(
class A; end
class A_A; end
), "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
end
end
context "with two pairs of conflicting rules" do
it "aborts because of an infinite loop" do
rules = [ClassToModule.new, ModuleToClass.new, AtoB.new, BtoA.new]
source = Source.new "class A_A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/ClassToModule, Ameba/AtoB -> Ameba/ModuleToClass, Ameba/BtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
end
context "with three rule cycle" do
it "aborts because of an infinite loop" do
rules = [AtoB.new, BtoC.new, CtoA.new]
source = Source.new "class A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoC -> Ameba/CtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
end
end
end
end

View file

@ -95,6 +95,133 @@ module Ameba
end
end
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
class DummyFormatter < Formatter::BaseFormatter
property started_sources : Array(Source)?
property finished_sources : Array(Source)?