ruby-lsp-rspec icon indicating copy to clipboard operation
ruby-lsp-rspec copied to clipboard

Resolving spec DSL references

Open johnmaxwell opened this issue 1 year ago • 4 comments

One of the most challenging things about working with Rspec tests that have been written in a subject-let-fixture style is tracking down the definitions of subjects and lets -- especially when the test file gets pretty long. This is a process that computers are better at than human brains. I feel like it would be hugely beneficial to offer something akin to go-to-definition for subjects and lets within a spec-formatted ruby file.

As someone who works on this project and ruby-lsp, how achievable would something like this be via a ruby-lsp addon? It seems the add-on would need its own index, similar to RubyIndexer, capable of interpreting and building an index from a spec DSL.

johnmaxwell avatar Jan 31 '24 00:01 johnmaxwell

tracking down the definitions of subjects and lets

Can you give me a few examples? Like do you mean when you cmd+click a subject call, it brings you to the associated subject { ... } block?

If that's the case, then it's pretty difficult to do it 100% accurate atm because subject could be:

  1. a local variable
  2. a method
  3. a RSpec subject

And for let it's even harder as it could be inherited from outer scopes.

Maybe we can use the document highlight feature to highlight potential subject(:foo) or let(:foo) when pointing at foo?

st0012 avatar Feb 10 '24 13:02 st0012

Thanks for taking the time to review and respond @st0012 !

Here's a simplified example of what I'm talking about. It's not great testing code, but it seems fairly typical for an rspec suite.

describe "#can_make_reservation?" do
  let(:user) { User.new(active: true) }
  subject { Reserver.new(user) }

  context "when the user is active" do
    it "is true" do
      expect(subject.can_make_reservation?).to eq true
    end
  end

  context "when the user is inactive" do
    let(:user) { User.new(active: false) }

    it "is false" do
      expect(subject.can_make_reservation?).to eq false
    end
  end
end

Could we help the user track down the subject/let fixtures for a given example?

The first example's ("#can_make_reservation? when user is active is true") text fixtures are the subject defined on line 3 and the let(:user) defined on line 2.

The second example's ("#can_make_reservation? when user is inactive is false") test fixtures are the subject defined on line 3 and the let(:user) defined on line 12, because user has been overridden within the nested context.

This is mental work that has to be done every time a human tries to maintain an rspec suite. It's work that becomes significantly more burdensome as the length and nesting-depth of an rspec test file grows.

Somehow what example you're interested in has to be established, otherwise the resolution wouldn't work correctly.

A naive go-to-method would work fine for the first example ("#can_make_reservation? when user is active is true"). The user could command-click subject on line 7 and be brought to the subject definition on line 3. Then, they could command-click user within the subject's block on line 3 and be brought to the let(:user) definition on line 2.

But for the second example ("#can_make_reservation? when user is inactive is false"), we'd need a different strategy that would respect the overridden let(:user) from line 12. So maybe a simple go-to-method doesn't work -- or at least it would need to be biased to the example the user is interested in.

Could there be an editor command to "print example fixtures" could be run for a given example? Using it for first example, would print out:

03: subject { Reserver.new(user) }
02: let(:user) { User.new(active: true) }

Using it for the second example would print out:

03: subject { Reserver.new(user) }
12: let(:user) { User.new(active: false) }

Does this help? Thank you again for taking the time to review and respond.

johnmaxwell avatar Feb 26 '24 01:02 johnmaxwell

I tried to prototype something today, but it's not possible without some addon API changes from ruby-lsp itself, which I proposed in https://github.com/Shopify/ruby-lsp/issues/2191.

st0012 avatar Jun 15 '24 12:06 st0012

@st0012 Nice! Thank you for looking into this!

johnmaxwell avatar Jun 15 '24 20:06 johnmaxwell