Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
8b11075
Add 'solargraph method_pin' command for debugging
apiology Jul 13, 2025
6fab42e
Merge remote-tracking branch 'origin/master' into method_pin
apiology Jul 13, 2025
bf61295
RuboCop and Solargraph fixes
apiology Jul 14, 2025
8b8c88b
Merge remote-tracking branch 'origin/master' into method_pin
apiology Jul 14, 2025
fdd3810
Linting fix
apiology Aug 4, 2025
30cdfc8
Add spec
apiology Aug 4, 2025
a30d790
Fix spec
apiology Aug 4, 2025
20dbf8e
Merge branch 'master' into method_pin
apiology Aug 19, 2025
e4afaad
Allow newer RBS gem versions, exclude incompatible ones (#995)
apiology Aug 19, 2025
a0a7878
Look for external requires before cataloging bench (#1021)
apiology Aug 19, 2025
0e86b88
Remove Library#folding_ranges (#904)
castwide Aug 19, 2025
c90f016
Complain in strong type-checking if an @sg-ignore line is not needed …
apiology Aug 19, 2025
8c7a5b8
Document a log level env variable (#894)
apiology Aug 21, 2025
fba485e
Fix hole in type checking evaluation (#1009)
apiology Aug 24, 2025
f61b1b6
Improve typechecking error message (#1014)
apiology Aug 24, 2025
9d4c711
Internal strict type-checking fixes (#1013)
apiology Aug 24, 2025
3bcbf85
Reproduce and fix a ||= (or-asgn) evaluation issue (#1017)
apiology Aug 24, 2025
25557b4
Define closure for Pin::Symbol, for completeness (#1027)
apiology Aug 24, 2025
32565d4
Fix 'all!' config to reporters (#1018)
apiology Aug 24, 2025
3946cc4
Fix DocMap.all_rbs_collection_gems_in_memory return type (#1037)
castwide Aug 24, 2025
43cacca
Fix RuboCop linting errors in regular expressions (#1038)
castwide Aug 24, 2025
d3bdfea
Resolve class aliases via Constant pins (#1029)
apiology Aug 24, 2025
4a10b44
Speed-up LSP completion response times (#1035)
lekemula Aug 24, 2025
7a633b1
Merge remote-tracking branch 'origin/master' into method_pin
apiology Aug 27, 2025
d1e6b12
RuboCop todo update
apiology Aug 27, 2025
5b612dd
Try rbs collection update before specs
apiology Aug 27, 2025
f2291f4
RuboCop fixes
apiology Aug 27, 2025
be46aa3
Add @sg-ignores
apiology Aug 27, 2025
0927309
Fix typo
apiology Aug 27, 2025
7899550
Exclude problematic combinations on Ruby head
apiology Aug 27, 2025
d5d6c5f
Fix indentation
apiology Aug 27, 2025
2c6cacd
method_pin -> pin, hide command
apiology Aug 27, 2025
56636ae
Fix type
apiology Aug 27, 2025
6acfa0c
RuboCop todo file stability
apiology Aug 30, 2025
14be6bf
Merge remote-tracking branch 'origin/master' into rubocop_stability
apiology Aug 30, 2025
61260f3
Fix merge issue
apiology Aug 30, 2025
9f7fe59
Add --references flag (superclass for now)
apiology Sep 2, 2025
2f0ff82
Merge remote-tracking branch 'origin/master' into method_pin
apiology Sep 2, 2025
79e8cd9
Add another @sg-ignore
apiology Sep 2, 2025
da205a3
Catch up with .rubocop_todo.yml
apiology Sep 2, 2025
4c486ed
Add another @sg-ignore
apiology Sep 2, 2025
200e7e4
Tolerate case statement in specs
apiology Sep 2, 2025
8edc773
Merge remote-tracking branch 'origin/master' into rubocop_stability
apiology Sep 6, 2025
bb0f607
Rerun rubocop todo
apiology Sep 6, 2025
0ede18a
Merge branch 'rubocop_stability' into method_pin
apiology Sep 7, 2025
837d7f6
Force build
apiology Sep 7, 2025
a4208e7
Restore
apiology Sep 7, 2025
f08b76a
Merge remote-tracking branch 'origin/master' into fix_solargraph_rspe…
apiology Sep 7, 2025
b66f2ac
install -> update with rbs collection
apiology Sep 7, 2025
a09a9af
Try Ruby 3.2
apiology Sep 7, 2025
6fc8feb
Update solargraph
apiology Sep 7, 2025
388c170
Re-add bundle install
apiology Sep 7, 2025
f80b73a
Drop MATRIX_SOLARGRAPH_VERSION
apiology Sep 7, 2025
ce2bee6
Drop debugging changes
apiology Sep 7, 2025
c261704
Merge remote-tracking branch 'origin/master' into rubocop_stability
apiology Sep 7, 2025
dbe9a3e
Update expectations from master branch
apiology Sep 7, 2025
620fa00
Merge branch 'fix_solargraph_rspec_check' into rubocop_stability
apiology Sep 7, 2025
b0ace93
Merge branch 'rubocop_stability' into method_pin
apiology Sep 7, 2025
1b12d27
Merge branch 'master' into rubocop_stability
apiology Sep 13, 2025
db725f7
Update rubocop todo
apiology Sep 13, 2025
267b685
Merge branch 'master' into method_pin
apiology Sep 13, 2025
0e00195
Merge branch 'rubocop_stability' into method_pin
apiology Sep 13, 2025
b6d86ec
Fix merge failure
apiology Sep 13, 2025
9d4ba44
Allow more valid method pin paths
apiology Sep 26, 2025
2712e66
RuboCop fix
apiology Sep 26, 2025
7bc2092
Linting
apiology Sep 26, 2025
d69cd60
Merge branch 'master' into method_pin
apiology Sep 30, 2025
053de42
Merge remote-tracking branch 'origin/master' into method_pin
apiology Sep 30, 2025
479d9d1
Show parameters
apiology Nov 15, 2025
fd7dc70
Add @sg-ignores
apiology Nov 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/rspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ jobs:
rbs-version: '3.9.4'
- ruby-version: '3.0'
rbs-version: '4.0.0.dev.4'
# Missing require in 'rbs collection update' - hopefully
# fixed in next RBS release
- ruby-version: 'head'
rbs-version: '4.0.0.dev.4'
- ruby-version: 'head'
rbs-version: '3.9.4'
- ruby-version: 'head'
rbs-version: '3.6.1'
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
Expand All @@ -48,6 +56,9 @@ jobs:
run: |
bundle install
bundle update rbs # use latest available for this Ruby version
- name: Update types
run: |
bundle exec rbs collection update
- name: Run tests
run: bundle exec rake spec
undercover:
Expand Down
13 changes: 0 additions & 13 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ Gemspec/OrderedDependencies:
# Configuration parameters: Severity.
Gemspec/RequireMFA:
Exclude:
- 'solargraph.gemspec'
- 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec'
- 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec'

Expand Down Expand Up @@ -664,14 +663,6 @@ RSpec/LetBeforeExamples:
Exclude:
- 'spec/complex_type_spec.rb'

# Configuration parameters: EnforcedStyle.
# SupportedStyles: have_received, receive
RSpec/MessageSpies:
Exclude:
- 'spec/doc_map_spec.rb'
- 'spec/language_server/host/diagnoser_spec.rb'
- 'spec/language_server/host/message_worker_spec.rb'

RSpec/MissingExampleGroupArgument:
Exclude:
- 'spec/diagnostics/rubocop_helpers_spec.rb'
Expand Down Expand Up @@ -732,10 +723,6 @@ RSpec/ScatteredLet:
Exclude:
- 'spec/complex_type_spec.rb'

RSpec/StubbedMock:
Exclude:
- 'spec/language_server/host/message_worker_spec.rb'

# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
RSpec/VerifiedDoubles:
Enabled: false
Expand Down
4 changes: 2 additions & 2 deletions lib/solargraph/api_map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private,
# @deprecated Use #get_path_pins instead.
#
# @param path [String] The path to find
# @return [Enumerable<Solargraph::Pin::Base>]
# @return [Array<Solargraph::Pin::Base>]
def get_path_suggestions path
return [] if path.nil?
resolve_method_aliases store.get_path_pins(path)
Expand All @@ -546,7 +546,7 @@ def get_path_suggestions path
# Get an array of pins that match the specified path.
#
# @param path [String]
# @return [Enumerable<Pin::Base>]
# @return [Array<Pin::Base>]
def get_path_pins path
get_path_suggestions(path)
end
Expand Down
108 changes: 108 additions & 0 deletions lib/solargraph/shell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,94 @@ def list
puts "#{workspace.filenames.length} files total."
end

# @sg-ignore Unresolved call to desc
desc 'pin [PATH]', 'Describe a pin', hide: true
# @sg-ignore Unresolved call to option
option :rbs, type: :boolean, desc: 'Output the pin as RBS', default: false
# @sg-ignore Unresolved call to option
option :typify, type: :boolean, desc: 'Output the calculated return type of the pin from annotations', default: false
# @sg-ignore Unresolved call to option
option :references, type: :boolean, desc: 'Show references', default: false
# @sg-ignore Unresolved call to option
option :probe, type: :boolean, desc: 'Output the calculated return type of the pin from annotations and inference', default: false
# @sg-ignore Unresolved call to option
option :stack, type: :boolean, desc: 'Show entire stack of a method pin by including definitions in superclasses', default: false
# @param path [String] The path to the method pin, e.g. 'Class#method' or 'Class.method'
# @return [void]
def pin path
api_map = Solargraph::ApiMap.load_with_cache('.', $stderr)
is_method = path.include?('#') || path.include?('.')
# @sg-ignore Unresolved call to options
if is_method && options[:stack]
scope, ns, meth = if path.include? '#'
[:instance, *path.split('#', 2)]
else
[:class, *path.split('.', 2)]
end

# @sg-ignore Wrong argument type for
# Solargraph::ApiMap#get_method_stack: rooted_tag
# expected String, received Array<String>
pins = api_map.get_method_stack(ns, meth, scope: scope)
else
pins = api_map.get_path_pins path
end
references = {}
pin = pins.first
case pin
when nil
$stderr.puts "Pin not found for path '#{path}'"
exit 1
when Pin::Namespace
# @sg-ignore Unresolved call to options
if options[:references]
superclass_tag = api_map.qualify_superclass(pin.return_type.tag)
superclass_pin = api_map.get_path_pins(superclass_tag).first if superclass_tag
references[:superclass] = superclass_pin if superclass_pin
end
when Pin::Method
# @sg-ignore Unresolved call to options
if options[:references]
([pin] + pin.signatures).compact.each do |sig|
sig.parameters.each do |param|
references[param.name] = param
end
if sig.block
references[:block] = sig.block
end
end
end
end

pins.each do |pin|
present_pin(pin, api_map)
end

references.each do |key, refpin|
puts "\n# #{key.to_s.capitalize}:\n\n"
present_pin(refpin, api_map)
end
end

private

# @param pin [Solargraph::Pin::Base]
# @param api_map [Solargraph::ApiMap]
# @return [void]
def present_pin pin, api_map
# @sg-ignore Unresolved call to options
if options[:typify] || options[:probe]
type = ComplexType::UNDEFINED
# @sg-ignore Unresolved call to options
type = pin.typify(api_map) if options[:typify]
# @sg-ignore Unresolved call to options
type = pin.probe(api_map) if options[:probe] && type.undefined?
print_type(type)
else
print_pin(pin)
end
end

# @param pin [Solargraph::Pin::Base]
# @return [String]
def pin_description pin
Expand All @@ -267,5 +353,27 @@ def do_cache gemspec, api_map
# typecheck doesn't complain on the below line
api_map.cache_gem(gemspec, rebuild: options.rebuild, out: $stdout)
end

# @param type [ComplexType]
# @return [void]
def print_type(type)
# @sg-ignore Unresolved call to options
if options[:rbs]
puts type.to_rbs
else
puts type.rooted_tag
end
end

# @param pin [Solargraph::Pin::Base]
# @return [void]
def print_pin(pin)
# @sg-ignore Unresolved call to options
if options[:rbs]
puts pin.to_rbs
else
puts pin.inspect
end
end
end
end
3 changes: 2 additions & 1 deletion spec/doc_map_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
it 'does not warn for redundant requires' do
# Requiring 'set' is unnecessary because it's already included in core. It
# might make sense to log redundant requires, but a warning is overkill.
expect(Solargraph.logger).not_to receive(:warn).with(/path set/)
allow(Solargraph.logger).to receive(:warn).and_call_original
Solargraph::DocMap.new(['set'], [])
expect(Solargraph.logger).not_to have_received(:warn).with(/path set/)
end

it 'ignores nil requires' do
Expand Down
3 changes: 2 additions & 1 deletion spec/language_server/host/diagnoser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
host = double(Solargraph::LanguageServer::Host, options: { 'diagnostics' => true }, synchronizing?: false)
diagnoser = Solargraph::LanguageServer::Host::Diagnoser.new(host)
diagnoser.schedule 'file.rb'
expect(host).to receive(:diagnose).with('file.rb')
allow(host).to receive(:diagnose)
diagnoser.tick
expect(host).to have_received(:diagnose).with('file.rb')
end
end
3 changes: 2 additions & 1 deletion spec/language_server/host/message_worker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
it "handle requests on queue" do
host = double(Solargraph::LanguageServer::Host)
message = {'method' => '$/example'}
expect(host).to receive(:receive).with(message).and_return(nil)
allow(host).to receive(:receive).with(message).and_return(nil)

worker = Solargraph::LanguageServer::Host::MessageWorker.new(host)
worker.queue(message)
expect(worker.messages).to eq [message]
worker.tick
expect(host).to have_received(:receive).with(message)
end
end
114 changes: 114 additions & 0 deletions spec/shell_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# frozen_string_literal: true

require 'tmpdir'
require 'open3'

describe Solargraph::Shell do
let(:shell) { described_class.new }

let(:temp_dir) { Dir.mktmpdir }

before do
Expand Down Expand Up @@ -41,4 +45,114 @@ def bundle_exec(*cmd)
expect(output).to include('Clearing pin cache in')
end
end

# @type cmd [Array<String>]
# @return [String]
def bundle_exec(*cmd)
# run the command in the temporary directory with bundle exec
Bundler.with_unbundled_env do
output, status = Open3.capture2e("bundle exec #{cmd.join(' ')}")
expect(status.success?).to be(true), "Command failed: #{output}"
output
end
end

describe 'pin' do
let(:api_map) { instance_double(Solargraph::ApiMap) }
let(:to_s_pin) { instance_double(Solargraph::Pin::Method, return_type: Solargraph::ComplexType.parse('String')) }

before do
allow(Solargraph::Pin::Method).to receive(:===).with(to_s_pin).and_return(true)
allow(Solargraph::ApiMap).to receive(:load_with_cache).and_return(api_map)
allow(api_map).to receive(:get_path_pins).with('String#to_s').and_return([to_s_pin])
end

context 'with no options' do
it 'prints a pin' do
allow(to_s_pin).to receive(:inspect).and_return('pin inspect result')

out = capture_both { shell.pin('String#to_s') }

expect(out).to eq("pin inspect result\n")
end
end

context 'with --rbs option' do
it 'prints a pin with RBS type' do
allow(to_s_pin).to receive(:to_rbs).and_return('pin RBS result')

out = capture_both do
shell.options = { rbs: true }
shell.pin('String#to_s')
end
expect(out).to eq("pin RBS result\n")
end
end

context 'with --stack option' do
it 'prints a pin using stack results' do
allow(to_s_pin).to receive(:to_rbs).and_return('pin RBS result')

allow(api_map).to receive(:get_method_stack).and_return([to_s_pin])
capture_both do
shell.options = { stack: true }
shell.pin('String#to_s')
end
expect(api_map).to have_received(:get_method_stack).with('String', 'to_s', scope: :instance)
end

it 'prints a static pin using stack results' do
# allow(to_s_pin).to receive(:to_rbs).and_return('pin RBS result')
string_new_pin = instance_double(Solargraph::Pin::Method, return_type: Solargraph::ComplexType.parse('String'))

allow(api_map).to receive(:get_method_stack).with('String', 'new', scope: :class).and_return([string_new_pin])
allow(Solargraph::Pin::Method).to receive(:===).with(string_new_pin).and_return(true)
allow(api_map).to receive(:get_path_pins).with('String.new').and_return([string_new_pin])
capture_both do
shell.options = { stack: true }
shell.pin('String.new')
end
expect(api_map).to have_received(:get_method_stack).with('String', 'new', scope: :class)
end
end

context 'with --typify option' do
it 'prints a pin with typify type' do
allow(to_s_pin).to receive(:typify).and_return(Solargraph::ComplexType.parse('::String'))

out = capture_both do
shell.options = { typify: true }
shell.pin('String#to_s')
end
expect(out).to eq("::String\n")
end
end

context 'with --typify --rbs options' do
it 'prints a pin with typify type' do
allow(to_s_pin).to receive(:typify).and_return(Solargraph::ComplexType.parse('::String'))

out = capture_both do
shell.options = { typify: true, rbs: true }
shell.pin('String#to_s')
end
expect(out).to eq("::String\n")
end
end

context 'with no pin' do
it 'prints error' do
allow(api_map).to receive(:get_path_pins).with('Not#found').and_return([])
allow(Solargraph::Pin::Method).to receive(:===).with(nil).and_return(false)

out = capture_both do
shell.options = {}
shell.pin('Not#found')
rescue SystemExit
# Ignore the SystemExit raised by the shell when no pin is found
end
expect(out).to include("Pin not found for path 'Not#found'")
end
end
end
end
26 changes: 26 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,29 @@ def with_env_var(name, value)
ENV[name] = old_value # Restore the old value
end
end

def capture_stdout &block
original_stdout = $stdout
$stdout = StringIO.new
begin
block.call
$stdout.string
ensure
$stdout = original_stdout
end
end

def capture_both &block
original_stdout = $stdout
original_stderr = $stderr
stringio = StringIO.new
$stdout = stringio
$stderr = stringio
begin
block.call
ensure
$stdout = original_stdout
$stderr = original_stderr
end
stringio.string
end
Loading