Do any source code analysis tools detect unused parameters in Ruby? - ruby

How can I detect unused parameters in Ruby?
Options I'm aware of include
Being rigorous with TDD.
Heckle (currently works only with Ruby 1.8 due to ParseTree issues)
Using an IDE such as RubyMine to detect unused parameters or automate the refactoring.
But do any source code analysis tools or warning options allow you to detect unused parameters?
Background: I was doing some refactoring. I changed from (code slightly simplified):
# Not within the Foo class (therefore can't be as easily accessed by unit testing)
# and in addition, the name of the configuration file is hard-wired
def parse_configuration
raw_configuration = YAML.load_file("configuration.yml")
# Do stuff with raw_configuration to produce configuration_options_for_foo
return configuration_options_for_foo
end
if __FILE__ == $0
configuration_options_for_foo = parse_configuration
foo = Foo.new(configuration_options_for_foo)
end
to
class Foo
# Now unit tests can call Foo.new_using_yaml("configuration.yml")
# or use "test_configuration.yml"
def self.new_using_yaml(yaml_filename)
# Where I went wrong, forgetting to replace "configuration.yml" with yaml_filename
raw_configuration = YAML.load_file("configuration.yml")
# Do stuff with raw_configuration to produce configuration_options_for_foo
new(configuration_options_for_foo)
end
end
if __FILE__ == $0
foo = Foo.new_using_yaml("configuration.yml")
end

I think Laser does this.
It's pretty alpha-y, but seems to do what you want.
http://github.com/michaeledgar/laser

Reek can detect unused parameters.

Ruby-lint can detect an unused parameter.
RuboCop has just added (April 2014) detection of unused parameters with the cop "UnusedMethodArgument".

Related

With Ruby and RSpec is there a way with rubocop to disallow constants being declared in the global scope?

I am working on setting up rubocop on a test suite I've taken over. The test suite contains a lib/constants.rb config that is made available to the test suite, and all tests are all found in a spec/* folder. I am setting up rubocop so that it only runs against the test specs (spec/*), and ignores the lib folder.
I have set up RSpec/LeakyConstantDeclaration with rubocop to ensure nobody is potentially re-assigning any of the global constants. This cop rule works for any specs written like this, and rubocop will correctly flag RSpec/LeakyConstantDeclaration on this file:
# spec/some_test_spec.rb
describe 'some test' do
SOME_CONSTANT = { 'blah' => 'blah' }
# SOME_CONSTANT used somewhere in here
it 'tests something' do
...
end
end
However, I've recently found some more tests that are also doing this (EEK!):
# spec/some_other_test_spec.rb
SOME_CONSTANT = { 'blah' => 'blah' }
describe 'some other test' do
# SOME_CONSTANT used somewhere in here
it 'tests something else' do
...
end
end
Some devs have started declaring their own constants explicitly in the global scope, and this could also potentially re-assign a constant created in the lib/constants.rb.
Since I'm running rubocop only against the test folder of spec/* is there any way to write a rubocop convention rule that disallows constants, and recommends using let or a local variable instead?
I'm thinking I may have to write a custom Cop to do this, but wondering if there is a known existing way to do it?
Thanks.

IntelliJ Ruby warning "Cannot resolve properly, was not processed"

I have many lines in my specs that result in this IntelliJ warning:
"Cannot resolve properly, was not processed"
The vast majority of the lines have this format:
expect(result[:err]).to include('(Check the file permissions.)')
If I move the literal string to a separate variable, the warning goes away:
msg = '(Check the file permissions.)'
expect(result[:err]).to include(msg)
Is there a way to make this error go away (other than moving all my string literals to variables)?
My guess is that the RubyMine parser thinks that include is the Ruby keyword to include a module and so it emits a warning telling it cannot find the corresponding module.
The only way I found to fix this warning is to use the inclusion alias proposed by the rspec include matcher :
expect(result[:err]).to inclusion('(Check the file permissions.)')
This fixes the warning and the expectation works the same, but sadly the english sentence is bad.
There is also 3 other aliases available, but they don't give better english syntax:
alias_matcher :a_collection_including, :include
alias_matcher :a_string_including, :include
alias_matcher :a_hash_including, :include
alias_matcher :including, :include
These alias definitions can be found here
Maybe by chance this answer could lead someone to a better solution.
If you're willing to switch from using the word include to something like contain, you could simply create a custom matcher:
RSpec::Matchers.define :contain do |expected|
match do |actual|
expect(actual).to include(expected)
end
end
You could either add that code directly in your rails_helper.rb file, or better yet in a separate file. For instance, create spec/support/custom_matchers.rb and place the code there. You'll need to make sure that file gets included when running rspec. To do that, you could uncomment the following line which appears in the default spec/rails_helper.rb file:
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
With that in place, your spec file would read:
expect(result[:err]).to contain('(Check the file permissions.)')
It can be fixed by adding this to rails_helper.rb or support/rubymine_stubs.rb:
# Rubymine IDE underlines `include` matchers with warning "Cannot resolve properly, was not processed"
# To fix this issue let's make an alias `contain` and use it instead
RSpec::Matchers.alias_matcher :contain, :include
module RubymineStubs
# create stub for `contain` so Rubymine won't underline it
def contain(*_args) end
end

