0

Lets say I have following code

let(:document) { FactoryBot.create :document }
let(:mail) do
  # @type [User]
  celebrated_user = FactoryBot.create :user
  # @type [Rule]
  rule = FactoryBot.create :rule, document: document
  CelebrationMailer.birthday celebrated_user, document, rule
end

CelebrationMailer#birthday is documented as follows

# @param [User] celebrated_user
# @param [Document] document
# @param [Rule] rule

In the let(:mail) RubyMine (and probably some other tools with types) recognize document variable as type Object.

RubyMine's tooltip about wrong type

How can I document the let, so tools recognize document let/variable as Document type?

I've tried @type, @return, @attr_reader.

Mailo Světel
  • 24,002
  • 5
  • 30
  • 40
  • In case that might be done with `macro` expansion as in the answer below then at the moment it's not supported in RubyMine, please add your vote to https://youtrack.jetbrains.com/issue/RUBY-20130 – Olivia Nov 02 '20 at 08:06

1 Answers1

1

Well, what does let do? It creates a method whose name is given by the symbol argument and whose return value is given by the return value of the block.

So, if you wanted to write a type for that, you would have to

  • take the symbol and turn it into a method name
  • take the return type of the block and turn it into the return type of the method

I am pretty sure that is not possible with RDoc or YARD type notation.

So, the best thing you can do is a synthetic attribute reader:

# @!attribute [r] document
#   @return [Document] a fake document
let(:document) { FactoryBot.create :document }

Note that YARD does support macros, but macros obviously only have access to what is in the source code, and the return type of the block is not part of the source code. In fact, the documentation block you showed looks like it was generated by a macro that looks somewhat like this:

# Defines a memoized method
# @param [Symbol] name the method name
# @param block how to compute the value
# @!macro [attach] let
#   @!attribute [r] $1
#     @return the value defined by the block
def let(name, &block) end

This will tell YARD that wherever it sees a call to let, it should insert a synthetic documentation block for an attribute reader whose name is the first argument to let.

However, it will not allow you to use the type of the block. These are the only interpolation variables that exist:

  • $0 - the method name being called
  • $1, $2, $3, … - the Nth argument in the method call
  • $& - the full source line

What would work, is if let took a second argument that is the type, then you could write a macro like this:

# Defines a memoized method
# @param [Symbol] name the method name
# @param [Module] type the type of the memoized value
# @param block how to compute the value
# @!macro [attach] let
#   @!attribute [r] $1
#     @return [${-1}] the value defined by the block
def let(name, type, &block) end

And then you would call it like this:

let(:document, Document) { FactoryBot.create :document }

But, that is not how RSpec is designed, so the best you can do is the synthetic attribute reader from my first suggestion.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653