New rule: EmptyLoop

This commit is contained in:
Vitalii Elenhaupt 2020-03-26 10:27:00 +02:00
parent 1a0468c653
commit a6958ab455
No known key found for this signature in database
GPG key ID: CD0BF17825928BC0
3 changed files with 157 additions and 0 deletions

View file

@ -0,0 +1,88 @@
require "../../../spec_helper"
module Ameba::Rule::Lint
describe EmptyLoop do
subject = EmptyLoop.new
it "does not report if there are not empty loops" do
s = Source.new %(
a = 1
while a < 10
a += 1
end
until a == 10
a += 1
end
loop do
a += 1
end
)
subject.catch(s).should be_valid
end
it "reports if there is an empty while loop" do
s = Source.new %(
a = 1
while true
end
)
subject.catch(s).should_not be_valid
end
it "doesn't report if while loop has non-literals in cond block" do
s = Source.new %(
a = 1
while a = gets.to_s
# nothing here
end
)
subject.catch(s).should be_valid
end
it "reports if there is an empty until loop" do
s = Source.new %(
do_something
until false
end
)
subject.catch(s).should_not be_valid
end
it "doesn't report if until loop has non-literals in cond block" do
s = Source.new %(
until socket_open?
end
)
subject.catch(s).should be_valid
end
it "reports if there an empty loop" do
s = Source.new %(
a = 1
loop do
end
)
subject.catch(s).should_not be_valid
end
it "reports rule, message and location" do
s = Source.new %(
a = 1
loop do
# comment goes here
end
), "source.cr"
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:1"
issue.end_location.to_s.should eq "source.cr:4:3"
issue.message.should eq EmptyLoop::MSG
end
end
end

View file

@ -25,6 +25,7 @@ module Ameba::AST
Var,
When,
While,
Until,
]
# An AST Visitor that traverses the source and allows all nodes

View file

@ -0,0 +1,68 @@
module Ameba::Rule::Lint
# A rule that disallows empty loops.
#
# This is considered invalid:
#
# ```
# while false
# end
#
# until 10
# end
#
# loop do
# # nothing here
# end
# ```
#
# And this is valid:
#
# ```
# a = 1
# while a < 10
# a += 1
# end
#
# until socket_opened?
# end
#
# loop do
# do_something_here
# end
# ```
#
# YAML configuration example:
#
# ```
# Lint/EmptyLoop:
# Enabled: true
# ```
struct EmptyLoop < Base
include AST::Util
properties do
description "Disallows empty loops"
end
MSG = "Empty loop detected"
def test(source, node : Crystal::Call)
return unless loop?(node)
check_node(source, node, node.block)
end
def test(source, node : Crystal::While)
check_node(source, node, node.body) if literal?(node.cond)
end
def test(source, node : Crystal::Until)
check_node(source, node, node.body) if literal?(node.cond)
end
private def check_node(source, node, loop_body)
body = loop_body.is_a?(Crystal::Block) ? loop_body.body : loop_body
issue_for node, MSG if body.nil? || body.nop?
end
end
end