Re-run autocorrect until all correctable issues have been corrected

This commit is contained in:
fn ⌃ ⌥ 2021-10-25 15:09:39 -07:00
parent 573881cb8a
commit f39a7a4cd4
12 changed files with 78 additions and 60 deletions

View file

@ -69,24 +69,24 @@ module Ameba::Formatter
describe "#affected_code" do
it "returns nil if there is no such a line number" do
source = Source.new %(
code = <<-EOF
a = 1
)
EOF
location = Crystal::Location.new("filename", 2, 1)
subject.affected_code(source, location).should be_nil
subject.affected_code(code, location).should be_nil
end
it "returns correct line if it is found" do
source = Source.new %(
code = <<-EOF
a = 1
)
EOF
location = Crystal::Location.new("filename", 1, 1)
subject.deansify(subject.affected_code(source, location))
subject.deansify(subject.affected_code(code, location))
.should eq "> a = 1\n ^\n"
end
it "returns correct line if it is found" do
source = Source.new <<-EOF
code = <<-EOF
# pre:1
# pre:2
# pre:3
@ -101,7 +101,7 @@ module Ameba::Formatter
EOF
location = Crystal::Location.new("filename", 6, 1)
subject.deansify(subject.affected_code(source, location, context_lines: 3))
subject.deansify(subject.affected_code(code, location, context_lines: 3))
.should eq <<-STR
> # pre:3
> # pre:4

View file

@ -3,7 +3,8 @@ require "../spec_helper"
module Ameba
describe Issue do
it "accepts rule and message" do
issue = Issue.new rule: DummyRule.new,
issue = Issue.new code: "",
rule: DummyRule.new,
location: nil,
end_location: nil,
message: "Blah",
@ -15,7 +16,8 @@ module Ameba
it "accepts location" do
location = Crystal::Location.new("path", 3, 2)
issue = Issue.new rule: DummyRule.new,
issue = Issue.new code: "",
rule: DummyRule.new,
location: location,
end_location: nil,
message: "Blah",
@ -27,7 +29,8 @@ module Ameba
it "accepts end_location" do
location = Crystal::Location.new("path", 3, 2)
issue = Issue.new rule: DummyRule.new,
issue = Issue.new code: "",
rule: DummyRule.new,
location: nil,
end_location: location,
message: "Blah",
@ -38,7 +41,8 @@ module Ameba
end
it "accepts status" do
issue = Issue.new rule: DummyRule.new,
issue = Issue.new code: "",
rule: DummyRule.new,
location: nil,
end_location: nil,
message: "",
@ -50,7 +54,8 @@ module Ameba
end
it "sets status to :enabled by default" do
issue = Issue.new rule: DummyRule.new,
issue = Issue.new code: "",
rule: DummyRule.new,
location: nil,
end_location: nil,
message: ""

View file

