2017-11-14 07:24:36 +00:00
|
|
|
require "../spec_helper"
|
|
|
|
|
|
|
|
module Ameba
|
|
|
|
private def runner(files = [__FILE__], formatter = DummyFormatter.new)
|
2017-12-01 17:00:11 +00:00
|
|
|
config = Config.load
|
2017-11-14 07:24:36 +00:00
|
|
|
config.formatter = formatter
|
2019-01-12 21:19:00 +00:00
|
|
|
config.globs = files
|
2017-11-14 07:24:36 +00:00
|
|
|
|
2018-02-02 20:11:18 +00:00
|
|
|
config.update_rule ErrorRule.rule_name, enabled: false
|
2021-04-15 14:26:49 +00:00
|
|
|
config.update_rule PerfRule.rule_name, enabled: false
|
2021-10-26 20:05:22 +00:00
|
|
|
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
|
2017-11-30 21:50:07 +00:00
|
|
|
|
2017-11-14 07:24:36 +00:00
|
|
|
Runner.new(config)
|
|
|
|
end
|
|
|
|
|
|
|
|
describe Runner do
|
|
|
|
formatter = DummyFormatter.new
|
2019-05-11 18:17:18 +00:00
|
|
|
default_severity = Severity::Convention
|
2017-11-14 07:24:36 +00:00
|
|
|
|
|
|
|
describe "#run" do
|
|
|
|
it "returns self" do
|
|
|
|
runner.run.should_not be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls started callback" do
|
|
|
|
runner(formatter: formatter).run
|
|
|
|
formatter.started_sources.should_not be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls finished callback" do
|
|
|
|
runner(formatter: formatter).run
|
|
|
|
formatter.finished_sources.should_not be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls source_started callback" do
|
|
|
|
runner(formatter: formatter).run
|
|
|
|
formatter.started_source.should_not be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls source_finished callback" do
|
|
|
|
runner(formatter: formatter).run
|
|
|
|
formatter.finished_source.should_not be_nil
|
|
|
|
end
|
2017-11-30 21:50:07 +00:00
|
|
|
|
|
|
|
it "skips rule check if source is excluded" do
|
|
|
|
path = "source.cr"
|
|
|
|
source = Source.new "", path
|
|
|
|
|
2018-05-28 08:48:07 +00:00
|
|
|
all_rules = ([] of Rule::Base).tap do |rules|
|
2017-11-30 21:50:07 +00:00
|
|
|
rule = ErrorRule.new
|
|
|
|
rule.excluded = [path]
|
|
|
|
rules << rule
|
|
|
|
end
|
|
|
|
|
2019-04-14 12:57:48 +00:00
|
|
|
Runner.new(all_rules, [source], formatter, default_severity).run.success?.should be_true
|
2017-11-30 21:50:07 +00:00
|
|
|
end
|
2018-01-25 09:50:11 +00:00
|
|
|
|
2021-10-26 20:05:22 +00:00
|
|
|
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"
|
2022-12-19 17:03:11 +00:00
|
|
|
|
2021-10-26 20:05:22 +00:00
|
|
|
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
|
|
|
|
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-11-09 17:31:41 +00:00
|
|
|
context "exception in rule" do
|
|
|
|
it "raises an exception raised in fiber while running a rule" do
|
|
|
|
rule = RaiseRule.new
|
|
|
|
rule.should_raise = true
|
|
|
|
rules = [rule] of Rule::Base
|
|
|
|
source = Source.new "", "source.cr"
|
|
|
|
|
|
|
|
expect_raises(Exception, "something went wrong") do
|
|
|
|
Runner.new(rules, [source], formatter, default_severity).run
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-01-25 09:50:11 +00:00
|
|
|
context "invalid syntax" do
|
|
|
|
it "reports a syntax error" do
|
2018-06-16 11:50:59 +00:00
|
|
|
rules = [Rule::Lint::Syntax.new] of Rule::Base
|
2018-01-25 09:50:11 +00:00
|
|
|
source = Source.new "def bad_syntax"
|
|
|
|
|
2019-04-14 12:57:48 +00:00
|
|
|
Runner.new(rules, [source], formatter, default_severity).run
|
2018-01-25 09:50:11 +00:00
|
|
|
source.should_not be_valid
|
2018-06-16 11:50:59 +00:00
|
|
|
source.issues.first.rule.name.should eq Rule::Lint::Syntax.rule_name
|
2018-01-25 09:50:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it "does not run other rules" do
|
2018-06-16 11:50:59 +00:00
|
|
|
rules = [Rule::Lint::Syntax.new, Rule::Style::ConstantNames.new] of Rule::Base
|
2022-12-19 17:03:11 +00:00
|
|
|
source = Source.new <<-CRYSTAL
|
|
|
|
MyBadConstant = 1
|
2018-01-25 09:50:11 +00:00
|
|
|
|
2022-12-19 17:03:11 +00:00
|
|
|
when my_bad_syntax
|
|
|
|
CRYSTAL
|
2018-01-25 09:50:11 +00:00
|
|
|
|
2019-04-14 12:57:48 +00:00
|
|
|
Runner.new(rules, [source], formatter, default_severity).run
|
2018-01-25 09:50:11 +00:00
|
|
|
source.should_not be_valid
|
2018-06-10 21:15:12 +00:00
|
|
|
source.issues.size.should eq 1
|
2018-01-25 09:50:11 +00:00
|
|
|
end
|
|
|
|
end
|
2018-02-02 20:11:18 +00:00
|
|
|
|
|
|
|
context "unneeded disables" do
|
2018-06-10 21:15:12 +00:00
|
|
|
it "reports an issue if such disable exists" do
|
2018-06-16 11:50:59 +00:00
|
|
|
rules = [Rule::Lint::UnneededDisableDirective.new] of Rule::Base
|
2022-12-19 17:03:11 +00:00
|
|
|
source = Source.new <<-CRYSTAL
|
2018-02-02 20:11:18 +00:00
|
|
|
a = 1 # ameba:disable LineLength
|
2022-12-19 17:03:11 +00:00
|
|
|
CRYSTAL
|
2018-02-02 20:11:18 +00:00
|
|
|
|
2019-04-14 12:57:48 +00:00
|
|
|
Runner.new(rules, [source], formatter, default_severity).run
|
2018-02-02 20:11:18 +00:00
|
|
|
source.should_not be_valid
|
2018-06-16 11:50:59 +00:00
|
|
|
source.issues.first.rule.name.should eq Rule::Lint::UnneededDisableDirective.rule_name
|
2018-02-02 20:11:18 +00:00
|
|
|
end
|
|
|
|
end
|
2017-11-14 07:24:36 +00:00
|
|
|
end
|
|
|
|
|
2018-12-27 21:34:10 +00:00
|
|
|
describe "#explain" do
|
|
|
|
io = IO::Memory.new
|
|
|
|
|
|
|
|
it "writes nothing if sources are valid" do
|
|
|
|
io.clear
|
|
|
|
runner = runner(formatter: formatter).run
|
|
|
|
runner.explain({file: "source.cr", line: 1, column: 2}, io)
|
|
|
|
io.to_s.should be_empty
|
|
|
|
end
|
|
|
|
|
|
|
|
it "writes the explanation if sources are not valid and location found" do
|
|
|
|
io.clear
|
|
|
|
rules = [ErrorRule.new] of Rule::Base
|
2022-12-19 17:03:11 +00:00
|
|
|
source = Source.new "a = 1", "source.cr"
|
2018-12-27 21:34:10 +00:00
|
|
|
|
2019-04-14 12:57:48 +00:00
|
|
|
runner = Runner.new(rules, [source], formatter, default_severity).run
|
2018-12-27 21:34:10 +00:00
|
|
|
runner.explain({file: "source.cr", line: 1, column: 1}, io)
|
|
|
|
io.to_s.should_not be_empty
|
|
|
|
end
|
|
|
|
|
|
|
|
it "writes nothing if sources are not valid and location is not found" do
|
|
|
|
io.clear
|
|
|
|
rules = [ErrorRule.new] of Rule::Base
|
2022-12-19 17:03:11 +00:00
|
|
|
source = Source.new "a = 1", "source.cr"
|
2018-12-27 21:34:10 +00:00
|
|
|
|
2019-04-14 12:57:48 +00:00
|
|
|
runner = Runner.new(rules, [source], formatter, default_severity).run
|
2018-12-27 21:34:10 +00:00
|
|
|
runner.explain({file: "source.cr", line: 1, column: 2}, io)
|
|
|
|
io.to_s.should be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-14 07:24:36 +00:00
|
|
|
describe "#success?" do
|
|
|
|
it "returns true if runner has not been run" do
|
|
|
|
runner.success?.should be_true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns true if all sources are valid" do
|
|
|
|
runner.run.success?.should be_true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns false if there are invalid sources" do
|
2018-05-22 14:55:44 +00:00
|
|
|
rules = Rule.rules.map &.new.as(Rule::Base)
|
2022-12-19 17:03:11 +00:00
|
|
|
source = Source.new "WrongConstant = 5"
|
|
|
|
|
|
|
|
Runner.new(rules, [source], formatter, default_severity).run.success?.should be_false
|
2019-04-14 12:57:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it "depends on the level of severity" do
|
|
|
|
rules = Rule.rules.map &.new.as(Rule::Base)
|
2022-12-19 16:53:43 +00:00
|
|
|
source = Source.new "WrongConstant = 5\n"
|
|
|
|
|
|
|
|
Runner.new(rules, [source], formatter, :error).run.success?.should be_true
|
|
|
|
Runner.new(rules, [source], formatter, :warning).run.success?.should be_true
|
|
|
|
Runner.new(rules, [source], formatter, :convention).run.success?.should be_false
|
2017-11-14 07:24:36 +00:00
|
|
|
end
|
2019-04-25 05:47:37 +00:00
|
|
|
|
|
|
|
it "returns false if issue is disabled" do
|
|
|
|
rules = [NamedRule.new] of Rule::Base
|
2022-12-19 17:03:11 +00:00
|
|
|
source = Source.new <<-CRYSTAL
|
2019-04-25 05:47:37 +00:00
|
|
|
def foo
|
|
|
|
bar = 1 # ameba:disable #{NamedRule.name}
|
|
|
|
end
|
2022-12-19 17:03:11 +00:00
|
|
|
CRYSTAL
|
2019-04-25 05:47:37 +00:00
|
|
|
source.add_issue NamedRule.new, location: {2, 1},
|
|
|
|
message: "Useless assignment"
|
|
|
|
|
|
|
|
Runner
|
|
|
|
.new(rules, [source], formatter, default_severity)
|
|
|
|
.run.success?.should be_true
|
|
|
|
end
|
2017-11-14 07:24:36 +00:00
|
|
|
end
|
2021-10-26 20:05:22 +00:00
|
|
|
|
|
|
|
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"
|
2022-12-19 17:03:11 +00:00
|
|
|
|
2021-10-26 20:05:22 +00:00
|
|
|
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]
|
2022-12-19 17:03:11 +00:00
|
|
|
source = Source.new <<-CRYSTAL, "source.cr"
|
2021-10-26 20:05:22 +00:00
|
|
|
class A; end
|
|
|
|
class A_A; end
|
2022-12-19 17:03:11 +00:00
|
|
|
CRYSTAL
|
2021-10-26 20:05:22 +00:00
|
|
|
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
|
2022-12-19 17:03:11 +00:00
|
|
|
|
2021-10-26 20:05:22 +00:00
|
|
|
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"
|
2022-12-19 17:03:11 +00:00
|
|
|
|
2021-10-26 20:05:22 +00:00
|
|
|
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"
|
2022-12-19 17:03:11 +00:00
|
|
|
|
2021-10-26 20:05:22 +00:00
|
|
|
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
|
|
|
|
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-11-14 07:24:36 +00:00
|
|
|
end
|
|
|
|
end
|