Rename Error to Issue

This commit is contained in:
Vitalii Elenhaupt 2018-06-11 00:15:12 +03:00 committed by V. Elenhaupt
parent e1b51f62a5
commit f8d14d4222
81 changed files with 475 additions and 384 deletions

View File

@ -16,8 +16,8 @@ module Ameba::Formatter
path = "source.cr" path = "source.cr"
s = Source.new("", path).tap do |source| s = Source.new("", path).tap do |source|
source.error(ErrorRule.new, 1, 2, "ErrorRule", :disabled) source.add_issue(ErrorRule.new, {1, 2}, message: "ErrorRule", status: :disabled)
source.error(NamedRule.new, 2, 2, "NamedRule", :disabled) source.add_issue(NamedRule.new, location: {2, 2}, message: "NamedRule", status: :disabled)
end end
subject.finished [s] subject.finished [s]
log = output.to_s log = output.to_s
@ -30,8 +30,9 @@ module Ameba::Formatter
it "does not write not-disabled rules" do it "does not write not-disabled rules" do
s = Source.new("", "source.cr").tap do |source| s = Source.new("", "source.cr").tap do |source|
source.error(ErrorRule.new, 1, 2, "ErrorRule") source.add_issue(ErrorRule.new, {1, 2}, "ErrorRule")
source.error(NamedRule.new, 2, 2, "NamedRule", :disabled) source.add_issue(NamedRule.new, location: {2, 2},
message: "NamedRule", status: :disabled)
end end
subject.finished [s] subject.finished [s]
output.to_s.should_not contain ErrorRule.name output.to_s.should_not contain ErrorRule.name

View File

@ -20,7 +20,7 @@ module Ameba::Formatter
it "writes invalid source" do it "writes invalid source" do
s = Source.new "" s = Source.new ""
s.error DummyRule.new, nil, "message" s.add_issue DummyRule.new, Crystal::Nop.new, "message"
subject.source_finished s subject.source_finished s
output.to_s.should contain "F" output.to_s.should contain "F"
end end
@ -37,11 +37,11 @@ module Ameba::Formatter
output.to_s.should contain "Finished in" output.to_s.should contain "Finished in"
end end
context "when errors found" do context "when issues found" do
it "writes each error" do it "writes each issue" do
s = Source.new("").tap do |source| s = Source.new("").tap do |source|
source.error(DummyRule.new, 1, 1, "DummyRuleError") source.add_issue(DummyRule.new, {1, 1}, "DummyRuleError")
source.error(NamedRule.new, 1, 2, "NamedRuleError") source.add_issue(NamedRule.new, {1, 2}, "NamedRuleError")
end end
subject.finished [s] subject.finished [s]
log = output.to_s log = output.to_s
@ -50,9 +50,10 @@ module Ameba::Formatter
log.should contain "NamedRuleError" log.should contain "NamedRuleError"
end end
it "does not write disabled errors" do it "does not write disabled issues" do
s = Source.new "" s = Source.new ""
s.error(DummyRule.new, 1, 1, "DummyRuleError", :disabled) s.add_issue(DummyRule.new, location: {1, 1},
message: "DummyRuleError", status: :disabled)
subject.finished [s] subject.finished [s]
output.to_s.should contain "1 inspected, 0 failures." output.to_s.should contain "1 inspected, 0 failures."
end end

View File

@ -16,9 +16,9 @@ module Ameba::Formatter
end end
context "when problems found" do context "when problems found" do
it "reports an error" do it "reports an issue" do
s = Source.new "a = 1", "source.cr" s = Source.new "a = 1", "source.cr"
s.error DummyRule.new, 1, 2, "message" s.add_issue DummyRule.new, {1, 2}, "message"
subject = flycheck subject = flycheck
subject.source_finished s subject.source_finished s
subject.output.to_s.should eq( subject.output.to_s.should eq(
@ -28,7 +28,7 @@ module Ameba::Formatter
it "properly reports multi-line message" do it "properly reports multi-line message" do
s = Source.new "a = 1", "source.cr" s = Source.new "a = 1", "source.cr"
s.error DummyRule.new, 1, 2, "multi\nline" s.add_issue DummyRule.new, {1, 2}, "multi\nline"
subject = flycheck subject = flycheck
subject.source_finished s subject.source_finished s
subject.output.to_s.should eq( subject.output.to_s.should eq(
@ -38,7 +38,7 @@ module Ameba::Formatter
it "reports nothing if location was not set" do it "reports nothing if location was not set" do
s = Source.new "a = 1", "source.cr" s = Source.new "a = 1", "source.cr"
s.error DummyRule.new, nil, "message" s.add_issue DummyRule.new, Crystal::Nop.new, "message"
subject = flycheck subject = flycheck
subject.source_finished s subject.source_finished s
subject.output.to_s.should eq "" subject.output.to_s.should eq ""

View File

@ -31,26 +31,26 @@ module Ameba
it "shows rule name" do it "shows rule name" do
s = Source.new "" s = Source.new ""
s.error DummyRule.new, 1, 2, "message1" s.add_issue DummyRule.new, {1, 2}, "message1"
result = get_result [s] result = get_result [s]
result["sources"][0]["errors"][0]["rule_name"].should eq DummyRule.name result["sources"][0]["issues"][0]["rule_name"].should eq DummyRule.name
end end
it "shows a message" do it "shows a message" do
s = Source.new "" s = Source.new ""
s.error DummyRule.new, 1, 2, "message" s.add_issue DummyRule.new, {1, 2}, "message"
result = get_result [s] result = get_result [s]
result["sources"][0]["errors"][0]["message"].should eq "message" result["sources"][0]["issues"][0]["message"].should eq "message"
end end
it "shows error location" do it "shows issue location" do
s = Source.new "" s = Source.new ""
s.error DummyRule.new, 1, 2, "message" s.add_issue DummyRule.new, {1, 2}, "message"
result = get_result [s] result = get_result [s]
location = result["sources"][0]["errors"][0]["location"] location = result["sources"][0]["issues"][0]["location"]
location["line"].should eq 1 location["line"].should eq 1
location["column"].should eq 2 location["column"].should eq 2
end end
@ -62,16 +62,16 @@ module Ameba
result["summary"]["target_sources_count"].should eq 2 result["summary"]["target_sources_count"].should eq 2
end end
it "shows errors count" do it "shows issues count" do
s1 = Source.new "" s1 = Source.new ""
s1.error DummyRule.new, 1, 2, "message1" s1.add_issue DummyRule.new, {1, 2}, "message1"
s1.error DummyRule.new, 1, 2, "message2" s1.add_issue DummyRule.new, {1, 2}, "message2"
s2 = Source.new "" s2 = Source.new ""
s2.error DummyRule.new, 1, 2, "message3" s2.add_issue DummyRule.new, {1, 2}, "message3"
result = get_result [s1, s2] result = get_result [s1, s2]
result["summary"]["errors_count"].should eq 3 result["summary"]["issues_count"].should eq 3
end end
end end
end end

View File

@ -6,7 +6,7 @@ module Ameba
formatter = Formatter::TODOFormatter.new IO::Memory.new, file formatter = Formatter::TODOFormatter.new IO::Memory.new, file
s = Source.new "a = 1", "source.cr" s = Source.new "a = 1", "source.cr"
s.error DummyRule.new, 1, 2, "message" s.add_issue DummyRule.new, {1, 2}, "message"
formatter.finished [s] formatter.finished [s]
file.to_s file.to_s
@ -57,7 +57,7 @@ module Ameba
formatter = Formatter::TODOFormatter.new IO::Memory.new, file formatter = Formatter::TODOFormatter.new IO::Memory.new, file
s = Source.new "def invalid_syntax" s = Source.new "def invalid_syntax"
s.error Rule::Syntax.new, 1, 2, "message" s.add_issue Rule::Syntax.new, {1, 2}, "message"
formatter.finished [s] formatter.finished [s]
content = file.to_s content = file.to_s

View File

@ -7,7 +7,7 @@ module Ameba
# ameba:disable #{NamedRule.name} # ameba:disable #{NamedRule.name}
Time.epoch(1483859302) Time.epoch(1483859302)
) )
s.error(NamedRule.new, 3, 12, "Error!") s.add_issue(NamedRule.new, location: {3, 12}, message: "Error!")
s.should be_valid s.should be_valid
end end
@ -15,7 +15,7 @@ module Ameba
s = Source.new %Q( s = Source.new %Q(
Time.epoch(1483859302) # ameba:disable #{NamedRule.name} Time.epoch(1483859302) # ameba:disable #{NamedRule.name}
) )
s.error(NamedRule.new, 2, 12, "Error!") s.add_issue(NamedRule.new, location: {2, 12}, message: "Error!")
s.should be_valid s.should be_valid
end end
@ -24,7 +24,7 @@ module Ameba
# ameba:disable WrongName # ameba:disable WrongName
Time.epoch(1483859302) Time.epoch(1483859302)
) )
s.error(NamedRule.new, 3, 12, "Error!") s.add_issue(NamedRule.new, location: {3, 12}, message: "Error!")
s.should_not be_valid s.should_not be_valid
end end
@ -33,7 +33,7 @@ module Ameba
# ameba:disable SomeRule LargeNumbers #{NamedRule.name} SomeOtherRule # ameba:disable SomeRule LargeNumbers #{NamedRule.name} SomeOtherRule
Time.epoch(1483859302) Time.epoch(1483859302)
) )
s.error(NamedRule.new, 3, 12, "") s.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should be_valid s.should be_valid
end end
@ -42,7 +42,7 @@ module Ameba
# ameba:disable SomeRule, LargeNumbers, #{NamedRule.name}, SomeOtherRule # ameba:disable SomeRule, LargeNumbers, #{NamedRule.name}, SomeOtherRule
Time.epoch(1483859302) Time.epoch(1483859302)
) )
s.error(NamedRule.new, 3, 12, "") s.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should be_valid s.should be_valid
end end
@ -51,7 +51,7 @@ module Ameba
# ameba:disable SomeRule, SomeOtherRule LargeNumbers # ameba:disable SomeRule, SomeOtherRule LargeNumbers
Time.epoch(1483859302) Time.epoch(1483859302)
) )
s.error(NamedRule.new, 3, 12, "") s.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should_not be_valid s.should_not be_valid
end end
@ -61,7 +61,7 @@ module Ameba
# #
Time.epoch(1483859302) Time.epoch(1483859302)
) )
s.error(NamedRule.new, 4, 12, "") s.add_issue(NamedRule.new, location: {4, 12}, message: "")
s.should_not be_valid s.should_not be_valid
end end
@ -71,7 +71,7 @@ module Ameba
Time.epoch(1483859302) Time.epoch(1483859302)
end end
) )
s.error(NamedRule.new, 3, 12, "") s.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should_not be_valid s.should_not be_valid
end end
@ -80,7 +80,7 @@ module Ameba
"ameba:disable #{NamedRule.name}" "ameba:disable #{NamedRule.name}"
Time.epoch(1483859302) Time.epoch(1483859302)
) )
s.error(NamedRule.new, 3, 12, "") s.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should_not be_valid s.should_not be_valid
end end
@ -89,7 +89,7 @@ module Ameba
# # ameba:disable #{NamedRule.name} # # ameba:disable #{NamedRule.name}
Time.epoch(1483859302) Time.epoch(1483859302)
) )
s.error(NamedRule.new, 3, 12, "") s.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should_not be_valid s.should_not be_valid
end end
@ -97,7 +97,7 @@ module Ameba
s = Source.new %Q( s = Source.new %Q(
a = 1 # Disable it: # ameba:disable #{NamedRule.name} a = 1 # Disable it: # ameba:disable #{NamedRule.name}
) )
s.error(NamedRule.new, 2, 12, "") s.add_issue(NamedRule.new, location: {2, 12}, message: "")
s.should_not be_valid s.should_not be_valid
end end
end end

