Skip to content

Commit

Permalink
Fix type inferrence for solargraph & ruby-3.4.x
Browse files Browse the repository at this point in the history
  • Loading branch information
lekemula committed Jan 26, 2025
1 parent 23e652d commit 414bc8e
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 27 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- (Hot)fix parsing and type inference for solargraph v0.51.x and ruby v3.4.x (008d3ea and 7e68707)

## v.0.4.0 - 2024-08-25

### Added
Expand Down
4 changes: 2 additions & 2 deletions lib/solargraph/rspec/convention.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def global(_yard_map)
Solargraph.logger.debug "[RSpec] added requires #{requires}"

Environ.new(requires: requires, pins: pins)
rescue StandardError => e
rescue StandardError, SyntaxError => e
raise e if ENV['SOLARGRAPH_DEBUG']

Solargraph.logger.warn(
Expand Down Expand Up @@ -141,7 +141,7 @@ def local(source_map)
end

Environ.new(requires: [], pins: pins)
rescue StandardError => e
rescue StandardError, SyntaxError => e
raise e if ENV['SOLARGRAPH_DEBUG']

Solargraph.logger.warn(
Expand Down
2 changes: 1 addition & 1 deletion lib/solargraph/rspec/spec_walker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def walk!
method_name = NodeTypes.let_method_name(block_ast)
next unless method_name

fake_method_ast = FakeLetMethod.transform_block(block_ast, @source_map.source.code)
fake_method_ast = FakeLetMethod.transform_block(block_ast, @source_map.source.code, method_name)

@handlers[:on_let_method].each do |handler|
handler.call(method_name, PinFactory.build_location_range(block_ast.children[0]), fake_method_ast)
Expand Down
82 changes: 63 additions & 19 deletions lib/solargraph/rspec/spec_walker/fake_let_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,74 @@ class FakeLetMethod
MATCH_DO_END = /.*? do(.*)end/m.freeze
MATCH_CURLY = /{(.*)}/m.freeze

# @param block_ast [RubyVM::AbstractSyntaxTree::Node]
# @return [RubyVM::AbstractSyntaxTree::Node, nil]
def self.transform_block(block_ast, code, method_name = nil)
method_name ||= NodeTypes.let_method_name(block_ast)
block_body = block_ast.children[1]
let_definition_code = code.lines[block_body.first_lineno - 1..block_body.last_lineno - 1].join
match_do_end = let_definition_code.match(MATCH_DO_END)&.captures&.first || ''
match_curly = let_definition_code.match(MATCH_CURLY)&.captures&.first || ''
method_body = [match_do_end, match_curly].max_by(&:length).strip

ast = RubyVM::AbstractSyntaxTree.parse <<~RUBY
def #{method_name}
#{method_body}
class << self
# Transforms let block to method ast node
# @param block_ast [RubyVM::AbstractSyntaxTree::Node]
# @return [RubyVM::AbstractSyntaxTree::Node, ::Parser::AST::Node, nil]
def transform_block(block_ast, code, method_name = nil)
method_name ||= NodeTypes.let_method_name(block_ast)

if Solargraph::Parser.rubyvm?
rubyvm_transform_block(block_ast, code, method_name)
else
parser_transform_block(block_ast, code, method_name)
end
RUBY
end

private

# @param block_ast [RubyVM::AbstractSyntaxTree::Node]
# @return [RubyVM::AbstractSyntaxTree::Node, nil]
def rubyvm_transform_block(block_ast, code, method_name = nil)
block_body = block_ast.children[1]
let_definition_code = code.lines[block_body.first_lineno - 1..block_body.last_lineno - 1].join
match_do_end = let_definition_code.match(MATCH_DO_END)&.captures&.first || ''
match_curly = let_definition_code.match(MATCH_CURLY)&.captures&.first || ''
method_body = [match_do_end, match_curly].max_by(&:length).strip

ast = RubyVM::AbstractSyntaxTree.parse <<~RUBY
def #{method_name}
#{method_body}
end
RUBY

ast.children[2]
rescue SyntaxError => e
Solargraph.logger.warn "[RSpec] Failed to build fake let method: #{e.message}, \
\n\nlet_definition_code: \n```\n#{let_definition_code}\n```, \
\n\nmethod_body: \n```\n#{method_body}\n```, \
\nast: #{block_ast.inspect}"
end

# @param block_ast [RubyVM::AbstractSyntaxTree::Node]
# @return [::Parser::AST::Node]
def parser_transform_block(block_ast, code, method_name = nil)
code_lines = code.split("\n")
# extract let definition block body code
first_line = code_lines[block_ast.first_lineno - 1]
last_line = code_lines[block_ast.last_lineno - 1]
code_lines[block_ast.first_lineno - 1] = first_line[(block_ast.first_column)..]
code_lines[block_ast.last_lineno - 1] = last_line[0..(block_ast.last_column)]
let_definition_code = code_lines[
(block_ast.first_lineno - 1)..(block_ast.last_lineno - 1)
].join("\n")

ast.children[2]
rescue SyntaxError => e
Solargraph.logger.warn "[RSpec] Failed to build fake let method: #{e.message}, \
let_definition_ast = ::Parser::CurrentRuby.parse(let_definition_code)
method_body = let_definition_ast.children[2]
::Parser::AST::Node.new( # transform let block to a method ast node
:def,
[
method_name.to_sym,
::Parser::AST::Node.new(:args, []),
method_body
]
)
rescue SyntaxError => e
Solargraph.logger.warn "[RSpec] Failed to build fake let method: #{e.message}, \
\n\nlet_definition_code: \n```\n#{let_definition_code}\n```, \
\n\nmethod_body: \n```\n#{method_body}\n```, \
\nast: #{block_ast.inspect}"
ensure
nil
end
end
end
end
Expand Down
18 changes: 13 additions & 5 deletions spec/solargraph/rspec/spec_walker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
let(:config) { Solargraph::Rspec::Config.new }
let(:source_map) { api_map.source_maps.first }

def parse_expected_let_method(code)
if Solargraph::Parser.rubyvm?
RubyVM::AbstractSyntaxTree.parse(code).children[2]
else
Parser::CurrentRuby.parse(code)
end
end

# @param code [String]
# @yieldparam [Solargraph::Rspec::SpecWalker]
# @return [void]
Expand Down Expand Up @@ -41,7 +49,7 @@ def walk_code(code)

describe '#on_let_method' do
let(:fake_let_with_block) do
RubyVM::AbstractSyntaxTree.parse(<<~RUBY).children[2]
parse_expected_let_method(<<~RUBY)
def fake_test_with_block
create(
:some_model,
Expand All @@ -55,7 +63,7 @@ def fake_test_with_block
end

let(:fake_let_with_curly_block) do
RubyVM::AbstractSyntaxTree.parse(<<~RUBY).children[2]
parse_expected_let_method(<<~RUBY)
def fake_test_with_curly_block
create(
:some_model,
Expand Down Expand Up @@ -118,7 +126,7 @@ def fake_test_with_curly_block

describe '#on_subject' do
let(:fake_subject_with_block) do
RubyVM::AbstractSyntaxTree.parse(<<~RUBY).children[2]
parse_expected_let_method(<<~RUBY)
def subject_with_block
create(
:some_model,
Expand All @@ -132,7 +140,7 @@ def subject_with_block
end

let(:fake_subject_with_curly_block) do
RubyVM::AbstractSyntaxTree.parse(<<~RUBY).children[2]
parse_expected_let_method(<<~RUBY)
def subject_with_curly_block
create(
:some_model,
Expand All @@ -146,7 +154,7 @@ def subject_with_curly_block
end

let(:fake_subject_without_name) do
RubyVM::AbstractSyntaxTree.parse(<<~RUBY).children[2]
parse_expected_let_method(<<~RUBY)
def subject
create(:some_model)
end
Expand Down

0 comments on commit 414bc8e

Please sign in to comment.