How can I "require" code from another .rb file like in PHP?

Coming to Ruby from a PHP background, I'm used to being able to use require, require_once, include, or include_once which all have a similar effect, but the key being they continue to process code in the same scope where the include / require command was invoked.
Example:
sub.php
<?php
echo $foo;
main.php
<?php
$foo = 1234;
include('sub.php'); // outputs '1234'
When I first started using Ruby I tried to include / require / require_relative / load other .rb files, and after becoming a little frustrated with not having it work how I would expect it to I decided that there were better ways to go about breaking up large files and that Ruby didn't need to behave in the same way PHP did.
However, occasionally I feel that for testing purposes it would be nice to to load code from another .rb file in the way PHP does - in the same scope with access to all the same variables - without having to use class / instance variables or constants. Is this possible? Maybe somehow using a proc / binding / or eval command?
Again, I'm not advocating that this should be used during development - but I am curious if it is possible - and if so, how?
Yes, this is possible, although certainly not something I'd recommend doing. This works:
includer.rb:
puts var
include.rb:
var = "Hello!"
eval(File.read("include.rb"), binding)
Running this (Ruby 2.2.1, Ruby 1.9.3) will print Hello!. It works simply: eval takes an optional binding with which to evaluate the code it is passed, and Kernel#binding returns the current binding.
To have code run in same binding, you could simply eval the file contents as follows:
example.rb
class Example
def self.called_by_include
"value for bar"
end
def foo
puts "Called foo"
end
eval( File.read( 'included.rb' ) )
end
Example.new.bar
included.rb
BAR_CONSTANT = called_by_include
def bar
puts BAR_CONSTANT
end
Running ruby example.rb produces output
value for bar
The important thing is the eval( File.read( 'included.rb' ) ) code, which if you really wanted you could define as a class method on Object, to allow arbitrary source to be included with a convenience function*. The use of constants, class variables etc just shows influences working in both directions between the two pieces of source code.
It would be bad practice to use this in any production code. Ruby gives you much better tools for meta-programming, such as ability to use mix-ins, re-open classes, define methods from blocks etc.
* Something like this
class Object
def self.include_source filename
eval( File.read( filename ) )
end
end
And the line in example.rb would become just
include_source 'included.rb'
Again I have to repeat this is not such a great idea . . .
To import external .rb file in your code, I'm not sure but I think it have to be a gem.
Use require followed by the name of the gem you want to import.
Example
require 'foobar'
# do some stuff
Or you can use load to import entire rb file
load 'foobar.rb'
# do some stuff
Good luck and sorry for my english

How to test a script that generates files