50
spec/ameba/issue_spec.cr Normal file
View File

@ -0,0 +1,50 @@
require "../spec_helper"
module Ameba
describe Issue do
it "accepts rule and message" do
issue = Issue.new rule: DummyRule.new,
location: nil,
end_location: nil,
message: "Blah",
status: nil
issue.rule.should_not be_nil
issue.message.should eq "Blah"
end
it "accepts location" do
location = Crystal::Location.new("path", 3, 2)
issue = Issue.new rule: DummyRule.new,
location: location,
end_location: nil,
message: "Blah",
status: nil
issue.location.to_s.should eq location.to_s
issue.end_location.should eq nil
end
it "accepts end_location" do
location = Crystal::Location.new("path", 3, 2)
issue = Issue.new rule: DummyRule.new,
location: nil,
end_location: location,
message: "Blah",
status: nil
issue.location.should eq nil
issue.end_location.to_s.should eq location.to_s
end
it "accepts status" do
issue = Issue.new rule: DummyRule.new,
location: nil,
end_location: nil,
message: "",
status: :enabled
issue.status.should eq :enabled
end
end
end

View File

@ -0,0 +1,40 @@
require "../spec_helper"
module Ameba
describe Reportable do
describe "#add_issue" do
it "adds a new issue for node" do
s = Source.new "", "source.cr"
s.add_issue(DummyRule.new, Crystal::Nop.new, "Error!")
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq ""
issue.message.should eq "Error!"
end
it "adds a new issue by line and column number" do
s = Source.new "", "source.cr"
s.add_issue(DummyRule.new, {23, 2}, "Error!")
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:23:2"
issue.message.should eq "Error!"
end
end
describe "#valid?" do
it "returns true if no issues added" do
s = Source.new "", "source.cr"
s.should be_valid
end
it "returns false if there are issues added" do
s = Source.new "", "source.cr"
s.add_issue DummyRule.new, {22, 2}, "ERROR!"
s.should_not be_valid
end
end
end
end

View File

@ -66,10 +66,10 @@ module Ameba::Rule
source = Source.new "a != true", "source.cr" source = Source.new "a != true", "source.cr"
subject.catch(source) subject.catch(source)
error = source.errors.first issue = source.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:1:1" issue.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Comparison to a boolean is pointless" issue.message.should eq "Comparison to a boolean is pointless"
end end
end end
@ -103,10 +103,10 @@ module Ameba::Rule
source = Source.new "true != a", "source.cr" source = Source.new "true != a", "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first issue = source.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:1:1" issue.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Comparison to a boolean is pointless" issue.message.should eq "Comparison to a boolean is pointless"
end end
end end
end end

View File

@ -7,7 +7,7 @@ module Ameba
it "reports constant name #{expected}" do it "reports constant name #{expected}" do
s = Source.new code s = Source.new code
Rule::ConstantNames.new.catch(s).should_not be_valid Rule::ConstantNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.issues.first.message.should contain expected
end end
end end
@ -40,10 +40,10 @@ module Ameba
Const = 1 Const = 1
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq( issue.message.should eq(
"Constant name should be screaming-cased: CONST, not Const" "Constant name should be screaming-cased: CONST, not Const"
) )
end end

View File

@ -35,10 +35,10 @@ module Ameba::Rule
s = Source.new "debugger", "source.cr" s = Source.new "debugger", "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:1:1" issue.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Possible forgotten debugger statement detected" issue.message.should eq "Possible forgotten debugger statement detected"
end end
end end
end end

View File

@ -58,11 +58,11 @@ module Ameba::Rule
end end
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:3:11" issue.location.to_s.should eq "source.cr:3:11"
error.message.should eq "Empty `ensure` block detected" issue.message.should eq "Empty `ensure` block detected"
end end
end end
end end

View File

@ -101,10 +101,10 @@ module Ameba
end end
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:12" issue.location.to_s.should eq "source.cr:2:12"
error.message.should eq "Avoid empty expressions" issue.message.should eq "Avoid empty expressions"
end end
end end
end end

View File

