1

I have a method called $muffinize and I would like to find where it can be found in my code. In other words, given the following code:

class A
    def foo
        $muffinize(1)
    end
    def bar
        ...
    end
end

class B
    def shoop
        $muffinize(2)
    end
    def woop
        ...
    end
end

class C
    def nope
        ...
    end
end

I would like to the result to be (written to a file):

A:foo
B:shoop

I was thinking of accomplishing this with a Regex, but I was wondering if there would be some way of accomplishing this with Ruby meta-programming (which I might be accidentally using as a buzz-word)?

Seanny123
  • 8,776
  • 13
  • 68
  • 124
  • 1
    Do some search on `caller`. http://stackoverflow.com/questions/5100299/how-to-get-the-name-of-the-calling-method – oldergod Sep 09 '13 at 05:54
  • Will do. Thanks for helping me help myself. – Seanny123 Sep 09 '13 at 05:57
  • So that would get me the caller by running the code, but I was hoping to do that without running the code, since sometimes the code for `muffinize` is only executed under weird conditions. I understand that would be absurd if I were to use inheritance, but for this simple use case I thought it would reasonable enough. – Seanny123 Sep 09 '13 at 06:00
  • Well if you simply want to see where the method is written in your source code, you could use something like `ack`, but of course this will not tell you anything about whether that method will actually ever be called. – Alex.Bullard Sep 09 '13 at 06:02
  • 1
    @Alex.Bullard Can you point to the source documentaion please of `ack` – Arup Rakshit Sep 09 '13 at 06:04
  • This is a situation where a good test coverage is your friend. Put a `puts caller[1]` at the beginning of your method and run the tests. – tessi Sep 09 '13 at 06:11
  • @Babai I think Alex is referencing this http://beyondgrep.com/ – Seanny123 Sep 09 '13 at 06:24
  • Simple `grep -rn muffinize lib/` (or `ack` mentioned by Babai) will get you a long way. However only dynamic analysis, i.e. good test coverage along with instrumentation using `caller`, will get you there. – Tero Tilus Sep 10 '13 at 03:55

3 Answers3

2

Kernel.caller() will help you show the line number and method that is calling it at runtime. If you put something like puts caller(1,1) in your muffinize function it will output those locations, but only if they are called at runtime.

If you want to do offline source analysis, you need to parse the AST (abstract syntax tree) with something like https://github.com/whitequark/parser.

Here is a quick example with ripper (built into new rubies) - this isn't strictly an AST but it's not extracting classes either

#!/usr/local/env ruby

require 'ripper'
#require 'pry'

contents = File.open('example.rb').read

code = Ripper.lex(contents)
code.each do |line|
    if(line[1] == :on_ident and line[2] == "muffinize")
        puts "muffinize found at line #{line.first.first}"
    end
end
Anko
  • 1,292
  • 10
  • 19
  • I've looked at offline parsers before, but they seem a little excessively complicated given what I'm trying to accomplish. Have you used them before and can you testify to the steepness of their learning curve? – Seanny123 Sep 09 '13 at 06:08
  • 1
    I don't know, for me the idea of code being represented as an AST makes a lot of sense, so I don't think the learning curve is steep. Either way, that's the way to do it. You are trying to do offline parsing so you need to use an offline parsing tool. – Anko Sep 09 '13 at 07:04
  • Although this is certainly the most correct answer (and that code sample is super-handy), I ended up going with my own less-correct, but easier solution, due to my phobia of AST. – Seanny123 Sep 10 '13 at 01:25
  • I later came crawling back since this seemed a more reliable way to parse. I tried `Parser` that you suggested in your link, but got stalled with calls to the method I was trying to find that were nested inside if statements. Ended up going with `ripper` and using your fabulous code. – Seanny123 Sep 12 '13 at 02:59
0

By getting a list of classes and methods via ri, I was then able to analyze each method to retreive their source code using the method_source gem and then searching for muffinize. This does not rule out the possibility of muffinize from appearing in a comment or a string, but I consider the likelihood of this happening to be small enough to ignore.

Seanny123
  • 8,776
  • 13
  • 68
  • 124
0

Ignoring the fact that your code isn't even syntactically valid, this is simply not possible.

Here's a simple example:

class A
  def foo
    bar
    muffinize(1)
  end
end

A#foo will call Object#muffinize if and only if bar terminates. Which means that figuring out whether or not A#foo calls Object#muffinize requires to solve the Halting Problem.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • While I agree with your analysis and I appreciate your efforts, I did not intend my question to imply that I wanted to see all cases that `muffinize` could be called, but just to see if the method appeared at all in the code. I have edited my question in the hopes of making that more clear. I apologize for my original lack of clarity. – Seanny123 Sep 10 '13 at 01:19