Skip to content

Commit 67cb24d

Browse files
Copilotkwerle
andauthored
Add RSpec 'let' and 'let!' variable capture to scope parser (#107)
* Initial plan * Add support for RSpec 'let' variables in scope parser Co-authored-by: kwerle <23320+kwerle@users.noreply.github.com> * Add support for RSpec 'let!' variables with comprehensive tests Co-authored-by: kwerle <23320+kwerle@users.noreply.github.com> * Improve code documentation for let command handling Co-authored-by: kwerle <23320+kwerle@users.noreply.github.com> * Update some test code * Simplify let! --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kwerle <23320+kwerle@users.noreply.github.com> Co-authored-by: Kurt Werle <kurt@CircleW.org>
1 parent 54914be commit 67cb24d

3 files changed

Lines changed: 74 additions & 12 deletions

File tree

lib/ruby_language_server/scope_parser_commands/rspec_commands.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ def on_it_command(line, args, rest)
1515
rspec_block_command('it', line, args, rest)
1616
end
1717

18+
def on_let_command(line, args, rest)
19+
# Extract the variable name from the symbol (e.g., :foo -> foo)
20+
# The rest array contains the extracted symbol values from extract_command_rest
21+
var_name = rest.flatten.first
22+
return unless var_name.is_a?(String)
23+
24+
# Extract the column from args structure: [:@ident, "let", [line, column]]
25+
(_, _, (_, column)) = args
26+
27+
# Add the variable to the current scope
28+
add_variable(var_name, line, column)
29+
end
30+
31+
# let! is an eager version of let - alias it using send to avoid syntax issues
32+
send(:alias_method, 'on_let!_command', :on_let_command)
33+
1834
private
1935

2036
def rspec_block_command(prefix, line, _args, rest)

spec/lib/ruby_language_server/scope_data/scope_spec.rb

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,16 @@ def baz; end
5252
end
5353
end
5454

55-
# describe '.scopes_at' do
56-
# it 'should find the deepest scope' do
57-
# assert_equal([], root_scope.scopes_at(OpenStruct.new(line: 0)))
58-
# assert_equal([], root_scope.scopes_at(OpenStruct.new(line: 1)))
59-
# assert_equal(foo_scope, root_scope.scopes_at(OpenStruct.new(line: 2)).first)
60-
# assert_equal(bar_class_scope, root_scope.scopes_at(OpenStruct.new(line: 3)).first)
61-
# assert_equal(baz_method_scope, root_scope.scopes_at(OpenStruct.new(line: 6)).first)
62-
# assert_equal(nar_class_scope, root_scope.scopes_at(OpenStruct.new(line: 13)).first)
63-
# assert_equal(naz_method_scope, root_scope.scopes_at(OpenStruct.new(line: 16)).first)
64-
# assert_equal(RubyLanguageServer::ScopeData::Base::TYPE_BLOCK, root_scope.scopes_at(OpenStruct.new(line: 19)).first.class_type)
65-
# end
66-
# end
55+
describe '.scopes_at' do
56+
it 'should find the deepest scope' do
57+
assert_equal([], root_scope.self_and_descendants.for_line(0).where.not(class_type: 'root').to_a)
58+
assert_equal([], root_scope.self_and_descendants.for_line(1).where.not(class_type: 'root').to_a)
59+
assert_equal(foo_scope, root_scope.self_and_descendants.for_line(2).by_path_length.first)
60+
assert_equal(bar_class_scope, root_scope.self_and_descendants.for_line(3).by_path_length.first)
61+
assert_equal(baz_method_scope, root_scope.self_and_descendants.for_line(6).by_path_length.first)
62+
assert_equal(nar_class_scope, root_scope.self_and_descendants.for_line(13).by_path_length.first)
63+
assert_equal(naz_method_scope, root_scope.self_and_descendants.for_line(16).by_path_length.first)
64+
assert_equal(RubyLanguageServer::ScopeData::Base::TYPE_BLOCK, root_scope.self_and_descendants.for_line(19).by_path_length.first.class_type)
65+
end
66+
end
6767
end

spec/lib/ruby_language_server/scope_parser_commands/rspec_commands_spec.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,50 @@
4646
assert_equal('context some context', context_block.name)
4747
end
4848
end
49+
50+
describe 'let variables' do
51+
it 'should capture let variables in the appropriate scope' do
52+
top_describe = @parser.root_scope.children.first
53+
block = top_describe.children.first
54+
55+
# Check that 'foo' variable is in the top describe block
56+
foo_variable = block.variables.find { |v| v.name == 'foo' }
57+
refute_nil(foo_variable, 'Expected to find foo variable in top describe block')
58+
assert_equal(2, foo_variable.line)
59+
end
60+
61+
it 'should capture let variables in nested scopes' do
62+
top_describe = @parser.root_scope.children.first
63+
block = top_describe.children.first
64+
some_thing_describe = block.children.find { |c| c.name == 'describe some thing' }
65+
some_thing_block = some_thing_describe.children.first
66+
67+
# Check that 'common' variable is in the nested describe block
68+
common_variable = some_thing_block.variables.find { |v| v.name == 'common' }
69+
refute_nil(common_variable, 'Expected to find common variable in nested describe block')
70+
assert_equal(5, common_variable.line)
71+
end
72+
end
73+
74+
describe 'let! variables' do
75+
before do
76+
@let_bang_code = <<-SOURCE
77+
describe 'eager evaluation' do
78+
let!(:eager_var) { 'eager_value' }
79+
end
80+
SOURCE
81+
@let_bang_parser = RubyLanguageServer::ScopeParser.new(@let_bang_code)
82+
end
83+
84+
it 'should capture let! variables' do
85+
# Get the describe scope for 'eager evaluation', not the first one which is from another test
86+
describe_scope = @let_bang_parser.root_scope.children.find { |c| c.name.include?('eager evaluation') }
87+
block = describe_scope.children.first
88+
89+
# Check that 'eager_var' variable is captured
90+
eager_variable = block.variables.find { |v| v.name == 'eager_var' }
91+
refute_nil(eager_variable, 'Expected to find eager_var from let!')
92+
assert_equal(2, eager_variable.line)
93+
end
94+
end
4995
end

0 commit comments

Comments
 (0)