docs/research.md
# Research
I love to research about codebase as data and prototyping ideas several times
doesn't fit in simple [shortcuts](/shortcuts).
Here is my first research that worth sharing:
## Combining Runtime metadata with AST complex searches
This example covers how to find RSpec `allow` combined with `and_return` missing
the `with` clause specifying the nested parameters.
Here is the [gist](https://gist.github.com/jonatas/c1e580dcb74e20d4f2df4632ceb084ef)
if you want to go straight and run it.
Scenario for simple example:
Given I have the following class:
```ruby
class Account
def withdraw(value)
if @total >= value
@total -= value
:ok
else
:not_allowed
end
end
end
```
And I'm testing it with `allow` and some possibilities:
```ruby
# bad
allow(Account).to receive(:withdraw).and_return(:ok)
# good
allow(Account).to receive(:withdraw).with(100).and_return(:ok)
```
**Objective:** find all bad cases of **any** class that does not respect the method
parameters signature.
First, let's understand the method signature of a method:
```ruby
Account.instance_method(:withdraw).parameters
# => [[:req, :value]]
```
Now, we can build a small script to use the node pattern to match the proper
specs that are using such pattern and later visit their method signatures.
```ruby
Fast.class_eval do
# Captures class and method name when find syntax like:
# `allow(...).to receive(...)` that does not end with `.with(...)`
pattern_with_captures = <<~FAST
(send (send nil allow (const nil $_)) to
(send (send nil receive (sym $_)) !with))
FAST
pattern = expression(pattern_with_captures.tr('$',''))
ruby_files_from('spec').each do |file|
results = search_file(pattern, file) || [] rescue next
results.each do |n|
clazz, method = capture(n, pattern_with_captures)
if klazz = Object.const_get(clazz.to_s) rescue nil
if klazz.respond_to?(method)
params = klazz.method(method).parameters
if params.any?{|e|e.first == :req}
code = n.loc.expression
range = [code.first_line, code.last_line].uniq.join(",")
boom_message = "BOOM! #{clazz}.#{method} does not include the REQUIRED parameters!"
puts boom_message, "#{file}:#{range}", code.source
end
end
end
end
end
end
```
!!! hint "Preload your environment **before** run the script"
Keep in mind that you should run it with your environment preloaded otherwise it
will skip the classes.
You can add elses for `const_get` and `respond_to` and report weird cases if
your environment is not preloading properly.