@ -32,10 +32,10 @@ module Ameba::Rule
h = {"a" => 1, "a" => 2} h = {"a" => 1, "a" => 2}
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:13" issue.location.to_s.should eq "source.cr:2:13"
error.message.should eq %(Duplicated keys in hash literal: "a") issue.message.should eq %(Duplicated keys in hash literal: "a")
end end
it "reports multiple duplicated keys" do it "reports multiple duplicated keys" do
@ -43,8 +43,8 @@ module Ameba::Rule
h = {"key1" => 1, "key1" => 2, "key2" => 3, "key2" => 4} h = {"key1" => 1, "key1" => 2, "key2" => 3, "key2" => 4}
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.message.should eq %(Duplicated keys in hash literal: "key1", "key2") issue.message.should eq %(Duplicated keys in hash literal: "key1", "key2")
end end
end end
end end

View File

@ -7,7 +7,7 @@ module Ameba
it "transforms large number #{number}" do it "transforms large number #{number}" do
s = Source.new number s = Source.new number
Rule::LargeNumbers.new.catch(s).should_not be_valid Rule::LargeNumbers.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.issues.first.message.should contain expected
end end
end end
@ -115,10 +115,10 @@ module Ameba
1200000 1200000
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:10" issue.location.to_s.should eq "source.cr:2:10"
error.message.should match /1_200_000/ issue.message.should match /1_200_000/
end end
context "properties" do context "properties" do

View File

@ -24,10 +24,10 @@ module Ameba::Rule
source = Source.new long_line, "source.cr" source = Source.new long_line, "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first issue = source.issues.first
error.rule.should eq subject issue.rule.should eq subject
error.location.to_s.should eq "source.cr:1:81" issue.location.to_s.should eq "source.cr:1:81"
error.message.should eq "Line too long" issue.message.should eq "Line too long"
end end
context "properties" do context "properties" do

View File

@ -62,11 +62,11 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 1 s.issues.size.should eq 1
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq "Literal value found in conditional" issue.message.should eq "Literal value found in conditional"
end end
end end
end end

View File

@ -32,10 +32,10 @@ module Ameba::Rule
s = Source.new %q("#{4}"), "source.cr" s = Source.new %q("#{4}"), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:1:1" issue.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Literal value found in interpolation" issue.message.should eq "Literal value found in interpolation"
end end
end end
end end

View File

@ -7,7 +7,7 @@ module Ameba
it "reports method name #{expected}" do it "reports method name #{expected}" do
s = Source.new code s = Source.new code
Rule::MethodNames.new.catch(s).should_not be_valid Rule::MethodNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.issues.first.message.should contain expected
end end
end end
@ -44,10 +44,10 @@ module Ameba
end end
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq( issue.message.should eq(
"Method name should be underscore-cased: bad_name, not bad_Name" "Method name should be underscore-cased: bad_name, not bad_Name"
) )
end end

View File

@ -59,10 +59,10 @@ module Ameba::Rule
s = Source.new ":nok unless !s.empty?", "source.cr" s = Source.new ":nok unless !s.empty?", "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:1:1" issue.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Avoid negated conditions in unless blocks" issue.message.should eq "Avoid negated conditions in unless blocks"
end end
end end
end end

View File

@ -44,10 +44,10 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq( issue.message.should eq(
"Symbols `,:` may be unwanted in %i array literals" "Symbols `,:` may be unwanted in %i array literals"
) )
end end
@ -58,10 +58,10 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq( issue.message.should eq(
"Symbols `,\"` may be unwanted in %w array literals" "Symbols `,\"` may be unwanted in %w array literals"
) )
end end

View File

@ -38,10 +38,10 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:3:11" issue.location.to_s.should eq "source.cr:3:11"
error.message.should eq( issue.message.should eq(
"Favour method name 'picture?' over 'has_picture?'") "Favour method name 'picture?' over 'has_picture?'")
end end

View File

@ -26,11 +26,11 @@ module Ameba::Rule
it "reports rule, location and a message" do it "reports rule, location and a message" do
s = Source.new "rand(1)", "source.cr" s = Source.new "rand(1)", "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:1:1" issue.location.to_s.should eq "source.cr:1:1"
error.message.should eq "rand(1) always returns 0" issue.message.should eq "rand(1) always returns 0"
end end
end end
end end

View File

@ -204,10 +204,10 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq "Redundant `begin` block detected" issue.message.should eq "Redundant `begin` block detected"
end end
end end
end end

View File

@ -156,10 +156,10 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:3:11" issue.location.to_s.should eq "source.cr:3:11"
error.message.should eq "Argument `bar` is assigned before it is used" issue.message.should eq "Argument `bar` is assigned before it is used"
end end
end end
end end

View File

@ -3,7 +3,7 @@ require "../../spec_helper"
private def check_shadowed(source, exceptions) private def check_shadowed(source, exceptions)
s = Ameba::Source.new source s = Ameba::Source.new source
Ameba::Rule::ShadowedException.new.catch(s).should_not be_valid Ameba::Rule::ShadowedException.new.catch(s).should_not be_valid
s.errors.first.message.should contain exceptions.join(", ") s.issues.first.message.should contain exceptions.join(", ")
end end
module Ameba::Rule module Ameba::Rule
@ -163,11 +163,11 @@ module Ameba::Rule
end end
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:3:11" issue.location.to_s.should eq "source.cr:3:11"
error.message.should eq( issue.message.should eq(
"Exception handler has shadowed exceptions: IndexError" "Exception handler has shadowed exceptions: IndexError"
) )
end end

View File

@ -68,7 +68,7 @@ module Ameba::Rule
) )
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
source.errors.size.should eq 2 source.issues.size.should eq 2
end end
it "reports if a splat block argument shadows local var" do it "reports if a splat block argument shadows local var" do
@ -89,7 +89,7 @@ module Ameba::Rule
end end
) )
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
source.errors.first.message.should eq "Shadowing outer local variable `block`" source.issues.first.message.should eq "Shadowing outer local variable `block`"
end end
it "reports if there are multiple args and one shadows local var" do it "reports if there are multiple args and one shadows local var" do
@ -100,7 +100,7 @@ module Ameba::Rule
end end
) )
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
source.errors.first.message.should eq "Shadowing outer local variable `foo`" source.issues.first.message.should eq "Shadowing outer local variable `foo`"
end end
it "doesn't report if an outer var is reassigned in a block" do it "doesn't report if an outer var is reassigned in a block" do
@ -131,10 +131,10 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first issue = source.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:3:20" issue.location.to_s.should eq "source.cr:3:20"
error.message.should eq "Shadowing outer local variable `foo`" issue.message.should eq "Shadowing outer local variable `foo`"
end end
end end
end end

View File

@ -27,11 +27,11 @@ module Ameba::Rule
it "reports rule, location and message" do it "reports rule, location and message" do
s = Source.new "def hello end", "source.cr" s = Source.new "def hello end", "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:1:11" issue.location.to_s.should eq "source.cr:1:11"
error.message.should eq "unexpected token: end (expected ';' or newline)" issue.message.should eq "unexpected token: end (expected ';' or newline)"
end end
end end
end end

View File

@ -28,10 +28,10 @@ module Ameba::Rule
source = Source.new "a = 1\n\n ", "source.cr" source = Source.new "a = 1\n\n ", "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first issue = source.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:3:1" issue.location.to_s.should eq "source.cr:3:1"
error.message.should eq "Blank lines detected at the end of the file" issue.message.should eq "Blank lines detected at the end of the file"
end end
end end
end end

View File

@ -18,10 +18,10 @@ module Ameba::Rule
source = Source.new "a = 1\n b = 2 ", "source.cr" source = Source.new "a = 1\n b = 2 ", "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first issue = source.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:7" issue.location.to_s.should eq "source.cr:2:7"
error.message.should eq "Trailing whitespace detected" issue.message.should eq "Trailing whitespace detected"
end end
end end
end end

View File

