Extend Lint/UnusedArgument rule with corrections

This commit is contained in:
Sijawusz Pur Rahnama 2022-12-19 15:40:30 +01:00
parent e6ebca7a5b
commit f26cd7f823
2 changed files with 125 additions and 114 deletions

View file

@ -6,7 +6,7 @@ module Ameba::Rule::Lint
describe UnusedArgument do describe UnusedArgument do
it "doesn't report if arguments are used" do it "doesn't report if arguments are used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(a, b, c) def method(a, b, c)
a + b + c a + b + c
end end
@ -16,155 +16,158 @@ module Ameba::Rule::Lint
end end
->(i : Int32) { i + 1 } ->(i : Int32) { i + 1 }
) CRYSTAL
subject.catch(s).should be_valid
end end
it "reports if method argument is unused" do it "reports if method argument is unused" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
def method(a, b, c) def method(a, b, c)
# ^ error: Unused argument `c`. If it's necessary, use `_c` as an argument name to indicate that it won't be used.
a + b a + b
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues.first.message.should eq "Unused argument `c`. If it's necessary, use `_c` " \ expect_correction source, <<-CRYSTAL
"as an argument name to indicate that it won't be used." def method(a, b, _c)
a + b
end
CRYSTAL
end end
it "reports if block argument is unused" do it "reports if block argument is unused" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
[1,2].each_with_index do |a, i| [1, 2].each_with_index do |a, i|
# ^ error: Unused argument `i`. [...]
a a
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues.first.message.should eq "Unused argument `i`. If it's necessary, use `_` " \ expect_correction source, <<-CRYSTAL
"as an argument name to indicate that it won't be used." [1, 2].each_with_index do |a, _|
a
end
CRYSTAL
end end
it "reports if proc argument is unused" do it "reports if proc argument is unused" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
-> (a : Int32, b : String) do -> (a : Int32, b : String) do
# ^ error: Unused argument `b`. If it's necessary, use `_b` as an argument name to indicate that it won't be used.
a = a + 1 a = a + 1
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues.first.message.should eq "Unused argument `b`. If it's necessary, use `_b` " \ expect_correction source, <<-CRYSTAL
"as an argument name to indicate that it won't be used." -> (a : Int32, _b : String) do
a = a + 1
end
CRYSTAL
end end
it "reports multiple unused args" do it "reports multiple unused args" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
def method(a, b, c) def method(a, b, c)
# ^ error: Unused argument `a`. If it's necessary, use `_a` as an argument name to indicate that it won't be used.
# ^ error: Unused argument `b`. If it's necessary, use `_b` as an argument name to indicate that it won't be used.
# ^ error: Unused argument `c`. If it's necessary, use `_c` as an argument name to indicate that it won't be used.
nil nil
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues[0].message.should eq "Unused argument `a`. If it's necessary, use `_a` " \ expect_correction source, <<-CRYSTAL
"as an argument name to indicate that it won't be used." def method(_a, _b, _c)
s.issues[1].message.should eq "Unused argument `b`. If it's necessary, use `_b` " \ nil
"as an argument name to indicate that it won't be used." end
s.issues[2].message.should eq "Unused argument `c`. If it's necessary, use `_c` " \ CRYSTAL
"as an argument name to indicate that it won't be used."
end end
it "doesn't report if it is an instance var argument" do it "doesn't report if it is an instance var argument" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
class A class A
def method(@name) def method(@name)
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if a typed argument is used" do it "doesn't report if a typed argument is used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(x : Int32) def method(x : Int32)
3.times do 3.times do
puts x puts x
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if an argument with default value is used" do it "doesn't report if an argument with default value is used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(x = 1) def method(x = 1)
puts x puts x
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if argument starts with a _" do it "doesn't report if argument starts with a _" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(_x) def method(_x)
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if it is a block and used" do it "doesn't report if it is a block and used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(&block) def method(&block)
block.call block.call
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if block arg is not used" do it "doesn't report if block arg is not used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(&block) def method(&block)
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if unused and there is yield" do it "doesn't report if unused and there is yield" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(&block) def method(&block)
yield 1 yield 1
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if it's an anonymous block" do it "doesn't report if it's an anonymous block" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(&) def method(&)
yield 1 yield 1
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if variable is referenced implicitly" do it "doesn't report if variable is referenced implicitly" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
class Bar < Foo class Bar < Foo
def method(a, b) def method(a, b)
super super
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if arg if referenced in case" do it "doesn't report if arg if referenced in case" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def foo(a) def foo(a)
case a case a
when /foo/ when /foo/
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if enum in a record" do it "doesn't report if enum in a record" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
class Class class Class
record Record do record Record do
enum Enum enum Enum
@ -172,59 +175,49 @@ module Ameba::Rule::Lint
end end
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
context "super" do context "super" do
it "reports if variable is not referenced implicitly by super" do it "reports if variable is not referenced implicitly by super" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
class Bar < Foo class Bar < Foo
def method(a, b) def method(a, b)
# ^ error: Unused argument `b`. If it's necessary, use `_b` as an argument name to indicate that it won't be used.
super a super a
end end
end end
) CRYSTAL
subject.catch(s).should_not be_valid
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."
end
it "reports rule, location and message" do expect_correction source, <<-CRYSTAL
s = Source.new %( class Bar < Foo
def method(a) def method(a, _b)
super a
end
end end
), "source.cr" CRYSTAL
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
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."
issue.location.to_s.should eq "source.cr:1:12"
end end
end end
context "macro" do context "macro" do
it "doesn't report if it is a used macro argument" do it "doesn't report if it is a used macro argument" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
macro my_macro(arg) macro my_macro(arg)
{% arg %} {% arg %}
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if it is a used macro block argument" do it "doesn't report if it is a used macro block argument" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
macro my_macro(&block) macro my_macro(&block)
{% block %} {% block %}
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report used macro args with equal names in record" do it "doesn't report used macro args with equal names in record" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
record X do record X do
macro foo(a, b) macro foo(a, b)
{{ a }} + {{ b }} {{ a }} + {{ b }}
@ -234,12 +227,11 @@ module Ameba::Rule::Lint
{{ a }} + {{ b }} + {{ c }} {{ a }} + {{ b }} + {{ c }}
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report used args in macro literals" do it "doesn't report used args in macro literals" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def print(f : Array(U)) forall U def print(f : Array(U)) forall U
f.size.times do |i| f.size.times do |i|
{% if U == Float64 %} {% if U == Float64 %}
@ -249,65 +241,73 @@ module Ameba::Rule::Lint
{% end %} {% end %}
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
end end
context "properties" do context "properties" do
describe "#ignore_defs" do describe "#ignore_defs" do
it "lets the rule to ignore def scopes if true" do it "lets the rule to ignore def scopes if true" do
subject.ignore_defs = true rule = UnusedArgument.new
s = Source.new %( rule.ignore_defs = true
expect_no_issues rule, <<-CRYSTAL
def method(a) def method(a)
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "lets the rule not to ignore def scopes if false" do it "lets the rule not to ignore def scopes if false" do
subject.ignore_defs = false rule = UnusedArgument.new
s = Source.new %( rule.ignore_defs = false
expect_issue rule, <<-CRYSTAL
def method(a) def method(a)
# ^ error: Unused argument `a`. If it's necessary, use `_a` as an argument name to indicate that it won't be used.
end end
) CRYSTAL
subject.catch(s).should_not be_valid
end end
end end
context "#ignore_blocks" do context "#ignore_blocks" do
it "lets the rule to ignore block scopes if true" do it "lets the rule to ignore block scopes if true" do
subject.ignore_blocks = true rule = UnusedArgument.new
s = Source.new %( rule.ignore_blocks = true
expect_no_issues rule, <<-CRYSTAL
3.times { |i| puts "yo!" } 3.times { |i| puts "yo!" }
) CRYSTAL
subject.catch(s).should be_valid
end end
it "lets the rule not to ignore block scopes if false" do it "lets the rule not to ignore block scopes if false" do
subject.ignore_blocks = false rule = UnusedArgument.new
s = Source.new %( rule.ignore_blocks = false
expect_issue rule, <<-CRYSTAL
3.times { |i| puts "yo!" } 3.times { |i| puts "yo!" }
) # ^ error: Unused argument `i`. If it's necessary, use `_` as an argument name to indicate that it won't be used.
subject.catch(s).should_not be_valid CRYSTAL
end end
end end
context "#ignore_procs" do context "#ignore_procs" do
it "lets the rule to ignore proc scopes if true" do it "lets the rule to ignore proc scopes if true" do
subject.ignore_procs = true rule = UnusedArgument.new
s = Source.new %( rule.ignore_procs = true
expect_no_issues rule, <<-CRYSTAL
->(a : Int32) {} ->(a : Int32) {}
) CRYSTAL
subject.catch(s).should be_valid
end end
it "lets the rule not to ignore proc scopes if false" do it "lets the rule not to ignore proc scopes if false" do
subject.ignore_procs = false rule = UnusedArgument.new
s = Source.new %( rule.ignore_procs = false
expect_issue rule, <<-CRYSTAL
->(a : Int32) {} ->(a : Int32) {}
) # ^ error: Unused argument `a`. If it's necessary, use `_a` as an argument name to indicate that it won't be used.
subject.catch(s).should_not be_valid CRYSTAL
end end
end end
end end

View file

@ -63,7 +63,18 @@ module Ameba::Rule::Lint
next if scope.references?(argument.variable) next if scope.references?(argument.variable)
name_suggestion = scope.node.is_a?(Crystal::Block) ? '_' : "_#{argument.name}" name_suggestion = scope.node.is_a?(Crystal::Block) ? '_' : "_#{argument.name}"
issue_for argument.node, MSG % {argument.name, name_suggestion} message = MSG % {argument.name, name_suggestion}
location = argument.node.location
end_location = location.try &.adjust(column_number: argument.name.size - 1)
if location && end_location
issue_for argument.node, message do |corrector|
corrector.replace(location, end_location, name_suggestion)
end
else
issue_for argument.node, message
end
end end
end end
end end