Ameba 0.12.0 has been released
Check out the release notes to see a full scope of changes. Here we will go through the most important changes.
Globs and Excluded configuration options
Ameba now allows to globally configure the list of sources to run the inspection
on. There are two new sections which can be added to .ameba.yml:
Globs:
- "**/*.cr"
- "!lib"
Excluded:
- src/compiler
Globsis used to define paths to include to the inspection. Defaults to%w[**/*.cr !lib]Excludedis used to exclude from the list defined byGlobs
Crystal 0.34 compatibility
Crystal 0.34 is not yet released, but thanks to @bcardiff
Ameba is now ready to the upcoming release and is still compatible to the current
Crystal version 0.33.
New rules
Style/RedundantNext
Crystal has next
keyword which can be used to go to the next iteration in a loop. However, it can
also be used to exit from a block, for example:
(1..3).each do |e|
next if e.even?
puts e
end # => 1, 3
And in some places next can be overused, especially when it combines in the
last expression in the block. For example:
block do |v|
case v
when .nil?
next "nil"
when .blank?
next "blank"
else
next "empty"
end
end
In all three places above the next keyword is redundant and is reported by the
new rule.
Lint/SharedVarInFiber
To achieve concurrency and parallelism Crystal uses Fibers and Channels. There is a tricky behavior which happens when a shared variable is used across multiple fibers and is mutated during iterations. For example:
n = 0
channel = Channel(Int32).new
while n < 3
n = n + 1
spawn { channel.send(n) }
end
3.times { puts channel.receive }
You might expect the code above to print 1 2 3, however it prints 3 3 3. The
problem is there is a shared variable n and when channel.receive is executed
its value is 3.
To solve it, the code above can be written to the following:
n = 0
channel = Channel(Int32).new
while n < 3
n = n + 1
+ m = n
- spawn { channel.send(n) }
+ spawn { channel.send(m) }
end
3.times { puts channel.receive }
So instead of using a shared variable n which is declared at the top and
mutated in a while loop, we reassign the value to variable m and use it in
our spawn. As the result, the code above prints the expected 1 2 3.
The new rule properly reports the issue on the first sample and passes on the
second one.
There are also other techniques to solve the problem above which are officially documented.
Lint/EmptyLoop
After some round of refactoring it can happen that the loop body becomes empty but for whatever reason such an empty loop is forgotten to be removed. A new rule is able to detect a few situations:
while true
# empty body
end
until false
# empty body
end
loop do
# empty block
end
And these samples are valid and not reported:
a = 1
while a < 10
a += 1
end
until socket_opened?
end
loop { run }
Lint/RedundantStringCoercion
This is typical situation when a value is being converted to string using
Object#to_s
method in the interpolation.
"Hello, #{name.to_s}"
Since each interpolated value (enclosed by #{ ... }) ends up implicitly
invoking Object#to_s, explicit calls are redundant and are now reported by Ameba.
The code above is forced to be changed to:
"Hello, #{name}"