@ -7,7 +7,7 @@ module Ameba
it "reports type name #{expected}" do it "reports type name #{expected}" do
s = Source.new code s = Source.new code
Rule::TypeNames.new.catch(s).should_not be_valid Rule::TypeNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.issues.first.message.should contain expected
end end
end end
@ -49,10 +49,10 @@ module Ameba
end end
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq( issue.message.should eq(
"Type name should be camelcased: MyClass, but it was My_class" "Type name should be camelcased: MyClass, but it was My_class"
) )
end end

View File

@ -34,11 +34,11 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.should_not be_nil issue.should_not be_nil
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq "Favour if over unless with else" issue.message.should eq "Favour if over unless with else"
end end
end end
end end

View File

@ -23,7 +23,8 @@ module Ameba::Rule
# ameba:disable #{NamedRule.name} # ameba:disable #{NamedRule.name}
a = 1 a = 1
) )
s.error NamedRule.new, 3, 9, "Useless assignment", :disabled s.add_issue NamedRule.new, location: {3, 9},
message: "Useless assignment", status: :disabled
subject.catch(s).should be_valid subject.catch(s).should be_valid
end end
@ -31,7 +32,8 @@ module Ameba::Rule
s = Source.new %Q( s = Source.new %Q(
a = 1 # ameba:disable #{NamedRule.name} a = 1 # ameba:disable #{NamedRule.name}
) )
s.error NamedRule.new, 2, 1, "Alarm!", :disabled s.add_issue NamedRule.new, location: {2, 1},
message: "Alarm!", status: :disabled
subject.catch(s).should be_valid subject.catch(s).should be_valid
end end
@ -40,7 +42,8 @@ module Ameba::Rule
# # ameba:disable #{NamedRule.name} # # ameba:disable #{NamedRule.name}
a = 1 a = 1
) )
s.error NamedRule.new, 3, 1, "Alarm!", :disabled s.add_issue NamedRule.new, location: {3, 1},
message: "Alarm!", status: :disabled
subject.catch(s).should be_valid subject.catch(s).should be_valid
end end
@ -50,7 +53,7 @@ module Ameba::Rule
a = 1 a = 1
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.first.message.should eq( s.issues.first.message.should eq(
"Unnecessary disabling of #{NamedRule.name}" "Unnecessary disabling of #{NamedRule.name}"
) )
end end
@ -58,7 +61,7 @@ module Ameba::Rule
it "fails if there is inline unneeded directive" do it "fails if there is inline unneeded directive" do
s = Source.new %Q(a = 1 # ameba:disable #{NamedRule.name}) s = Source.new %Q(a = 1 # ameba:disable #{NamedRule.name})
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.first.message.should eq( s.issues.first.message.should eq(
"Unnecessary disabling of #{NamedRule.name}" "Unnecessary disabling of #{NamedRule.name}"
) )
end end
@ -69,9 +72,9 @@ module Ameba::Rule
a = 1 # ameba:disable Rule3 a = 1 # ameba:disable Rule3
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 2 s.issues.size.should eq 2
s.errors.first.message.should contain "Rule1, Rule2" s.issues.first.message.should contain "Rule1, Rule2"
s.errors.last.message.should contain "Rule3" s.issues.last.message.should contain "Rule3"
end end
it "fails if there is disabled UnneededDisableDirective" do it "fails if there is disabled UnneededDisableDirective" do
@ -79,20 +82,21 @@ module Ameba::Rule
# ameba:disable #{UnneededDisableDirective.rule_name} # ameba:disable #{UnneededDisableDirective.rule_name}
a = 1 a = 1
), "source.cr" ), "source.cr"
s.error UnneededDisableDirective.new, 3, 1, "Alarm!", :disabled s.add_issue UnneededDisableDirective.new, location: {3, 1},
message: "Alarm!", status: :disabled
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
end end
it "reports error, location and message" do it "reports issue, location and message" do
s = Source.new %Q( s = Source.new %Q(
# ameba:disable Rule1, Rule2 # ameba:disable Rule1, Rule2
a = 1 a = 1
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq "Unnecessary disabling of Rule1, Rule2" issue.message.should eq "Unnecessary disabling of Rule1, Rule2"
end end
end end
end end

View File

@ -27,7 +27,7 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.first.message.should eq "Unused argument `c`. If it's necessary, use `_c` " \ s.issues.first.message.should eq "Unused argument `c`. If it's necessary, use `_c` " \
"as an argument name to indicate that it won't be used." "as an argument name to indicate that it won't be used."
end end
@ -38,7 +38,7 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.first.message.should eq "Unused argument `i`. If it's necessary, use `_` " \ s.issues.first.message.should eq "Unused argument `i`. If it's necessary, use `_` " \
"as an argument name to indicate that it won't be used." "as an argument name to indicate that it won't be used."
end end
@ -49,7 +49,7 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.first.message.should eq "Unused argument `b`. If it's necessary, use `_b` " \ s.issues.first.message.should eq "Unused argument `b`. If it's necessary, use `_b` " \
"as an argument name to indicate that it won't be used." "as an argument name to indicate that it won't be used."
end end
@ -60,11 +60,11 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors[0].message.should eq "Unused argument `a`. If it's necessary, use `_a` " \ s.issues[0].message.should eq "Unused argument `a`. If it's necessary, use `_a` " \
"as an argument name to indicate that it won't be used." "as an argument name to indicate that it won't be used."
s.errors[1].message.should eq "Unused argument `b`. If it's necessary, use `_b` " \ s.issues[1].message.should eq "Unused argument `b`. If it's necessary, use `_b` " \
"as an argument name to indicate that it won't be used." "as an argument name to indicate that it won't be used."
s.errors[2].message.should eq "Unused argument `c`. If it's necessary, use `_c` " \ s.issues[2].message.should eq "Unused argument `c`. If it's necessary, use `_c` " \
"as an argument name to indicate that it won't be used." "as an argument name to indicate that it won't be used."
end end
@ -164,7 +164,7 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.first.message.should eq "Unused argument `b`. If it's necessary, use `_b` " \ s.issues.first.message.should eq "Unused argument `b`. If it's necessary, use `_b` " \
"as an argument name to indicate that it won't be used." "as an argument name to indicate that it won't be used."
end end
@ -174,11 +174,11 @@ module Ameba::Rule
end end
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.message.should eq "Unused argument `a`. If it's necessary, use `_a` " \ issue.message.should eq "Unused argument `a`. If it's necessary, use `_a` " \
"as an argument name to indicate that it won't be used." "as an argument name to indicate that it won't be used."
error.location.to_s.should eq "source.cr:2:22" issue.location.to_s.should eq "source.cr:2:22"
end end
end end

View File

@ -75,10 +75,10 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:3:11" issue.location.to_s.should eq "source.cr:3:11"
error.message.should eq "Useless assignment to variable `a`" issue.message.should eq "Useless assignment to variable `a`"
end end
it "does not report useless assignment of instance var" do it "does not report useless assignment of instance var" do
@ -137,7 +137,7 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.first.location.to_s.should eq ":3:11" s.issues.first.location.to_s.should eq ":3:11"
end end
it "reports if variable reassigned and not used" do it "reports if variable reassigned and not used" do
@ -314,10 +314,10 @@ module Ameba::Rule
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.last issue = s.issues.last
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:5:13" issue.location.to_s.should eq "source.cr:5:13"
error.message.should eq "Useless assignment to variable `a`" issue.message.should eq "Useless assignment to variable `a`"
end end
end end
@ -340,9 +340,9 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.location.to_s.should eq ":3:16" issue.location.to_s.should eq ":3:16"
error.message.should eq "Useless assignment to variable `b`" issue.message.should eq "Useless assignment to variable `b`"
end end
it "reports if both assigns are reassigned and useless" do it "reports if both assigns are reassigned and useless" do
@ -363,13 +363,13 @@ module Ameba::Rule
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.location.to_s.should eq ":3:13" issue.location.to_s.should eq ":3:13"
error.message.should eq "Useless assignment to variable `a`" issue.message.should eq "Useless assignment to variable `a`"
error = s.errors.last issue = s.issues.last
error.location.to_s.should eq ":3:16" issue.location.to_s.should eq ":3:16"
error.message.should eq "Useless assignment to variable `b`" issue.message.should eq "Useless assignment to variable `b`"
end end
end end
@ -380,9 +380,9 @@ module Ameba::Rule
a = 2 a = 2
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 2 s.issues.size.should eq 2
s.errors.first.location.to_s.should eq ":2:11" s.issues.first.location.to_s.should eq ":2:11"
s.errors.last.location.to_s.should eq ":3:11" s.issues.last.location.to_s.should eq ":3:11"
end end
it "doesn't report if assignments are referenced" do it "doesn't report if assignments are referenced" do
@ -476,8 +476,8 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 1 s.issues.size.should eq 1
s.errors.first.location.to_s.should eq ":5:17" s.issues.first.location.to_s.should eq ":5:17"
end end
it "does not report of assignments are referenced in all branches" do it "does not report of assignments are referenced in all branches" do
@ -545,8 +545,8 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 1 s.issues.size.should eq 1
s.errors.first.location.to_s.should eq ":5:17" s.issues.first.location.to_s.should eq ":5:17"
end end
end end
@ -578,9 +578,9 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 2 s.issues.size.should eq 2
s.errors.first.location.to_s.should eq ":5:17" s.issues.first.location.to_s.should eq ":5:17"
s.errors.last.location.to_s.should eq ":7:17" s.issues.last.location.to_s.should eq ":7:17"
end end
it "doesn't report if assignment is referenced in cond" do it "doesn't report if assignment is referenced in cond" do
@ -615,8 +615,8 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 1 s.issues.size.should eq 1
s.errors.first.location.to_s.should eq ":3:27" s.issues.first.location.to_s.should eq ":3:27"
end end
end end
@ -642,8 +642,8 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 1 s.issues.size.should eq 1
s.errors.first.location.to_s.should eq ":4:17" s.issues.first.location.to_s.should eq ":4:17"
end end
it "does not report if assignment is referenced in a loop" do it "does not report if assignment is referenced in a loop" do
@ -737,8 +737,8 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 1 s.issues.size.should eq 1
s.errors.first.location.to_s.should eq ":4:17" s.issues.first.location.to_s.should eq ":4:17"
end end
end end
@ -785,8 +785,8 @@ module Ameba::Rule
end end
) )
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 1 s.issues.size.should eq 1
s.errors.first.location.to_s.should eq ":4:15" s.issues.first.location.to_s.should eq ":4:15"
end end
end end
end end