I am creating a Rubygem that will let me generate jekyll post files. One of the reasons I am developing this project is to learn TDD. This gem is strictly functional on the command line, and it has to make a series of checks to make sure that it finds the _posts directory. This depends on two things:
Wether or not a location option was passed
Is that location option valid?
A location option was not passed
Is the posts dir in the current directory?
Is the posts dir the current working directory?
At that point, I am really having a hard time testing that part of the application. So I have two questions:
is it acceptable/okay to skip tests for small parts of the application like the one described above?
If not, how do you test file manipulation in ruby using minitest?
Some projects I've seen implement their command line tools as Command objects (for example: Rubygems and my linebreak gem). These objects are initialized with the ARGV simply have a call or execute method which then starts the whole process. This enables these projects to put their command line applications into a virtual environment. They could, for example hold the input and output stream objects in instance variables of the command object to make the application independant of using STDOUT/STDIN. And thus, making it possible to test the input/output of the command line application. In the same way I imagine, you could hold your current working directory in an instance variable to make your command line application independent of your real working directory. You could then create a temporary directory for each test and set this one as the working directory for your Command object.
And now some code:
require 'pathname'
class MyCommand
attr_accessor :input, :output, :error, :working_dir
def initialize(options = {})
#input = options[:input] ? options[:input] : STDIN
#output = options[:output] ? options[:output] : STDOUT
#error = options[:error] ? options[:error] : STDERR
#working_dir = options[:working_dir] ? Pathname.new(options[:working_dir]) : Pathname.pwd
end
# Override the puts method to use the specified output stream
def puts(output = nil)
#output.puts(output)
end
def execute(arguments = ARGV)
# Change to the given working directory
Dir.chdir(working_dir) do
# Analyze the arguments
if arguments[0] == '--readfile'
posts_dir = Pathname.new('posts')
my_file = posts_dir + 'myfile'
puts my_file.read
end
end
end
end
# Start the command without mockups if the ruby script is called directly
if __FILE__ == $PROGRAM_NAME
MyCommand.new.execute
end
Now in your test's setup and teardown methods you could do:
require 'pathname'
require 'tmpdir'
require 'stringio'
def setup
#working_dir = Pathname.new(Dir.mktmpdir('mycommand'))
#output = StringIO.new
#error = StringIO.new
#command = MyCommand.new(:working_dir => #working_dir, :output => #output, :error => #error)
end
def test_some_stuff
#command.execute(['--readfile'])
# ...
end
def teardown
#working_dir.rmtree
end
(In the example I'm using Pathname, which is a really nice object oriented file system API from Ruby's standard library and StringIO, which is useful for for mocking STDOUT as it's an IO object which streams into a simple String)
In the acutal test you could now use the #working_dir variable to test for existence or content of files:
path = #working_dir + 'posts' + 'myfile'
path.exist?
path.file?
path.directory?
path.read == "abc\n"
From my experience (and thus this is VERY subjective), I think it's ok sometimes to skip unit testing in some areas which are difficult to test. You need to find out what you get in return and the cost for testing or not. My rule of thumb is that the decision to not test a class should be very unusual (around less than 1 in 300 classes)
If what you're trying to test is very difficult, because of the dependencies with the file system, I think you could try to extract all the bits that interact with the file system.

How do I temporarily change the require path in Ruby ($:)?

I'm doing some trickery with a bunch of Rake tasks for a complex project, gradually refactoring away some of the complexity in chunks at a time. This has exposed the bizarre web of dependencies left behind by the previous project maintainer.
What I'd like to be able to do is to add a specific path in the project to require's list of paths to be searched, aka $:. However, I only want that path to be searched in the context of one particular method. Right now I'm doing something like this:
def foo()
# Look up old paths, add new special path.
paths = $:
$: << special_path
# Do work ...
bar()
baz()
quux()
# Reset.
$:.clear
$: << paths
end
def bar()
require '...' # If called from within foo(), will also search special_path.
...
end
This is clearly a monstrous hack. Is there a better way?
Since $: is an Array, you have to be careful about what you are doing. You need to take a copy (via dup) and replace it later. It' simpler to simply remove what you have added, though:
def foo
$: << special_path
# Do work ...
bar()
ensure
# Reset.
$:.delete(special_path)
end
Without more info, it's difficult to know if there is a better way.
require is actually a method, it's Kernel#require (which calls rb_require_safe) so you could at least perform your hackery in a monkey-patched version. If you like that kind of thing.
Alias the orignal require out of the way
If passed an absolute path, call the original require method
Else iterate over load path by creating an absolute path and calling the original require method.
Just for fun I had a quick bash at that, prototype is below. This isn't fully tested, I haven't checked the semantics of rb_require_safe, and you probably would also need to look at #load and #include for completeness -- and this remains a monkey-patch of the Kernel module. It's perhaps not entirely monstrous, but it's certainly a hack. Your call if it's better or worse than messing with the global $: variable.
module Kernel
alias original_require require
# Just like standard require but takes an
# optional second argument (a string or an
# array of strings) for additional directories
# to search.
def require(file, more_dirs=[])
if file =~ /^\// # absolute path
original_require(file)
else
($: + [ more_dirs ].flatten).each do |dir|
path = File.join(dir, file)
begin
return original_require(path)
rescue LoadError
end
end
raise LoadError,
"no such file to load -- #{file}"
end
end
end
Examples:
require 'mymod'
require 'mymod', '/home/me/lib'
require 'mymod', [ '/home/me/lib', '/home/you/lib' ]

Resources