@ -1,6 +1,7 @@
require "../../spec_helper"
private def dummy_issue(message,
private def dummy_issue(code,
message,
position : {Int32, Int32}?,
end_position : {Int32, Int32}?,
path = "")
@ -9,6 +10,7 @@ private def dummy_issue(message,
end_location = Crystal::Location.new(path, *end_position) if end_position
Ameba::Issue.new(
code: code,
rule: Ameba::DummyRule.new,
location: location,
end_location: end_location,
@ -25,7 +27,7 @@ private def expect_invalid_location(code,
expect_raises Exception, exception_message, file, line do
Ameba::Spec::AnnotatedSource.new(
lines: code.lines,
issues: [dummy_issue("Message", position, end_position, "path")]
issues: [dummy_issue(code, "Message", position, end_position, "path")]
)
end
end

View file

@ -50,7 +50,7 @@ module Ameba::Formatter
"#{issue.rule.name}: " \
"#{issue.message}".colorize(:red)
if show_affected_code && (code = affected_code(source, location, issue.end_location))
if show_affected_code && (code = affected_code(issue))
output << code.colorize(:default)
end

View file

@ -38,10 +38,7 @@ module Ameba::Formatter
private def explain(source, issue)
rule = issue.rule
location, end_location =
issue.location, issue.end_location
return unless location
return unless (location = issue.location)
output_title "ISSUE INFO"
output_paragraph [
@ -49,7 +46,7 @@ module Ameba::Formatter
location.to_s.colorize(:cyan).to_s,
]
if affected_code = affected_code(source, location, end_location, context_lines: 3)
if affected_code = affected_code(issue, context_lines: 3)
output_title "AFFECTED CODE"
output_paragraph affected_code
end

View file

@ -40,8 +40,14 @@ module Ameba::Formatter
{pre_context, post_context}
end
def affected_code(source, location, end_location = nil, context_lines = 0, max_length = 120, ellipsis = " ...", prompt = "> ")
lines = source.lines
def affected_code(issue : Issue, context_lines = 0, max_length = 120, ellipsis = " ...", prompt = "> ")
return unless (location = issue.location)
affected_code(issue.code, location, issue.end_location, context_lines, max_length, ellipsis, prompt)
end
def affected_code(code, location, end_location = nil, context_lines = 0, max_length = 120, ellipsis = " ...", prompt = "> ")
lines = code.split('\n') # must preserve trailing newline
lineno, column =
location.line_number, location.column_number

View file

@ -6,6 +6,9 @@ module Ameba
Disabled
end
# The source code that triggered this issue.
getter code : String
# A rule that triggers this issue.
getter rule : Rule::Base
@ -24,7 +27,7 @@ module Ameba
delegate :enabled?, :disabled?,
to: status
def initialize(@rule, @location, @end_location, @message, status : Status? = nil, @block : (Source::Corrector ->)? = nil)
def initialize(@code, @rule, @location, @end_location, @message, status : Status? = nil, @block : (Source::Corrector ->)? = nil)
@status = status || Status::Enabled
end

View file

@ -9,7 +9,7 @@ module Ameba
status ||=
Issue::Status::Disabled if location_disabled?(location, rule)
Issue.new(rule, location, end_location, message, status, block).tap do |issue|
Issue.new(code, rule, location, end_location, message, status, block).tap do |issue|
issues << issue
end
end

View file

@ -93,17 +93,23 @@ module Ameba
private def run_source(source)
@formatter.source_started source
# TODO: run autocorrection recursively. A new `Issue#source` property must
# be added so that `affected_code` will return the code from the old
# source instead of the autocorrected one.
if @syntax_rule.catch(source).valid?
corrected_issues = [] of Issue
loop do
@syntax_rule.test(source)
break unless source.valid?
@rules.each do |rule|
next if rule.excluded?(source)
rule.test(source)
end
check_unneeded_directives(source)
autocorrect(source) if autocorrect?
break unless autocorrect? && source.correct
corrected_issues += source.issues.select(&.correctable?)
source.issues.clear
end
corrected_issues.reverse_each { |issue| source.issues.unshift(issue) }
File.write(source.path, source.code) if corrected_issues.any?
@formatter.source_finished source
end
@ -143,12 +149,5 @@ module Ameba
rule.test(source)
end
end
private def autocorrect(source)
corrected_code = Source::Corrector.correct(source)
return unless corrected_code
File.write(source.path, corrected_code)
end
end
end

View file

@ -11,6 +11,9 @@ module Ameba
# Crystal code (content of a source file).
getter code : String
@lines : Array(String)?
@ast : Crystal::ASTNode?
# Creates a new source by `code` and `path`.
#
# For example:
@ -22,6 +25,18 @@ module Ameba
def initialize(@code, @path = "")
end
def correct
corrector = Corrector.new(code)
issues.each(&.correct(corrector))
corrected_code = corrector.process
return false if code == corrected_code
@code = corrected_code
@lines = nil
@ast = nil
true
end
# Returns lines of code splitted by new line character.
# Since `code` is immutable and can't be changed, this
# method caches lines in an instance variable, so calling
@ -33,7 +48,9 @@ module Ameba
# source.lines # => ["a = 1", "b = 2"]
# ```
#
getter lines : Array(String) { code.split('\n') }
def lines : Array(String)
@lines ||= code.split('\n')
end
# Returns AST nodes constructed by `Crystal::Parser`.
#
@ -42,11 +59,12 @@ module Ameba
# source.ast
# ```
#
getter ast : Crystal::ASTNode do
Crystal::Parser.new(code)
.tap(&.wants_doc = true)
.tap(&.filename = path)
.parse
def ast : Crystal::ASTNode
@ast ||=
Crystal::Parser.new(code)
.tap(&.wants_doc = true)
.tap(&.filename = path)
.parse
end
getter fullpath : String do

View file

@ -2,16 +2,6 @@ require "./rewriter"
class Ameba::Source
class Corrector
def self.correct(source : Source)
code = source.code
corrector = new(code)
source.issues.each(&.correct(corrector))
corrected_code = corrector.process
return if code == corrected_code
corrected_code
end
@line_sizes : Array(Int32)
def initialize(code : String)

View file

@ -85,9 +85,8 @@ module Ameba::Spec::ExpectIssue
source = ExpectIssue.source
raise "`expect_correction` must follow `expect_issue`" unless source
corrected_code = Source::Corrector.correct(source) # TODO: recursive
raise "Use `expect_no_corrections` if the code will not change" unless corrected_code
return if correction == corrected_code
raise "Use `expect_no_corrections` if the code will not change" unless source.correct
return if correction == source.code
fail <<-MSG, file, line
Expected correction:
@ -96,7 +95,7 @@ module Ameba::Spec::ExpectIssue
Got:
#{corrected_code}
#{source.code}
MSG
end
@ -104,13 +103,12 @@ module Ameba::Spec::ExpectIssue
source = ExpectIssue.source
raise "`expect_no_corrections` must follow `expect_offense`" unless source
corrected_code = Source::Corrector.correct(source)
return unless corrected_code
return unless source.correct
fail <<-MSG, file, line
Expected no corrections, but got:
#{corrected_code}
#{source.code}
MSG
end