View File

@ -36,10 +36,10 @@ module Ameba::Rule
end end
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:6:23" issue.location.to_s.should eq "source.cr:6:23"
error.message.should eq "Useless condition in when detected" issue.message.should eq "Useless condition in when detected"
end end
end end
end end

View File

@ -7,7 +7,7 @@ module Ameba
it "reports method name #{expected}" do it "reports method name #{expected}" do
s = Source.new code s = Source.new code
Rule::VariableNames.new.catch(s).should_not be_valid Rule::VariableNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.issues.first.message.should contain expected
end end
end end
@ -50,10 +50,10 @@ module Ameba
badName = "Yeah" badName = "Yeah"
), "source.cr" ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first issue = s.issues.first
error.rule.should_not be_nil issue.rule.should_not be_nil
error.location.to_s.should eq "source.cr:2:9" issue.location.to_s.should eq "source.cr:2:9"
error.message.should eq( issue.message.should eq(
"Var name should be underscore-cased: bad_name, not badName" "Var name should be underscore-cased: bad_name, not badName"
) )
end end

View File

@ -34,9 +34,9 @@ module Ameba::Rule
source = Source.new invalid_source, "source.cr" source = Source.new invalid_source, "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first issue = source.issues.first
error.location.to_s.should eq "source.cr:2:1" issue.location.to_s.should eq "source.cr:2:1"
error.message.should eq "While statement using true literal as condition" issue.message.should eq "While statement using true literal as condition"
end end
end end
end end

View File

@ -59,7 +59,7 @@ module Ameba
Runner.new(rules, [source], formatter).run Runner.new(rules, [source], formatter).run
source.should_not be_valid source.should_not be_valid
source.errors.first.rule.name.should eq Rule::Syntax.rule_name source.issues.first.rule.name.should eq Rule::Syntax.rule_name
end end
it "does not run other rules" do it "does not run other rules" do
@ -72,12 +72,12 @@ module Ameba
Runner.new(rules, [source], formatter).run Runner.new(rules, [source], formatter).run
source.should_not be_valid source.should_not be_valid
source.errors.size.should eq 1 source.issues.size.should eq 1
end end
end end
context "unneeded disables" do context "unneeded disables" do
it "reports an error if such disable exists" do it "reports an issue if such disable exists" do
rules = [Rule::UnneededDisableDirective.new] of Rule::Base rules = [Rule::UnneededDisableDirective.new] of Rule::Base
source = Source.new %( source = Source.new %(
a = 1 # ameba:disable LineLength a = 1 # ameba:disable LineLength
@ -85,7 +85,7 @@ module Ameba
Runner.new(rules, [source], formatter).run Runner.new(rules, [source], formatter).run
source.should_not be_valid source.should_not be_valid
source.errors.first.rule.name.should eq Rule::UnneededDisableDirective.rule_name source.issues.first.rule.name.should eq Rule::UnneededDisableDirective.rule_name
end end
end end
end end

View File

@ -11,19 +11,6 @@ module Ameba
end end
end end
describe "#error" do
it "adds and error" do
s = Source.new "", "source.cr"
s.error(DummyRule.new, 23, 2, "Error!")
s.should_not be_valid
error = s.errors.first
error.rule.should_not be_nil
error.location.to_s.should eq "source.cr:23:2"
error.message.should eq "Error!"
end
end
describe "#fullpath" do describe "#fullpath" do
it "returns a relative path of the source" do it "returns a relative path of the source" do
s = Source.new "", "./source_spec.cr" s = Source.new "", "./source_spec.cr"

View File

@ -26,7 +26,7 @@ module Ameba
struct ErrorRule < Rule::Base struct ErrorRule < Rule::Base
def test(source) def test(source)
source.error self, 1, 1, "This rule always adds an error" issue_for({1, 1}, "This rule always adds an error")
end end
end end
@ -71,8 +71,8 @@ module Ameba
def failure_message(source) def failure_message(source)
String.build do |str| String.build do |str|
str << "Source expected to be valid, but there are errors:\n\n" str << "Source expected to be valid, but there are issues: \n\n"
source.errors.reject(&.disabled?).each do |e| source.issues.reject(&.disabled?).each do |e|
str << " * #{e.rule.name}: #{e.message}\n" str << " * #{e.rule.name}: #{e.message}\n"
end end
end end

View File

@ -5,7 +5,7 @@ module Ameba::Formatter
output << "Disabled rules using inline directives: \n\n" output << "Disabled rules using inline directives: \n\n"
sources.each do |source| sources.each do |source|
source.errors.select(&.disabled?).each do |e| source.issues.select(&.disabled?).each do |e|
if loc = e.location if loc = e.location
output << "#{source.path}:#{loc.line_number}".colorize(:cyan) output << "#{source.path}:#{loc.line_number}".colorize(:cyan)
output << " #{e.rule.name}\n" output << " #{e.rule.name}\n"

View File

@ -24,10 +24,10 @@ module Ameba::Formatter
failed_sources = sources.reject &.valid? failed_sources = sources.reject &.valid?
failed_sources.each do |source| failed_sources.each do |source|
source.errors.each do |error| source.issues.each do |issue|
next if error.disabled? next if issue.disabled?
output << "#{error.location}\n".colorize(:cyan) output << "#{issue.location}\n".colorize(:cyan)
output << "#{error.rule.name}: #{error.message}\n\n".colorize(:red) output << "#{issue.rule.name}: #{issue.message}\n\n".colorize(:red)
end end
end end
@ -71,7 +71,7 @@ module Ameba::Formatter
private def final_message(sources, failed_sources) private def final_message(sources, failed_sources)
total = sources.size total = sources.size
failures = failed_sources.map { |f| f.errors.size }.sum failures = failed_sources.map { |f| f.issues.size }.sum
color = failures == 0 ? :green : :red color = failures == 0 ? :green : :red
s = failures != 1 ? "s" : "" s = failures != 1 ? "s" : ""

View File

@ -1,7 +1,7 @@
module Ameba::Formatter module Ameba::Formatter
class FlycheckFormatter < BaseFormatter class FlycheckFormatter < BaseFormatter
def source_finished(source : Source) def source_finished(source : Source)
source.errors.each do |e| source.issues.each do |e|
next if e.disabled? next if e.disabled?
if loc = e.location if loc = e.location
output.printf "%s:%d:%d: %s: [%s] %s\n", output.printf "%s:%d:%d: %s: [%s] %s\n",

View File

@ -13,7 +13,7 @@ module Ameba::Formatter
# }, # },
# "sources": [ # "sources": [
# { # {
# "errors": [ # "issues": [
# { # {
# "location": { # "location": {
# "column": 7, # "column": 7,
@ -43,7 +43,7 @@ module Ameba::Formatter
# }, # },
# ], # ],
# "summary": { # "summary": {
# "errors_count": 3, # "issues_count": 3,
# "target_sources_count": 1, # "target_sources_count": 1,
# }, # },
# } # }
@ -61,10 +61,10 @@ module Ameba::Formatter
def source_finished(source : Source) def source_finished(source : Source)
json_source = AsJSON::Source.new source.path json_source = AsJSON::Source.new source.path
source.errors.each do |e| source.issues.each do |e|
next if e.disabled? next if e.disabled?
json_source.errors << AsJSON::Error.new(e.rule.name, e.location, e.message) json_source.issues << AsJSON::Issue.new(e.rule.name, e.location, e.message)
@result.summary.errors_count += 1 @result.summary.issues_count += 1
end end
@result.sources << json_source @result.sources << json_source
@ -87,13 +87,13 @@ module Ameba::Formatter
record Source, record Source,
path : String, path : String,
errors = [] of Error do issues = [] of Issue do
def to_json(json) def to_json(json)
{path: path, errors: errors}.to_json(json) {path: path, issues: issues}.to_json(json)
end end
end end
record Error, record Issue,
rule_name : String, rule_name : String,
location : Crystal::Location?, location : Crystal::Location?,
message : String do message : String do
@ -120,12 +120,12 @@ module Ameba::Formatter
class Summary class Summary
property target_sources_count = 0 property target_sources_count = 0
property errors_count = 0 property issues_count = 0
def to_json(json) def to_json(json)
json.object do json.object do
json.field :target_sources_count, target_sources_count json.field :target_sources_count, target_sources_count
json.field :errors_count, errors_count json.field :issues_count, issues_count
end end
end end
end end

View File

@ -1,6 +1,6 @@
module Ameba::Formatter module Ameba::Formatter
# A formatter that creates a todo config. # A formatter that creates a todo config.
# Basically, it takes all errors reported and disables corresponding rules # Basically, it takes all issues reported and disables corresponding rules
# or excludes failed sources from these rules. # or excludes failed sources from these rules.
class TODOFormatter < DotFormatter class TODOFormatter < DotFormatter
@io : IO::FileDescriptor | IO::Memory @io : IO::FileDescriptor | IO::Memory
@ -10,30 +10,30 @@ module Ameba::Formatter
def finished(sources) def finished(sources)
super super
errors = sources.map(&.errors).flatten issues = sources.map(&.issues).flatten
generate_todo_config errors if errors.any? generate_todo_config issues if issues.any?
if (io = @io).is_a?(File) if (io = @io).is_a?(File)
@output << "Created #{io.path}\n" @output << "Created #{io.path}\n"
end end
end end
private def generate_todo_config(errors) private def generate_todo_config(issues)
@io << header @io << header
rule_errors_map(errors).each do |rule, rule_errors| rule_issues_map(issues).each do |rule, rule_issues|
@io << "\n# Problems found: #{rule_errors.size}" @io << "\n# Problems found: #{rule_issues.size}"
@io << "\n# Run `ameba --only #{rule.name}` for details" @io << "\n# Run `ameba --only #{rule.name}` for details"
@io << rule_todo(rule, rule_errors).gsub("---", "") @io << rule_todo(rule, rule_issues).gsub("---", "")
end end
ensure ensure
@io.flush @io.flush
end end
private def rule_errors_map(errors) private def rule_issues_map(issues)
Hash(Rule::Base, Array(Source::Error)).new.tap do |h| Hash(Rule::Base, Array(Issue)).new.tap do |h|
errors.each do |error| issues.each do |issue|
next if error.disabled? || error.rule.is_a? Rule::Syntax next if issue.disabled? || issue.rule.is_a? Rule::Syntax
h[error.rule] ||= Array(Source::Error).new h[issue.rule] ||= Array(Issue).new
h[error.rule] << error h[issue.rule] << issue
end end
end end
end end
@ -48,9 +48,9 @@ module Ameba::Formatter
HEADER HEADER
end end
private def rule_todo(rule, errors) private def rule_todo(rule, issues)
rule.excluded = rule.excluded =
errors.map(&.location.try &.filename.try &.to_s) issues.map(&.location.try &.filename.try &.to_s)
.compact .compact
.uniq! .uniq!

22
src/ameba/issue.cr Normal file
View File

@ -0,0 +1,22 @@
module Ameba
# Represents an issue reported by Ameba.
record Issue,
# A rule that triggers this issue.
rule : Rule::Base,
# Location of the issue.
location : Crystal::Location?,
# End location of the issue.
end_location : Crystal::Location?,
# Issue message.
message : String,
# Issue status.
status : Symbol? do
def disabled?
status == :disabled
end
end
end

34
src/ameba/reportable.cr Normal file
View File

@ -0,0 +1,34 @@
module Ameba
# Represents a module used to report issues.
module Reportable
# List of reported issues.
getter issues = [] of Issue
# Adds a new issue to the list of issues.
def add_issue(rule, location, end_location, message, status = nil)
status ||= :disabled if location_disabled?(location, rule.name)
issues << Issue.new rule, location, end_location, message, status
end
# Adds a new issue for AST *node*.
def add_issue(rule, node, message, **args)
add_issue rule, node.location, node.end_location, message, **args
end
# Adds a new issue for Crystal *token*.
def add_issue(rule, token, message, **args)
add_issue rule, token.location, nil, message, **args
end
# Adds a new issue for *location* defined by line and column numbers.
def add_issue(rule, location : Tuple(Int32, Int32), message, **args)
location = Crystal::Location.new path, *location
add_issue rule, location, nil, message, **args
end
# Returns true if the list of not disabled issues is empty, false otherwise.
def valid?
issues.reject(&.disabled?).empty?
end
end
end

View File

@ -13,7 +13,7 @@ module Ameba::Rule
# struct MyRule < Ameba::Rule::Base # struct MyRule < Ameba::Rule::Base
# def test(source) # def test(source)
# if invalid?(source) # if invalid?(source)
# source.error self, location, "Something wrong." # issue_for line, column, "Something wrong."
# end # end
# end # end
# #
@ -25,13 +25,13 @@ module Ameba::Rule
# #
# Enforces rules to implement an abstract `#test` method which # Enforces rules to implement an abstract `#test` method which
# is designed to test the source passed in. If source has issues # is designed to test the source passed in. If source has issues
# that are tested by this rule, it should add an error. # that are tested by this rule, it should add an issue.
# #
abstract struct Base abstract struct Base
include Config::RuleConfig include Config::RuleConfig
# This method is designed to test the source passed in. If source has issues # This method is designed to test the source passed in. If source has issues
# that are tested by this rule, it should add an error. # that are tested by this rule, it should add an issue.
abstract def test(source : Source) abstract def test(source : Source)
def test(source : Source, node : Crystal::ASTNode, *opts) def test(source : Source, node : Crystal::ASTNode, *opts)
@ -91,6 +91,10 @@ module Ameba::Rule
SPECIAL.includes? name SPECIAL.includes? name
end end
macro issue_for(*args)
source.add_issue self, {{*args}}
end
protected def self.rule_name protected def self.rule_name
name.gsub("Ameba::Rule::", "") name.gsub("Ameba::Rule::", "")
end end

View File

@ -39,7 +39,7 @@ module Ameba::Rule
return unless comparison? && to_boolean? return unless comparison? && to_boolean?
source.error self, node.location, MSG issue_for node, MSG
end end
end end
end end

View File

@ -38,7 +38,7 @@ module Ameba::Rule
name = target.names.first name = target.names.first
return if (expected = name.upcase) == name return if (expected = name.upcase) == name
source.error self, node.location, sprintf(MSG, expected, name) issue_for node, MSG % {expected, name}
end end
end end
end end

View File

@ -27,7 +27,7 @@ module Ameba::Rule
node.args.empty? && node.args.empty? &&
node.obj.nil? node.obj.nil?
source.error self, node.location, MSG issue_for node, MSG
end end
end end
end end

View File

@ -47,7 +47,7 @@ module Ameba::Rule
node_ensure = node.ensure node_ensure = node.ensure
return if node_ensure.nil? || !node_ensure.nop? return if node_ensure.nil? || !node_ensure.nop?
source.error self, node.location, MSG issue_for node, MSG
end end
end end
end end

View File

@ -47,12 +47,12 @@ module Ameba::Rule
return if exp.nil? || exp == "nil" return if exp.nil? || exp == "nil"
source.error self, node.location, MSG % exp issue_for node, MSG % exp
end end
def test(source, node : Crystal::Expressions) def test(source, node : Crystal::Expressions)
if node.expressions.size == 1 && node.expressions.first.nop? if node.expressions.size == 1 && node.expressions.first.nop?
source.error self, node.location, MSG_EXRS issue_for node, MSG_EXRS
end end
end end
end end

View File

@ -34,7 +34,7 @@ module Ameba::Rule
def test(source, node : Crystal::HashLiteral) def test(source, node : Crystal::HashLiteral)
return unless (keys = duplicated_keys(node.entries)).any? return unless (keys = duplicated_keys(node.entries)).any?
source.error self, node.location, MSG % keys.join(", ") issue_for node, MSG % keys.join(", ")
end end
private def duplicated_keys(entries) private def duplicated_keys(entries)

View File

@ -42,7 +42,7 @@ module Ameba::Rule
parsed = parse_number token.raw parsed = parse_number token.raw
if allowed?(*parsed) && (expected = underscored *parsed) != token.raw if allowed?(*parsed) && (expected = underscored *parsed) != token.raw
source.error self, token.location, MSG % expected issue_for token, MSG % expected
end end
end end
end end

View File

@ -22,7 +22,7 @@ module Ameba::Rule
source.lines.each_with_index do |line, index| source.lines.each_with_index do |line, index|
next unless line.size > max_length next unless line.size > max_length
source.error self, index + 1, line.size, MSG issue_for({index + 1, line.size}, MSG)
end end
end end
end end

View File

@ -36,7 +36,7 @@ module Ameba::Rule
def check_node(source, node) def check_node(source, node)
return unless literal?(node.cond) return unless literal?(node.cond)
source.error self, node.location, MSG issue_for node, MSG
end end
def test(source, node : Crystal::If) def test(source, node : Crystal::If)

View File

@ -32,7 +32,7 @@ module Ameba::Rule
def test(source, node : Crystal::StringInterpolation) def test(source, node : Crystal::StringInterpolation)
found = node.expressions.any? { |e| !string_literal?(e) && literal?(e) } found = node.expressions.any? { |e| !string_literal?(e) && literal?(e) }
return unless found return unless found
source.error self, node.location, MSG issue_for node, MSG
end end
end end
end end

View File

@ -52,7 +52,7 @@ module Ameba::Rule
def test(source, node : Crystal::Def) def test(source, node : Crystal::Def)
return if (expected = node.name.underscore) == node.name return if (expected = node.name.underscore) == node.name
source.error self, node.location, sprintf(MSG, expected, node.name) issue_for node, MSG % {expected, node.name}
end end
end end
end end

View File

@ -40,7 +40,7 @@ module Ameba::Rule
def test(source, node : Crystal::Unless) def test(source, node : Crystal::Unless)
return unless negated_condition? node.cond return unless negated_condition? node.cond
source.error self, node.location, MSG issue_for node, MSG
end end
private def negated_condition?(node) private def negated_condition?(node)

View File

@ -34,21 +34,21 @@ module Ameba::Rule
MSG = "Symbols `%s` may be unwanted in %s array literals" MSG = "Symbols `%s` may be unwanted in %s array literals"
def test(source) def test(source)
error = start_token = nil issue = start_token = nil
Tokenizer.new(source).run do |token| Tokenizer.new(source).run do |token|
case token.type case token.type
when :STRING_ARRAY_START, :SYMBOL_ARRAY_START when :STRING_ARRAY_START, :SYMBOL_ARRAY_START
start_token = token.dup start_token = token.dup
when :STRING when :STRING
if start_token && error.nil? if start_token && issue.nil?
error = array_entry_invalid?(token.value, start_token.not_nil!.raw) issue = array_entry_invalid?(token.value, start_token.not_nil!.raw)
end end
when :STRING_ARRAY_END, :SYMBOL_ARRAY_END when :STRING_ARRAY_END, :SYMBOL_ARRAY_END
if error if issue
source.error(self, start_token.try &.location, error.not_nil!) issue_for start_token.not_nil!, issue.not_nil!
end end
error = start_token = nil issue = start_token = nil
end end
end end
end end
@ -64,7 +64,7 @@ module Ameba::Rule
private def check_array_entry(entry, symbols, literal) private def check_array_entry(entry, symbols, literal)
return unless entry =~ /[#{symbols}]/ return unless entry =~ /[#{symbols}]/
sprintf(MSG, symbols, literal) MSG % {symbols, literal}
end end
end end
end end

View File

@ -45,7 +45,7 @@ module Ameba::Rule
alternative = $2 alternative = $2
return unless alternative =~ /^[a-z][a-zA-Z_0-9]*$/ return unless alternative =~ /^[a-z][a-zA-Z_0-9]*$/
source.error self, node.location, sprintf(MSG, alternative, node.name) issue_for node, MSG % {alternative, node.name}
end end
end end
end end

View File

@ -42,7 +42,7 @@ module Ameba::Rule
(value = arg.value) && (value = arg.value) &&
(value == "0" || value == "1") (value == "0" || value == "1")
source.error self, node.location, MSG % node issue_for node, MSG % node
end end
end end
end end

View File

@ -71,7 +71,7 @@ module Ameba::Rule
def test(source, node : Crystal::Def) def test(source, node : Crystal::Def)
return unless redundant_begin?(source, node) return unless redundant_begin?(source, node)
source.error self, node.location, MSG issue_for node, MSG
end end
private def redundant_begin?(source, node) private def redundant_begin?(source, node)

View File

@ -51,7 +51,7 @@ module Ameba::Rule
scope.arguments.each do |arg| scope.arguments.each do |arg|
next unless assign = arg.variable.assign_before_reference next unless assign = arg.variable.assign_before_reference
source.error self, assign.location, MSG % arg.name issue_for assign, MSG % arg.name
end end
end end
end end

View File

@ -49,7 +49,7 @@ module Ameba::Rule
return unless excs = node.rescues return unless excs = node.rescues
if (excs = shadowed excs.map(&.types)).any? if (excs = shadowed excs.map(&.types)).any?
source.error self, node.location, MSG % excs.join(", ") issue_for node, MSG % excs.join(", ")
end end
end end

View File

@ -54,7 +54,7 @@ module Ameba::Rule
private def find_shadowing(source, scope) private def find_shadowing(source, scope)
scope.arguments.each do |arg| scope.arguments.each do |arg|
if scope.outer_scope.try &.find_variable(arg.name) if scope.outer_scope.try &.find_variable(arg.name)
source.error self, arg.location, MSG % arg.name issue_for arg, MSG % arg.name
end end
end end
end end

View File

@ -23,7 +23,7 @@ module Ameba::Rule
def test(source) def test(source)
source.ast source.ast
rescue e : Crystal::SyntaxException rescue e : Crystal::SyntaxException
source.error self, e.line_number, e.column_number, e.message.to_s issue_for({e.line_number, e.column_number}, e.message.to_s)
end end
end end
end end

View File

@ -17,7 +17,7 @@ module Ameba::Rule
def test(source) def test(source)
if source.lines.size > 1 && source.lines[-2, 2].join.strip.empty? if source.lines.size > 1 && source.lines[-2, 2].join.strip.empty?
source.error self, source.lines.size, 1, MSG issue_for({source.lines.size, 1}, MSG)
end end
end end
end end

View File

@ -18,7 +18,7 @@ module Ameba::Rule
def test(source) def test(source)
source.lines.each_with_index do |line, index| source.lines.each_with_index do |line, index|
next unless line =~ /\s$/ next unless line =~ /\s$/
source.error self, index + 1, line.size, MSG issue_for({index + 1, line.size}, MSG)
end end
end end
end end

View File

@ -68,7 +68,7 @@ module Ameba::Rule
expected = name.camelcase expected = name.camelcase
return if expected == name return if expected == name
source.error self, node.location, sprintf(MSG, expected, name) issue_for node, MSG % {expected, name}
end end
def test(source, node : Crystal::ClassDef) def test(source, node : Crystal::ClassDef)

View File

@ -56,7 +56,7 @@ module Ameba::Rule
def test(source, node : Crystal::Unless) def test(source, node : Crystal::Unless)
return if node.else.nop? return if node.else.nop?
source.error self, node.location, MSG issue_for node, MSG
end end
end end
end end

View File

@ -32,7 +32,7 @@ module Ameba::Rule
next unless names = unneeded_disables(source, directive, token.location) next unless names = unneeded_disables(source, directive, token.location)
next unless names.any? next unless names.any?
source.error self, token.location, MSG % names.join(", ") issue_for token, MSG % names.join(", ")
end end
end end
@ -40,19 +40,19 @@ module Ameba::Rule
return unless directive[:action] == "disable" return unless directive[:action] == "disable"
directive[:rules].reject do |rule_name| directive[:rules].reject do |rule_name|
source.errors.any? do |error| source.issues.any? do |issue|
error.rule.name == rule_name && issue.rule.name == rule_name &&
error.disabled? && issue.disabled? &&
error_at_location?(source, error, location) issue_at_location?(source, issue, location)
end && rule_name != self.name end && rule_name != self.name
end end
end end
private def error_at_location?(source, error, location) private def issue_at_location?(source, issue, location)
return false unless error_line_number = error.location.try(&.line_number) return false unless issue_line_number = issue.location.try(&.line_number)
error_line_number == location.line_number || issue_line_number == location.line_number ||
((prev_line_number = error_line_number - 1) && ((prev_line_number = issue_line_number - 1) &&
prev_line_number == location.line_number && prev_line_number == location.line_number &&
source.comment?(prev_line_number - 1)) source.comment?(prev_line_number - 1))
end end

View File

@ -58,7 +58,7 @@ module Ameba::Rule
next if argument.ignored? || scope.references?(argument.variable) next if argument.ignored? || scope.references?(argument.variable)
name_suggestion = scope.node.is_a?(Crystal::Block) ? '_' : "_#{argument.name}" name_suggestion = scope.node.is_a?(Crystal::Block) ? '_' : "_#{argument.name}"
source.error self, argument.location, MSG % {argument.name, name_suggestion} issue_for argument, MSG % {argument.name, name_suggestion}
end end
end end
end end

View File

@ -43,7 +43,7 @@ module Ameba::Rule
var.assignments.each do |assign| var.assignments.each do |assign|
next if assign.referenced? next if assign.referenced?
source.error self, assign.location, MSG % var.name issue_for assign, MSG % var.name
end end
end end
end end

View File

@ -48,7 +48,7 @@ module Ameba::Rule
.map(&.to_s) .map(&.to_s)
.none? { |c| c == cond_s } .none? { |c| c == cond_s }
source.error self, cond.location, MSG issue_for cond, MSG
end end
def test(source) def test(source)

View File

@ -45,7 +45,7 @@ module Ameba::Rule
private def check_node(source, node) private def check_node(source, node)
return if (expected = node.name.underscore) == node.name return if (expected = node.name.underscore) == node.name
source.error self, node.location, sprintf(MSG, expected, node.name) issue_for node, MSG % {expected, node.name}
end end
def test(source, node : Crystal::Var) def test(source, node : Crystal::Var)

View File

@ -39,7 +39,7 @@ module Ameba::Rule
def test(source, node : Crystal::While) def test(source, node : Crystal::While)
return unless node.cond.true_literal? return unless node.cond.true_literal?
source.error self, node.location, MSG issue_for node, MSG
end end
end end
end end

View File

@ -52,7 +52,7 @@ module Ameba
# Performs the inspection. Iterates through all sources and test it using # Performs the inspection. Iterates through all sources and test it using
# list of rules. If a specific rule fails on a specific source, it adds # list of rules. If a specific rule fails on a specific source, it adds
# an error to that source. # an issue to that source.
# #
# This action also notifies formatter when inspection is started/finished, # This action also notifies formatter when inspection is started/finished,
# and when a specific source started/finished to be inspected. # and when a specific source started/finished to be inspected.

View File

@ -1,22 +1,9 @@
module Ameba module Ameba
# An entity that represents a Crystal source file. # An entity that represents a Crystal source file.
# Has path, lines of code and errors reported by rules. # Has path, lines of code and issues reported by rules.
class Source class Source
include InlineComments include InlineComments
include Reportable
# Represents an error caught by Ameba.
#
# Each error has the rule that created this error,
# location of the issue, message and status.
record Error,
rule : Rule::Base,
location : Crystal::Location?,
message : String,
status : Symbol? do
def disabled?
status == :disabled
end
end
# Path to the source file. # Path to the source file.
getter path : String getter path : String
@ -24,9 +11,6 @@ module Ameba
# Crystal code (content of a source file). # Crystal code (content of a source file).
getter code : String getter code : String
# List of errors reported.
getter errors = [] of Error
@lines : Array(String)? @lines : Array(String)?
@ast : Crystal::ASTNode? @ast : Crystal::ASTNode?
@fullpath : String? @fullpath : String?
@ -43,42 +27,6 @@ module Ameba
def initialize(@code : String, @path = "") def initialize(@code : String, @path = "")
end end
# Adds a new error to the list of errors.
#
# ```
# source.error rule, location, "Line too long"
# ```
#
def error(rule : Rule::Base, location, message : String, status = nil)
status ||= :disabled if location_disabled?(location, rule.name)
errors << Error.new rule, location, message, status
end
# Adds a new error to the list of errors using line and column number.
#
# ```
# source.error rule, line_number, column_number, "Bad code"
# ```
#
def error(rule : Rule::Base, l, c, message : String, status = nil)
location = Crystal::Location.new path, l, c
error rule, location, message, status
end
# Indicates whether source is valid or not.
# Returns true if the list or errors empty, false otherwise.
#
# ```
# source = Ameba::Source.new code, path
# source.valid? # => true
# source.error rule, location, message
# source.valid? # => false
# ```
#
def valid?
errors.reject(&.disabled?).empty?
end
# Returns lines of code splitted by new line character. # Returns lines of code splitted by new line character.
# Since `code` is immutable and can't be changed, this # Since `code` is immutable and can't be changed, this
# method caches lines in an instance variable, so calling # method caches lines in an instance variable, so calling